From fa934f647e71e2ac84f3c53ba9ffc2326b238dcc Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Thu, 20 Oct 2022 12:54:20 +0300 Subject: [PATCH 001/111] Initial notification system structures and dao layer --- .../NotificationTargetController.java | 56 +++++++++++ .../DefaultNotificationProcessingService.java | 98 +++++++++++++++++++ .../NotificationProcessingService.java | 25 +++++ .../service/security/permission/Resource.java | 3 +- .../AbstractSubscriptionService.java | 2 +- .../DefaultAlarmSubscriptionService.java | 2 +- .../dao/notification/NotificationService.java | 39 ++++++++ .../NotificationTargetService.java | 37 +++++++ .../server/common/data/id/NotificationId.java | 27 +++++ .../common/data/id/NotificationRequestId.java | 27 +++++ .../common/data/id/NotificationTargetId.java | 27 +++++ .../data/notification/Notification.java | 51 ++++++++++ .../notification/NotificationRequest.java | 53 ++++++++++ .../notification/NotificationSettings.java | 20 ++++ .../notification/NotificationSeverity.java | 22 +++++ .../data/notification/NotificationStatus.java | 21 ++++ .../NonConfirmedNotificationEscalation.java | 23 +++++ .../notification/rule/NotificationRule.java | 38 +++++++ .../targets/NotificationTarget.java | 39 ++++++++ .../targets/NotificationTargetConfig.java | 32 ++++++ .../targets/NotificationTargetConfigType.java | 23 +++++ .../SingleUserNotificationTargetConfig.java | 31 ++++++ .../server/dao/model/BaseSqlEntity.java | 19 ++++ .../server/dao/model/ModelConstants.java | 9 ++ .../dao/model/sql/NotificationEntity.java | 29 ++++++ .../model/sql/NotificationRequestEntity.java | 29 ++++++ .../model/sql/NotificationTargetEntity.java | 77 +++++++++++++++ .../DefaultNotificationService.java | 76 ++++++++++++++ .../DefaultNotificationTargetService.java | 75 ++++++++++++++ .../dao/notification/NotificationDao.java | 22 +++++ .../notification/NotificationRequestDao.java | 22 +++++ .../notification/NotificationTargetDao.java | 22 +++++ .../sql/notification/JpaNotificationDao.java | 46 +++++++++ .../JpaNotificationRequestDao.java | 46 +++++++++ .../JpaNotificationTargetDao.java | 46 +++++++++ .../notification/NotificationRepository.java | 26 +++++ .../NotificationRequestRepository.java | 26 +++++ .../NotificationTargetRepository.java | 26 +++++ 38 files changed, 1289 insertions(+), 3 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRequestId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTargetId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationSettings.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationSeverity.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationStatus.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NonConfirmedNotificationEscalation.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfigType.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/SingleUserNotificationTargetConfig.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java new file mode 100644 index 0000000000..220b4f2be2 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.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.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.notification.targets.NotificationTarget; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.dao.notification.NotificationTargetService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.model.SecurityUser; + +@RestController +@TbCoreComponent +@RequestMapping("/api/notification") +@RequiredArgsConstructor +@Slf4j +public class NotificationTargetController extends BaseController { + + private final NotificationTargetService notificationTargetService; + + @PostMapping("/target") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + public NotificationTarget saveNotificationTarget(NotificationTarget notificationTarget) throws Exception { + SecurityUser user = getCurrentUser(); + // fixme: permission check, log action + return notificationTargetService.saveNotificationTarget(user.getTenantId(), notificationTarget); + } + + @GetMapping("/targets") + public PageData getNotificationTargets(@AuthenticationPrincipal SecurityUser user) throws ThingsboardException { +// notificationTargetService.findNotificationTargetsByTenantIdAndPageLink() + return null; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java new file mode 100644 index 0000000000..3aa74862a6 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java @@ -0,0 +1,98 @@ +/** + * 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.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.dao.notification.NotificationService; +import org.thingsboard.server.dao.notification.NotificationTargetService; +import org.thingsboard.server.dao.user.UserService; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.service.executors.DbCallbackExecutorService; +import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.permission.AccessControlService; +import org.thingsboard.server.service.telemetry.AbstractSubscriptionService; + +import java.util.List; + +@Service +@Slf4j +public class DefaultNotificationProcessingService extends AbstractSubscriptionService implements NotificationProcessingService { + + private final NotificationTargetService notificationTargetService; + private final NotificationService notificationService; + private final UserService userService; + private final AccessControlService accessControlService; + private final DbCallbackExecutorService dbCallbackExecutorService; + + public DefaultNotificationProcessingService(TbClusterService clusterService, PartitionService partitionService, + NotificationTargetService notificationTargetService, + NotificationService notificationService, UserService userService, + AccessControlService accessControlService, + DbCallbackExecutorService dbCallbackExecutorService) { + super(clusterService, partitionService); + this.notificationTargetService = notificationTargetService; + this.notificationService = notificationService; + this.userService = userService; + this.accessControlService = accessControlService; + this.dbCallbackExecutorService = dbCallbackExecutorService; + } + + @Override + public void processNotificationRequest(SecurityUser user, NotificationRequest notificationRequest) { + TenantId tenantId = user.getTenantId(); + notificationRequest = notificationService.createNotificationRequest(tenantId, notificationRequest); + + List recipients = notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId()); + for (UserId recipientId : recipients) { + try { + // todo: check read permission for recipientId + Notification notification = Notification.builder() + .tenantId(tenantId) + .requestId(notificationRequest.getId()) + .status(null) + .recipientId(recipientId) + .text(formatNotificationText(notificationRequest.getTextTemplate(), null)) + .severity(notificationRequest.getSeverity()) + .senderId(notificationRequest.getSenderId()) + .build(); + notification = notificationService.createNotification(tenantId, notification); + onNewNotification(notification); + } catch (Exception e) { + // fixme: handle + } + } + + } + + private void onNewNotification(Notification notification) { + } + + private String formatNotificationText(String template, Object context) { + return template; + } + + @Override + protected String getExecutorPrefix() { + return "notification"; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingService.java new file mode 100644 index 0000000000..d094c5e4b4 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingService.java @@ -0,0 +1,25 @@ +/** + * 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 org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.service.security.model.SecurityUser; + +public interface NotificationProcessingService { + + void processNotificationRequest(SecurityUser user, NotificationRequest notificationRequest); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java index a9484a2bd3..7f8ea8e158 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java @@ -43,7 +43,8 @@ public enum Resource { EDGE(EntityType.EDGE), RPC(EntityType.RPC), QUEUE(EntityType.QUEUE), - VERSION_CONTROL; + VERSION_CONTROL, + NOTIFICATION; private final EntityType entityType; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java index 7645465b19..b207de6a9c 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java @@ -64,7 +64,7 @@ public abstract class AbstractSubscriptionService extends TbApplicationEventList this.subscriptionManagerService = subscriptionManagerService; } - abstract String getExecutorPrefix(); + protected abstract String getExecutorPrefix(); @PostConstruct public void initExecutor() { diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index 4dc0dc238b..25f412da20 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -81,7 +81,7 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService } @Override - String getExecutorPrefix() { + protected String getExecutorPrefix() { return "alarm"; } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java new file mode 100644 index 0000000000..3dfca4ae36 --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java @@ -0,0 +1,39 @@ +/** + * 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.NotificationId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationStatus; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; + +public interface NotificationService { + + NotificationRequest createNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); + + PageData findNotificationRequestsByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); + + Notification createNotification(TenantId tenantId, Notification notification); + + void updateNotificationStatus(TenantId tenantId, NotificationId notificationId, NotificationStatus status); + + PageData findNotificationsByUserIdAndPageLink(TenantId tenantId, UserId userId, PageLink pageLink); + +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java new file mode 100644 index 0000000000..52b757191b --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.notification; + +import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.targets.NotificationTarget; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; + +import java.util.List; + +public interface NotificationTargetService { + + NotificationTarget saveNotificationTarget(TenantId tenantId, NotificationTarget notificationTarget); + + NotificationTarget findNotificationTargetById(TenantId tenantId, NotificationTargetId id); + + PageData findNotificationTargetsByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); + + List findRecipientsForNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationId.java new file mode 100644 index 0000000000..a52bfdd9d1 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationId.java @@ -0,0 +1,27 @@ +/** + * 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 java.util.UUID; + +public class NotificationId extends UUIDBased { + @JsonCreator + public NotificationId(UUID id) { + super(id); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRequestId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRequestId.java new file mode 100644 index 0000000000..cffea0f6f5 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRequestId.java @@ -0,0 +1,27 @@ +/** + * 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 java.util.UUID; + +public class NotificationRequestId extends UUIDBased { + @JsonCreator + public NotificationRequestId(UUID id) { + super(id); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTargetId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTargetId.java new file mode 100644 index 0000000000..3d8dddfe0e --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTargetId.java @@ -0,0 +1,27 @@ +/** + * 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 java.util.UUID; + +public class NotificationTargetId extends UUIDBased { + @JsonCreator + public NotificationTargetId(UUID id) { + super(id); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java new file mode 100644 index 0000000000..3e9c88d45e --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java @@ -0,0 +1,51 @@ +/** + * 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; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.SearchTextBased; +import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; + +import java.util.UUID; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class Notification extends SearchTextBased { + + private NotificationRequestId requestId; + private TenantId tenantId; + private UserId recipientId; + private String text; + private NotificationSeverity severity; + private NotificationStatus status; + private UserId senderId; + + @Override + public String getSearchText() { + return text; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java new file mode 100644 index 0000000000..3b3b2c527e --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.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.common.data.notification; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.SearchTextBased; +import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; + +import java.util.UUID; + +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationRequest extends SearchTextBased { + + private NotificationTargetId targetId; + private String textTemplate; // html with params? + private Object notificationType; // ALARM, ADMIN, + private Object notificationInfo; // for alarms: alarm details, link to dashboard etc. + private NotificationSeverity severity; + private TenantId tenantId; + private UserId senderId; + + @Override + public String getSearchText() { + return textTemplate; + } + + // todo: scheduling + +} + +/* +* NotificationService - manages NotificationRequest and Notification entities +* NotificationTargetService - manages NotificationTarget +* */ \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationSettings.java new file mode 100644 index 0000000000..2632d1b76e --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationSettings.java @@ -0,0 +1,20 @@ +/** + * 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; + +public class NotificationSettings { + // location on the screen, shown notifications count, timings of displaying +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationSeverity.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationSeverity.java new file mode 100644 index 0000000000..17f28bee4b --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationSeverity.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.common.data.notification; + +public enum NotificationSeverity { + NORMAL, + CRITICAL, + URGENT // send pop-up error +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationStatus.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationStatus.java new file mode 100644 index 0000000000..14008c34bb --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationStatus.java @@ -0,0 +1,21 @@ +/** + * 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; + +public enum NotificationStatus { + DELIVERED, + READ +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NonConfirmedNotificationEscalation.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NonConfirmedNotificationEscalation.java new file mode 100644 index 0000000000..befc666281 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NonConfirmedNotificationEscalation.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.notification.rule; + +import java.util.UUID; + +public class NonConfirmedNotificationEscalation { + private long delayMs; // delay since initial notification request // if no one from previous escalation item has read the notification, send notifications after this time to other recipients + private UUID notificationTargetId; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java new file mode 100644 index 0000000000..340a49c54d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.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.rule; + +import lombok.Data; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@Data +public class NotificationRule { + + // we may choose it in the alarm rule config, or maybe it's better to configure evrth in the triggers? + + private UUID id; // NotificationRuleId id; + private String name; + private Map triggers; // or maybe bad idea + // Map - concrete alarmRule or alarm rule search (e.g. alarm rule of device profiles of particular transport type with certain severity) + // triggerConfiguration (??) - alarm filter: severity, specific device profile, alarm rule + + private UUID initialNotificationTargetId; + private List escalations; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java new file mode 100644 index 0000000000..0048f24be5 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java @@ -0,0 +1,39 @@ +/** + * 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.targets; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.SearchTextBased; +import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.TenantId; + +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationTarget extends SearchTextBased implements HasTenantId, HasName { + + private TenantId tenantId; + private String name; + private NotificationTargetConfig configuration; + + @Override + public String getSearchText() { + return name; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java new file mode 100644 index 0000000000..366f78ef6e --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.notification.targets; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +@JsonSubTypes({ + @Type(value = SingleUserNotificationTargetConfig.class, name = "SINGLE_USER") +}) +public interface NotificationTargetConfig { + + NotificationTargetConfigType getType(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfigType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfigType.java new file mode 100644 index 0000000000..8bc6d04c56 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfigType.java @@ -0,0 +1,23 @@ +/** + * 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.targets; + +public enum NotificationTargetConfigType { + SINGLE_USER, + USER_GROUP, + USERS_WITH_ROLE, + QUERY // ? +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/SingleUserNotificationTargetConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/SingleUserNotificationTargetConfig.java new file mode 100644 index 0000000000..cf0b4316d0 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/SingleUserNotificationTargetConfig.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.notification.targets; + +import lombok.Data; +import org.thingsboard.server.common.data.id.UserId; + +@Data +public class SingleUserNotificationTargetConfig implements NotificationTargetConfig { + + private UserId userId; + + @Override + public NotificationTargetConfigType getType() { + return NotificationTargetConfigType.SINGLE_USER; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java index bb801766ab..6e8a4a7076 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java @@ -16,11 +16,13 @@ package org.thingsboard.server.dao.model; import lombok.Data; +import org.thingsboard.server.common.data.id.UUIDBased; import javax.persistence.Column; import javax.persistence.Id; import javax.persistence.MappedSuperclass; import java.util.UUID; +import java.util.function.Function; /** * Created by ashvayka on 13.07.17. @@ -56,4 +58,21 @@ public abstract class BaseSqlEntity implements BaseEntity { this.createdTime = createdTime; } } + + protected static UUID getUuid(UUIDBased uuidBased) { + if (uuidBased != null) { + return uuidBased.getId(); + } else { + return null; + } + } + + protected static I createId(UUID uuid, Function creator) { + if (uuid != null) { + return creator.apply(uuid); + } else { + return null; + } + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index e24bde91fb..4ebcaaf873 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -43,6 +43,7 @@ public class ModelConstants { public static final String CUSTOMER_ID_PROPERTY = "customer_id"; public static final String DEVICE_ID_PROPERTY = "device_id"; public static final String TITLE_PROPERTY = "title"; + public static final String NAME_PROPERTY = "name"; public static final String ALIAS_PROPERTY = "alias"; public static final String SEARCH_TEXT_PROPERTY = "search_text"; public static final String ADDITIONAL_INFO_PROPERTY = "additional_info"; @@ -644,6 +645,14 @@ public class ModelConstants { public static final String QUEUE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY; + /** + * Notification constants + * */ + + public static final String NOTIFICATION_TARGET_TABLE_NAME = "notification_target"; + public static final String NOTIFICATION_TARGET_CONFIGURATION_PROPERTY = "configuration"; + + protected static final String[] NONE_AGGREGATION_COLUMNS = new String[]{LONG_VALUE_COLUMN, DOUBLE_VALUE_COLUMN, BOOLEAN_VALUE_COLUMN, STRING_VALUE_COLUMN, JSON_VALUE_COLUMN, KEY_COLUMN, TS_COLUMN}; protected static final String[] COUNT_AGGREGATION_COLUMNS = new String[]{count(LONG_VALUE_COLUMN), count(DOUBLE_VALUE_COLUMN), count(BOOLEAN_VALUE_COLUMN), count(STRING_VALUE_COLUMN), count(JSON_VALUE_COLUMN), max(TS_COLUMN)}; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java new file mode 100644 index 0000000000..782600c742 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.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.dao.model.sql; + +import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.dao.model.BaseSqlEntity; + +import javax.persistence.Entity; + +@Entity +public class NotificationEntity extends BaseSqlEntity { + @Override + public Notification toData() { + return null; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java new file mode 100644 index 0000000000..6ba482dd5e --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.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.dao.model.sql; + +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.dao.model.BaseSqlEntity; + +import javax.persistence.Entity; + +@Entity +public class NotificationRequestEntity extends BaseSqlEntity { + @Override + public NotificationRequest toData() { + return null; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java new file mode 100644 index 0000000000..6cbb8c93c9 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java @@ -0,0 +1,77 @@ +/** + * 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.common.util.JacksonUtil; +import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.targets.NotificationTarget; +import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.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_TARGET_TABLE_NAME) +public class NotificationTargetEntity extends BaseSqlEntity { + + @Column(name = ModelConstants.TENANT_ID_PROPERTY) + private UUID tenantId; + + @Column(name = ModelConstants.NAME_PROPERTY) + private String name; + + @Type(type = "json") + @Column(name = ModelConstants.NOTIFICATION_TARGET_CONFIGURATION_PROPERTY) + private JsonNode configuration; + + public NotificationTargetEntity() {} + + public NotificationTargetEntity(NotificationTarget notificationTarget) { + setId(notificationTarget.getUuidId()); + setCreatedTime(notificationTarget.getCreatedTime()); + setTenantId(getUuid(notificationTarget.getTenantId())); + setName(notificationTarget.getName()); + setConfiguration(JacksonUtil.valueToTree(notificationTarget.getConfiguration())); + } + + @Override + public NotificationTarget toData() { + NotificationTarget notificationTarget = new NotificationTarget(); + notificationTarget.setId(new NotificationTargetId(id)); + notificationTarget.setCreatedTime(createdTime); + notificationTarget.setTenantId(createId(tenantId, TenantId::fromUUID)); + notificationTarget.setName(name); + if (configuration != null) { + notificationTarget.setConfiguration(JacksonUtil.treeToValue(configuration, NotificationTargetConfig.class)); + } + return notificationTarget; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java new file mode 100644 index 0000000000..23e7149866 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java @@ -0,0 +1,76 @@ +/** + * 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 lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationStatus; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.service.DataValidator; + +@Service +@Slf4j +@RequiredArgsConstructor +public class DefaultNotificationService implements NotificationService { + + private final NotificationRequestDao notificationRequestDao; + private final NotificationDao notificationDao; + private final NotificationTargetService notificationTargetService; + private final NotificationRequestValidator notificationRequestValidator = new NotificationRequestValidator(); + + @Override + public NotificationRequest createNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { + if (notificationRequest.getId() != null) { + throw new IllegalArgumentException(); + } + notificationRequestValidator.validate(notificationRequest, NotificationRequest::getTenantId); + return notificationRequestDao.save(tenantId, notificationRequest); + } + + @Override + public PageData findNotificationRequestsByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) { + return null; + } + + @Override + public Notification createNotification(TenantId tenantId, Notification notification) { + if (notification.getId() != null) { + throw new IllegalArgumentException(); + } + return notificationDao.save(tenantId, notification); + } + + @Override + public void updateNotificationStatus(TenantId tenantId, NotificationId notificationId, NotificationStatus status) { + + } + + @Override + public PageData findNotificationsByUserIdAndPageLink(TenantId tenantId, UserId userId, PageLink pageLink) { + return null; + } + + private static class NotificationRequestValidator extends DataValidator { + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java new file mode 100644 index 0000000000..6e8548d233 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java @@ -0,0 +1,75 @@ +/** + * 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 lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +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.SingleUserNotificationTargetConfig; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.service.DataValidator; + +import java.util.ArrayList; +import java.util.List; + +@Service +@Slf4j +@RequiredArgsConstructor +public class DefaultNotificationTargetService implements NotificationTargetService { + + private final NotificationTargetDao notificationTargetDao; + private final NotificationTargetValidator validator = new NotificationTargetValidator(); + + @Override + public NotificationTarget saveNotificationTarget(TenantId tenantId, NotificationTarget notificationTarget) { + validator.validate(notificationTarget, NotificationTarget::getTenantId); + return notificationTargetDao.save(tenantId, notificationTarget); + } + + @Override + public NotificationTarget findNotificationTargetById(TenantId tenantId, NotificationTargetId id) { + return null; + } + + @Override + public PageData findNotificationTargetsByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) { + return null; + } + + @Override + public List findRecipientsForNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId) { + NotificationTarget notificationTarget = findNotificationTargetById(tenantId, notificationTargetId); + NotificationTargetConfig configuration = notificationTarget.getConfiguration(); + List recipients = new ArrayList<>(); + switch (configuration.getType()) { + case SINGLE_USER: + SingleUserNotificationTargetConfig singleUserNotificationTargetConfig = (SingleUserNotificationTargetConfig) configuration; + recipients.add(singleUserNotificationTargetConfig.getUserId()); + break; + } + return recipients; + } + + private static class NotificationTargetValidator extends DataValidator { + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java new file mode 100644 index 0000000000..f7d8013f3e --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.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.Notification; +import org.thingsboard.server.dao.Dao; + +public interface NotificationDao extends Dao { +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java new file mode 100644 index 0000000000..c93ca57beb --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.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.NotificationRequest; +import org.thingsboard.server.dao.Dao; + +public interface NotificationRequestDao extends Dao { +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java new file mode 100644 index 0000000000..07101f60ee --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.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.targets.NotificationTarget; +import org.thingsboard.server.dao.Dao; + +public interface NotificationTargetDao extends Dao { +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java new file mode 100644 index 0000000000..909456e765 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.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.Notification; +import org.thingsboard.server.dao.model.sql.NotificationEntity; +import org.thingsboard.server.dao.notification.NotificationDao; +import org.thingsboard.server.dao.sql.JpaAbstractDao; +import org.thingsboard.server.dao.util.SqlDao; + +import java.util.UUID; + +@Component +@SqlDao +@RequiredArgsConstructor +public class JpaNotificationDao extends JpaAbstractDao implements NotificationDao { + + private final NotificationRepository notificationRepository; + + @Override + protected Class getEntityClass() { + return NotificationEntity.class; + } + + @Override + protected JpaRepository getRepository() { + return notificationRepository; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java new file mode 100644 index 0000000000..6b6119b871 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.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.NotificationRequest; +import org.thingsboard.server.dao.model.sql.NotificationRequestEntity; +import org.thingsboard.server.dao.notification.NotificationRequestDao; +import org.thingsboard.server.dao.sql.JpaAbstractDao; +import org.thingsboard.server.dao.util.SqlDao; + +import java.util.UUID; + +@Component +@SqlDao +@RequiredArgsConstructor +public class JpaNotificationRequestDao extends JpaAbstractDao implements NotificationRequestDao { + + private final NotificationRequestRepository notificationRequestRepository; + + @Override + protected Class getEntityClass() { + return NotificationRequestEntity.class; + } + + @Override + protected JpaRepository getRepository() { + return notificationRequestRepository; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java new file mode 100644 index 0000000000..e22f342272 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.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.targets.NotificationTarget; +import org.thingsboard.server.dao.model.sql.NotificationTargetEntity; +import org.thingsboard.server.dao.notification.NotificationTargetDao; +import org.thingsboard.server.dao.sql.JpaAbstractDao; +import org.thingsboard.server.dao.util.SqlDao; + +import java.util.UUID; + +@Component +@SqlDao +@RequiredArgsConstructor +public class JpaNotificationTargetDao extends JpaAbstractDao implements NotificationTargetDao { + + private final NotificationTargetRepository notificationTargetRepository; + + @Override + protected Class getEntityClass() { + return NotificationTargetEntity.class; + } + + @Override + protected JpaRepository getRepository() { + return notificationTargetRepository; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java new file mode 100644 index 0000000000..db8727f167 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.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.NotificationEntity; + +import java.util.UUID; + +@Repository +public interface NotificationRepository extends JpaRepository { +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java new file mode 100644 index 0000000000..370e434c14 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.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.NotificationRequestEntity; + +import java.util.UUID; + +@Repository +public interface NotificationRequestRepository extends JpaRepository { +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java new file mode 100644 index 0000000000..e3a4260102 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.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.NotificationTargetEntity; + +import java.util.UUID; + +@Repository +public interface NotificationTargetRepository extends JpaRepository { +} From f5247386fa6bec142d50546ec435d43f5466810e Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Wed, 26 Oct 2022 17:50:33 +0300 Subject: [PATCH 002/111] Notification system improvements and refactoring --- .../controller/NotificationController.java | 104 ++++++++++++++++++ .../NotificationTargetController.java | 67 +++++++++-- .../DefaultNotificationProcessingService.java | 72 ++++++++---- .../NotificationProcessingService.java | 3 +- .../src/main/resources/thingsboard.yml | 2 + .../dao/notification/NotificationService.java | 5 +- .../NotificationTargetService.java | 2 + .../data/notification/Notification.java | 7 +- .../data/notification/NotificationInfo.java | 37 +++++++ .../notification/NotificationRequest.java | 46 ++++---- .../NotificationRequestConfig.java | 23 ++++ .../data/notification/NotificationStatus.java | 1 + .../targets/NotificationTarget.java | 9 +- .../targets/NotificationTargetConfig.java | 3 +- .../targets/NotificationTargetConfigType.java | 8 +- .../UserListNotificationTargetConfig.java | 33 ++++++ .../server/dao/model/ModelConstants.java | 15 +++ .../dao/model/sql/NotificationEntity.java | 54 ++++++++- .../model/sql/NotificationRequestEntity.java | 81 +++++++++++++- .../model/sql/NotificationTargetEntity.java | 1 + .../DefaultNotificationService.java | 46 ++++++-- .../DefaultNotificationTargetService.java | 15 ++- .../dao/notification/NotificationDao.java | 13 +++ .../notification/NotificationRequestDao.java | 6 + .../notification/NotificationTargetDao.java | 6 + .../sql/notification/JpaNotificationDao.java | 44 ++++++++ .../JpaNotificationRequestDao.java | 11 ++ .../JpaNotificationTargetDao.java | 11 ++ .../notification/NotificationRepository.java | 17 +++ .../NotificationRequestRepository.java | 11 ++ .../NotificationTargetRepository.java | 7 ++ 31 files changed, 681 insertions(+), 79 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/controller/NotificationController.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/UserListNotificationTargetConfig.java diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java new file mode 100644 index 0000000000..454a8422e5 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -0,0 +1,104 @@ +/** + * 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.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationStatus; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.notification.NotificationService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.notification.NotificationProcessingService; +import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; + +import java.util.UUID; + +@RestController +@TbCoreComponent +@RequestMapping("/api") +@RequiredArgsConstructor +@Slf4j +public class NotificationController extends BaseController { + + private final NotificationService notificationService; + private final NotificationProcessingService notificationProcessingService; + + @GetMapping("/notifications") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + public PageData getNotifications(@RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder, + @RequestParam(defaultValue = "false") boolean unreadOnly, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return notificationService.findNotificationsByUserIdAndReadStatusAndPageLink(user.getTenantId(), user.getId(), unreadOnly, pageLink); + } + + @PutMapping("/notification/{id}/read") // or maybe to NotificationUpdateRequest for the future + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + public void markNotificationAsRead(@PathVariable UUID id, + @AuthenticationPrincipal SecurityUser user) { + NotificationId notificationId = new NotificationId(id); + notificationService.updateNotificationStatus(user.getTenantId(), notificationId, NotificationStatus.READ); + } + + + @PostMapping("/notification/request") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationRequest createNotificationRequest(@RequestBody NotificationRequest notificationRequest, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + accessControlService.checkPermission(user, Resource.NOTIFICATION, Operation.CREATE); + return notificationProcessingService.processNotificationRequest(user, notificationRequest); + } + + @GetMapping("/notification/requests") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public PageData getNotificationRequests(@RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + accessControlService.checkPermission(user, Resource.NOTIFICATION, Operation.CREATE); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return notificationService.findNotificationRequestsByTenantIdAndPageLink(user.getTenantId(), pageLink); + } + + // delete request and sent notifications + public void deleteNotificationRequest() { + + } + +} diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java index 220b4f2be2..471b72e015 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java @@ -19,16 +19,32 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.notification.NotificationTargetService; +import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.permission.Operation; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; @RestController @TbCoreComponent @@ -36,21 +52,58 @@ import org.thingsboard.server.service.security.model.SecurityUser; @RequiredArgsConstructor @Slf4j public class NotificationTargetController extends BaseController { + // fixme: permission check, log action, events private final NotificationTargetService notificationTargetService; + private final UserService userService; @PostMapping("/target") - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - public NotificationTarget saveNotificationTarget(NotificationTarget notificationTarget) throws Exception { - SecurityUser user = getCurrentUser(); - // fixme: permission check, log action + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationTarget saveNotificationTarget(@RequestBody NotificationTarget notificationTarget, + @AuthenticationPrincipal SecurityUser user) { return notificationTargetService.saveNotificationTarget(user.getTenantId(), notificationTarget); } + @GetMapping("/target/{id}") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationTarget getNotificationTargetById(@PathVariable UUID id, + @AuthenticationPrincipal SecurityUser user) { + NotificationTargetId notificationTargetId = new NotificationTargetId(id); + return notificationTargetService.findNotificationTargetById(user.getTenantId(), notificationTargetId); + } + + @GetMapping("/target/{id}/recipients") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public List getRecipientsForNotificationTarget(@PathVariable UUID id, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + NotificationTargetId notificationTargetId = new NotificationTargetId(id); + // fixme: to page data + // todo: check read permission for recipients + List recipients = new ArrayList<>(); + for (UserId userId : notificationTargetService.findRecipientsForNotificationTarget(user.getTenantId(), notificationTargetId)) { + recipients.add(checkUserId(userId, Operation.READ)); + } + return recipients; + } + @GetMapping("/targets") - public PageData getNotificationTargets(@AuthenticationPrincipal SecurityUser user) throws ThingsboardException { -// notificationTargetService.findNotificationTargetsByTenantIdAndPageLink() - return null; + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public PageData getNotificationTargets(@RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return notificationTargetService.findNotificationTargetsByTenantIdAndPageLink(user.getTenantId(), pageLink); + } + + @DeleteMapping("/target/{id}") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public void deleteNotificationTarget(@PathVariable UUID id, + @AuthenticationPrincipal SecurityUser user) { + NotificationTargetId notificationTargetId = new NotificationTargetId(id); + notificationTargetService.deleteNotificationTarget(user.getTenantId(), notificationTargetId); } } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java index 3aa74862a6..04bc45b9d4 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java @@ -17,11 +17,15 @@ package org.thingsboard.server.service.notification; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +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.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.user.UserService; @@ -29,9 +33,13 @@ import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.AccessControlService; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.telemetry.AbstractSubscriptionService; +import java.util.ArrayList; import java.util.List; +import java.util.Map; @Service @Slf4j @@ -57,37 +65,57 @@ public class DefaultNotificationProcessingService extends AbstractSubscriptionSe } @Override - public void processNotificationRequest(SecurityUser user, NotificationRequest notificationRequest) { + public NotificationRequest processNotificationRequest(SecurityUser user, NotificationRequest notificationRequest) throws ThingsboardException { TenantId tenantId = user.getTenantId(); - notificationRequest = notificationService.createNotificationRequest(tenantId, notificationRequest); - - List recipients = notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId()); - for (UserId recipientId : recipients) { - try { - // todo: check read permission for recipientId - Notification notification = Notification.builder() - .tenantId(tenantId) - .requestId(notificationRequest.getId()) - .status(null) - .recipientId(recipientId) - .text(formatNotificationText(notificationRequest.getTextTemplate(), null)) - .severity(notificationRequest.getSeverity()) - .senderId(notificationRequest.getSenderId()) - .build(); - notification = notificationService.createNotification(tenantId, notification); + List recipientsIds = notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId()); + List recipients = new ArrayList<>(); + for (UserId recipientId : recipientsIds) { + User recipient = userService.findUserById(tenantId, recipientId); // todo: add caching + accessControlService.checkPermission(user, Resource.USER, Operation.READ, recipientId, recipient); + recipients.add(recipient); + } + + notificationRequest.setTenantId(tenantId); + notificationRequest.setSenderId(user.getId()); + NotificationRequest savedNotificationRequest = notificationService.createNotificationRequest(tenantId, notificationRequest); + + // todo: delayed sending; check all delayed notification requests on start up, schedule send + + for (User recipient : recipients) { + dbCallbackExecutorService.submit(() -> { + Notification notification = createNotification(recipient, notificationRequest); onNewNotification(notification); - } catch (Exception e) { - // fixme: handle - } + }); } + return savedNotificationRequest; + } + + private Notification createNotification(User recipient, NotificationRequest notificationRequest) { + Notification notification = Notification.builder() + .requestId(notificationRequest.getId()) + .recipientId(recipient.getId()) + .text(formatNotificationText(notificationRequest.getTextTemplate(), recipient)) + .severity(notificationRequest.getNotificationSeverity()) + .status(NotificationStatus.SENT) + .build(); + notification = notificationService.createNotification(recipient.getTenantId(), notification); + return notification; } private void onNewNotification(Notification notification) { + wsCallBackExecutor.submit(() -> { + + }) } - private String formatNotificationText(String template, Object context) { - return template; + private String formatNotificationText(String template, User recipient) { + Map context = Map.of( + "recipientEmail", recipient.getEmail(), + "recipientFirstName", recipient.getFirstName(), + "recipientLastName", recipient.getLastName() + ); + return TbNodeUtils.processTemplate(template, context); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingService.java index d094c5e4b4..1293ba999d 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingService.java @@ -15,11 +15,12 @@ */ package org.thingsboard.server.service.notification; +import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.service.security.model.SecurityUser; public interface NotificationProcessingService { - void processNotificationRequest(SecurityUser user, NotificationRequest notificationRequest); + NotificationRequest processNotificationRequest(SecurityUser user, NotificationRequest notificationRequest) throws ThingsboardException; } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index f5ac40afd5..87e5487a7c 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -267,6 +267,8 @@ sql: stats_print_interval_ms: "${SQL_EDGE_EVENTS_BATCH_STATS_PRINT_MS:10000}" audit_logs: partition_size: "${SQL_AUDIT_LOGS_PARTITION_SIZE_HOURS:168}" # Default value - 1 week + notifications: + partition_size: "${SQL_NOTIFICATIONS_PARTITION_SIZE_HOURS:168}" # Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks batch_sort: "${SQL_BATCH_SORT:false}" # Specify whether to remove null characters from strValue of attributes and timeseries before insert diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java index 3dfca4ae36..a582758639 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java @@ -30,10 +30,13 @@ public interface NotificationService { PageData findNotificationRequestsByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); + Notification createNotification(TenantId tenantId, Notification notification); void updateNotificationStatus(TenantId tenantId, NotificationId notificationId, NotificationStatus status); - PageData findNotificationsByUserIdAndPageLink(TenantId tenantId, UserId userId, PageLink pageLink); + PageData findNotificationsByUserIdAndReadStatusAndPageLink(TenantId tenantId, UserId userId, boolean unreadOnly, PageLink pageLink); + + PageData findLatestUnreadNotificationsByUserId(TenantId tenantId, UserId userId, int limit); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java index 52b757191b..f6cdf780e5 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java @@ -34,4 +34,6 @@ public interface NotificationTargetService { List findRecipientsForNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId); + void deleteNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId); + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java index 3e9c88d45e..02f0fb2aa4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java @@ -23,12 +23,8 @@ import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.SearchTextBased; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; -import org.thingsboard.server.common.data.id.NotificationTargetId; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; -import java.util.UUID; - @Data @AllArgsConstructor @NoArgsConstructor @@ -37,12 +33,11 @@ import java.util.UUID; public class Notification extends SearchTextBased { private NotificationRequestId requestId; - private TenantId tenantId; private UserId recipientId; private String text; private NotificationSeverity severity; private NotificationStatus status; - private UserId senderId; +// private UserId senderId; @Override public String getSearchText() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationInfo.java new file mode 100644 index 0000000000..4831e056cd --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationInfo.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.notification; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Data; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.validation.NoXss; + + +@Data +//@JsonIgnoreProperties(ignoreUnknown = true) +//@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "notificationType", visible = true, defaultImpl = NotificationInfo.class) +//@JsonSubTypes({ +// @Type(name = "ALARM", value = DeviceExportData.class), +//}) +public class NotificationInfo { + @NoXss + private String description; + + private ObjectNode alarmDetails; // move to child class + private DashboardId dashboardId; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java index 3b3b2c527e..392063ad93 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java @@ -15,39 +15,45 @@ */ package org.thingsboard.server.common.data.notification; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; -import org.thingsboard.server.common.data.SearchTextBased; -import org.thingsboard.server.common.data.id.NotificationId; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.validation.NoXss; -import java.util.UUID; +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; @Data @EqualsAndHashCode(callSuper = true) -public class NotificationRequest extends SearchTextBased { +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class NotificationRequest extends BaseData implements HasTenantId { - private NotificationTargetId targetId; - private String textTemplate; // html with params? - private Object notificationType; // ALARM, ADMIN, - private Object notificationInfo; // for alarms: alarm details, link to dashboard etc. - private NotificationSeverity severity; private TenantId tenantId; + @NotNull(message = "Target is not specified") + private NotificationTargetId targetId; + @NoXss + private String notificationReason; // "Alarm", "Scheduled event". "General" by default + // @NoXss + @NotBlank(message = "Notification text template is missing") + private String textTemplate; + @Valid + private NotificationInfo notificationInfo; + private NotificationSeverity notificationSeverity; + private NotificationRequestConfig additionalConfig; private UserId senderId; - @Override - public String getSearchText() { - return textTemplate; - } - - // todo: scheduling + public static final String GENERAL_NOTIFICATION_REASON = "General"; + public static final String ALARM_NOTIFICATION_REASON = "Alarm"; } - -/* -* NotificationService - manages NotificationRequest and Notification entities -* NotificationTargetService - manages NotificationTarget -* */ \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java new file mode 100644 index 0000000000..8f65427f21 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java @@ -0,0 +1,23 @@ +/** + * 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; + +import lombok.Data; + +@Data +public class NotificationRequestConfig { + private Long sendingDelayMs; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationStatus.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationStatus.java index 14008c34bb..7e504cbe15 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationStatus.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationStatus.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.data.notification; public enum NotificationStatus { + SENT, DELIVERED, READ } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java index 0048f24be5..957a6d359f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java @@ -17,23 +17,18 @@ package org.thingsboard.server.common.data.notification.targets; 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.SearchTextBased; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; @Data @EqualsAndHashCode(callSuper = true) -public class NotificationTarget extends SearchTextBased implements HasTenantId, HasName { +public class NotificationTarget extends BaseData implements HasTenantId, HasName { private TenantId tenantId; private String name; private NotificationTargetConfig configuration; - @Override - public String getSearchText() { - return name; - } - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java index 366f78ef6e..0623bf07b9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java @@ -23,7 +23,8 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @JsonSubTypes({ - @Type(value = SingleUserNotificationTargetConfig.class, name = "SINGLE_USER") + @Type(value = SingleUserNotificationTargetConfig.class, name = "SINGLE_USER"), + @Type(value = UserListNotificationTargetConfig.class, name = "USER_LIST") }) public interface NotificationTargetConfig { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfigType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfigType.java index 8bc6d04c56..2413eb9326 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfigType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfigType.java @@ -17,7 +17,9 @@ package org.thingsboard.server.common.data.notification.targets; public enum NotificationTargetConfigType { SINGLE_USER, - USER_GROUP, - USERS_WITH_ROLE, - QUERY // ? + USER_LIST, + +// USER_GROUP, +// USERS_WITH_ROLE, +// QUERY // ? } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/UserListNotificationTargetConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/UserListNotificationTargetConfig.java new file mode 100644 index 0000000000..22bc26703d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/UserListNotificationTargetConfig.java @@ -0,0 +1,33 @@ +/** + * 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.targets; + +import lombok.Data; +import org.thingsboard.server.common.data.id.UserId; + +import java.util.List; + +@Data +public class UserListNotificationTargetConfig implements NotificationTargetConfig { + + private List usersIds; + + @Override + public NotificationTargetConfigType getType() { + return NotificationTargetConfigType.USER_LIST; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 4ebcaaf873..5c938de98c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -652,6 +652,21 @@ public class ModelConstants { public static final String NOTIFICATION_TARGET_TABLE_NAME = "notification_target"; public static final String NOTIFICATION_TARGET_CONFIGURATION_PROPERTY = "configuration"; + public static final String NOTIFICATION_TABLE_NAME = "notification"; + public static final String NOTIFICATION_REQUEST_ID_PROPERTY = "request_id"; + public static final String NOTIFICATION_RECIPIENT_ID_PROPERTY = "recipient_id"; + public static final String NOTIFICATION_TEXT_PROPERTY = "text"; + public static final String NOTIFICATION_SEVERITY_PROPERTY = "severity"; + public static final String NOTIFICATION_STATUS_PROPERTY = "status"; + + public static final String NOTIFICATION_REQUEST_TABLE_NAME = "notification_request"; + public static final String NOTIFICATION_REQUEST_TARGET_ID_PROPERTY = "target_id"; + public static final String NOTIFICATION_REQUEST_TEXT_TEMPLATE_PROPERTY = "text_template"; + public static final String NOTIFICATION_REQUEST_NOTIFICATION_REASON_PROPERTY = "notification_reason"; + public static final String NOTIFICATION_REQUEST_NOTIFICATION_INFO_PROPERTY = "notification_info"; + public static final String NOTIFICATION_REQUEST_NOTIFICATION_SEVERITY_PROPERTY = "notification_severity"; + public static final String NOTIFICATION_REQUEST_ADDITIONAL_CONFIG_PROPERTY = "additional_config"; + public static final String NOTIFICATION_REQUEST_SENDER_ID_PROPERTY = "sender_id"; 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}; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java index 782600c742..ad7c506d4b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java @@ -15,15 +15,67 @@ */ package org.thingsboard.server.dao.model.sql; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationSeverity; +import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Table; +import java.util.UUID; +@Data +@EqualsAndHashCode(callSuper = true) @Entity +@Table(name = ModelConstants.NOTIFICATION_TABLE_NAME) public class NotificationEntity extends BaseSqlEntity { + + @Column(name = ModelConstants.NOTIFICATION_REQUEST_ID_PROPERTY) + private UUID requestId; + + @Column(name = ModelConstants.NOTIFICATION_RECIPIENT_ID_PROPERTY) + private UUID recipientId; + + @Column(name = ModelConstants.NOTIFICATION_TEXT_PROPERTY) + private String text; + + @Column(name = ModelConstants.NOTIFICATION_SEVERITY_PROPERTY) + private NotificationSeverity severity; + + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.NOTIFICATION_STATUS_PROPERTY) + private NotificationStatus status; + + public NotificationEntity() {} + + public NotificationEntity(Notification notification) { + setId(notification.getUuidId()); + setCreatedTime(notification.getCreatedTime()); + setRequestId(getUuid(notification.getRequestId())); + setRecipientId(getUuid(notification.getRecipientId())); + setText(notification.getText()); + setStatus(notification.getStatus()); + } + @Override public Notification toData() { - return null; + Notification notification = new Notification(); + notification.setId(new NotificationId(id)); + notification.setCreatedTime(createdTime); + notification.setRequestId(createId(requestId, NotificationRequestId::new)); + notification.setRecipientId(createId(recipientId, UserId::new)); + notification.setText(text); + notification.setStatus(status); + return notification; } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java index 6ba482dd5e..23a8ae1059 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java @@ -15,15 +15,94 @@ */ 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.common.util.JacksonUtil; +import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationRequestConfig; +import org.thingsboard.server.common.data.notification.NotificationSeverity; import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.util.mapping.JsonStringType; +import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Table; +import java.util.UUID; +@Data +@EqualsAndHashCode(callSuper = true) @Entity +@TypeDef(name = "json", typeClass = JsonStringType.class) +@Table(name = ModelConstants.NOTIFICATION_REQUEST_TABLE_NAME) public class NotificationRequestEntity extends BaseSqlEntity { + + @Column(name = ModelConstants.TENANT_ID_PROPERTY) + private UUID tenantId; + + @Column(name = ModelConstants.NOTIFICATION_REQUEST_TARGET_ID_PROPERTY) + private UUID targetId; + + @Column(name = ModelConstants.NOTIFICATION_REQUEST_NOTIFICATION_REASON_PROPERTY) + private String notificationReason; + + @Column(name = ModelConstants.NOTIFICATION_REQUEST_TEXT_TEMPLATE_PROPERTY) + private String textTemplate; + + @Type(type = "json") + @Column(name = ModelConstants.NOTIFICATION_REQUEST_NOTIFICATION_INFO_PROPERTY) + private JsonNode notificationInfo; + + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.NOTIFICATION_REQUEST_NOTIFICATION_SEVERITY_PROPERTY) + private NotificationSeverity notificationSeverity; + + @Type(type = "json") + @Column(name = ModelConstants.NOTIFICATION_REQUEST_ADDITIONAL_CONFIG_PROPERTY) + private JsonNode additionalConfig; + + @Column(name = ModelConstants.NOTIFICATION_REQUEST_SENDER_ID_PROPERTY) + private UUID senderId; + + public NotificationRequestEntity() {} + + public NotificationRequestEntity(NotificationRequest notificationRequest) { + setId(notificationRequest.getUuidId()); + setCreatedTime(notificationRequest.getCreatedTime()); + setTenantId(getUuid(notificationRequest.getTenantId())); + setTargetId(getUuid(notificationRequest.getTargetId())); + setNotificationReason(notificationRequest.getNotificationReason()); + setTextTemplate(notificationRequest.getTextTemplate()); + setNotificationInfo(JacksonUtil.valueToTree(notificationRequest.getNotificationInfo())); + setNotificationSeverity(notificationRequest.getNotificationSeverity()); + setAdditionalConfig(JacksonUtil.valueToTree(notificationRequest.getAdditionalConfig())); + setSenderId(getUuid(notificationRequest.getSenderId())); + } + @Override public NotificationRequest toData() { - return null; + NotificationRequest notificationRequest = new NotificationRequest(); + notificationRequest.setId(new NotificationRequestId(id)); + notificationRequest.setCreatedTime(createdTime); + notificationRequest.setTenantId(createId(tenantId, TenantId::new)); + notificationRequest.setTargetId(createId(targetId, NotificationTargetId::new)); + notificationRequest.setNotificationReason(notificationReason); + notificationRequest.setTextTemplate(textTemplate); + notificationRequest.setNotificationInfo(JacksonUtil.treeToValue(notificationInfo, NotificationInfo.class)); + notificationRequest.setNotificationSeverity(notificationSeverity); + notificationRequest.setAdditionalConfig(JacksonUtil.treeToValue(additionalConfig, NotificationRequestConfig.class)); + notificationRequest.setSenderId(createId(senderId, UserId::new)); + return notificationRequest; } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java index 6cbb8c93c9..70d51833a4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.notification.targets.NotificationTarge import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.model.SearchTextEntity; import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java index 23e7149866..8b1b994b17 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java @@ -17,16 +17,21 @@ package org.thingsboard.server.dao.notification; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationSeverity; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.page.SortOrder; +import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.sql.query.EntityKeyMapping; @Service @Slf4j @@ -35,13 +40,16 @@ public class DefaultNotificationService implements NotificationService { private final NotificationRequestDao notificationRequestDao; private final NotificationDao notificationDao; - private final NotificationTargetService notificationTargetService; + private final NotificationRequestValidator notificationRequestValidator = new NotificationRequestValidator(); @Override public NotificationRequest createNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { - if (notificationRequest.getId() != null) { - throw new IllegalArgumentException(); + if (StringUtils.isBlank(notificationRequest.getNotificationReason())) { + notificationRequest.setNotificationReason(NotificationRequest.GENERAL_NOTIFICATION_REASON); + } + if (notificationRequest.getNotificationSeverity() == null) { + notificationRequest.setNotificationSeverity(NotificationSeverity.NORMAL); } notificationRequestValidator.validate(notificationRequest, NotificationRequest::getTenantId); return notificationRequestDao.save(tenantId, notificationRequest); @@ -49,28 +57,52 @@ public class DefaultNotificationService implements NotificationService { @Override public PageData findNotificationRequestsByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) { - return null; + return notificationRequestDao.findByTenantIdAndPageLink(tenantId, pageLink); } @Override public Notification createNotification(TenantId tenantId, Notification notification) { if (notification.getId() != null) { - throw new IllegalArgumentException(); + throw new DataValidationException("Notification cannot be updated"); // tmp ? } return notificationDao.save(tenantId, notification); } @Override public void updateNotificationStatus(TenantId tenantId, NotificationId notificationId, NotificationStatus status) { + notificationDao.updateStatus(tenantId, notificationId, status); + } + @Override + public PageData findNotificationsByUserIdAndReadStatusAndPageLink(TenantId tenantId, UserId userId, boolean unreadOnly, PageLink pageLink) { + if (unreadOnly) { + return notificationDao.findUnreadByUserIdAndPageLink(tenantId, userId, pageLink); + } else { + return notificationDao.findByUserIdAndPageLink(tenantId, userId, pageLink); + } } @Override - public PageData findNotificationsByUserIdAndPageLink(TenantId tenantId, UserId userId, PageLink pageLink) { - return null; + public PageData findLatestUnreadNotificationsByUserId(TenantId tenantId, UserId userId, int limit) { + SortOrder sortOrder = new SortOrder(EntityKeyMapping.CREATED_TIME, SortOrder.Direction.DESC); + PageLink pageLink = new PageLink(limit, 0, null, sortOrder); + return findNotificationsByUserIdAndReadStatusAndPageLink(tenantId, userId, true, pageLink); } private static class NotificationRequestValidator extends DataValidator { + + @Override + protected void validateDataImpl(TenantId tenantId, NotificationRequest notificationRequest) { + if (notificationRequest.getId() != null) { + throw new DataValidationException("Notification request cannot be changed once created"); + } + if (notificationRequest.getSenderId() != null) { + if (notificationRequest.getNotificationReason().equalsIgnoreCase(NotificationRequest.ALARM_NOTIFICATION_REASON)) { + throw new DataValidationException("'Alarm' notification reason is for internal usage"); + } + } + } + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java index 6e8548d233..a7d5393d43 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.UserId; 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.SingleUserNotificationTargetConfig; +import org.thingsboard.server.common.data.notification.targets.UserListNotificationTargetConfig; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.service.DataValidator; @@ -47,12 +48,12 @@ public class DefaultNotificationTargetService implements NotificationTargetServi @Override public NotificationTarget findNotificationTargetById(TenantId tenantId, NotificationTargetId id) { - return null; + return notificationTargetDao.findById(tenantId, id.getId()); } @Override public PageData findNotificationTargetsByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) { - return null; + return notificationTargetDao.findByTenantIdAndPageLink(tenantId, pageLink); } @Override @@ -65,10 +66,20 @@ public class DefaultNotificationTargetService implements NotificationTargetServi SingleUserNotificationTargetConfig singleUserNotificationTargetConfig = (SingleUserNotificationTargetConfig) configuration; recipients.add(singleUserNotificationTargetConfig.getUserId()); break; + case USER_LIST: + UserListNotificationTargetConfig userListNotificationTargetConfig = (UserListNotificationTargetConfig) configuration; + recipients.addAll(userListNotificationTargetConfig.getUsersIds()); + break; } return recipients; } + @Override + public void deleteNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId) { + notificationTargetDao.removeById(tenantId, notificationTargetId.getId()); + // todo: delete related notification requests (?) + } + private static class NotificationTargetValidator extends DataValidator { } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java index f7d8013f3e..b088d75c35 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java @@ -15,8 +15,21 @@ */ package org.thingsboard.server.dao.notification; +import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationStatus; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; public interface NotificationDao extends Dao { + + PageData findUnreadByUserIdAndPageLink(TenantId tenantId, UserId userId, PageLink pageLink); + + PageData findByUserIdAndPageLink(TenantId tenantId, UserId userId, PageLink pageLink); + + void updateStatus(TenantId tenantId, NotificationId notificationId, NotificationStatus status); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java index c93ca57beb..d518a4e245 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java @@ -15,8 +15,14 @@ */ package org.thingsboard.server.dao.notification; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; public interface NotificationRequestDao extends Dao { + + PageData findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java index 07101f60ee..0bcae5c456 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java @@ -15,8 +15,14 @@ */ package org.thingsboard.server.dao.notification; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; public interface NotificationTargetDao extends Dao { + + PageData findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java index 909456e765..bb9c30a71b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java @@ -15,16 +15,28 @@ */ package org.thingsboard.server.dao.sql.notification; +import com.datastax.oss.driver.api.core.uuid.Uuids; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationStatus; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.sql.NotificationEntity; import org.thingsboard.server.dao.notification.NotificationDao; import org.thingsboard.server.dao.sql.JpaAbstractDao; +import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository; import org.thingsboard.server.dao.util.SqlDao; import java.util.UUID; +import java.util.concurrent.TimeUnit; @Component @SqlDao @@ -32,6 +44,38 @@ import java.util.UUID; public class JpaNotificationDao extends JpaAbstractDao implements NotificationDao { private final NotificationRepository notificationRepository; + private final SqlPartitioningRepository partitioningRepository; + + @Value("${sql.notifications.partition_size:168}") + private int partitionSizeInHours; + + @Override + public Notification save(TenantId tenantId, Notification notification) { + if (notification.getId() == null) { + UUID uuid = Uuids.timeBased(); + notification.setId(new NotificationId(uuid)); + notification.setCreatedTime(Uuids.unixTimestamp(uuid)); + // todo: regarding ttl, it might be better to remove notifications on NotificationRequest level + partitioningRepository.createPartitionIfNotExists(ModelConstants.NOTIFICATION_TABLE_NAME, + notification.getCreatedTime(), TimeUnit.HOURS.toMillis(partitionSizeInHours)); + } + return super.save(tenantId, notification); + } + + @Override + public PageData findUnreadByUserIdAndPageLink(TenantId tenantId, UserId userId, PageLink pageLink) { + return DaoUtil.toPageData(notificationRepository.findByRecipientIdAndStatusNot(userId.getId(), NotificationStatus.READ, DaoUtil.toPageable(pageLink))); + } + + @Override + public PageData findByUserIdAndPageLink(TenantId tenantId, UserId userId, PageLink pageLink) { + return DaoUtil.toPageData(notificationRepository.findByRecipientId(userId.getId(), DaoUtil.toPageable(pageLink))); + } + + @Override + public void updateStatus(TenantId tenantId, NotificationId notificationId, NotificationStatus status) { + notificationRepository.updateStatus(notificationId.getId(), status); + } @Override protected Class getEntityClass() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java index 6b6119b871..099218abcc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java @@ -15,10 +15,15 @@ */ package org.thingsboard.server.dao.sql.notification; +import com.google.common.base.Strings; import lombok.RequiredArgsConstructor; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.NotificationRequestEntity; import org.thingsboard.server.dao.notification.NotificationRequestDao; import org.thingsboard.server.dao.sql.JpaAbstractDao; @@ -33,6 +38,12 @@ public class JpaNotificationRequestDao extends JpaAbstractDao findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) { + return DaoUtil.toPageData(notificationRequestRepository.findByTenantIdAndSearchText(tenantId.getId(), + Strings.nullToEmpty(pageLink.getTextSearch()), DaoUtil.toPageable(pageLink))); + } + @Override protected Class getEntityClass() { return NotificationRequestEntity.class; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java index e22f342272..26b9bfed76 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java @@ -15,10 +15,15 @@ */ package org.thingsboard.server.dao.sql.notification; +import com.google.common.base.Strings; import lombok.RequiredArgsConstructor; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.NotificationTargetEntity; import org.thingsboard.server.dao.notification.NotificationTargetDao; import org.thingsboard.server.dao.sql.JpaAbstractDao; @@ -33,6 +38,12 @@ public class JpaNotificationTargetDao extends JpaAbstractDao findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) { + return DaoUtil.toPageData(notificationTargetRepository.findByTenantIdAndNameContainingIgnoreCase(tenantId.getId(), + Strings.nullToEmpty(pageLink.getTextSearch()), DaoUtil.toPageable(pageLink))); + } + @Override protected Class getEntityClass() { return NotificationTargetEntity.class; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java index db8727f167..9ee5b07121 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java @@ -15,12 +15,29 @@ */ package org.thingsboard.server.dao.sql.notification; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.dao.model.sql.NotificationEntity; import java.util.UUID; @Repository public interface NotificationRepository extends JpaRepository { + + Page findByRecipientIdAndStatusNot(UUID recipientId, NotificationStatus status, Pageable pageable); + + Page findByRecipientId(UUID recipientId, Pageable pageable); + + @Modifying + @Transactional + @Query("UPDATE NotificationEntity n SET n.status = :status WHERE n.id = :id") + void updateStatus(@Param("id") UUID id, @Param("status") NotificationStatus status); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java index 370e434c14..1303c99034 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java @@ -15,7 +15,11 @@ */ package org.thingsboard.server.dao.sql.notification; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import org.thingsboard.server.dao.model.sql.NotificationRequestEntity; @@ -23,4 +27,11 @@ import java.util.UUID; @Repository public interface NotificationRequestRepository extends JpaRepository { + + @Query("SELECT r FROM NotificationRequestEntity r WHERE r.tenantId = :tenantId AND " + + "(lower(r.notificationReason) LIKE lower(concat('%', :searchText, '%')) OR " + + "lower(r.textTemplate) LIKE lower(concat('%', :searchText, '%')))") + Page findByTenantIdAndSearchText(@Param("tenantId") UUID tenantId, + @Param("searchText") String searchText, Pageable pageable); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java index e3a4260102..a558ad5163 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java @@ -15,7 +15,11 @@ */ package org.thingsboard.server.dao.sql.notification; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import org.thingsboard.server.dao.model.sql.NotificationTargetEntity; @@ -23,4 +27,7 @@ import java.util.UUID; @Repository public interface NotificationTargetRepository extends JpaRepository { + + Page findByTenantIdAndNameContainingIgnoreCase(UUID tenantId, String searchText, Pageable pageable); + } From 4c61d631499626c9e4bea088a9e11692b029e764 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Mon, 31 Oct 2022 14:04:29 +0200 Subject: [PATCH 003/111] Upgrade script, indexes --- .../main/data/upgrade/3.4.2/schema_update.sql | 54 +++++++++++++++++++ .../server/config/SwaggerConfiguration.java | 2 + .../dao/notification/NotificationService.java | 7 ++- .../server/common/data/EntityType.java | 4 +- .../common/data/id/EntityIdFactory.java | 4 ++ .../server/common/data/id/NotificationId.java | 3 +- .../common/data/id/NotificationRequestId.java | 13 ++++- .../common/data/id/NotificationTargetId.java | 13 ++++- .../data/notification/Notification.java | 2 + .../data/notification/NotificationStatus.java | 1 - .../targets/NotificationTarget.java | 8 +++ .../SingleUserNotificationTargetConfig.java | 3 ++ .../server/dao/model/BaseSqlEntity.java | 15 ++++++ .../server/dao/model/ModelConstants.java | 2 + .../dao/model/sql/NotificationEntity.java | 30 +++++++++-- .../model/sql/NotificationRequestEntity.java | 16 +++--- .../model/sql/NotificationTargetEntity.java | 6 +-- .../notification/NotificationRepository.java | 2 + .../resources/sql/schema-entities-idx.sql | 8 +++ .../main/resources/sql/schema-entities.sql | 35 ++++++++++++ 20 files changed, 206 insertions(+), 22 deletions(-) create mode 100644 application/src/main/data/upgrade/3.4.2/schema_update.sql diff --git a/application/src/main/data/upgrade/3.4.2/schema_update.sql b/application/src/main/data/upgrade/3.4.2/schema_update.sql new file mode 100644 index 0000000000..9b259cca3e --- /dev/null +++ b/application/src/main/data/upgrade/3.4.2/schema_update.sql @@ -0,0 +1,54 @@ +-- +-- 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. +-- + + +CREATE TABLE IF NOT EXISTS notification_target ( + id UUID NOT NULL CONSTRAINT notification_target_pkey PRIMARY KEY, + created_time BIGINT NOT NULL, + tenant_id UUID NOT NULL, + name VARCHAR(255) NOT NULL, + configuration varchar(1000) NOT NULL +); +CREATE INDEX IF NOT EXISTS idx_notification_target_tenant_id_and_created_time ON notification_target(tenant_id, created_time DESC); + +CREATE TABLE IF NOT EXISTS notification_request ( + id UUID NOT NULL CONSTRAINT notification_request_pkey PRIMARY KEY, + created_time BIGINT NOT NULL, + tenant_id UUID NOT NULL, + target_id UUID NOT NULL CONSTRAINT fk_notification_request_target_id REFERENCES notification_target(id), + notification_reason VARCHAR NOT NULL, + text_template VARCHAR NOT NULL, + notification_info VARCHAR(1000), + notification_severity VARCHAR(32), + additional_config VARCHAR(1000), + sender_id UUID +); +CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id_and_created_time ON notification_request(tenant_id, created_time DESC); + +CREATE TABLE IF NOT EXISTS notification ( + id UUID NOT NULL, + created_time BIGINT NOT NULL, + request_id UUID NOT NULL CONSTRAINT fk_notification_request_id + REFERENCES notification_request(id) ON DELETE CASCADE, + recipient_id UUID NOT NULL, + reason VARCHAR NOT NULL, + text VARCHAR NOT NULL, + info VARCHAR(1000), + severity VARCHAR(32), + status VARCHAR(32) +) PARTITION BY RANGE (created_time); +CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_and_created_time ON notification(recipient_id, created_time DESC); +CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_and_status_and_created_time ON notification(recipient_id, status, created_time DESC); diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index c53cf1b504..49e041f1af 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -25,6 +25,7 @@ import org.springframework.core.annotation.Order; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.security.Authority; @@ -138,6 +139,7 @@ public class SwaggerConfiguration { ) .securitySchemes(newArrayList(httpLogin())) .securityContexts(newArrayList(securityContext())) + .ignoredParameterTypes(AuthenticationPrincipal.class) .enableUrlTemplating(true); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java index a582758639..c8e1a7ea08 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao.notification; import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; @@ -28,12 +29,16 @@ public interface NotificationService { NotificationRequest createNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); + NotificationRequest findNotificationRequestById(TenantId tenantId, NotificationRequestId id); + PageData findNotificationRequestsByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); + void deleteNotificationRequest(TenantId tenantId, NotificationRequestId id); + Notification createNotification(TenantId tenantId, Notification notification); - void updateNotificationStatus(TenantId tenantId, NotificationId notificationId, NotificationStatus status); + Notification updateNotificationStatus(TenantId tenantId, NotificationId notificationId, NotificationStatus status); PageData findNotificationsByUserIdAndReadStatusAndPageLink(TenantId tenantId, UserId userId, boolean unreadOnly, PageLink pageLink); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index 0798647903..bdcff739f7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -19,5 +19,7 @@ package org.thingsboard.server.common.data; * @author Andrew Shvayka */ public enum EntityType { - TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, ASSET_PROFILE, API_USAGE_STATE, TB_RESOURCE, OTA_PACKAGE, EDGE, RPC, QUEUE; + TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, + TENANT_PROFILE, DEVICE_PROFILE, ASSET_PROFILE, API_USAGE_STATE, TB_RESOURCE, OTA_PACKAGE, EDGE, RPC, QUEUE, + NOTIFICATION_TARGET, NOTIFICATION_REQUEST; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index da9a5c49eb..93080269b2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -81,6 +81,10 @@ public class EntityIdFactory { return new RpcId(uuid); case QUEUE: return new QueueId(uuid); + case NOTIFICATION_TARGET: + return new NotificationTargetId(uuid); + case NOTIFICATION_REQUEST: + return new NotificationRequestId(uuid); } throw new IllegalArgumentException("EntityType " + type + " is not supported!"); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationId.java index a52bfdd9d1..2a467d3a07 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationId.java @@ -16,12 +16,13 @@ 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 NotificationId extends UUIDBased { @JsonCreator - public NotificationId(UUID id) { + public NotificationId(@JsonProperty("id") UUID id) { super(id); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRequestId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRequestId.java index cffea0f6f5..3272d93d6f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRequestId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRequestId.java @@ -16,12 +16,21 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -public class NotificationRequestId extends UUIDBased { +public class NotificationRequestId extends UUIDBased implements EntityId { + @JsonCreator - public NotificationRequestId(UUID id) { + public NotificationRequestId(@JsonProperty("id") UUID id) { super(id); } + + @Override + public EntityType getEntityType() { + return EntityType.NOTIFICATION_REQUEST; + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTargetId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTargetId.java index 3d8dddfe0e..44211d85fd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTargetId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTargetId.java @@ -16,12 +16,21 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -public class NotificationTargetId extends UUIDBased { +public class NotificationTargetId extends UUIDBased implements EntityId { + @JsonCreator - public NotificationTargetId(UUID id) { + public NotificationTargetId(@JsonProperty("id") UUID id) { super(id); } + + @Override + public EntityType getEntityType() { + return EntityType.NOTIFICATION_TARGET; + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java index 02f0fb2aa4..a633ad6433 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java @@ -34,7 +34,9 @@ public class Notification extends SearchTextBased { private NotificationRequestId requestId; private UserId recipientId; + private String reason; private String text; + private NotificationInfo info; private NotificationSeverity severity; private NotificationStatus status; // private UserId senderId; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationStatus.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationStatus.java index 7e504cbe15..3484768860 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationStatus.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationStatus.java @@ -17,6 +17,5 @@ package org.thingsboard.server.common.data.notification; public enum NotificationStatus { SENT, - DELIVERED, READ } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java index 957a6d359f..8b873b5ca9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java @@ -23,12 +23,20 @@ import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + @Data @EqualsAndHashCode(callSuper = true) public class NotificationTarget extends BaseData implements HasTenantId, HasName { + @NotNull private TenantId tenantId; + @NotBlank private String name; + @NotNull + @Valid private NotificationTargetConfig configuration; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/SingleUserNotificationTargetConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/SingleUserNotificationTargetConfig.java index cf0b4316d0..9fbb6ca600 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/SingleUserNotificationTargetConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/SingleUserNotificationTargetConfig.java @@ -18,9 +18,12 @@ package org.thingsboard.server.common.data.notification.targets; import lombok.Data; import org.thingsboard.server.common.data.id.UserId; +import javax.validation.constraints.NotNull; + @Data public class SingleUserNotificationTargetConfig implements NotificationTargetConfig { + @NotNull private UserId userId; @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java index 6e8a4a7076..5256eee7db 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java @@ -15,7 +15,10 @@ */ package org.thingsboard.server.dao.model; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.id.UUIDBased; import javax.persistence.Column; @@ -75,4 +78,16 @@ public abstract class BaseSqlEntity implements BaseEntity { } } + protected JsonNode toJson(Object value) { + if (value != null) { + return JacksonUtil.valueToTree(value); + } else { + return null; + } + } + + protected T fromJson(JsonNode json) { + return JacksonUtil.convertValue(json, new TypeReference() {}); + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 5c938de98c..13d149bcf5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -655,7 +655,9 @@ public class ModelConstants { public static final String NOTIFICATION_TABLE_NAME = "notification"; public static final String NOTIFICATION_REQUEST_ID_PROPERTY = "request_id"; public static final String NOTIFICATION_RECIPIENT_ID_PROPERTY = "recipient_id"; + public static final String NOTIFICATION_REASON_PROPERTY = "reason"; public static final String NOTIFICATION_TEXT_PROPERTY = "text"; + public static final String NOTIFICATION_INFO_PROPERTY = "info"; public static final String NOTIFICATION_SEVERITY_PROPERTY = "severity"; public static final String NOTIFICATION_STATUS_PROPERTY = "status"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java index ad7c506d4b..be3532e300 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java @@ -15,16 +15,22 @@ */ 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.common.util.JacksonUtil; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationSeverity; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; @@ -36,18 +42,26 @@ import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @Entity +@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.NOTIFICATION_TABLE_NAME) public class NotificationEntity extends BaseSqlEntity { - @Column(name = ModelConstants.NOTIFICATION_REQUEST_ID_PROPERTY) + @Column(name = ModelConstants.NOTIFICATION_REQUEST_ID_PROPERTY, nullable = false) private UUID requestId; - @Column(name = ModelConstants.NOTIFICATION_RECIPIENT_ID_PROPERTY) + @Column(name = ModelConstants.NOTIFICATION_RECIPIENT_ID_PROPERTY, nullable = false) private UUID recipientId; - @Column(name = ModelConstants.NOTIFICATION_TEXT_PROPERTY) + @Column(name = ModelConstants.NOTIFICATION_REASON_PROPERTY, nullable = false) + private String reason; + + @Column(name = ModelConstants.NOTIFICATION_TEXT_PROPERTY, nullable = false) private String text; + @Type(type = "json") + @Column(name = ModelConstants.NOTIFICATION_INFO_PROPERTY) + private JsonNode info; + @Column(name = ModelConstants.NOTIFICATION_SEVERITY_PROPERTY) private NotificationSeverity severity; @@ -62,7 +76,12 @@ public class NotificationEntity extends BaseSqlEntity { setCreatedTime(notification.getCreatedTime()); setRequestId(getUuid(notification.getRequestId())); setRecipientId(getUuid(notification.getRecipientId())); + setReason(notification.getReason()); setText(notification.getText()); + if (notification.getInfo() != null) { + setInfo(JacksonUtil.valueToTree(notification.getInfo())); + } + setSeverity(notification.getSeverity()); setStatus(notification.getStatus()); } @@ -73,7 +92,12 @@ public class NotificationEntity extends BaseSqlEntity { notification.setCreatedTime(createdTime); notification.setRequestId(createId(requestId, NotificationRequestId::new)); notification.setRecipientId(createId(recipientId, UserId::new)); + notification.setReason(reason); notification.setText(text); + if (info != null) { + notification.setInfo(JacksonUtil.treeToValue(info, NotificationInfo.class)); + } + notification.setSeverity(severity); notification.setStatus(status); return notification; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java index 23a8ae1059..f469f4aed7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java @@ -47,16 +47,16 @@ import java.util.UUID; @Table(name = ModelConstants.NOTIFICATION_REQUEST_TABLE_NAME) public class NotificationRequestEntity extends BaseSqlEntity { - @Column(name = ModelConstants.TENANT_ID_PROPERTY) + @Column(name = ModelConstants.TENANT_ID_PROPERTY, nullable = false) private UUID tenantId; - @Column(name = ModelConstants.NOTIFICATION_REQUEST_TARGET_ID_PROPERTY) + @Column(name = ModelConstants.NOTIFICATION_REQUEST_TARGET_ID_PROPERTY, nullable = false) private UUID targetId; - @Column(name = ModelConstants.NOTIFICATION_REQUEST_NOTIFICATION_REASON_PROPERTY) + @Column(name = ModelConstants.NOTIFICATION_REQUEST_NOTIFICATION_REASON_PROPERTY, nullable = false) private String notificationReason; - @Column(name = ModelConstants.NOTIFICATION_REQUEST_TEXT_TEMPLATE_PROPERTY) + @Column(name = ModelConstants.NOTIFICATION_REQUEST_TEXT_TEMPLATE_PROPERTY, nullable = false) private String textTemplate; @Type(type = "json") @@ -83,9 +83,9 @@ public class NotificationRequestEntity extends BaseSqlEntity { - @Column(name = ModelConstants.TENANT_ID_PROPERTY) + @Column(name = ModelConstants.TENANT_ID_PROPERTY, nullable = false) private UUID tenantId; - @Column(name = ModelConstants.NAME_PROPERTY) + @Column(name = ModelConstants.NAME_PROPERTY, nullable = false) private String name; @Type(type = "json") - @Column(name = ModelConstants.NOTIFICATION_TARGET_CONFIGURATION_PROPERTY) + @Column(name = ModelConstants.NOTIFICATION_TARGET_CONFIGURATION_PROPERTY, nullable = false) private JsonNode configuration; public NotificationTargetEntity() {} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java index 9ee5b07121..07fc48e13f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java @@ -40,4 +40,6 @@ public interface NotificationRepository extends JpaRepository Date: Mon, 31 Oct 2022 14:05:08 +0200 Subject: [PATCH 004/111] WebSocket API for notifications; refactoring --- .../controller/AbstractRpcController.java | 2 +- .../controller/NotificationController.java | 20 +- .../server/controller/RpcV2Controller.java | 2 +- .../controller/TelemetryController.java | 4 +- .../controller/plugin/TbWebSocketHandler.java | 54 +++-- .../exception/AccessDeniedException.java | 2 +- .../exception/EntityNotFoundException.java | 2 +- .../exception/InternalErrorException.java | 2 +- .../exception/InvalidParametersException.java | 2 +- .../exception/ToErrorResponseEntity.java | 2 +- .../exception/UnauthorizedException.java | 2 +- .../exception/UncheckedApiException.java | 2 +- .../DefaultNotificationProcessingService.java | 58 +++-- .../NotificationProcessingService.java | 6 + .../queue/DefaultTbCoreConsumerService.java | 25 ++ .../service/security/AccessValidator.java | 2 +- .../service/security/ValidationCallback.java | 8 +- .../permission/TenantAdminPermissions.java | 1 + .../DefaultSubscriptionManagerService.java | 49 +++- ...efaultTbEntityDataSubscriptionService.java | 45 ++-- .../DefaultTbLocalSubscriptionService.java | 30 ++- .../subscription/ReadTsKvQueryInfo.java | 2 +- .../SubscriptionErrorCode.java | 2 +- .../SubscriptionManagerService.java | 6 + .../subscription/TbAbstractDataSubCtx.java | 14 +- .../subscription/TbAbstractSubCtx.java | 19 +- .../subscription/TbAlarmDataSubCtx.java | 16 +- .../subscription/TbAlarmsSubscription.java | 9 +- .../subscription/TbAttributeSubscription.java | 6 +- .../subscription/TbEntityCountSubCtx.java | 10 +- .../subscription/TbEntityDataSubCtx.java | 18 +- .../TbEntityDataSubscriptionService.java | 17 +- .../TbLocalSubscriptionService.java | 7 +- .../service/subscription/TbSubscription.java | 2 +- .../subscription/TbSubscriptionType.java | 2 +- .../subscription/TbSubscriptionUtils.java | 71 +++++- .../TbTimeseriesSubscription.java | 6 +- .../AbstractSubscriptionService.java | 22 +- .../DefaultAlarmSubscriptionService.java | 32 +-- .../DefaultTelemetrySubscriptionService.java | 91 +++---- ...vice.java => DefaultWebSocketService.java} | 225 +++++++++++------- .../telemetry/InternalTelemetryService.java | 2 - .../{telemetry => ws}/SessionEvent.java | 2 +- .../WebSocketMsgEndpoint.java} | 11 +- .../WebSocketSessionRef.java} | 30 +-- .../service/ws/WebSocketSessionType.java | 38 +++ .../{telemetry => ws}/WsSessionMetaData.java | 10 +- .../cmd/MarkNotificationAsReadCmd.java | 30 +++ .../cmd/NotificationCmdsWrapper.java | 28 +++ .../notification/cmd/NotificationsSubCmd.java | 28 +++ .../cmd/NotificationsUnsubCmd.java | 24 ++ .../cmd/UnreadNotificationsUpdate.java | 54 +++++ ...faultNotificationsSubscriptionService.java | 183 ++++++++++++++ .../sub/NotificationsSubscription.java | 64 +++++ .../sub/NotificationsSubscriptionService.java | 42 ++++ .../sub/NotificationsSubscriptionUpdate.java | 32 +++ .../{ => ws}/telemetry/TelemetryFeature.java | 2 +- .../telemetry/TelemetryWebSocketTextMsg.java | 5 +- .../telemetry/WebSocketService.java} | 15 +- .../cmd/TelemetryPluginCmdsWrapper.java | 20 +- .../cmd/v1/AttributesSubscriptionCmd.java | 4 +- .../telemetry/cmd/v1/GetHistoryCmd.java | 2 +- .../telemetry/cmd/v1/SubscriptionCmd.java | 4 +- .../telemetry/cmd/v1/TelemetryPluginCmd.java | 2 +- .../cmd/v1/TimeseriesSubscriptionCmd.java | 4 +- .../telemetry/cmd/v2/AggHistoryCmd.java | 2 +- .../{ => ws}/telemetry/cmd/v2/AggKey.java | 2 +- .../telemetry/cmd/v2/AggTimeSeriesCmd.java | 2 +- .../telemetry/cmd/v2/AlarmDataCmd.java | 2 +- .../cmd/v2/AlarmDataUnsubscribeCmd.java | 2 +- .../telemetry/cmd/v2/AlarmDataUpdate.java | 4 +- .../{ => ws}/telemetry/cmd/v2/CmdUpdate.java | 2 +- .../telemetry/cmd/v2/CmdUpdateType.java | 5 +- .../{ => ws}/telemetry/cmd/v2/DataCmd.java | 3 +- .../{ => ws}/telemetry/cmd/v2/DataUpdate.java | 4 +- .../telemetry/cmd/v2/EntityCountCmd.java | 3 +- .../cmd/v2/EntityCountUnsubscribeCmd.java | 2 +- .../telemetry/cmd/v2/EntityCountUpdate.java | 8 +- .../telemetry/cmd/v2/EntityDataCmd.java | 2 +- .../cmd/v2/EntityDataUnsubscribeCmd.java | 2 +- .../telemetry/cmd/v2/EntityDataUpdate.java | 4 +- .../telemetry/cmd/v2/EntityHistoryCmd.java | 2 +- .../{ => ws}/telemetry/cmd/v2/GetTsCmd.java | 2 +- .../telemetry/cmd/v2/LatestValueCmd.java | 2 +- .../telemetry/cmd/v2/TimeSeriesCmd.java | 2 +- .../telemetry/cmd/v2/UnsubscribeCmd.java | 2 +- .../sub/AlarmSubscriptionUpdate.java | 12 +- .../telemetry/sub/SubscriptionState.java | 4 +- .../sub/TelemetrySubscriptionUpdate.java | 3 +- .../controller/AbstractControllerTest.java | 21 +- .../controller/BaseWebsocketApiTest.java | 6 +- .../controller/TbTestWebSocketClient.java | 16 +- .../NotificationsWebSocketClient.java | 75 ++++++ .../notification/NotificationsWsApiTest.java | 147 ++++++++++++ .../lwm2m/AbstractLwM2MIntegrationTest.java | 8 +- ...AbstractMqttAttributesIntegrationTest.java | 3 +- common/cluster-api/src/main/proto/queue.proto | 30 +++ .../server/common/data/StringUtils.java | 9 + .../DefaultNotificationService.java | 17 +- .../DefaultNotificationTargetService.java | 6 + 100 files changed, 1481 insertions(+), 468 deletions(-) rename application/src/main/java/org/thingsboard/server/{service/telemetry => }/exception/AccessDeniedException.java (94%) rename application/src/main/java/org/thingsboard/server/{service/telemetry => }/exception/EntityNotFoundException.java (94%) rename application/src/main/java/org/thingsboard/server/{service/telemetry => }/exception/InternalErrorException.java (94%) rename application/src/main/java/org/thingsboard/server/{service/telemetry => }/exception/InvalidParametersException.java (94%) rename application/src/main/java/org/thingsboard/server/{service/telemetry => }/exception/ToErrorResponseEntity.java (93%) rename application/src/main/java/org/thingsboard/server/{service/telemetry => }/exception/UnauthorizedException.java (94%) rename application/src/main/java/org/thingsboard/server/{service/telemetry => }/exception/UncheckedApiException.java (95%) rename application/src/main/java/org/thingsboard/server/service/{telemetry/sub => subscription}/SubscriptionErrorCode.java (96%) rename application/src/main/java/org/thingsboard/server/service/telemetry/{DefaultTelemetryWebSocketService.java => DefaultWebSocketService.java} (82%) rename application/src/main/java/org/thingsboard/server/service/{telemetry => ws}/SessionEvent.java (96%) rename application/src/main/java/org/thingsboard/server/service/{telemetry/TelemetryWebSocketMsgEndpoint.java => ws/WebSocketMsgEndpoint.java} (63%) rename application/src/main/java/org/thingsboard/server/service/{telemetry/TelemetryWebSocketSessionRef.java => ws/WebSocketSessionRef.java} (70%) create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java rename application/src/main/java/org/thingsboard/server/service/{telemetry => ws}/WsSessionMetaData.java (80%) create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationAsReadCmd.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/sub/DefaultNotificationsSubscriptionService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionUpdate.java rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/TelemetryFeature.java (94%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/TelemetryWebSocketTextMsg.java (82%) rename application/src/main/java/org/thingsboard/server/service/{telemetry/TelemetryWebSocketService.java => ws/telemetry/WebSocketService.java} (63%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/TelemetryPluginCmdsWrapper.java (62%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v1/AttributesSubscriptionCmd.java (87%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v1/GetHistoryCmd.java (94%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v1/SubscriptionCmd.java (90%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v1/TelemetryPluginCmd.java (92%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java (89%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/AggHistoryCmd.java (92%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/AggKey.java (93%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/AggTimeSeriesCmd.java (92%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/AlarmDataCmd.java (94%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java (92%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/AlarmDataUpdate.java (94%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/CmdUpdate.java (94%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/CmdUpdateType.java (87%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/DataCmd.java (89%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/DataUpdate.java (91%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/EntityCountCmd.java (90%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java (92%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/EntityCountUpdate.java (85%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/EntityDataCmd.java (97%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java (92%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/EntityDataUpdate.java (93%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/EntityHistoryCmd.java (94%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/GetTsCmd.java (93%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/LatestValueCmd.java (92%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/TimeSeriesCmd.java (95%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/UnsubscribeCmd.java (91%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/sub/AlarmSubscriptionUpdate.java (85%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/sub/SubscriptionState.java (95%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/sub/TelemetrySubscriptionUpdate.java (96%) create mode 100644 application/src/test/java/org/thingsboard/server/service/notification/NotificationsWebSocketClient.java create mode 100644 application/src/test/java/org/thingsboard/server/service/notification/NotificationsWsApiTest.java diff --git a/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java b/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java index e5a217893e..946a743370 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java @@ -43,7 +43,7 @@ import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.security.AccessValidator; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; -import org.thingsboard.server.service.telemetry.exception.ToErrorResponseEntity; +import org.thingsboard.server.exception.ToErrorResponseEntity; import javax.annotation.Nullable; import java.util.Optional; diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index 454a8422e5..f6f48aeb51 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -19,6 +19,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -29,6 +30,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationStatus; @@ -71,7 +73,7 @@ public class NotificationController extends BaseController { public void markNotificationAsRead(@PathVariable UUID id, @AuthenticationPrincipal SecurityUser user) { NotificationId notificationId = new NotificationId(id); - notificationService.updateNotificationStatus(user.getTenantId(), notificationId, NotificationStatus.READ); + notificationProcessingService.markNotificationAsRead(user, notificationId); } @@ -83,6 +85,14 @@ public class NotificationController extends BaseController { return notificationProcessingService.processNotificationRequest(user, notificationRequest); } + @GetMapping("/notification/request/{id}") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationRequest getNotificationRequestById(@PathVariable UUID id, + @AuthenticationPrincipal SecurityUser user) { + NotificationRequestId notificationRequestId = new NotificationRequestId(id); + return notificationService.findNotificationRequestById(user.getTenantId(), notificationRequestId); + } + @GetMapping("/notification/requests") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public PageData getNotificationRequests(@RequestParam int pageSize, @@ -96,9 +106,11 @@ public class NotificationController extends BaseController { return notificationService.findNotificationRequestsByTenantIdAndPageLink(user.getTenantId(), pageLink); } - // delete request and sent notifications - public void deleteNotificationRequest() { - + @DeleteMapping("/notification/request/{id}") + public void deleteNotificationRequest(@PathVariable UUID id, + @AuthenticationPrincipal SecurityUser user) { + NotificationRequestId notificationRequestId = new NotificationRequestId(id); + notificationProcessingService.deleteNotificationRequest(user, notificationRequestId); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java b/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java index 300d522dbe..ea795dabfb 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java @@ -47,7 +47,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.rpc.RemoveRpcActorMsg; import org.thingsboard.server.service.security.permission.Operation; -import org.thingsboard.server.service.telemetry.exception.ToErrorResponseEntity; +import org.thingsboard.server.exception.ToErrorResponseEntity; import javax.annotation.Nullable; import java.util.UUID; diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index 4498b51c6c..026ce24723 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -84,8 +84,8 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.telemetry.AttributeData; import org.thingsboard.server.service.telemetry.TsData; -import org.thingsboard.server.service.telemetry.exception.InvalidParametersException; -import org.thingsboard.server.service.telemetry.exception.UncheckedApiException; +import org.thingsboard.server.exception.InvalidParametersException; +import org.thingsboard.server.exception.UncheckedApiException; import javax.annotation.Nullable; import javax.annotation.PostConstruct; diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index 432b35fd80..187603afc5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -38,10 +38,11 @@ import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; -import org.thingsboard.server.service.telemetry.SessionEvent; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketMsgEndpoint; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; +import org.thingsboard.server.service.ws.SessionEvent; +import org.thingsboard.server.service.ws.WebSocketMsgEndpoint; +import org.thingsboard.server.service.ws.WebSocketSessionType; +import org.thingsboard.server.service.ws.telemetry.WebSocketService; +import org.thingsboard.server.service.ws.WebSocketSessionRef; import javax.websocket.RemoteEndpoint; import javax.websocket.SendHandler; @@ -57,19 +58,19 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.LinkedBlockingQueue; -import static org.thingsboard.server.service.telemetry.DefaultTelemetryWebSocketService.NUMBER_OF_PING_ATTEMPTS; +import static org.thingsboard.server.service.telemetry.DefaultWebSocketService.NUMBER_OF_PING_ATTEMPTS; @Service @TbCoreComponent @Slf4j -public class TbWebSocketHandler extends TextWebSocketHandler implements TelemetryWebSocketMsgEndpoint { +public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocketMsgEndpoint { private static final ConcurrentMap internalSessionMap = new ConcurrentHashMap<>(); private static final ConcurrentMap externalSessionMap = new ConcurrentHashMap<>(); @Autowired - private TelemetryWebSocketService webSocketService; + private WebSocketService webSocketService; @Autowired private TbTenantProfileCache tenantProfileCache; @@ -79,7 +80,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr @Value("${server.ws.ping_timeout:30000}") private long pingTimeout; - private ConcurrentMap blacklistedSessions = new ConcurrentHashMap<>(); + private ConcurrentMap blacklistedSessions = new ConcurrentHashMap<>(); private ConcurrentMap perSessionUpdateLimits = new ConcurrentHashMap<>(); private ConcurrentMap> tenantSessionsMap = new ConcurrentHashMap<>(); @@ -130,7 +131,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } } String internalSessionId = session.getId(); - TelemetryWebSocketSessionRef sessionRef = toRef(session); + WebSocketSessionRef sessionRef = toRef(session); String externalSessionId = sessionRef.getSessionId(); if (!checkLimits(session, sessionRef)) { @@ -178,7 +179,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } } - private void processInWebSocketService(TelemetryWebSocketSessionRef sessionRef, SessionEvent event) { + private void processInWebSocketService(WebSocketSessionRef sessionRef, SessionEvent event) { try { webSocketService.handleWebSocketSessionEvent(sessionRef, event); } catch (BeanCreationNotAllowedException e) { @@ -186,7 +187,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } } - private TelemetryWebSocketSessionRef toRef(WebSocketSession session) throws IOException { + private WebSocketSessionRef toRef(WebSocketSession session) throws IOException { URI sessionUri = session.getUri(); String path = sessionUri.getPath(); path = path.substring(WebSocketConfiguration.WS_PLUGIN_PREFIX.length()); @@ -195,25 +196,30 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } String[] pathElements = path.split("/"); String serviceToken = pathElements[0]; - if (!"telemetry".equalsIgnoreCase(serviceToken)) { - throw new InvalidParameterException("Can't find plugin with specified token!"); - } else { - SecurityUser currentUser = (SecurityUser) ((Authentication) session.getPrincipal()).getPrincipal(); - return new TelemetryWebSocketSessionRef(UUID.randomUUID().toString(), currentUser, session.getLocalAddress(), session.getRemoteAddress()); - } + WebSocketSessionType sessionType = WebSocketSessionType.forName(serviceToken) + .orElseThrow(() -> new InvalidParameterException("Can't find plugin with specified token!")); + + SecurityUser currentUser = (SecurityUser) ((Authentication) session.getPrincipal()).getPrincipal(); + return WebSocketSessionRef.builder() + .sessionId(UUID.randomUUID().toString()) + .securityCtx(currentUser) + .localAddress(session.getLocalAddress()) + .remoteAddress(session.getRemoteAddress()) + .sessionType(sessionType) + .build(); } private class SessionMetaData implements SendHandler { private final WebSocketSession session; private final RemoteEndpoint.Async asyncRemote; - private final TelemetryWebSocketSessionRef sessionRef; + private final WebSocketSessionRef sessionRef; private volatile boolean isSending = false; private final Queue> msgQueue; private volatile long lastActivityTime; - SessionMetaData(WebSocketSession session, TelemetryWebSocketSessionRef sessionRef, int maxMsgQueuePerSession) { + SessionMetaData(WebSocketSession session, WebSocketSessionRef sessionRef, int maxMsgQueuePerSession) { super(); this.session = session; Session nativeSession = ((NativeWebSocketSession) session).getNativeSession(Session.class); @@ -309,7 +315,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } @Override - public void send(TelemetryWebSocketSessionRef sessionRef, int subscriptionId, String msg) throws IOException { + public void send(WebSocketSessionRef sessionRef, int subscriptionId, String msg) throws IOException { String externalId = sessionRef.getSessionId(); log.debug("[{}] Processing {}", externalId, msg); String internalId = externalSessionMap.get(externalId); @@ -343,7 +349,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } @Override - public void sendPing(TelemetryWebSocketSessionRef sessionRef, long currentTime) throws IOException { + public void sendPing(WebSocketSessionRef sessionRef, long currentTime) throws IOException { String externalId = sessionRef.getSessionId(); String internalId = externalSessionMap.get(externalId); if (internalId != null) { @@ -359,7 +365,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } @Override - public void close(TelemetryWebSocketSessionRef sessionRef, CloseStatus reason) throws IOException { + public void close(WebSocketSessionRef sessionRef, CloseStatus reason) throws IOException { String externalId = sessionRef.getSessionId(); log.debug("[{}] Processing close request", externalId); String internalId = externalSessionMap.get(externalId); @@ -375,7 +381,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } } - private boolean checkLimits(WebSocketSession session, TelemetryWebSocketSessionRef sessionRef) throws Exception { + private boolean checkLimits(WebSocketSession session, WebSocketSessionRef sessionRef) throws Exception { var tenantProfileConfiguration = tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultProfileConfiguration(); if (tenantProfileConfiguration == null) { @@ -443,7 +449,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr return true; } - private void cleanupLimits(WebSocketSession session, TelemetryWebSocketSessionRef sessionRef) { + private void cleanupLimits(WebSocketSession session, WebSocketSessionRef sessionRef) { var tenantProfileConfiguration = tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultProfileConfiguration(); String sessionId = session.getId(); diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/AccessDeniedException.java b/application/src/main/java/org/thingsboard/server/exception/AccessDeniedException.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/telemetry/exception/AccessDeniedException.java rename to application/src/main/java/org/thingsboard/server/exception/AccessDeniedException.java index 43a7f8c56f..d427c20dcc 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/AccessDeniedException.java +++ b/application/src/main/java/org/thingsboard/server/exception/AccessDeniedException.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.exception; +package org.thingsboard.server.exception; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/EntityNotFoundException.java b/application/src/main/java/org/thingsboard/server/exception/EntityNotFoundException.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/telemetry/exception/EntityNotFoundException.java rename to application/src/main/java/org/thingsboard/server/exception/EntityNotFoundException.java index 3ede4e7e70..574b40feab 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/EntityNotFoundException.java +++ b/application/src/main/java/org/thingsboard/server/exception/EntityNotFoundException.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.exception; +package org.thingsboard.server.exception; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/InternalErrorException.java b/application/src/main/java/org/thingsboard/server/exception/InternalErrorException.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/telemetry/exception/InternalErrorException.java rename to application/src/main/java/org/thingsboard/server/exception/InternalErrorException.java index ae68ffdc3d..05e9b3e937 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/InternalErrorException.java +++ b/application/src/main/java/org/thingsboard/server/exception/InternalErrorException.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.exception; +package org.thingsboard.server.exception; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/InvalidParametersException.java b/application/src/main/java/org/thingsboard/server/exception/InvalidParametersException.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/telemetry/exception/InvalidParametersException.java rename to application/src/main/java/org/thingsboard/server/exception/InvalidParametersException.java index 7452d25a56..467e8cf4e2 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/InvalidParametersException.java +++ b/application/src/main/java/org/thingsboard/server/exception/InvalidParametersException.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.exception; +package org.thingsboard.server.exception; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/ToErrorResponseEntity.java b/application/src/main/java/org/thingsboard/server/exception/ToErrorResponseEntity.java similarity index 93% rename from application/src/main/java/org/thingsboard/server/service/telemetry/exception/ToErrorResponseEntity.java rename to application/src/main/java/org/thingsboard/server/exception/ToErrorResponseEntity.java index 769c5ffe5e..f44effc827 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/ToErrorResponseEntity.java +++ b/application/src/main/java/org/thingsboard/server/exception/ToErrorResponseEntity.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.exception; +package org.thingsboard.server.exception; import org.springframework.http.ResponseEntity; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/UnauthorizedException.java b/application/src/main/java/org/thingsboard/server/exception/UnauthorizedException.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/telemetry/exception/UnauthorizedException.java rename to application/src/main/java/org/thingsboard/server/exception/UnauthorizedException.java index b597a84e29..f7c046abe2 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/UnauthorizedException.java +++ b/application/src/main/java/org/thingsboard/server/exception/UnauthorizedException.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.exception; +package org.thingsboard.server.exception; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/UncheckedApiException.java b/application/src/main/java/org/thingsboard/server/exception/UncheckedApiException.java similarity index 95% rename from application/src/main/java/org/thingsboard/server/service/telemetry/exception/UncheckedApiException.java rename to application/src/main/java/org/thingsboard/server/exception/UncheckedApiException.java index 8ee4bfa753..1d7658fdfb 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/UncheckedApiException.java +++ b/application/src/main/java/org/thingsboard/server/exception/UncheckedApiException.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.exception; +package org.thingsboard.server.exception; import org.springframework.http.ResponseEntity; diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java index 04bc45b9d4..862ce7389a 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java @@ -15,12 +15,15 @@ */ package org.thingsboard.server.service.notification; +import com.google.common.base.Strings; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; 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.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; @@ -29,13 +32,12 @@ import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.user.UserService; -import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.AccessControlService; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; -import org.thingsboard.server.service.telemetry.AbstractSubscriptionService; +import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionService; import java.util.ArrayList; import java.util.List; @@ -43,26 +45,15 @@ import java.util.Map; @Service @Slf4j -public class DefaultNotificationProcessingService extends AbstractSubscriptionService implements NotificationProcessingService { +@RequiredArgsConstructor +public class DefaultNotificationProcessingService implements NotificationProcessingService { private final NotificationTargetService notificationTargetService; private final NotificationService notificationService; private final UserService userService; private final AccessControlService accessControlService; private final DbCallbackExecutorService dbCallbackExecutorService; - - public DefaultNotificationProcessingService(TbClusterService clusterService, PartitionService partitionService, - NotificationTargetService notificationTargetService, - NotificationService notificationService, UserService userService, - AccessControlService accessControlService, - DbCallbackExecutorService dbCallbackExecutorService) { - super(clusterService, partitionService); - this.notificationTargetService = notificationTargetService; - this.notificationService = notificationService; - this.userService = userService; - this.accessControlService = accessControlService; - this.dbCallbackExecutorService = dbCallbackExecutorService; - } + private final NotificationsSubscriptionService notificationsSubscriptionService; @Override public NotificationRequest processNotificationRequest(SecurityUser user, NotificationRequest notificationRequest) throws ThingsboardException { @@ -83,19 +74,33 @@ public class DefaultNotificationProcessingService extends AbstractSubscriptionSe for (User recipient : recipients) { dbCallbackExecutorService.submit(() -> { - Notification notification = createNotification(recipient, notificationRequest); - onNewNotification(notification); + Notification notification = createNotification(recipient, savedNotificationRequest); + notificationsSubscriptionService.onNewNotification(recipient.getTenantId(), recipient.getId(), notification); }); } return savedNotificationRequest; } + @Override + public void markNotificationAsRead(SecurityUser user, NotificationId notificationId) { + Notification notification = notificationService.updateNotificationStatus(user.getTenantId(), notificationId, NotificationStatus.READ); + notificationsSubscriptionService.onNotificationUpdated(user.getTenantId(), user.getId(), notification); + } + + @Override + public void deleteNotificationRequest(SecurityUser user, NotificationRequestId notificationRequestId) { + notificationService.deleteNotificationRequest(user.getTenantId(), notificationRequestId); + notificationsSubscriptionService.onNotificationRequestDeleted(user.getTenantId(), notificationRequestId); + } + private Notification createNotification(User recipient, NotificationRequest notificationRequest) { Notification notification = Notification.builder() .requestId(notificationRequest.getId()) .recipientId(recipient.getId()) + .reason(notificationRequest.getNotificationReason()) .text(formatNotificationText(notificationRequest.getTextTemplate(), recipient)) + .info(notificationRequest.getNotificationInfo()) .severity(notificationRequest.getNotificationSeverity()) .status(NotificationStatus.SENT) .build(); @@ -103,24 +108,15 @@ public class DefaultNotificationProcessingService extends AbstractSubscriptionSe return notification; } - private void onNewNotification(Notification notification) { - wsCallBackExecutor.submit(() -> { - - }) - } - private String formatNotificationText(String template, User recipient) { Map context = Map.of( "recipientEmail", recipient.getEmail(), - "recipientFirstName", recipient.getFirstName(), - "recipientLastName", recipient.getLastName() + "recipientFirstName", Strings.nullToEmpty(recipient.getFirstName()), + "recipientLastName", Strings.nullToEmpty(recipient.getLastName()) ); return TbNodeUtils.processTemplate(template, context); } - @Override - protected String getExecutorPrefix() { - return "notification"; - } + // handle markAsRead and deleteNotificationRequest - send UnreadNotificationsUpdate with updated list } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingService.java index 1293ba999d..57aba583ed 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingService.java @@ -16,6 +16,8 @@ package org.thingsboard.server.service.notification; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.service.security.model.SecurityUser; @@ -23,4 +25,8 @@ public interface NotificationProcessingService { NotificationRequest processNotificationRequest(SecurityUser user, NotificationRequest notificationRequest) throws ThingsboardException; + void markNotificationAsRead(SecurityUser user, NotificationId notificationId); + + void deleteNotificationRequest(SecurityUser user, NotificationRequestId notificationRequestId); + } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index ec58ac1aa9..0a7abdab51 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -27,7 +27,10 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.TbActorMsg; @@ -77,6 +80,7 @@ import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.sync.vc.GitVersionControlQueueService; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; +import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -456,6 +460,13 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService subscriptions = subscriptionsByEntityId.get(recipientId); + if (subscriptions != null) { + NotificationsSubscriptionUpdate subscriptionUpdate = NotificationsSubscriptionUpdate.builder() + .notification(notification) + .build(); + subscriptions.stream() + .filter(subscription -> subscription.getType() == TbSubscriptionType.NOTIFICATIONS) + .forEach(subscription -> { + if (serviceId.equals(subscription.getServiceId())) { + localSubscriptionService.onSubscriptionUpdate(subscription.getSessionId(), + subscription.getSubscriptionId(), subscriptionUpdate, TbCallback.EMPTY); + } else { + TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, subscription.getServiceId()); + ToCoreNotificationMsg updateProto = TbSubscriptionUtils.notificationsSubUpdateToProto(subscription, subscriptionUpdate); + TbProtoQueueMsg queueMsg = new TbProtoQueueMsg<>(subscription.getEntityId().getId(), updateProto); + toCoreNotificationsProducer.send(tpi, queueMsg, null); + } + }); + } + callback.onSuccess(); + } + + @Override + public void onNotificationRequestDeleted(TenantId tenantId, NotificationRequestId notificationRequestId, TbCallback callback) { + NotificationsSubscriptionUpdate subscriptionUpdate = NotificationsSubscriptionUpdate.builder() + .notificationRequestDeleted(true) + .notificationRequestId(notificationRequestId) + .build(); + subscriptionsByEntityId.entrySet().stream() + .filter(subEntry -> subEntry.getKey().getEntityType() == EntityType.USER) + .flatMap(subEntry -> subEntry.getValue().stream() + .filter(sub -> sub.getType() == TbSubscriptionType.NOTIFICATIONS) + .filter(sub -> sub.getServiceId().equals(serviceId))) + .forEach(subscription -> { + localSubscriptionService.onSubscriptionUpdate(subscription.getSessionId(), subscription.getSubscriptionId(), subscriptionUpdate, TbCallback.EMPTY); + }); + callback.onSuccess(); + } + @Override public void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List keys, TbCallback callback) { onLocalTelemetrySubUpdate(entityId, diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java index a5a45be5b2..eed12acf3e 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java @@ -48,22 +48,21 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.executors.DbCallbackExecutorService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; -import org.thingsboard.server.service.telemetry.cmd.v2.AggHistoryCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.AggKey; -import org.thingsboard.server.service.telemetry.cmd.v2.AggTimeSeriesCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUpdate; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.GetTsCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd; -import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; +import org.thingsboard.server.service.ws.telemetry.WebSocketService; +import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggHistoryCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggKey; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggTimeSeriesCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityHistoryCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.GetTsCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.TimeSeriesCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -95,7 +94,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc private final Map> subscriptionsBySessionId = new ConcurrentHashMap<>(); @Autowired - private TelemetryWebSocketService wsService; + private WebSocketService wsService; @Autowired private EntityService entityService; @@ -166,7 +165,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc } @Override - public void handleCmd(TelemetryWebSocketSessionRef session, EntityDataCmd cmd) { + public void handleCmd(WebSocketSessionRef session, EntityDataCmd cmd) { TbEntityDataSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId()); if (ctx != null) { log.debug("[{}][{}] Updating existing subscriptions using: {}", session.getSessionId(), cmd.getCmdId(), cmd); @@ -357,7 +356,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc } @Override - public void handleCmd(TelemetryWebSocketSessionRef session, EntityCountCmd cmd) { + public void handleCmd(WebSocketSessionRef session, EntityCountCmd cmd) { TbEntityCountSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId()); if (ctx == null) { ctx = createSubCtx(session, cmd); @@ -377,7 +376,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc } @Override - public void handleCmd(TelemetryWebSocketSessionRef session, AlarmDataCmd cmd) { + public void handleCmd(WebSocketSessionRef session, AlarmDataCmd cmd) { TbAlarmDataSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId()); if (ctx == null) { log.debug("[{}][{}] Creating new alarm subscription using: {}", session.getSessionId(), cmd.getCmdId(), cmd); @@ -468,7 +467,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc } } - private TbEntityDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) { + private TbEntityDataSubCtx createSubCtx(WebSocketSessionRef sessionRef, EntityDataCmd cmd) { Map sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); TbEntityDataSubCtx ctx = new TbEntityDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmd.getCmdId(), maxEntitiesPerDataSubscription); @@ -479,7 +478,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc return ctx; } - private TbEntityCountSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) { + private TbEntityCountSubCtx createSubCtx(WebSocketSessionRef sessionRef, EntityCountCmd cmd) { Map sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); TbEntityCountSubCtx ctx = new TbEntityCountSubCtx(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmd.getCmdId()); @@ -491,7 +490,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc } - private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) { + private TbAlarmDataSubCtx createSubCtx(WebSocketSessionRef sessionRef, AlarmDataCmd cmd) { Map sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription, diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java index 968e15ea4b..e9001f3be3 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java @@ -21,18 +21,19 @@ import org.springframework.context.annotation.Lazy; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardExecutors; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent; -import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; -import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.msg.queue.ServiceType; -import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; +import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.cluster.TbClusterService; -import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -152,7 +153,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer update.getLatestValues().forEach((key, value) -> attrSub.getKeyStates().put(key, value)); break; } - subscriptionUpdateExecutor.submit(() -> subscription.getUpdateConsumer().accept(sessionId, update)); + subscriptionUpdateExecutor.submit(() -> subscription.getUpdateProcessor().accept(subscription, update)); } callback.onSuccess(); } @@ -163,7 +164,16 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer TbSubscription subscription = subscriptionsBySessionId .getOrDefault(sessionId, Collections.emptyMap()).get(update.getSubscriptionId()); if (subscription != null && subscription.getType() == TbSubscriptionType.ALARMS) { - subscriptionUpdateExecutor.submit(() -> subscription.getUpdateConsumer().accept(sessionId, update)); + subscriptionUpdateExecutor.submit(() -> subscription.getUpdateProcessor().accept(subscription, update)); + } + callback.onSuccess(); + } + + @Override + public void onSubscriptionUpdate(String sessionId, int subscriptionId, NotificationsSubscriptionUpdate update, TbCallback callback) { + TbSubscription subscription = subscriptionsBySessionId.getOrDefault(sessionId, Collections.emptyMap()).get(subscriptionId); + if (subscription != null && subscription.getType() == TbSubscriptionType.NOTIFICATIONS) { + subscriptionUpdateExecutor.submit(() -> subscription.getUpdateProcessor().accept(subscription, update)); } callback.onSuccess(); } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/ReadTsKvQueryInfo.java b/application/src/main/java/org/thingsboard/server/service/subscription/ReadTsKvQueryInfo.java index 28ca2f6729..b80181ab84 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/ReadTsKvQueryInfo.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/ReadTsKvQueryInfo.java @@ -17,7 +17,7 @@ package org.thingsboard.server.service.subscription; import lombok.Data; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; -import org.thingsboard.server.service.telemetry.cmd.v2.AggKey; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggKey; @Data public class ReadTsKvQueryInfo { diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/SubscriptionErrorCode.java b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionErrorCode.java similarity index 96% rename from application/src/main/java/org/thingsboard/server/service/telemetry/sub/SubscriptionErrorCode.java rename to application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionErrorCode.java index e5fe16de01..b6d156aa10 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/SubscriptionErrorCode.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionErrorCode.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.sub; +package org.thingsboard.server.service.subscription; public enum SubscriptionErrorCode { diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java index 37c6181910..6724674143 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java @@ -18,9 +18,12 @@ package org.thingsboard.server.service.subscription; import org.springframework.context.ApplicationListener; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; @@ -46,5 +49,8 @@ public interface SubscriptionManagerService extends ApplicationListener data; - public TbAbstractDataSubCtx(String serviceId, TelemetryWebSocketService wsService, + public TbAbstractDataSubCtx(String serviceId, WebSocketService wsService, EntityService entityService, TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService, SubscriptionServiceStatistics stats, - TelemetryWebSocketSessionRef sessionRef, int cmdId) { + WebSocketSessionRef sessionRef, int cmdId) { super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId); this.subToEntityIdMap = new ConcurrentHashMap<>(); } @@ -184,7 +184,7 @@ public abstract class TbAbstractDataSubCtx sendWsMsg(s, subscriptionUpdate, keysType)) + .updateProcessor((sub, subscriptionUpdate) -> sendWsMsg(sub.getSessionId(), subscriptionUpdate, keysType)) .allKeys(false) .keyStates(keyStates) .scope(scope) @@ -215,7 +215,7 @@ public abstract class TbAbstractDataSubCtx sendWsMsg(sessionId, subscriptionUpdate, EntityKeyType.TIME_SERIES, resultToLatestValues)) + .updateProcessor((sub, subscriptionUpdate) -> sendWsMsg(sub.getSessionId(), subscriptionUpdate, EntityKeyType.TIME_SERIES, resultToLatestValues)) .allKeys(false) .keyStates(keyStates) .latestValues(latestValues) diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java index 20e81f0662..d7fde23b31 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java @@ -31,7 +31,6 @@ import org.thingsboard.server.common.data.query.ComplexFilterPredicate; import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValueSourceType; import org.thingsboard.server.common.data.query.EntityCountQuery; -import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.FilterPredicateType; import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.data.query.KeyFilterPredicate; @@ -39,10 +38,10 @@ import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate; import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entity.EntityService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; -import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.telemetry.WebSocketService; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; import java.util.ArrayList; import java.util.HashMap; @@ -64,11 +63,11 @@ public abstract class TbAbstractSubCtx { protected final Lock wsLock = new ReentrantLock(true); protected final String serviceId; protected final SubscriptionServiceStatistics stats; - private final TelemetryWebSocketService wsService; + private final WebSocketService wsService; protected final EntityService entityService; protected final TbLocalSubscriptionService localSubscriptionService; protected final AttributesService attributesService; - protected final TelemetryWebSocketSessionRef sessionRef; + protected final WebSocketSessionRef sessionRef; protected final int cmdId; protected final Set subToDynamicValueKeySet; @Getter @@ -80,10 +79,10 @@ public abstract class TbAbstractSubCtx { protected volatile ScheduledFuture refreshTask; protected volatile boolean stopped; - public TbAbstractSubCtx(String serviceId, TelemetryWebSocketService wsService, + public TbAbstractSubCtx(String serviceId, WebSocketService wsService, EntityService entityService, TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService, SubscriptionServiceStatistics stats, - TelemetryWebSocketSessionRef sessionRef, int cmdId) { + WebSocketSessionRef sessionRef, int cmdId) { this.serviceId = serviceId; this.wsService = wsService; this.entityService = entityService; @@ -142,7 +141,7 @@ public abstract class TbAbstractSubCtx { .subscriptionId(subIdx) .tenantId(sessionRef.getSecurityCtx().getTenantId()) .entityId(entityId) - .updateConsumer((s, subscriptionUpdate) -> dynamicValueSubUpdate(s, subscriptionUpdate, dynamicValueKeySubMap)) + .updateProcessor((subscription, subscriptionUpdate) -> dynamicValueSubUpdate(subscription.getSessionId(), subscriptionUpdate, dynamicValueKeySubMap)) .allKeys(false) .keyStates(keyStates) .scope(TbAttributeSubscriptionScope.SERVER_SCOPE) diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java index 5804002c77..c66cdc4828 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java @@ -38,11 +38,11 @@ import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; -import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUpdate; -import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.WebSocketService; +import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; import java.util.ArrayList; import java.util.Collection; @@ -81,10 +81,10 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx { private int alarmInvocationAttempts; - public TbAlarmDataSubCtx(String serviceId, TelemetryWebSocketService wsService, + public TbAlarmDataSubCtx(String serviceId, WebSocketService wsService, EntityService entityService, TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService, SubscriptionServiceStatistics stats, AlarmService alarmService, - TelemetryWebSocketSessionRef sessionRef, int cmdId, + WebSocketSessionRef sessionRef, int cmdId, int maxEntitiesPerAlarmSubscription, int maxAlarmQueriesPerRefreshInterval) { super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId); this.maxEntitiesPerAlarmSubscription = maxEntitiesPerAlarmSubscription; @@ -175,7 +175,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx { .subscriptionId(subIdx) .tenantId(sessionRef.getSecurityCtx().getTenantId()) .entityId(entityData.getEntityId()) - .updateConsumer(this::sendWsMsg) + .updateProcessor((sub, update) -> sendWsMsg(sub.getSessionId(), update)) .ts(startTs) .build(); localSubscriptionService.addSubscription(subscription); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmsSubscription.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmsSubscription.java index fba351f755..9d14a56ae4 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmsSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmsSubscription.java @@ -17,13 +17,10 @@ package org.thingsboard.server.service.subscription; import lombok.Builder; import lombok.Getter; -import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; -import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate; -import java.util.List; import java.util.function.BiConsumer; public class TbAlarmsSubscription extends TbSubscription { @@ -33,8 +30,8 @@ public class TbAlarmsSubscription extends TbSubscription updateConsumer, long ts) { - super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.ALARMS, updateConsumer); + BiConsumer, AlarmSubscriptionUpdate> updateProcessor, long ts) { + super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.ALARMS, updateProcessor); this.ts = ts; } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscription.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscription.java index ebd3fe548d..2d05f359d7 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscription.java @@ -19,7 +19,7 @@ import lombok.Builder; import lombok.Getter; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; import java.util.Map; import java.util.function.BiConsumer; @@ -32,9 +32,9 @@ public class TbAttributeSubscription extends TbSubscription updateConsumer, + BiConsumer, TelemetrySubscriptionUpdate> updateProcessor, boolean allKeys, Map keyStates, TbAttributeSubscriptionScope scope) { - super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.ATTRIBUTES, updateConsumer); + super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.ATTRIBUTES, updateProcessor); this.allKeys = allKeys; this.keyStates = keyStates; this.scope = scope; diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java index 7fc87eb17c..f0cc2260b6 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java @@ -19,18 +19,18 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.query.EntityCountQuery; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entity.EntityService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate; +import org.thingsboard.server.service.ws.telemetry.WebSocketService; +import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate; @Slf4j public class TbEntityCountSubCtx extends TbAbstractSubCtx { private volatile int result; - public TbEntityCountSubCtx(String serviceId, TelemetryWebSocketService wsService, EntityService entityService, + public TbEntityCountSubCtx(String serviceId, WebSocketService wsService, EntityService entityService, TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService, - SubscriptionServiceStatistics stats, TelemetryWebSocketSessionRef sessionRef, int cmdId) { + SubscriptionServiceStatistics stats, WebSocketSessionRef sessionRef, int cmdId) { super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId); } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java index 385661f177..975ecea1c2 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java @@ -27,13 +27,13 @@ import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entity.EntityService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; -import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.WebSocketService; +import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.TimeSeriesCmd; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; import java.util.ArrayList; import java.util.Collections; @@ -58,9 +58,9 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx { private final int maxEntitiesPerDataSubscription; private Map> latestTsEntityData; - public TbEntityDataSubCtx(String serviceId, TelemetryWebSocketService wsService, EntityService entityService, + public TbEntityDataSubCtx(String serviceId, WebSocketService wsService, EntityService entityService, TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService, - SubscriptionServiceStatistics stats, TelemetryWebSocketSessionRef sessionRef, int cmdId, int maxEntitiesPerDataSubscription) { + SubscriptionServiceStatistics stats, WebSocketSessionRef sessionRef, int cmdId, int maxEntitiesPerDataSubscription) { super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId); this.maxEntitiesPerDataSubscription = maxEntitiesPerDataSubscription; } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubscriptionService.java index 784fa7d842..1b62d3c03b 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubscriptionService.java @@ -15,20 +15,19 @@ */ package org.thingsboard.server.service.subscription; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; -import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd; +import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; public interface TbEntityDataSubscriptionService { - void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityDataCmd cmd); + void handleCmd(WebSocketSessionRef sessionId, EntityDataCmd cmd); - void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityCountCmd cmd); + void handleCmd(WebSocketSessionRef sessionId, EntityCountCmd cmd); - void handleCmd(TelemetryWebSocketSessionRef sessionId, AlarmDataCmd cmd); + void handleCmd(WebSocketSessionRef sessionId, AlarmDataCmd cmd); void cancelSubscription(String sessionId, UnsubscribeCmd subscriptionId); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java index e398111f1f..bdc6d41877 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java @@ -18,8 +18,9 @@ package org.thingsboard.server.service.subscription; import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.common.msg.queue.TbCallback; -import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; public interface TbLocalSubscriptionService { @@ -33,6 +34,8 @@ public interface TbLocalSubscriptionService { void onSubscriptionUpdate(String sessionId, AlarmSubscriptionUpdate update, TbCallback callback); + void onSubscriptionUpdate(String sessionId, int subscriptionId, NotificationsSubscriptionUpdate update, TbCallback callback); + void onApplicationEvent(PartitionChangeEvent event); void onApplicationEvent(ClusterTopologyChangeEvent event); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java index 95078d8504..a4d9125b65 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java @@ -33,7 +33,7 @@ public abstract class TbSubscription { private final TenantId tenantId; private final EntityId entityId; private final TbSubscriptionType type; - private final BiConsumer updateConsumer; + private final BiConsumer, T> updateProcessor; @Override public boolean equals(Object o) { diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionType.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionType.java index 86a98ba682..380e74fa78 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionType.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionType.java @@ -16,5 +16,5 @@ package org.thingsboard.server.service.subscription; public enum TbSubscriptionType { - TIMESERIES, ATTRIBUTES, ALARMS + TIMESERIES, ATTRIBUTES, ALARMS, NOTIFICATIONS } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java index d8642f8f9a..603b1903f0 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java @@ -19,7 +19,9 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; @@ -31,6 +33,7 @@ import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType; @@ -50,9 +53,10 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesSubscrip import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; -import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; -import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscription; +import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; import java.util.ArrayList; import java.util.HashMap; @@ -105,6 +109,12 @@ public class TbSubscriptionUtils { .setTs(alarmSub.getTs()); msgBuilder.setAlarmSub(alarmSubProto.build()); break; + case NOTIFICATIONS: + NotificationsSubscription notificationsSub = (NotificationsSubscription) subscription; + msgBuilder.setNotificationsSub(TransportProtos.NotificationsSubscriptionProto.newBuilder() + .setSub(subscriptionProto) + .setLimit(notificationsSub.getLimit())); + break; } return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build(); } @@ -166,6 +176,18 @@ public class TbSubscriptionUtils { return builder.build(); } + public static NotificationsSubscription fromProto(TransportProtos.NotificationsSubscriptionProto notificationsSub) { + TbSubscriptionProto sub = notificationsSub.getSub(); + return NotificationsSubscription.builder() + .serviceId(sub.getServiceId()) + .sessionId(sub.getSessionId()) + .subscriptionId(sub.getSubscriptionId()) + .tenantId(TenantId.fromUUID(new UUID(sub.getTenantIdMSB(), sub.getTenantIdLSB()))) + .entityId(EntityIdFactory.getByTypeAndUuid(sub.getEntityType(), new UUID(sub.getEntityIdMSB(), sub.getEntityIdLSB()))) + .limit(notificationsSub.getLimit()) + .build(); + } + public static TelemetrySubscriptionUpdate fromProto(TbSubscriptionUpdateProto proto) { if (proto.getErrorCode() > 0) { return new TelemetrySubscriptionUpdate(proto.getSubscriptionId(), SubscriptionErrorCode.forCode(proto.getErrorCode()), proto.getErrorMsg()); @@ -340,4 +362,47 @@ public class TbSubscriptionUtils { msgBuilder.setAlarmDelete(builder); return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build(); } + + public static TransportProtos.ToCoreNotificationMsg notificationsSubUpdateToProto(TbSubscription subscription, NotificationsSubscriptionUpdate update) { + TransportProtos.NotificationsSubscriptionUpdateProto updateProto = TransportProtos.NotificationsSubscriptionUpdateProto.newBuilder() + .setSessionId(subscription.getSessionId()) + .setSubscriptionId(subscription.getSubscriptionId()) + .setNotification(JacksonUtil.toString(update.getNotification())) + .build(); + return TransportProtos.ToCoreNotificationMsg.newBuilder() + .setToLocalSubscriptionServiceMsg(TransportProtos.LocalSubscriptionServiceMsgProto.newBuilder() + .setNotificationsSubUpdate(updateProto) + .build()) + .build(); + } + + public static ToCoreMsg notificationUpdateToProto(TenantId tenantId, UserId recipientId, Notification notification) { + TransportProtos.NotificationUpdateProto updateProto = TransportProtos.NotificationUpdateProto.newBuilder() + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setRecipientIdMSB(recipientId.getId().getMostSignificantBits()) + .setRecipientIdLSB(recipientId.getId().getLeastSignificantBits()) + .setNotification(JacksonUtil.toString(notification)) + .build(); + return ToCoreMsg.newBuilder() + .setToSubscriptionMgrMsg(SubscriptionMgrMsgProto.newBuilder() + .setNotificationUpdate(updateProto) + .build()) + .build(); + } + + public static ToCoreMsg notificationRequestDeletedToProto(TenantId tenantId, NotificationRequestId notificationRequestId) { + TransportProtos.NotificationRequestDeleteProto deleteProto = TransportProtos.NotificationRequestDeleteProto.newBuilder() + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setNotificationRequestIdMSB(notificationRequestId.getId().getMostSignificantBits()) + .setNotificationRequestIdLSB(notificationRequestId.getId().getLeastSignificantBits()) + .build(); + return ToCoreMsg.newBuilder() + .setToSubscriptionMgrMsg(SubscriptionMgrMsgProto.newBuilder() + .setNotificationRequestDelete(deleteProto) + .build()) + .build(); + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbTimeseriesSubscription.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbTimeseriesSubscription.java index e08f25a280..e343fb4643 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbTimeseriesSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbTimeseriesSubscription.java @@ -19,7 +19,7 @@ import lombok.Builder; import lombok.Getter; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; import java.util.Map; import java.util.function.BiConsumer; @@ -39,9 +39,9 @@ public class TbTimeseriesSubscription extends TbSubscription updateConsumer, + BiConsumer, TelemetrySubscriptionUpdate> updateProcessor, boolean allKeys, Map keyStates, long startTime, long endTime, boolean latestValues) { - super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.TIMESERIES, updateConsumer); + super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.TIMESERIES, updateProcessor); this.allKeys = allKeys; this.keyStates = keyStates; this.startTime = startTime; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java index b207de6a9c..c32a3e25dd 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java @@ -21,8 +21,11 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; @@ -38,12 +41,13 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.Consumer; +import java.util.function.Supplier; /** * Created by ashvayka on 27.03.18. */ @Slf4j -public abstract class AbstractSubscriptionService extends TbApplicationEventListener{ +public abstract class AbstractSubscriptionService extends TbApplicationEventListener { protected final Set currentPartitions = ConcurrentHashMap.newKeySet(); @@ -86,6 +90,22 @@ public abstract class AbstractSubscriptionService extends TbApplicationEventList } } + protected void forwardToSubscriptionManagerServiceOrSendToCore(TenantId tenantId, EntityId entityId, + Consumer toSubscriptionManagerService, + Supplier toCore) { + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); + if (currentPartitions.contains(tpi)) { + if (subscriptionManagerService.isPresent()) { + toSubscriptionManagerService.accept(subscriptionManagerService.get()); + } else { + log.warn("Possible misconfiguration because subscriptionManagerService is null!"); + } + } else { + TransportProtos.ToCoreMsg toCoreMsg = toCore.get(); + clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null); + } + } + protected void addWsCallback(ListenableFuture saveFuture, Consumer callback) { Futures.addCallback(saveFuture, new FutureCallback() { @Override diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index 25f412da20..386904918f 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -164,17 +164,11 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService Alarm alarm = result.getAlarm(); TenantId tenantId = result.getAlarm().getTenantId(); for (EntityId entityId : result.getPropagatedEntitiesList()) { - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); - if (currentPartitions.contains(tpi)) { - if (subscriptionManagerService.isPresent()) { - subscriptionManagerService.get().onAlarmUpdate(tenantId, entityId, alarm, TbCallback.EMPTY); - } else { - log.warn("Possible misconfiguration because subscriptionManagerService is null!"); - } - } else { - TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm); - clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null); - } + forwardToSubscriptionManagerServiceOrSendToCore(tenantId, entityId, subscriptionManagerService -> { + subscriptionManagerService.onAlarmUpdate(tenantId, entityId, alarm, TbCallback.EMPTY); + }, () -> { + return TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm); + }); } }); } @@ -184,17 +178,11 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService Alarm alarm = result.getAlarm(); TenantId tenantId = result.getAlarm().getTenantId(); for (EntityId entityId : result.getPropagatedEntitiesList()) { - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); - if (currentPartitions.contains(tpi)) { - if (subscriptionManagerService.isPresent()) { - subscriptionManagerService.get().onAlarmDeleted(tenantId, entityId, alarm, TbCallback.EMPTY); - } else { - log.warn("Possible misconfiguration because subscriptionManagerService is null!"); - } - } else { - TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAlarmDeletedProto(tenantId, entityId, alarm); - clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null); - } + forwardToSubscriptionManagerServiceOrSendToCore(tenantId, entityId, subscriptionManagerService -> { + subscriptionManagerService.onAlarmDeleted(tenantId, entityId, alarm, TbCallback.EMPTY); + }, () -> { + return TbSubscriptionUtils.toAlarmDeletedProto(tenantId, entityId, alarm); + }); } }); } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index a5f66968da..567f5cb9ff 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -40,12 +40,9 @@ import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult; -import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; -import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.timeseries.TimeseriesService; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; @@ -369,73 +366,49 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer } private void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List attributes, boolean notifyDevice) { - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); - if (currentPartitions.contains(tpi)) { - if (subscriptionManagerService.isPresent()) { - subscriptionManagerService.get().onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice, TbCallback.EMPTY); - } else { - log.warn("Possible misconfiguration because subscriptionManagerService is null!"); - } - } else { - TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAttributesUpdateProto(tenantId, entityId, scope, attributes); - clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null); - } + forwardToSubscriptionManagerServiceOrSendToCore(tenantId, entityId, subscriptionManagerService -> { + subscriptionManagerService.onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice, TbCallback.EMPTY); + }, () -> { + return TbSubscriptionUtils.toAttributesUpdateProto(tenantId, entityId, scope, attributes); + }); } private void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List keys) { - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); - if (currentPartitions.contains(tpi)) { - if (subscriptionManagerService.isPresent()) { - subscriptionManagerService.get().onAttributesDelete(tenantId, entityId, scope, keys, TbCallback.EMPTY); - } else { - log.warn("Possible misconfiguration because subscriptionManagerService is null!"); - } - } else { - TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAttributesDeleteProto(tenantId, entityId, scope, keys); - clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null); - } + forwardToSubscriptionManagerServiceOrSendToCore(tenantId, entityId, subscriptionManagerService -> { + subscriptionManagerService.onAttributesDelete(tenantId, entityId, scope, keys, TbCallback.EMPTY); + }, () -> { + return TbSubscriptionUtils.toAttributesDeleteProto(tenantId, entityId, scope, keys); + }); } private void onTimeSeriesUpdate(TenantId tenantId, EntityId entityId, List ts) { - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); - if (currentPartitions.contains(tpi)) { - if (subscriptionManagerService.isPresent()) { - subscriptionManagerService.get().onTimeSeriesUpdate(tenantId, entityId, ts, TbCallback.EMPTY); - } else { - log.warn("Possible misconfiguration because subscriptionManagerService is null!"); - } - } else { - TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toTimeseriesUpdateProto(tenantId, entityId, ts); - clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null); - } + forwardToSubscriptionManagerServiceOrSendToCore(tenantId, entityId, subscriptionManagerService -> { + subscriptionManagerService.onTimeSeriesUpdate(tenantId, entityId, ts, TbCallback.EMPTY); + }, () -> { + return TbSubscriptionUtils.toTimeseriesUpdateProto(tenantId, entityId, ts); + }); } private void onTimeSeriesDelete(TenantId tenantId, EntityId entityId, List keys, List ts) { - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); - if (currentPartitions.contains(tpi)) { - if (subscriptionManagerService.isPresent()) { - List updated = new ArrayList<>(); - List deleted = new ArrayList<>(); - - ts.stream().filter(Objects::nonNull).forEach(res -> { - if (res.isRemoved()) { - if (res.getData() != null) { - updated.add(res.getData()); - } else { - deleted.add(res.getKey()); - } + forwardToSubscriptionManagerServiceOrSendToCore(tenantId, entityId, subscriptionManagerService -> { + List updated = new ArrayList<>(); + List deleted = new ArrayList<>(); + + ts.stream().filter(Objects::nonNull).forEach(res -> { + if (res.isRemoved()) { + if (res.getData() != null) { + updated.add(res.getData()); + } else { + deleted.add(res.getKey()); } - }); + } + }); - subscriptionManagerService.get().onTimeSeriesUpdate(tenantId, entityId, updated, TbCallback.EMPTY); - subscriptionManagerService.get().onTimeSeriesDelete(tenantId, entityId, deleted, TbCallback.EMPTY); - } else { - log.warn("Possible misconfiguration because subscriptionManagerService is null!"); - } - } else { - TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toTimeseriesDeleteProto(tenantId, entityId, keys); - clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null); - } + subscriptionManagerService.onTimeSeriesUpdate(tenantId, entityId, updated, TbCallback.EMPTY); + subscriptionManagerService.onTimeSeriesDelete(tenantId, entityId, deleted, TbCallback.EMPTY); + }, () -> { + return TbSubscriptionUtils.toTimeseriesDeleteProto(tenantId, entityId, keys); + }); } private void addVoidCallback(ListenableFuture saveFuture, final FutureCallback callback) { diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultWebSocketService.java similarity index 82% rename from application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java rename to application/src/main/java/org/thingsboard/server/service/telemetry/DefaultWebSocketService.java index d9beb1ed33..86d41456da 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultWebSocketService.java @@ -47,6 +47,7 @@ import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.util.TenantRateLimitException; +import org.thingsboard.server.exception.UnauthorizedException; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.AccessValidator; @@ -55,26 +56,35 @@ import org.thingsboard.server.service.security.ValidationResult; import org.thingsboard.server.service.security.ValidationResultCode; import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.subscription.SubscriptionErrorCode; import org.thingsboard.server.service.subscription.TbAttributeSubscription; import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope; import org.thingsboard.server.service.subscription.TbEntityDataSubscriptionService; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.subscription.TbTimeseriesSubscription; -import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; -import org.thingsboard.server.service.telemetry.cmd.v1.AttributesSubscriptionCmd; -import org.thingsboard.server.service.telemetry.cmd.v1.GetHistoryCmd; -import org.thingsboard.server.service.telemetry.cmd.v1.SubscriptionCmd; -import org.thingsboard.server.service.telemetry.cmd.v1.TelemetryPluginCmd; -import org.thingsboard.server.service.telemetry.cmd.v1.TimeseriesSubscriptionCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; -import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd; -import org.thingsboard.server.service.telemetry.exception.UnauthorizedException; -import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.SessionEvent; +import org.thingsboard.server.service.ws.WebSocketMsgEndpoint; +import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.WsSessionMetaData; +import org.thingsboard.server.service.ws.notification.cmd.NotificationCmdsWrapper; +import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationAsReadCmd; +import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; +import org.thingsboard.server.service.ws.notification.cmd.NotificationsUnsubCmd; +import org.thingsboard.server.service.ws.notification.sub.DefaultNotificationsSubscriptionService; +import org.thingsboard.server.service.ws.telemetry.WebSocketService; +import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryPluginCmdsWrapper; +import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v1.GetHistoryCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v1.SubscriptionCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v1.TelemetryPluginCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v1.TimeseriesSubscriptionCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; import javax.annotation.Nullable; import javax.annotation.PostConstruct; @@ -103,7 +113,7 @@ import java.util.stream.Collectors; @Service @TbCoreComponent @Slf4j -public class DefaultTelemetryWebSocketService implements TelemetryWebSocketService { +public class DefaultWebSocketService implements WebSocketService { public static final int NUMBER_OF_PING_ATTEMPTS = 3; @@ -126,7 +136,10 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi private TbEntityDataSubscriptionService entityDataSubService; @Autowired - private TelemetryWebSocketMsgEndpoint msgEndpoint; + private DefaultNotificationsSubscriptionService notificationsSubService; + + @Autowired + private WebSocketMsgEndpoint msgEndpoint; @Autowired private AccessValidator accessValidator; @@ -177,7 +190,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } @Override - public void handleWebSocketSessionEvent(TelemetryWebSocketSessionRef sessionRef, SessionEvent event) { + public void handleWebSocketSessionEvent(WebSocketSessionRef sessionRef, SessionEvent event) { String sessionId = sessionRef.getSessionId(); log.debug(PROCESSING_MSG, sessionId, event); switch (event.getEventType()) { @@ -197,49 +210,19 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } @Override - public void handleWebSocketMsg(TelemetryWebSocketSessionRef sessionRef, String msg) { + public void handleWebSocketMsg(WebSocketSessionRef sessionRef, String msg) { if (log.isTraceEnabled()) { log.trace("[{}] Processing: {}", sessionRef.getSessionId(), msg); } try { - TelemetryPluginCmdsWrapper cmdsWrapper = jsonMapper.readValue(msg, TelemetryPluginCmdsWrapper.class); - if (cmdsWrapper != null) { - if (cmdsWrapper.getAttrSubCmds() != null) { - cmdsWrapper.getAttrSubCmds().forEach(cmd -> { - if (processSubscription(sessionRef, cmd)) { - handleWsAttributesSubscriptionCmd(sessionRef, cmd); - } - }); - } - if (cmdsWrapper.getTsSubCmds() != null) { - cmdsWrapper.getTsSubCmds().forEach(cmd -> { - if (processSubscription(sessionRef, cmd)) { - handleWsTimeseriesSubscriptionCmd(sessionRef, cmd); - } - }); - } - if (cmdsWrapper.getHistoryCmds() != null) { - cmdsWrapper.getHistoryCmds().forEach(cmd -> handleWsHistoryCmd(sessionRef, cmd)); - } - if (cmdsWrapper.getEntityDataCmds() != null) { - cmdsWrapper.getEntityDataCmds().forEach(cmd -> handleWsEntityDataCmd(sessionRef, cmd)); - } - if (cmdsWrapper.getAlarmDataCmds() != null) { - cmdsWrapper.getAlarmDataCmds().forEach(cmd -> handleWsAlarmDataCmd(sessionRef, cmd)); - } - if (cmdsWrapper.getEntityCountCmds() != null) { - cmdsWrapper.getEntityCountCmds().forEach(cmd -> handleWsEntityCountCmd(sessionRef, cmd)); - } - if (cmdsWrapper.getEntityDataUnsubscribeCmds() != null) { - cmdsWrapper.getEntityDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd)); - } - if (cmdsWrapper.getAlarmDataUnsubscribeCmds() != null) { - cmdsWrapper.getAlarmDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd)); - } - if (cmdsWrapper.getEntityCountUnsubscribeCmds() != null) { - cmdsWrapper.getEntityCountUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd)); - } + switch (sessionRef.getSessionType()) { + case TELEMETRY: + processTelemetryCmds(sessionRef, msg); + break; + case NOTIFICATIONS: + processNotificationCmds(sessionRef, msg); + break; } } catch (IOException e) { log.warn("Failed to decode subscription cmd: {}", e.getMessage(), e); @@ -247,7 +230,81 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void handleWsEntityDataCmd(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) { + private void processTelemetryCmds(WebSocketSessionRef sessionRef, String msg) throws JsonProcessingException { + TelemetryPluginCmdsWrapper cmdsWrapper = jsonMapper.readValue(msg, TelemetryPluginCmdsWrapper.class); + if (cmdsWrapper == null) { + return; + } + if (cmdsWrapper.getAttrSubCmds() != null) { + cmdsWrapper.getAttrSubCmds().forEach(cmd -> { + if (processSubscription(sessionRef, cmd)) { + handleWsAttributesSubscriptionCmd(sessionRef, cmd); + } + }); + } + if (cmdsWrapper.getTsSubCmds() != null) { + cmdsWrapper.getTsSubCmds().forEach(cmd -> { + if (processSubscription(sessionRef, cmd)) { + handleWsTimeseriesSubscriptionCmd(sessionRef, cmd); + } + }); + } + if (cmdsWrapper.getHistoryCmds() != null) { + cmdsWrapper.getHistoryCmds().forEach(cmd -> handleWsHistoryCmd(sessionRef, cmd)); + } + if (cmdsWrapper.getEntityDataCmds() != null) { + cmdsWrapper.getEntityDataCmds().forEach(cmd -> handleWsEntityDataCmd(sessionRef, cmd)); + } + if (cmdsWrapper.getAlarmDataCmds() != null) { + cmdsWrapper.getAlarmDataCmds().forEach(cmd -> handleWsAlarmDataCmd(sessionRef, cmd)); + } + if (cmdsWrapper.getEntityCountCmds() != null) { + cmdsWrapper.getEntityCountCmds().forEach(cmd -> handleWsEntityCountCmd(sessionRef, cmd)); + } + if (cmdsWrapper.getEntityDataUnsubscribeCmds() != null) { + cmdsWrapper.getEntityDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd)); + } + if (cmdsWrapper.getAlarmDataUnsubscribeCmds() != null) { + cmdsWrapper.getAlarmDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd)); + } + if (cmdsWrapper.getEntityCountUnsubscribeCmds() != null) { + cmdsWrapper.getEntityCountUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd)); + } + } + + private void processNotificationCmds(WebSocketSessionRef sessionRef, String msg) throws IOException { + NotificationCmdsWrapper cmdsWrapper = jsonMapper.readValue(msg, NotificationCmdsWrapper.class); + if (cmdsWrapper.getUnreadSubCmd() != null) { + handleUnreadNotificationsSubCmd(sessionRef, cmdsWrapper.getUnreadSubCmd()); + } else if (cmdsWrapper.getUnreadUnsubCmd() != null) { + handleUnreadNotificationsUnsubCmd(sessionRef, cmdsWrapper.getUnreadUnsubCmd()); + } else if (cmdsWrapper.getMarkAsReadCmd() != null) { + handleMarkNotificationAsReadCmd(sessionRef, cmdsWrapper.getMarkAsReadCmd()); + } + } + + private void handleUnreadNotificationsSubCmd(WebSocketSessionRef sessionRef, NotificationsSubCmd cmd) { + String sessionId = sessionRef.getSessionId(); + if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)) { + notificationsSubService.handleUnreadNotificationsSubCmd(sessionRef, cmd); + } + } + + private void handleUnreadNotificationsUnsubCmd(WebSocketSessionRef sessionRef, NotificationsUnsubCmd cmd) { + String sessionId = sessionRef.getSessionId(); + if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)) { + notificationsSubService.handleUnsubCmd(sessionRef, cmd); + } + } + + private void handleMarkNotificationAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationAsReadCmd cmd) { + String sessionId = sessionRef.getSessionId(); + if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)) { + notificationsSubService.handleMarkAsReadCmd(sessionRef, cmd); + } + } + + private void handleWsEntityDataCmd(WebSocketSessionRef sessionRef, EntityDataCmd cmd) { String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); @@ -257,7 +314,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void handleWsEntityCountCmd(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) { + private void handleWsEntityCountCmd(WebSocketSessionRef sessionRef, EntityCountCmd cmd) { String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); @@ -267,7 +324,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void handleWsAlarmDataCmd(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) { + private void handleWsAlarmDataCmd(WebSocketSessionRef sessionRef, AlarmDataCmd cmd) { String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); @@ -277,7 +334,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void handleWsDataUnsubscribeCmd(TelemetryWebSocketSessionRef sessionRef, UnsubscribeCmd cmd) { + private void handleWsDataUnsubscribeCmd(WebSocketSessionRef sessionRef, UnsubscribeCmd cmd) { String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); @@ -315,7 +372,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void processSessionClose(TelemetryWebSocketSessionRef sessionRef) { + private void processSessionClose(WebSocketSessionRef sessionRef) { var tenantProfileConfiguration = tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultProfileConfiguration(); if (tenantProfileConfiguration != null) { String sessionId = "[" + sessionRef.getSessionId() + "]"; @@ -349,7 +406,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private boolean processSubscription(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd) { + private boolean processSubscription(WebSocketSessionRef sessionRef, SubscriptionCmd cmd) { var tenantProfileConfiguration = (DefaultTenantProfileConfiguration) tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultProfileConfiguration(); String subId = "[" + sessionRef.getSessionId() + "]:[" + cmd.getCmdId() + "]"; @@ -420,7 +477,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi return true; } - private void handleWsAttributesSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, AttributesSubscriptionCmd cmd) { + private void handleWsAttributesSubscriptionCmd(WebSocketSessionRef sessionRef, AttributesSubscriptionCmd cmd) { String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); @@ -441,7 +498,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void handleWsAttributesSubscriptionByKeys(TelemetryWebSocketSessionRef sessionRef, + private void handleWsAttributesSubscriptionByKeys(WebSocketSessionRef sessionRef, AttributesSubscriptionCmd cmd, String sessionId, EntityId entityId, List keys) { FutureCallback> callback = new FutureCallback<>() { @@ -465,7 +522,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi .allKeys(false) .keyStates(subState) .scope(scope) - .updateConsumer(DefaultTelemetryWebSocketService.this::sendWsMsg) + .updateProcessor((subscription, update) -> sendWsMsg(subscription.getSessionId(), update)) .build(); oldSubService.addSubscription(sub); } @@ -492,7 +549,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void handleWsHistoryCmd(TelemetryWebSocketSessionRef sessionRef, GetHistoryCmd cmd) { + private void handleWsHistoryCmd(WebSocketSessionRef sessionRef, GetHistoryCmd cmd) { String sessionId = sessionRef.getSessionId(); WsSessionMetaData sessionMD = wsSessionsMap.get(sessionId); if (sessionMD == null) { @@ -542,7 +599,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi on(r -> Futures.addCallback(tsService.findAll(sessionRef.getSecurityCtx().getTenantId(), entityId, queries), callback, executor), callback::onFailure)); } - private void handleWsAttributesSubscription(TelemetryWebSocketSessionRef sessionRef, + private void handleWsAttributesSubscription(WebSocketSessionRef sessionRef, AttributesSubscriptionCmd cmd, String sessionId, EntityId entityId) { FutureCallback> callback = new FutureCallback<>() { @Override @@ -563,7 +620,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi .entityId(entityId) .allKeys(true) .keyStates(subState) - .updateConsumer(DefaultTelemetryWebSocketService.this::sendWsMsg) + .updateProcessor((subscription, update) -> sendWsMsg(subscription.getSessionId(), update)) .scope(scope).build(); oldSubService.addSubscription(sub); } @@ -585,7 +642,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void handleWsTimeseriesSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, TimeseriesSubscriptionCmd cmd) { + private void handleWsTimeseriesSubscriptionCmd(WebSocketSessionRef sessionRef, TimeseriesSubscriptionCmd cmd) { String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); @@ -605,7 +662,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void handleWsTimeseriesSubscriptionByKeys(TelemetryWebSocketSessionRef sessionRef, + private void handleWsTimeseriesSubscriptionByKeys(WebSocketSessionRef sessionRef, TimeseriesSubscriptionCmd cmd, String sessionId, EntityId entityId) { long startTs; if (cmd.getTimeWindow() > 0) { @@ -629,7 +686,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void handleWsTimeseriesSubscription(TelemetryWebSocketSessionRef sessionRef, + private void handleWsTimeseriesSubscription(WebSocketSessionRef sessionRef, TimeseriesSubscriptionCmd cmd, String sessionId, EntityId entityId) { FutureCallback> callback = new FutureCallback>() { @Override @@ -644,7 +701,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi .subscriptionId(cmd.getCmdId()) .tenantId(sessionRef.getSecurityCtx().getTenantId()) .entityId(entityId) - .updateConsumer(DefaultTelemetryWebSocketService.this::sendWsMsg) + .updateProcessor((subscription, update) -> sendWsMsg(subscription.getSessionId(), update)) .allKeys(true) .keyStates(subState).build(); oldSubService.addSubscription(sub); @@ -667,7 +724,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi on(r -> Futures.addCallback(tsService.findAllLatest(sessionRef.getSecurityCtx().getTenantId(), entityId), callback, executor), callback::onFailure)); } - private FutureCallback> getSubscriptionCallback(final TelemetryWebSocketSessionRef sessionRef, final TimeseriesSubscriptionCmd cmd, final String sessionId, final EntityId entityId, final long startTs, final List keys) { + private FutureCallback> getSubscriptionCallback(final WebSocketSessionRef sessionRef, final TimeseriesSubscriptionCmd cmd, final String sessionId, final EntityId entityId, final long startTs, final List keys) { return new FutureCallback<>() { @Override public void onSuccess(List data) { @@ -682,7 +739,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi .subscriptionId(cmd.getCmdId()) .tenantId(sessionRef.getSecurityCtx().getTenantId()) .entityId(entityId) - .updateConsumer(DefaultTelemetryWebSocketService.this::sendWsMsg) + .updateProcessor((subscription, update) -> sendWsMsg(subscription.getSessionId(), update)) .allKeys(false) .keyStates(subState).build(); oldSubService.addSubscription(sub); @@ -702,7 +759,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi }; } - private void unsubscribe(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) { + private void unsubscribe(WebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) { if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty()) { oldSubService.cancelAllSessionSubscriptions(sessionId); } else { @@ -710,7 +767,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) { + private boolean validateSubscriptionCmd(WebSocketSessionRef sessionRef, EntityDataCmd cmd) { if (cmd.getCmdId() < 0) { TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, "Cmd id is negative value!"); @@ -725,7 +782,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi return true; } - private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) { + private boolean validateSubscriptionCmd(WebSocketSessionRef sessionRef, EntityCountCmd cmd) { if (cmd.getCmdId() < 0) { TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, "Cmd id is negative value!"); @@ -739,7 +796,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi return true; } - private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) { + private boolean validateSubscriptionCmd(WebSocketSessionRef sessionRef, AlarmDataCmd cmd) { if (cmd.getCmdId() < 0) { TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, "Cmd id is negative value!"); @@ -754,7 +811,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi return true; } - private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd) { + private boolean validateSubscriptionCmd(WebSocketSessionRef sessionRef, SubscriptionCmd cmd) { if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty()) { TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, "Device id is empty!"); @@ -764,11 +821,11 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi return true; } - private boolean validateSessionMetadata(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) { + private boolean validateSessionMetadata(WebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) { return validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId); } - private boolean validateSessionMetadata(TelemetryWebSocketSessionRef sessionRef, int cmdId, String sessionId) { + private boolean validateSessionMetadata(WebSocketSessionRef sessionRef, int cmdId, String sessionId) { WsSessionMetaData sessionMD = wsSessionsMap.get(sessionId); if (sessionMD == null) { log.warn("[{}] Session meta data not found. ", sessionId); @@ -781,15 +838,15 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void sendWsMsg(TelemetryWebSocketSessionRef sessionRef, EntityDataUpdate update) { + private void sendWsMsg(WebSocketSessionRef sessionRef, EntityDataUpdate update) { sendWsMsg(sessionRef, update.getCmdId(), update); } - private void sendWsMsg(TelemetryWebSocketSessionRef sessionRef, TelemetrySubscriptionUpdate update) { + private void sendWsMsg(WebSocketSessionRef sessionRef, TelemetrySubscriptionUpdate update) { sendWsMsg(sessionRef, update.getSubscriptionId(), update); } - private void sendWsMsg(TelemetryWebSocketSessionRef sessionRef, int cmdId, Object update) { + private void sendWsMsg(WebSocketSessionRef sessionRef, int cmdId, Object update) { try { String msg = jsonMapper.writeValueAsString(update); executor.submit(() -> { diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java index 035e262a53..c35864ee92 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java @@ -41,6 +41,4 @@ public interface InternalTelemetryService extends RuleEngineTelemetryService { void deleteLatestInternal(TenantId tenantId, EntityId entityId, List keys, FutureCallback callback); - - } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/SessionEvent.java b/application/src/main/java/org/thingsboard/server/service/ws/SessionEvent.java similarity index 96% rename from application/src/main/java/org/thingsboard/server/service/telemetry/SessionEvent.java rename to application/src/main/java/org/thingsboard/server/service/ws/SessionEvent.java index 527f5dc667..6a1bc2bd7d 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/SessionEvent.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/SessionEvent.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry; +package org.thingsboard.server.service.ws; import lombok.Getter; import lombok.ToString; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketMsgEndpoint.java b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketMsgEndpoint.java similarity index 63% rename from application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketMsgEndpoint.java rename to application/src/main/java/org/thingsboard/server/service/ws/WebSocketMsgEndpoint.java index d71777ae49..4e178f88d2 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketMsgEndpoint.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketMsgEndpoint.java @@ -13,20 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry; +package org.thingsboard.server.service.ws; import org.springframework.web.socket.CloseStatus; +import org.thingsboard.server.service.ws.WebSocketSessionRef; import java.io.IOException; /** * Created by ashvayka on 27.03.18. */ -public interface TelemetryWebSocketMsgEndpoint { +public interface WebSocketMsgEndpoint { - void send(TelemetryWebSocketSessionRef sessionRef, int subscriptionId, String msg) throws IOException; + void send(WebSocketSessionRef sessionRef, int subscriptionId, String msg) throws IOException; - void sendPing(TelemetryWebSocketSessionRef sessionRef, long currentTime) throws IOException; + void sendPing(WebSocketSessionRef sessionRef, long currentTime) throws IOException; - void close(TelemetryWebSocketSessionRef sessionRef, CloseStatus withReason) throws IOException; + void close(WebSocketSessionRef sessionRef, CloseStatus withReason) throws IOException; } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketSessionRef.java b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionRef.java similarity index 70% rename from application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketSessionRef.java rename to application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionRef.java index da8a8962e6..cb4eea6101 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketSessionRef.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionRef.java @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry; +package org.thingsboard.server.service.ws; +import lombok.Builder; import lombok.Getter; +import lombok.RequiredArgsConstructor; import org.thingsboard.server.service.security.model.SecurityUser; import java.net.InetSocketAddress; @@ -25,34 +27,25 @@ import java.util.concurrent.atomic.AtomicInteger; /** * Created by ashvayka on 27.03.18. */ -public class TelemetryWebSocketSessionRef { +@RequiredArgsConstructor +@Builder +@Getter +public class WebSocketSessionRef { private static final long serialVersionUID = 1L; - @Getter private final String sessionId; - @Getter private final SecurityUser securityCtx; - @Getter private final InetSocketAddress localAddress; - @Getter private final InetSocketAddress remoteAddress; - @Getter - private final AtomicInteger sessionSubIdSeq; - - public TelemetryWebSocketSessionRef(String sessionId, SecurityUser securityCtx, InetSocketAddress localAddress, InetSocketAddress remoteAddress) { - this.sessionId = sessionId; - this.securityCtx = securityCtx; - this.localAddress = localAddress; - this.remoteAddress = remoteAddress; - this.sessionSubIdSeq = new AtomicInteger(); - } + private final WebSocketSessionType sessionType; + private final AtomicInteger sessionSubIdSeq = new AtomicInteger(); @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - TelemetryWebSocketSessionRef that = (TelemetryWebSocketSessionRef) o; + WebSocketSessionRef that = (WebSocketSessionRef) o; return Objects.equals(sessionId, that.sessionId); } @@ -63,10 +56,11 @@ public class TelemetryWebSocketSessionRef { @Override public String toString() { - return "TelemetryWebSocketSessionRef{" + + return "WebSocketSessionRef{" + "sessionId='" + sessionId + '\'' + ", localAddress=" + localAddress + ", remoteAddress=" + remoteAddress + + ", sessionType=" + sessionType + '}'; } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java new file mode 100644 index 0000000000..3f55b921b4 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.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.service.ws; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.Optional; + +@RequiredArgsConstructor +@Getter +public enum WebSocketSessionType { + TELEMETRY("telemetry"), + NOTIFICATIONS("notifications"); + + private final String name; + + public static Optional forName(String name) { + return Arrays.stream(values()) + .filter(sessionType -> sessionType.getName().equals(name)) + .findFirst(); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/WsSessionMetaData.java b/application/src/main/java/org/thingsboard/server/service/ws/WsSessionMetaData.java similarity index 80% rename from application/src/main/java/org/thingsboard/server/service/telemetry/WsSessionMetaData.java rename to application/src/main/java/org/thingsboard/server/service/ws/WsSessionMetaData.java index ab353c0581..0567a067ed 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/WsSessionMetaData.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/WsSessionMetaData.java @@ -13,27 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry; +package org.thingsboard.server.service.ws; /** * Created by ashvayka on 27.03.18. */ public class WsSessionMetaData { - private TelemetryWebSocketSessionRef sessionRef; + private WebSocketSessionRef sessionRef; private long lastActivityTime; - public WsSessionMetaData(TelemetryWebSocketSessionRef sessionRef) { + public WsSessionMetaData(WebSocketSessionRef sessionRef) { super(); this.sessionRef = sessionRef; this.lastActivityTime = System.currentTimeMillis(); } - public TelemetryWebSocketSessionRef getSessionRef() { + public WebSocketSessionRef getSessionRef() { return sessionRef; } - public void setSessionRef(TelemetryWebSocketSessionRef sessionRef) { + public void setSessionRef(WebSocketSessionRef sessionRef) { this.sessionRef = sessionRef; } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationAsReadCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationAsReadCmd.java new file mode 100644 index 0000000000..50844623eb --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationAsReadCmd.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.service.ws.notification.cmd; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class MarkNotificationAsReadCmd { + private int cmdId; + private UUID notificationId; +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java new file mode 100644 index 0000000000..367a6cc450 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java @@ -0,0 +1,28 @@ +/** + * 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.ws.notification.cmd; + +import lombok.Data; +import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationAsReadCmd; +import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; + +@Data +public class NotificationCmdsWrapper { + private NotificationsSubCmd unreadSubCmd; + private NotificationsUnsubCmd unreadUnsubCmd; + + private MarkNotificationAsReadCmd markAsReadCmd; +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java new file mode 100644 index 0000000000..376b34b18e --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java @@ -0,0 +1,28 @@ +/** + * 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.ws.notification.cmd; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class NotificationsSubCmd { + private int cmdId; + private int limit; +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java new file mode 100644 index 0000000000..1daeb570cc --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java @@ -0,0 +1,24 @@ +/** + * 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.ws.notification.cmd; + +import lombok.Data; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; + +@Data +public class NotificationsUnsubCmd implements UnsubscribeCmd { + private int cmdId; +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java new file mode 100644 index 0000000000..0597612569 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java @@ -0,0 +1,54 @@ +/** + * 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.ws.notification.cmd; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Getter; +import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdateType; + +import java.util.Collection; +import java.util.List; + +@Getter +public class UnreadNotificationsUpdate extends CmdUpdate { + + private final Collection notifications; + private final Notification update; + private final int totalUnreadCount; + + @Builder + @JsonCreator + public UnreadNotificationsUpdate(@JsonProperty("cmdId") int cmdId, @JsonProperty("errorCode") int errorCode, + @JsonProperty("errorMsg") String errorMsg, + @JsonProperty("notifications") Collection notifications, + @JsonProperty("update") Notification update, + @JsonProperty("totalUnreadCount") int totalUnreadCount) { + super(cmdId, errorCode, errorMsg); + this.notifications = notifications; + this.update = update; + this.totalUnreadCount = totalUnreadCount; + } + + @Override + public CmdUpdateType getCmdUpdateType() { + return CmdUpdateType.NOTIFICATIONS; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/DefaultNotificationsSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/DefaultNotificationsSubscriptionService.java new file mode 100644 index 0000000000..119c14dc23 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/DefaultNotificationsSubscriptionService.java @@ -0,0 +1,183 @@ +/** + * 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.ws.notification.sub; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.id.IdBased; +import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationStatus; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.dao.notification.NotificationService; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.discovery.NotificationsTopicService; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.notification.NotificationProcessingService; +import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; +import org.thingsboard.server.service.subscription.TbSubscriptionUtils; +import org.thingsboard.server.service.telemetry.AbstractSubscriptionService; +import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationAsReadCmd; +import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; +import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; +import org.thingsboard.server.service.ws.telemetry.WebSocketService; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +@TbCoreComponent +@Slf4j +public class DefaultNotificationsSubscriptionService extends AbstractSubscriptionService implements NotificationsSubscriptionService { + + private final WebSocketService wsService; + private final TbLocalSubscriptionService localSubscriptionService; + private final NotificationService notificationService; + private final TbServiceInfoProvider serviceInfoProvider; + private final NotificationsTopicService notificationsTopicService; + private final NotificationProcessingService notificationProcessingService; + + public DefaultNotificationsSubscriptionService(TbClusterService clusterService, PartitionService partitionService, + @Lazy WebSocketService wsService, TbLocalSubscriptionService localSubscriptionService, + NotificationService notificationService, TbServiceInfoProvider serviceInfoProvider, + NotificationsTopicService notificationsTopicService, + @Lazy NotificationProcessingService notificationProcessingService) { + super(clusterService, partitionService); + this.wsService = wsService; + this.localSubscriptionService = localSubscriptionService; + this.notificationService = notificationService; + this.serviceInfoProvider = serviceInfoProvider; + this.notificationsTopicService = notificationsTopicService; + this.notificationProcessingService = notificationProcessingService; + } + + @Override + public void handleUnreadNotificationsSubCmd(WebSocketSessionRef sessionRef, NotificationsSubCmd cmd) { + SecurityUser user = sessionRef.getSecurityCtx(); + NotificationsSubscription subscription = NotificationsSubscription.builder() + .serviceId(serviceInfoProvider.getServiceId()) + .sessionId(sessionRef.getSessionId()) + .subscriptionId(cmd.getCmdId()) + .tenantId(user.getTenantId()) + .entityId(user.getId()) + .updateProcessor(this::handleSubscriptionUpdate) + .limit(cmd.getLimit()) + .build(); + localSubscriptionService.addSubscription(subscription); + + fetchUnreadNotifications(subscription); + sendUpdate(sessionRef.getSessionId(), subscription.createFullUpdate()); + } + + @Override + public void handleMarkAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationAsReadCmd cmd) { + NotificationId notificationId = new NotificationId(cmd.getNotificationId()); + notificationProcessingService.markNotificationAsRead(sessionRef.getSecurityCtx(), notificationId); + } + + @Override + public void handleUnsubCmd(WebSocketSessionRef sessionRef, UnsubscribeCmd cmd) { + localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), cmd.getCmdId()); + } + + private void fetchUnreadNotifications(NotificationsSubscription subscription) { + PageData notifications = notificationService.findLatestUnreadNotificationsByUserId(subscription.getTenantId(), + (UserId) subscription.getEntityId(), subscription.getLimit()); + subscription.getUnreadNotifications().clear(); + subscription.getUnreadNotifications().putAll(notifications.getData().stream().collect(Collectors.toMap(IdBased::getUuidId, n -> n))); + subscription.getTotalUnreadCount().set((int) notifications.getTotalElements()); + } + + @Override + public void onNewNotification(TenantId tenantId, UserId recipientId, Notification notification) { + onNotificationUpdate(tenantId, recipientId, notification); + } + + @Override + public void onNotificationUpdated(TenantId tenantId, UserId recipientId, Notification notification) { + onNotificationUpdate(tenantId, recipientId, notification); + } + + private void onNotificationUpdate(TenantId tenantId, UserId recipientId, Notification notification) { + forwardToSubscriptionManagerServiceOrSendToCore(tenantId, recipientId, subscriptionManagerService -> { + subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, notification, TbCallback.EMPTY); + }, () -> { + return TbSubscriptionUtils.notificationUpdateToProto(tenantId, recipientId, notification); + }); + } + + @Override + public void onNotificationRequestDeleted(TenantId tenantId, NotificationRequestId notificationRequestId) { + TransportProtos.ToCoreMsg notificationRequestDeletedProto = TbSubscriptionUtils.notificationRequestDeletedToProto(tenantId, notificationRequestId); + Set coreServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_CORE)); + for (String serviceId : coreServices) { + TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, serviceId); + clusterService.pushMsgToCore(tpi, UUID.randomUUID(), notificationRequestDeletedProto, null); + } + } + + + private void handleSubscriptionUpdate(NotificationsSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) { + if (subscriptionUpdate.getNotification() != null) { + Notification notification = subscriptionUpdate.getNotification(); + if (notification.getStatus() == NotificationStatus.READ) { + fetchUnreadNotifications(subscription); + sendUpdate(subscription.getSessionId(), subscription.createFullUpdate()); + } else { + Notification previous = subscription.getUnreadNotifications().put(notification.getUuidId(), notification); + if (previous == null) { + subscription.getTotalUnreadCount().incrementAndGet(); + Set beyondLimit = subscription.getUnreadNotifications().keySet().stream() + .skip(subscription.getLimit()) + .collect(Collectors.toSet()); + beyondLimit.forEach(notificationId -> subscription.getUnreadNotifications().remove(notificationId)); + } + sendUpdate(subscription.getSessionId(), subscription.createPartialUpdate(notification)); + } + } else if (subscriptionUpdate.isNotificationRequestDeleted()) { + if (subscription.getUnreadNotifications().values().stream() + .anyMatch(notification -> notification.getRequestId().equals(subscriptionUpdate.getNotificationRequestId()))) { + fetchUnreadNotifications(subscription); + sendUpdate(subscription.getSessionId(), subscription.createFullUpdate()); + } + } + } + + private void sendUpdate(String sessionId, UnreadNotificationsUpdate update) { + wsService.sendWsMsg(sessionId, update);; + } + + @Override + protected String getExecutorPrefix() { + return "notification"; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java new file mode 100644 index 0000000000..872e4a9671 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java @@ -0,0 +1,64 @@ +/** + * 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.ws.notification.sub; + +import lombok.Builder; +import lombok.Getter; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.service.subscription.TbSubscription; +import org.thingsboard.server.service.subscription.TbSubscriptionType; +import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; + +@Getter +public class NotificationsSubscription extends TbSubscription { + + private final Map unreadNotifications = new LinkedHashMap<>(); + private final int limit; + private final AtomicInteger totalUnreadCount = new AtomicInteger(); + + @Builder + public NotificationsSubscription(String serviceId, String sessionId, int subscriptionId, TenantId tenantId, EntityId entityId, + BiConsumer updateProcessor, + int limit) { + super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.NOTIFICATIONS, updateProcessor); + this.limit = limit; + } + + public UnreadNotificationsUpdate createFullUpdate() { + return UnreadNotificationsUpdate.builder() + .cmdId(getSubscriptionId()) + .notifications(unreadNotifications.values()) + .totalUnreadCount(totalUnreadCount.get()) + .build(); + } + + public UnreadNotificationsUpdate createPartialUpdate(Notification notification) { + return UnreadNotificationsUpdate.builder() + .cmdId(getSubscriptionId()) + .update(notification) + .totalUnreadCount(totalUnreadCount.get()) + .build(); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionService.java new file mode 100644 index 0000000000..0afd8dcd39 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionService.java @@ -0,0 +1,42 @@ +/** + * 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.ws.notification.sub; + +import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationAsReadCmd; +import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; + +public interface NotificationsSubscriptionService { + + void handleUnreadNotificationsSubCmd(WebSocketSessionRef sessionRef, NotificationsSubCmd cmd); + + void handleMarkAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationAsReadCmd cmd); + + void handleUnsubCmd(WebSocketSessionRef sessionRef, UnsubscribeCmd cmd); + + + void onNewNotification(TenantId tenantId, UserId recipientId, Notification notification); + + void onNotificationUpdated(TenantId tenantId, UserId recipientId, Notification notification); + + void onNotificationRequestDeleted(TenantId tenantId, NotificationRequestId notificationRequestId); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionUpdate.java new file mode 100644 index 0000000000..c674e149d9 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionUpdate.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.notification.sub; + +import lombok.Builder; +import lombok.Data; +import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.notification.Notification; + +@Data +@Builder +public class NotificationsSubscriptionUpdate { + + private final Notification notification; + + private final boolean notificationRequestDeleted; + private final NotificationRequestId notificationRequestId; + +} diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryFeature.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/TelemetryFeature.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryFeature.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/TelemetryFeature.java index b0a63970a5..ea3b1d8752 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryFeature.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/TelemetryFeature.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry; +package org.thingsboard.server.service.ws.telemetry; /** * Created by ashvayka on 08.05.17. diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketTextMsg.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/TelemetryWebSocketTextMsg.java similarity index 82% rename from application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketTextMsg.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/TelemetryWebSocketTextMsg.java index 44f2ea0943..9c4087e0a0 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketTextMsg.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/TelemetryWebSocketTextMsg.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry; +package org.thingsboard.server.service.ws.telemetry; import lombok.Data; +import org.thingsboard.server.service.ws.WebSocketSessionRef; /** * Created by ashvayka on 27.03.18. @@ -23,7 +24,7 @@ import lombok.Data; @Data public class TelemetryWebSocketTextMsg { - private final TelemetryWebSocketSessionRef sessionRef; + private final WebSocketSessionRef sessionRef; private final String payload; } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/WebSocketService.java similarity index 63% rename from application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketService.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/WebSocketService.java index 510283b31d..d0d8dab59e 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/WebSocketService.java @@ -13,21 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry; +package org.thingsboard.server.service.ws.telemetry; import org.springframework.web.socket.CloseStatus; -import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate; -import org.thingsboard.server.service.telemetry.cmd.v2.DataUpdate; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.SessionEvent; +import org.thingsboard.server.service.ws.WebSocketSessionRef; /** * Created by ashvayka on 27.03.18. */ -public interface TelemetryWebSocketService { +public interface WebSocketService { - void handleWebSocketSessionEvent(TelemetryWebSocketSessionRef sessionRef, SessionEvent sessionEvent); + void handleWebSocketSessionEvent(WebSocketSessionRef sessionRef, SessionEvent sessionEvent); - void handleWebSocketMsg(TelemetryWebSocketSessionRef sessionRef, String msg); + void handleWebSocketMsg(WebSocketSessionRef sessionRef, String msg); void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate update); diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/TelemetryPluginCmdsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryPluginCmdsWrapper.java similarity index 62% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/TelemetryPluginCmdsWrapper.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryPluginCmdsWrapper.java index 0c46f90610..99840e271a 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/TelemetryPluginCmdsWrapper.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryPluginCmdsWrapper.java @@ -13,18 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd; +package org.thingsboard.server.service.ws.telemetry.cmd; import lombok.Data; -import org.thingsboard.server.service.telemetry.cmd.v1.AttributesSubscriptionCmd; -import org.thingsboard.server.service.telemetry.cmd.v1.GetHistoryCmd; -import org.thingsboard.server.service.telemetry.cmd.v1.TimeseriesSubscriptionCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUnsubscribeCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUnsubscribeCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v1.GetHistoryCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v1.TimeseriesSubscriptionCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUnsubscribeCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataUnsubscribeCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUnsubscribeCmd; import java.util.List; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/AttributesSubscriptionCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/AttributesSubscriptionCmd.java similarity index 87% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/AttributesSubscriptionCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/AttributesSubscriptionCmd.java index f5e2630af8..b6ef06f2c9 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/AttributesSubscriptionCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/AttributesSubscriptionCmd.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v1; +package org.thingsboard.server.service.ws.telemetry.cmd.v1; import lombok.NoArgsConstructor; -import org.thingsboard.server.service.telemetry.TelemetryFeature; +import org.thingsboard.server.service.ws.telemetry.TelemetryFeature; /** * @author Andrew Shvayka diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/GetHistoryCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/GetHistoryCmd.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/GetHistoryCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/GetHistoryCmd.java index b74f2f7ea7..4429498cd1 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/GetHistoryCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/GetHistoryCmd.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v1; +package org.thingsboard.server.service.ws.telemetry.cmd.v1; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/SubscriptionCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/SubscriptionCmd.java similarity index 90% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/SubscriptionCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/SubscriptionCmd.java index 0c912fac30..f786c43ea3 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/SubscriptionCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/SubscriptionCmd.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v1; +package org.thingsboard.server.service.ws.telemetry.cmd.v1; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import org.thingsboard.server.service.telemetry.TelemetryFeature; +import org.thingsboard.server.service.ws.telemetry.TelemetryFeature; @NoArgsConstructor @AllArgsConstructor diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/TelemetryPluginCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TelemetryPluginCmd.java similarity index 92% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/TelemetryPluginCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TelemetryPluginCmd.java index 7423e9e4b0..71b1b7eb7b 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/TelemetryPluginCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TelemetryPluginCmd.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v1; +package org.thingsboard.server.service.ws.telemetry.cmd.v1; /** * @author Andrew Shvayka diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java similarity index 89% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java index 309a9297d6..facd48f6c1 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v1; +package org.thingsboard.server.service.ws.telemetry.cmd.v1; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import org.thingsboard.server.service.telemetry.TelemetryFeature; +import org.thingsboard.server.service.ws.telemetry.TelemetryFeature; /** * @author Andrew Shvayka diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggHistoryCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AggHistoryCmd.java similarity index 92% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggHistoryCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AggHistoryCmd.java index 423c55bd0d..0ffb9afa6f 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggHistoryCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AggHistoryCmd.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggKey.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AggKey.java similarity index 93% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggKey.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AggKey.java index d567149d01..6ffb303d73 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggKey.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AggKey.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; import org.thingsboard.server.common.data.kv.Aggregation; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggTimeSeriesCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AggTimeSeriesCmd.java similarity index 92% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggTimeSeriesCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AggTimeSeriesCmd.java index 1f4d88d2c3..37f35258e6 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggTimeSeriesCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AggTimeSeriesCmd.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataCmd.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataCmd.java index c2c01f8f77..1caf75faa5 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataCmd.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java similarity index 92% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java index 153935b7a7..9cee3f707d 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataUpdate.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataUpdate.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataUpdate.java index afd9d12b16..cdcdd41b54 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataUpdate.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; @@ -21,7 +21,7 @@ import lombok.Getter; import lombok.ToString; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.AlarmData; -import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; +import org.thingsboard.server.service.subscription.SubscriptionErrorCode; import java.util.List; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdate.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdate.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdate.java index 075c80b92b..4ce684e8b2 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdate.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.AllArgsConstructor; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdateType.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdateType.java similarity index 87% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdateType.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdateType.java index cd78e760a6..76b63ad9b8 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdateType.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdateType.java @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; public enum CmdUpdateType { ENTITY_DATA, ALARM_DATA, - COUNT_DATA + COUNT_DATA, + NOTIFICATIONS } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataCmd.java similarity index 89% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataCmd.java index b93af17d89..acbd5a8d1f 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataCmd.java @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; import lombok.Getter; -import lombok.NoArgsConstructor; @Data public class DataCmd { diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataUpdate.java similarity index 91% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataUpdate.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataUpdate.java index eda60b87b4..19855158d3 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataUpdate.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Getter; import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; +import org.thingsboard.server.service.subscription.SubscriptionErrorCode; import java.util.List; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountCmd.java similarity index 90% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountCmd.java index c71884df38..d543d0fccc 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountCmd.java @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import org.thingsboard.server.common.data.query.EntityCountQuery; -import org.thingsboard.server.common.data.query.EntityDataQuery; public class EntityCountCmd extends DataCmd { diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java similarity index 92% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java index c8daf30833..3bbbee784a 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountUpdate.java similarity index 85% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUpdate.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountUpdate.java index de61d0e5fb..42f87610a3 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountUpdate.java @@ -13,17 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.ToString; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.query.EntityData; -import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; - -import java.util.List; +import org.thingsboard.server.service.subscription.SubscriptionErrorCode; @ToString public class EntityCountUpdate extends CmdUpdate { diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataCmd.java similarity index 97% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataCmd.java index 8a187afa7b..e6daf4d6be 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataCmd.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java similarity index 92% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java index 45aa0c5216..1f0e1762fe 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataUpdate.java similarity index 93% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUpdate.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataUpdate.java index 8da696ce9c..03855a8aee 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataUpdate.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; @@ -21,7 +21,7 @@ import lombok.Getter; import lombok.ToString; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.EntityData; -import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; +import org.thingsboard.server.service.subscription.SubscriptionErrorCode; import java.util.List; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityHistoryCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityHistoryCmd.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityHistoryCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityHistoryCmd.java index 946775889f..147715a1ce 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityHistoryCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityHistoryCmd.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; import org.thingsboard.server.common.data.kv.Aggregation; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/GetTsCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/GetTsCmd.java similarity index 93% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/GetTsCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/GetTsCmd.java index 724c2d6fd2..a5ba6fea4c 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/GetTsCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/GetTsCmd.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import org.thingsboard.server.common.data.kv.Aggregation; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/LatestValueCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/LatestValueCmd.java similarity index 92% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/LatestValueCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/LatestValueCmd.java index 184026ac54..5bce180204 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/LatestValueCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/LatestValueCmd.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; import org.thingsboard.server.common.data.query.EntityKey; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/TimeSeriesCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/TimeSeriesCmd.java similarity index 95% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/TimeSeriesCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/TimeSeriesCmd.java index daf9c32fbd..6ea18baf4d 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/TimeSeriesCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/TimeSeriesCmd.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/UnsubscribeCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/UnsubscribeCmd.java similarity index 91% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/UnsubscribeCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/UnsubscribeCmd.java index eec564e7a8..790bfc4416 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/UnsubscribeCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/UnsubscribeCmd.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; public interface UnsubscribeCmd { diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/AlarmSubscriptionUpdate.java similarity index 85% rename from application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/AlarmSubscriptionUpdate.java index 3f4bc9ce1e..34528906ed 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/AlarmSubscriptionUpdate.java @@ -13,19 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.sub; +package org.thingsboard.server.service.ws.telemetry.sub; import lombok.Getter; import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.kv.TsKvEntry; -import org.thingsboard.server.common.data.query.AlarmData; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import java.util.stream.Collectors; +import org.thingsboard.server.service.subscription.SubscriptionErrorCode; public class AlarmSubscriptionUpdate { diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/SubscriptionState.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/SubscriptionState.java similarity index 95% rename from application/src/main/java/org/thingsboard/server/service/telemetry/sub/SubscriptionState.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/SubscriptionState.java index 1343527444..7c11361bc7 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/SubscriptionState.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/SubscriptionState.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.sub; +package org.thingsboard.server.service.ws.telemetry.sub; import lombok.AllArgsConstructor; import lombok.Getter; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.service.telemetry.TelemetryFeature; +import org.thingsboard.server.service.ws.telemetry.TelemetryFeature; import java.util.Map; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/TelemetrySubscriptionUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/TelemetrySubscriptionUpdate.java similarity index 96% rename from application/src/main/java/org/thingsboard/server/service/telemetry/sub/TelemetrySubscriptionUpdate.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/TelemetrySubscriptionUpdate.java index cbbce43405..cddc6bbc13 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/TelemetrySubscriptionUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/TelemetrySubscriptionUpdate.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.sub; +package org.thingsboard.server.service.ws.telemetry.sub; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.service.subscription.SubscriptionErrorCode; import java.util.ArrayList; import java.util.Collections; diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java index ed8d8632da..96bde3f36f 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java @@ -53,6 +53,7 @@ public abstract class AbstractControllerTest extends AbstractNotifyEntityTest { protected int wsPort; private TbTestWebSocketClient wsClient; // lazy + private TbTestWebSocketClient anotherWsClient; // lazy public TbTestWebSocketClient getWsClient() { if (wsClient == null) { @@ -69,6 +70,21 @@ public abstract class AbstractControllerTest extends AbstractNotifyEntityTest { return wsClient; } + public TbTestWebSocketClient getAnotherWsClient() { + if (anotherWsClient == null) { + synchronized (this) { + try { + if (anotherWsClient == null) { + anotherWsClient = buildAndConnectWebSocketClient(); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + return anotherWsClient; + } + @Before public void beforeWsTest() throws Exception { // placeholder @@ -79,9 +95,12 @@ public abstract class AbstractControllerTest extends AbstractNotifyEntityTest { if (wsClient != null) { wsClient.close(); } + if (anotherWsClient != null) { + anotherWsClient.close(); + } } - private TbTestWebSocketClient buildAndConnectWebSocketClient() throws URISyntaxException, InterruptedException { + protected TbTestWebSocketClient buildAndConnectWebSocketClient() throws URISyntaxException, InterruptedException { TbTestWebSocketClient wsClient = new TbTestWebSocketClient(new URI(WS_URL + wsPort + "/api/ws/plugins/telemetry?token=" + token)); assertThat(wsClient.connectBlocking(TIMEOUT, TimeUnit.SECONDS)).isTrue(); return wsClient; diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java index f7838d9689..ba229b68a5 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java @@ -44,9 +44,9 @@ import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; import java.util.Arrays; import java.util.Collections; diff --git a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java index be7973d517..ac170a170d 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java +++ b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java @@ -24,14 +24,14 @@ import org.thingsboard.server.common.data.query.EntityDataPageLink; import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.common.data.query.EntityFilter; import org.thingsboard.server.common.data.query.EntityKey; -import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryPluginCmdsWrapper; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityHistoryCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.TimeSeriesCmd; import java.net.URI; import java.nio.channels.NotYetConnectedException; diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWebSocketClient.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWebSocketClient.java new file mode 100644 index 0000000000..cba34a3f28 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWebSocketClient.java @@ -0,0 +1,75 @@ +package org.thingsboard.server.service.notification; + +import lombok.Getter; +import org.apache.commons.lang3.RandomUtils; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.id.IdBased; +import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.controller.TbTestWebSocketClient; +import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationAsReadCmd; +import org.thingsboard.server.service.ws.notification.cmd.NotificationCmdsWrapper; +import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; +import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +public class NotificationsWebSocketClient extends TbTestWebSocketClient { + + private final Map currentNotifications = new LinkedHashMap<>(); + @Getter + private int totalUnreadCount; + @Getter + private UnreadNotificationsUpdate lastUpdate; + + public NotificationsWebSocketClient(String wsUrl, String token) throws URISyntaxException { + super(new URI(wsUrl + "/api/ws/plugins/notifications?token=" + token)); + } + + public void subscribeForUnreadNotifications(int limit) { + NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper(); + cmdsWrapper.setUnreadSubCmd(new NotificationsSubCmd(newCmdId(), limit)); + sendCmd(cmdsWrapper); + } + + public void markNotificationAsRead(UUID notificationId) { + NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper(); + cmdsWrapper.setMarkAsReadCmd(new MarkNotificationAsReadCmd(newCmdId(), notificationId)); + sendCmd(cmdsWrapper); + } + + + private void handleUpdate(UnreadNotificationsUpdate update) { + totalUnreadCount = update.getTotalUnreadCount(); + if (update.getNotifications() != null) { + currentNotifications.clear(); + currentNotifications.putAll(update.getNotifications().stream().collect(Collectors.toMap(IdBased::getUuidId, n -> n))); + } else if (update.getUpdate() != null) { + Notification notification = update.getUpdate(); + currentNotifications.put(notification.getUuidId(), notification); + } + } + + + public void sendCmd(NotificationCmdsWrapper cmdsWrapper) { + send(JacksonUtil.toString(cmdsWrapper)); + } + + @Override + public void onMessage(String s) { + UnreadNotificationsUpdate update = JacksonUtil.fromString(s, UnreadNotificationsUpdate.class); + lastUpdate = update; + handleUpdate(update); + super.onMessage(s); + } + + private static int newCmdId() { + return RandomUtils.nextInt(1, 1000); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWsApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWsApiTest.java new file mode 100644 index 0000000000..12b2eb189f --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWsApiTest.java @@ -0,0 +1,147 @@ +package org.thingsboard.server.service.notification; + +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.targets.NotificationTarget; +import org.thingsboard.server.common.data.notification.targets.SingleUserNotificationTargetConfig; +import org.thingsboard.server.controller.AbstractControllerTest; +import org.thingsboard.server.controller.TbTestWebSocketClient; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; + +import java.net.URISyntaxException; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +@DaoSqlTest +public class NotificationsWsApiTest extends AbstractControllerTest { + + @Before + public void beforeEach() throws Exception { + loginTenantAdmin(); + } + + @Test + public void testSubscribingToUnreadNotifications_multipleSessions() throws Exception { + NotificationTarget notificationTarget = createNotificationTarget(tenantAdminUserId); + String notificationText1 = "Notification 1"; + submitNotificationRequest(notificationTarget.getId(), "Just a test", notificationText1); + String notificationText2 = "Notification 2"; + submitNotificationRequest(notificationTarget.getId(), "Just a test", notificationText2); + + getWsClient().subscribeForUnreadNotifications(10); + getAnotherWsClient().subscribeForUnreadNotifications(10); + getWsClient().waitForReply(); + getAnotherWsClient().waitForReply(); + + checkFullNotificationsUpdate(getWsClient().getLastUpdate(), notificationText1, notificationText2); + checkFullNotificationsUpdate(getAnotherWsClient().getLastUpdate(), notificationText1, notificationText2); + } + + @Test + public void testReceivingNotificationUpdates_multipleSessions() { + getWsClient().subscribeForUnreadNotifications(10); + getAnotherWsClient().subscribeForUnreadNotifications(10); + getWsClient().waitForReply(); + getAnotherWsClient().waitForReply(); + UnreadNotificationsUpdate notificationsUpdate = getWsClient().getLastUpdate(); + assertThat(notificationsUpdate.getTotalUnreadCount()).isZero(); + + getWsClient().registerWaitForUpdate(); + getAnotherWsClient().registerWaitForUpdate(); + NotificationTarget notificationTarget = createNotificationTarget(tenantAdminUserId); + String notificationText = "Notification 1"; + submitNotificationRequest(notificationTarget.getId(), "Just a test", notificationText); + getWsClient().waitForUpdate(); + getAnotherWsClient().waitForUpdate(); + + checkPartialNotificationsUpdate(getWsClient().getLastUpdate(), notificationText, 1); + checkPartialNotificationsUpdate(getAnotherWsClient().getLastUpdate(), notificationText, 1); + } + + @Test + public void testMarkingAsRead_multipleSessions() { + getWsClient().subscribeForUnreadNotifications(10); + getAnotherWsClient().subscribeForUnreadNotifications(10); + getWsClient().waitForReply(); + getAnotherWsClient().waitForReply(); + + NotificationTarget notificationTarget = createNotificationTarget(tenantAdminUserId); + getWsClient().registerWaitForUpdate(); + String notificationText1 = "Notification 1"; + submitNotificationRequest(notificationTarget.getId(), "Just a test", notificationText1); + getWsClient().waitForUpdate(); + Notification notification1 = getWsClient().getLastUpdate().getUpdate(); + + getWsClient().registerWaitForUpdate(); + String notificationText2 = "Notification 2"; + submitNotificationRequest(notificationTarget.getId(), "Just a test", notificationText2); + getWsClient().waitForUpdate(); + assertThat(getWsClient().getLastUpdate().getTotalUnreadCount()).isEqualTo(2); + + getWsClient().registerWaitForUpdate(); + getAnotherWsClient().registerWaitForUpdate(); + getWsClient().markNotificationAsRead(notification1.getUuidId()); + getWsClient().waitForUpdate(); + getAnotherWsClient().waitForUpdate(); + + checkFullNotificationsUpdate(getWsClient().getLastUpdate(), notificationText2); + checkFullNotificationsUpdate(getAnotherWsClient().getLastUpdate(), notificationText2); + } + + public void testReceivingUpdatesWhenSubscriptionAtAnotherInstance() {} + + + private void checkFullNotificationsUpdate(UnreadNotificationsUpdate notificationsUpdate, String... expectedNotifications) { + assertThat(notificationsUpdate.getNotifications()).extracting(Notification::getText).containsOnly(expectedNotifications); + assertThat(notificationsUpdate.getTotalUnreadCount()).isEqualTo(expectedNotifications.length); + } + + private void checkPartialNotificationsUpdate(UnreadNotificationsUpdate notificationsUpdate, String expectedNotification, int expectedUnreadCount) { + assertThat(notificationsUpdate.getUpdate()).extracting(Notification::getText).isEqualTo(expectedNotification); + assertThat(notificationsUpdate.getTotalUnreadCount()).isEqualTo(expectedUnreadCount); + } + + private NotificationTarget createNotificationTarget(UserId userId) { + NotificationTarget notificationTarget = new NotificationTarget(); + notificationTarget.setTenantId(tenantId); + notificationTarget.setName("User " + userId); + SingleUserNotificationTargetConfig config = new SingleUserNotificationTargetConfig(); + config.setUserId(userId); + notificationTarget.setConfiguration(config); + return doPost("/api/notification/target", notificationTarget, NotificationTarget.class); + } + + private NotificationRequest submitNotificationRequest(NotificationTargetId targetId, String notificationReason, String text) { + NotificationRequest notificationRequest = NotificationRequest.builder() + .tenantId(tenantId) + .targetId(targetId) + .notificationReason(notificationReason) + .textTemplate(text) + .build(); + return doPost("/api/notification/request", notificationRequest, NotificationRequest.class); + } + + @Override + protected TbTestWebSocketClient buildAndConnectWebSocketClient() throws URISyntaxException, InterruptedException { + NotificationsWebSocketClient wsClient = new NotificationsWebSocketClient(WS_URL + wsPort, token); + assertThat(wsClient.connectBlocking(TIMEOUT, TimeUnit.SECONDS)).isTrue(); + return wsClient; + } + + @Override + public NotificationsWebSocketClient getWsClient() { + return (NotificationsWebSocketClient) super.getWsClient(); + } + + @Override + public NotificationsWebSocketClient getAnotherWsClient() { + return (NotificationsWebSocketClient) super.getAnotherWsClient(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index ac272d0bb4..6ef55e3a20 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -64,10 +64,10 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; -import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryPluginCmdsWrapper; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd; import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler; diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java index 6d030aca9f..6e03a15797 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java @@ -22,7 +22,6 @@ import com.google.protobuf.InvalidProtocolBufferException; import com.squareup.wire.schema.internal.parser.ProtoFileElement; import io.netty.handler.codec.mqtt.MqttQoS; import lombok.extern.slf4j.Slf4j; -import org.springframework.test.context.TestPropertySource; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.TransportPayloadType; @@ -38,7 +37,7 @@ import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.SingleEntityFilter; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import org.thingsboard.server.transport.mqtt.MqttTestCallback; import org.thingsboard.server.transport.mqtt.MqttTestClient; diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index cb3443a71f..7fb764579d 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -547,6 +547,11 @@ message TbAlarmSubscriptionProto { int64 ts = 2; } +message NotificationsSubscriptionProto { + TbSubscriptionProto sub = 1; + int32 limit = 2; +} + message TbSubscriptionUpdateProto { string sessionId = 1; int32 subscriptionId = 2; @@ -564,6 +569,27 @@ message TbAlarmSubscriptionUpdateProto { bool deleted = 6; } +message NotificationsSubscriptionUpdateProto { + string sessionId = 1; + int32 subscriptionId = 2; + string notification = 3; +} + +message NotificationUpdateProto { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + int64 recipientIdMSB = 3; + int64 recipientIdLSB = 4; + string notification = 5; +} + +message NotificationRequestDeleteProto { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + int64 notificationRequestIdMSB = 3; + int64 notificationRequestIdLSB = 4; +} + message TbAttributeUpdateProto { string entityType = 1; int64 entityIdMSB = 2; @@ -665,11 +691,15 @@ message SubscriptionMgrMsgProto { TbAlarmUpdateProto alarmUpdate = 8; TbAlarmDeleteProto alarmDelete = 9; TbTimeSeriesDeleteProto tsDelete = 10; + NotificationsSubscriptionProto notificationsSub = 11; + NotificationUpdateProto notificationUpdate = 12; + NotificationRequestDeleteProto notificationRequestDelete = 13; } message LocalSubscriptionServiceMsgProto { TbSubscriptionUpdateProto subUpdate = 1; TbAlarmSubscriptionUpdateProto alarmSubUpdate = 2; + NotificationsSubscriptionUpdateProto notificationsSubUpdate = 3; } message FromDeviceRPCResponseProto { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java index 9acbb15646..cb97f56bcf 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java @@ -142,6 +142,15 @@ public class StringUtils { return org.apache.commons.lang3.StringUtils.equals(str1, str2); } + public static boolean equalsAny(String string, String... otherStrings) { + for (String otherString : otherStrings) { + if (equals(string, otherString)) { + return true; + } + } + return false; + } + public static String substringAfterLast(String str, String sep) { return org.apache.commons.lang3.StringUtils.substringAfterLast(str, sep); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java index 8b1b994b17..054222a454 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java @@ -19,7 +19,9 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; @@ -55,11 +57,22 @@ public class DefaultNotificationService implements NotificationService { return notificationRequestDao.save(tenantId, notificationRequest); } + @Override + public NotificationRequest findNotificationRequestById(TenantId tenantId, NotificationRequestId id) { + return notificationRequestDao.findById(tenantId, id.getId()); + } + @Override public PageData findNotificationRequestsByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) { return notificationRequestDao.findByTenantIdAndPageLink(tenantId, pageLink); } + // ON DELETE CASCADE is used: notifications for request are deleted as well + @Override + public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId id) { + notificationRequestDao.removeById(tenantId, id.getId()); + } + @Override public Notification createNotification(TenantId tenantId, Notification notification) { if (notification.getId() != null) { @@ -68,9 +81,11 @@ public class DefaultNotificationService implements NotificationService { return notificationDao.save(tenantId, notification); } + @Transactional @Override - public void updateNotificationStatus(TenantId tenantId, NotificationId notificationId, NotificationStatus status) { + public Notification updateNotificationStatus(TenantId tenantId, NotificationId notificationId, NotificationStatus status) { notificationDao.updateStatus(tenantId, notificationId, status); + return notificationDao.findById(tenantId, notificationId.getId()); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java index a7d5393d43..52f9f8b61b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java @@ -42,6 +42,7 @@ public class DefaultNotificationTargetService implements NotificationTargetServi @Override public NotificationTarget saveNotificationTarget(TenantId tenantId, NotificationTarget notificationTarget) { + notificationTarget.setTenantId(tenantId); validator.validate(notificationTarget, NotificationTarget::getTenantId); return notificationTargetDao.save(tenantId, notificationTarget); } @@ -81,6 +82,11 @@ public class DefaultNotificationTargetService implements NotificationTargetServi } private static class NotificationTargetValidator extends DataValidator { + + @Override + protected void validateDataImpl(TenantId tenantId, NotificationTarget notificationTarget) { + super.validateDataImpl(tenantId, notificationTarget); + } } } From 4d34c328d605237d8fde6bba437b6a54abee0069 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Mon, 31 Oct 2022 17:44:21 +0200 Subject: [PATCH 005/111] New TbNotificationNode; refactoring --- .../main/data/upgrade/3.4.2/schema_update.sql | 3 +- .../server/actors/ActorSystemContext.java | 5 ++ .../actors/ruleChain/DefaultTbContext.java | 6 ++ .../controller/NotificationController.java | 11 +-- .../NotificationTargetController.java | 1 + .../DefaultNotificationProcessingService.java | 89 +++++++++++++------ .../NotificationsSubscription.java | 5 +- .../subscription/TbSubscriptionUtils.java | 1 - .../telemetry/DefaultWebSocketService.java | 11 +-- ...> DefaultNotificationCommandsHandler.java} | 82 +++-------------- ....java => NotificationCommandsHandler.java} | 15 +--- .../NotificationsWebSocketClient.java | 16 +++- .../notification/NotificationsWsApiTest.java | 15 ++++ .../NotificationProcessingService.java | 12 +-- .../data/notification/Notification.java | 8 +- .../notification/NotificationRequest.java | 1 - .../server/dao/model/ModelConstants.java | 1 - .../dao/model/sql/NotificationEntity.java | 1 + .../model/sql/NotificationRequestEntity.java | 5 -- .../DefaultNotificationService.java | 5 -- .../main/resources/sql/schema-entities.sql | 3 +- .../rule/engine/api/TbContext.java | 3 + .../notification/TbNotificationNode.java | 83 +++++++++++++++++ .../TbNotificationNodeConfiguration.java | 40 +++++++++ 24 files changed, 269 insertions(+), 153 deletions(-) rename application/src/main/java/org/thingsboard/server/service/{ws/notification/sub => subscription}/NotificationsSubscription.java (92%) rename application/src/main/java/org/thingsboard/server/service/ws/notification/{sub/DefaultNotificationsSubscriptionService.java => DefaultNotificationCommandsHandler.java} (60%) rename application/src/main/java/org/thingsboard/server/service/ws/notification/{sub/NotificationsSubscriptionService.java => NotificationCommandsHandler.java} (64%) rename {application/src/main/java/org/thingsboard/server/service => common/dao-api/src/main/java/org/thingsboard/server/dao}/notification/NotificationProcessingService.java (65%) create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNodeConfiguration.java diff --git a/application/src/main/data/upgrade/3.4.2/schema_update.sql b/application/src/main/data/upgrade/3.4.2/schema_update.sql index 9b259cca3e..17c75773ad 100644 --- a/application/src/main/data/upgrade/3.4.2/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.2/schema_update.sql @@ -33,8 +33,7 @@ CREATE TABLE IF NOT EXISTS notification_request ( text_template VARCHAR NOT NULL, notification_info VARCHAR(1000), notification_severity VARCHAR(32), - additional_config VARCHAR(1000), - sender_id UUID + additional_config VARCHAR(1000) ); CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id_and_created_time ON notification_request(tenant_id, created_time DESC); diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index f4e7eb2463..2a574f5788 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -64,6 +64,7 @@ import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor; import org.thingsboard.server.dao.nosql.CassandraBufferedRateWriteExecutor; +import org.thingsboard.server.dao.notification.NotificationProcessingService; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; @@ -304,6 +305,10 @@ public class ActorSystemContext { @Getter private SmsSenderFactory smsSenderFactory; + @Autowired + @Getter + private NotificationProcessingService notificationProcessingService; + @Lazy @Autowired(required = false) @Getter diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 62d4829085..c0197a0f89 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -79,6 +79,7 @@ import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.nosql.CassandraStatementTask; import org.thingsboard.server.dao.nosql.TbResultSetFuture; +import org.thingsboard.server.dao.notification.NotificationProcessingService; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; @@ -630,6 +631,11 @@ class DefaultTbContext implements TbContext { return mainCtx.getSmsSenderFactory(); } + @Override + public NotificationProcessingService getNotificationProcessingService() { + return mainCtx.getNotificationProcessingService(); + } + @Override public RuleEngineRpcService getRpcService() { return mainCtx.getTbRuleEngineDeviceRpcService(); diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index f6f48aeb51..c3cfdc24e8 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -33,12 +33,11 @@ import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationRequest; -import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.notification.NotificationProcessingService; +import org.thingsboard.server.dao.notification.NotificationProcessingService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -73,16 +72,18 @@ public class NotificationController extends BaseController { public void markNotificationAsRead(@PathVariable UUID id, @AuthenticationPrincipal SecurityUser user) { NotificationId notificationId = new NotificationId(id); - notificationProcessingService.markNotificationAsRead(user, notificationId); + notificationProcessingService.markNotificationAsRead(user.getTenantId(), user.getId(), notificationId); } + // delete notification? @PostMapping("/notification/request") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public NotificationRequest createNotificationRequest(@RequestBody NotificationRequest notificationRequest, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { accessControlService.checkPermission(user, Resource.NOTIFICATION, Operation.CREATE); - return notificationProcessingService.processNotificationRequest(user, notificationRequest); + // read permission for target's users + return notificationProcessingService.processNotificationRequest(user.getTenantId(), notificationRequest); } @GetMapping("/notification/request/{id}") @@ -110,7 +111,7 @@ public class NotificationController extends BaseController { public void deleteNotificationRequest(@PathVariable UUID id, @AuthenticationPrincipal SecurityUser user) { NotificationRequestId notificationRequestId = new NotificationRequestId(id); - notificationProcessingService.deleteNotificationRequest(user, notificationRequestId); + notificationProcessingService.deleteNotificationRequest(user.getTenantId(), notificationRequestId); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java index 471b72e015..3eec3b6ffc 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java @@ -61,6 +61,7 @@ public class NotificationTargetController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public NotificationTarget saveNotificationTarget(@RequestBody NotificationTarget notificationTarget, @AuthenticationPrincipal SecurityUser user) { + // fixme: read permission check for users in target return notificationTargetService.saveNotificationTarget(user.getTenantId(), notificationTarget); } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java index 862ce7389a..57b295bddd 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java @@ -16,66 +16,84 @@ package org.thingsboard.server.service.notification; import com.google.common.base.Strings; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; 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.exception.ThingsboardException; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationRequestConfig; import org.thingsboard.server.common.data.notification.NotificationStatus; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.dao.notification.NotificationProcessingService; import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.user.UserService; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.discovery.NotificationsTopicService; +import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; -import org.thingsboard.server.service.security.model.SecurityUser; -import org.thingsboard.server.service.security.permission.AccessControlService; -import org.thingsboard.server.service.security.permission.Operation; -import org.thingsboard.server.service.security.permission.Resource; -import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionService; +import org.thingsboard.server.service.subscription.TbSubscriptionUtils; +import org.thingsboard.server.service.telemetry.AbstractSubscriptionService; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.UUID; @Service @Slf4j -@RequiredArgsConstructor -public class DefaultNotificationProcessingService implements NotificationProcessingService { +public class DefaultNotificationProcessingService extends AbstractSubscriptionService implements NotificationProcessingService { private final NotificationTargetService notificationTargetService; private final NotificationService notificationService; private final UserService userService; - private final AccessControlService accessControlService; private final DbCallbackExecutorService dbCallbackExecutorService; - private final NotificationsSubscriptionService notificationsSubscriptionService; + private final NotificationsTopicService notificationsTopicService; + + public DefaultNotificationProcessingService(TbClusterService clusterService, PartitionService partitionService, + NotificationTargetService notificationTargetService, + NotificationService notificationService, UserService userService, + DbCallbackExecutorService dbCallbackExecutorService, + NotificationsTopicService notificationsTopicService) { + super(clusterService, partitionService); + this.notificationTargetService = notificationTargetService; + this.notificationService = notificationService; + this.userService = userService; + this.dbCallbackExecutorService = dbCallbackExecutorService; + this.notificationsTopicService = notificationsTopicService; + } @Override - public NotificationRequest processNotificationRequest(SecurityUser user, NotificationRequest notificationRequest) throws ThingsboardException { - TenantId tenantId = user.getTenantId(); + public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { List recipientsIds = notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId()); List recipients = new ArrayList<>(); for (UserId recipientId : recipientsIds) { User recipient = userService.findUserById(tenantId, recipientId); // todo: add caching - accessControlService.checkPermission(user, Resource.USER, Operation.READ, recipientId, recipient); recipients.add(recipient); } notificationRequest.setTenantId(tenantId); - notificationRequest.setSenderId(user.getId()); NotificationRequest savedNotificationRequest = notificationService.createNotificationRequest(tenantId, notificationRequest); - // todo: delayed sending; check all delayed notification requests on start up, schedule send + if (notificationRequest.getAdditionalConfig() != null) { + NotificationRequestConfig config = notificationRequest.getAdditionalConfig(); + // todo: delayed sending; check all delayed notification requests on start up, schedule send + } for (User recipient : recipients) { dbCallbackExecutorService.submit(() -> { Notification notification = createNotification(recipient, savedNotificationRequest); - notificationsSubscriptionService.onNewNotification(recipient.getTenantId(), recipient.getId(), notification); + onNotificationUpdate(recipient.getTenantId(), recipient.getId(), notification); }); } @@ -83,15 +101,15 @@ public class DefaultNotificationProcessingService implements NotificationProcess } @Override - public void markNotificationAsRead(SecurityUser user, NotificationId notificationId) { - Notification notification = notificationService.updateNotificationStatus(user.getTenantId(), notificationId, NotificationStatus.READ); - notificationsSubscriptionService.onNotificationUpdated(user.getTenantId(), user.getId(), notification); + public void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId) { + Notification notification = notificationService.updateNotificationStatus(tenantId, notificationId, NotificationStatus.READ); + onNotificationUpdate(tenantId, recipientId, notification); } @Override - public void deleteNotificationRequest(SecurityUser user, NotificationRequestId notificationRequestId) { - notificationService.deleteNotificationRequest(user.getTenantId(), notificationRequestId); - notificationsSubscriptionService.onNotificationRequestDeleted(user.getTenantId(), notificationRequestId); + public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId) { + notificationService.deleteNotificationRequest(tenantId, notificationRequestId); + onNotificationRequestDeleted(tenantId, notificationRequestId); } private Notification createNotification(User recipient, NotificationRequest notificationRequest) { @@ -104,8 +122,7 @@ public class DefaultNotificationProcessingService implements NotificationProcess .severity(notificationRequest.getNotificationSeverity()) .status(NotificationStatus.SENT) .build(); - notification = notificationService.createNotification(recipient.getTenantId(), notification); - return notification; + return notificationService.createNotification(recipient.getTenantId(), notification); } private String formatNotificationText(String template, User recipient) { @@ -117,6 +134,26 @@ public class DefaultNotificationProcessingService implements NotificationProcess return TbNodeUtils.processTemplate(template, context); } - // handle markAsRead and deleteNotificationRequest - send UnreadNotificationsUpdate with updated list + private void onNotificationUpdate(TenantId tenantId, UserId recipientId, Notification notification) { + forwardToSubscriptionManagerServiceOrSendToCore(tenantId, recipientId, subscriptionManagerService -> { + subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, notification, TbCallback.EMPTY); + }, () -> { + return TbSubscriptionUtils.notificationUpdateToProto(tenantId, recipientId, notification); + }); + } + + public void onNotificationRequestDeleted(TenantId tenantId, NotificationRequestId notificationRequestId) { + TransportProtos.ToCoreMsg notificationRequestDeletedProto = TbSubscriptionUtils.notificationRequestDeletedToProto(tenantId, notificationRequestId); + Set coreServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_CORE)); + for (String serviceId : coreServices) { + TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, serviceId); + clusterService.pushMsgToCore(tpi, UUID.randomUUID(), notificationRequestDeletedProto, null); + } + } + + @Override + protected String getExecutorPrefix() { + return "notification"; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java b/application/src/main/java/org/thingsboard/server/service/subscription/NotificationsSubscription.java similarity index 92% rename from application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java rename to application/src/main/java/org/thingsboard/server/service/subscription/NotificationsSubscription.java index 872e4a9671..00d23770e1 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/NotificationsSubscription.java @@ -13,16 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.ws.notification.sub; +package org.thingsboard.server.service.subscription; import lombok.Builder; import lombok.Getter; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.Notification; -import org.thingsboard.server.service.subscription.TbSubscription; -import org.thingsboard.server.service.subscription.TbSubscriptionType; import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; import java.util.LinkedHashMap; import java.util.Map; diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java index 603b1903f0..5fd0aafae4 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java @@ -54,7 +54,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdatePr import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; -import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscription; import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate; import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultWebSocketService.java index 86d41456da..f6df94b46f 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultWebSocketService.java @@ -66,11 +66,12 @@ import org.thingsboard.server.service.ws.SessionEvent; import org.thingsboard.server.service.ws.WebSocketMsgEndpoint; import org.thingsboard.server.service.ws.WebSocketSessionRef; import org.thingsboard.server.service.ws.WsSessionMetaData; +import org.thingsboard.server.service.ws.notification.DefaultNotificationCommandsHandler; +import org.thingsboard.server.service.ws.notification.NotificationCommandsHandler; import org.thingsboard.server.service.ws.notification.cmd.NotificationCmdsWrapper; import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationAsReadCmd; import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; import org.thingsboard.server.service.ws.notification.cmd.NotificationsUnsubCmd; -import org.thingsboard.server.service.ws.notification.sub.DefaultNotificationsSubscriptionService; import org.thingsboard.server.service.ws.telemetry.WebSocketService; import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryPluginCmdsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; @@ -136,7 +137,7 @@ public class DefaultWebSocketService implements WebSocketService { private TbEntityDataSubscriptionService entityDataSubService; @Autowired - private DefaultNotificationsSubscriptionService notificationsSubService; + private NotificationCommandsHandler notificationCmdsHandler; @Autowired private WebSocketMsgEndpoint msgEndpoint; @@ -286,21 +287,21 @@ public class DefaultWebSocketService implements WebSocketService { private void handleUnreadNotificationsSubCmd(WebSocketSessionRef sessionRef, NotificationsSubCmd cmd) { String sessionId = sessionRef.getSessionId(); if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)) { - notificationsSubService.handleUnreadNotificationsSubCmd(sessionRef, cmd); + notificationCmdsHandler.handleUnreadNotificationsSubCmd(sessionRef, cmd); } } private void handleUnreadNotificationsUnsubCmd(WebSocketSessionRef sessionRef, NotificationsUnsubCmd cmd) { String sessionId = sessionRef.getSessionId(); if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)) { - notificationsSubService.handleUnsubCmd(sessionRef, cmd); + notificationCmdsHandler.handleUnsubCmd(sessionRef, cmd); } } private void handleMarkNotificationAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationAsReadCmd cmd) { String sessionId = sessionRef.getSessionId(); if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)) { - notificationsSubService.handleMarkAsReadCmd(sessionRef, cmd); + notificationCmdsHandler.handleMarkAsReadCmd(sessionRef, cmd); } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/DefaultNotificationsSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java similarity index 60% rename from application/src/main/java/org/thingsboard/server/service/ws/notification/sub/DefaultNotificationsSubscriptionService.java rename to application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java index 119c14dc23..ad3f9ca4b0 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/DefaultNotificationsSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java @@ -13,71 +13,45 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.ws.notification.sub; +package org.thingsboard.server.service.ws.notification; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Lazy; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.id.IdBased; import org.thingsboard.server.common.data.id.NotificationId; -import org.thingsboard.server.common.data.id.NotificationRequestId; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.msg.queue.ServiceType; -import org.thingsboard.server.common.msg.queue.TbCallback; -import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.notification.NotificationService; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.discovery.NotificationsTopicService; -import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.notification.NotificationProcessingService; +import org.thingsboard.server.dao.notification.NotificationProcessingService; import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.subscription.NotificationsSubscription; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; -import org.thingsboard.server.service.subscription.TbSubscriptionUtils; -import org.thingsboard.server.service.telemetry.AbstractSubscriptionService; import org.thingsboard.server.service.ws.WebSocketSessionRef; import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationAsReadCmd; import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; import org.thingsboard.server.service.ws.telemetry.WebSocketService; import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; -import java.util.HashSet; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @Service @TbCoreComponent -@Slf4j -public class DefaultNotificationsSubscriptionService extends AbstractSubscriptionService implements NotificationsSubscriptionService { +@RequiredArgsConstructor +public class DefaultNotificationCommandsHandler implements NotificationCommandsHandler { + private final NotificationService notificationService; private final WebSocketService wsService; private final TbLocalSubscriptionService localSubscriptionService; - private final NotificationService notificationService; - private final TbServiceInfoProvider serviceInfoProvider; - private final NotificationsTopicService notificationsTopicService; private final NotificationProcessingService notificationProcessingService; - - public DefaultNotificationsSubscriptionService(TbClusterService clusterService, PartitionService partitionService, - @Lazy WebSocketService wsService, TbLocalSubscriptionService localSubscriptionService, - NotificationService notificationService, TbServiceInfoProvider serviceInfoProvider, - NotificationsTopicService notificationsTopicService, - @Lazy NotificationProcessingService notificationProcessingService) { - super(clusterService, partitionService); - this.wsService = wsService; - this.localSubscriptionService = localSubscriptionService; - this.notificationService = notificationService; - this.serviceInfoProvider = serviceInfoProvider; - this.notificationsTopicService = notificationsTopicService; - this.notificationProcessingService = notificationProcessingService; - } + private final TbServiceInfoProvider serviceInfoProvider; @Override public void handleUnreadNotificationsSubCmd(WebSocketSessionRef sessionRef, NotificationsSubCmd cmd) { @@ -100,7 +74,7 @@ public class DefaultNotificationsSubscriptionService extends AbstractSubscriptio @Override public void handleMarkAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationAsReadCmd cmd) { NotificationId notificationId = new NotificationId(cmd.getNotificationId()); - notificationProcessingService.markNotificationAsRead(sessionRef.getSecurityCtx(), notificationId); + notificationProcessingService.markNotificationAsRead(sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), notificationId); } @Override @@ -116,35 +90,6 @@ public class DefaultNotificationsSubscriptionService extends AbstractSubscriptio subscription.getTotalUnreadCount().set((int) notifications.getTotalElements()); } - @Override - public void onNewNotification(TenantId tenantId, UserId recipientId, Notification notification) { - onNotificationUpdate(tenantId, recipientId, notification); - } - - @Override - public void onNotificationUpdated(TenantId tenantId, UserId recipientId, Notification notification) { - onNotificationUpdate(tenantId, recipientId, notification); - } - - private void onNotificationUpdate(TenantId tenantId, UserId recipientId, Notification notification) { - forwardToSubscriptionManagerServiceOrSendToCore(tenantId, recipientId, subscriptionManagerService -> { - subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, notification, TbCallback.EMPTY); - }, () -> { - return TbSubscriptionUtils.notificationUpdateToProto(tenantId, recipientId, notification); - }); - } - - @Override - public void onNotificationRequestDeleted(TenantId tenantId, NotificationRequestId notificationRequestId) { - TransportProtos.ToCoreMsg notificationRequestDeletedProto = TbSubscriptionUtils.notificationRequestDeletedToProto(tenantId, notificationRequestId); - Set coreServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_CORE)); - for (String serviceId : coreServices) { - TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, serviceId); - clusterService.pushMsgToCore(tpi, UUID.randomUUID(), notificationRequestDeletedProto, null); - } - } - - private void handleSubscriptionUpdate(NotificationsSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) { if (subscriptionUpdate.getNotification() != null) { Notification notification = subscriptionUpdate.getNotification(); @@ -172,12 +117,7 @@ public class DefaultNotificationsSubscriptionService extends AbstractSubscriptio } private void sendUpdate(String sessionId, UnreadNotificationsUpdate update) { - wsService.sendWsMsg(sessionId, update);; - } - - @Override - protected String getExecutorPrefix() { - return "notification"; + wsService.sendWsMsg(sessionId, update); } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java similarity index 64% rename from application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionService.java rename to application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java index 0afd8dcd39..b45e512045 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java @@ -13,18 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.ws.notification.sub; +package org.thingsboard.server.service.ws.notification; -import org.thingsboard.server.common.data.id.NotificationRequestId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.service.ws.WebSocketSessionRef; import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationAsReadCmd; import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; -public interface NotificationsSubscriptionService { +public interface NotificationCommandsHandler { void handleUnreadNotificationsSubCmd(WebSocketSessionRef sessionRef, NotificationsSubCmd cmd); @@ -32,11 +28,4 @@ public interface NotificationsSubscriptionService { void handleUnsubCmd(WebSocketSessionRef sessionRef, UnsubscribeCmd cmd); - - void onNewNotification(TenantId tenantId, UserId recipientId, Notification notification); - - void onNotificationUpdated(TenantId tenantId, UserId recipientId, Notification notification); - - void onNotificationRequestDeleted(TenantId tenantId, NotificationRequestId notificationRequestId); - } diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWebSocketClient.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWebSocketClient.java index cba34a3f28..70fd9ad51d 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWebSocketClient.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWebSocketClient.java @@ -1,3 +1,18 @@ +/** + * 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.Getter; @@ -10,7 +25,6 @@ import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationAsRead import org.thingsboard.server.service.ws.notification.cmd.NotificationCmdsWrapper; import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; -import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; import java.net.URI; import java.net.URISyntaxException; diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWsApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWsApiTest.java index 12b2eb189f..8ff88b7e3c 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWsApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWsApiTest.java @@ -1,3 +1,18 @@ +/** + * 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 org.junit.Before; diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationProcessingService.java similarity index 65% rename from application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingService.java rename to common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationProcessingService.java index 57aba583ed..b25d4943b7 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationProcessingService.java @@ -13,20 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.notification; +package org.thingsboard.server.dao.notification; -import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.NotificationRequest; -import org.thingsboard.server.service.security.model.SecurityUser; public interface NotificationProcessingService { - NotificationRequest processNotificationRequest(SecurityUser user, NotificationRequest notificationRequest) throws ThingsboardException; + NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); - void markNotificationAsRead(SecurityUser user, NotificationId notificationId); + void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId); - void deleteNotificationRequest(SecurityUser user, NotificationRequestId notificationRequestId); + void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java index a633ad6433..827bddc980 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java @@ -20,7 +20,7 @@ import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; -import org.thingsboard.server.common.data.SearchTextBased; +import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.UserId; @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.id.UserId; @NoArgsConstructor @Builder @EqualsAndHashCode(callSuper = true) -public class Notification extends SearchTextBased { +public class Notification extends BaseData { private NotificationRequestId requestId; private UserId recipientId; @@ -41,8 +41,4 @@ public class Notification extends SearchTextBased { private NotificationStatus status; // private UserId senderId; - @Override - public String getSearchText() { - return text; - } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java index 392063ad93..42cff008d0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java @@ -51,7 +51,6 @@ public class NotificationRequest extends BaseData impleme private NotificationInfo notificationInfo; private NotificationSeverity notificationSeverity; private NotificationRequestConfig additionalConfig; - private UserId senderId; public static final String GENERAL_NOTIFICATION_REASON = "General"; public static final String ALARM_NOTIFICATION_REASON = "Alarm"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 13d149bcf5..2ae524f1e2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -668,7 +668,6 @@ public class ModelConstants { public static final String NOTIFICATION_REQUEST_NOTIFICATION_INFO_PROPERTY = "notification_info"; public static final String NOTIFICATION_REQUEST_NOTIFICATION_SEVERITY_PROPERTY = "notification_severity"; public static final String NOTIFICATION_REQUEST_ADDITIONAL_CONFIG_PROPERTY = "additional_config"; - public static final String NOTIFICATION_REQUEST_SENDER_ID_PROPERTY = "sender_id"; 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}; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java index be3532e300..c79ba6f30b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java @@ -62,6 +62,7 @@ public class NotificationEntity extends BaseSqlEntity { @Column(name = ModelConstants.NOTIFICATION_INFO_PROPERTY) private JsonNode info; + @Enumerated(EnumType.STRING) @Column(name = ModelConstants.NOTIFICATION_SEVERITY_PROPERTY) private NotificationSeverity severity; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java index f469f4aed7..65e1fe8214 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java @@ -71,9 +71,6 @@ public class NotificationRequestEntity extends BaseSqlEntity { + return ctx.getNotificationProcessingService().processNotificationRequest(ctx.getTenantId(), notificationRequest); + }), + r -> { + TbMsgMetaData msgMetaData = msg.getMetaData().copy(); + msgMetaData.putValue("notificationRequestId", r.getUuidId().toString()); + msgMetaData.putValue("notificationTextTemplate", r.getTextTemplate()); + ctx.tellSuccess(TbMsg.transformMsg(msg, msgMetaData)); + }, + e -> ctx.tellFailure(msg, e)); + } + + private void validateConfig(TbNotificationNodeConfiguration config) throws TbNodeException { + if (config.getTargetId() == null) { + throw new TbNodeException("Notification target is not specified"); + } + if (StringUtils.isBlank(config.getNotificationTextTemplate())) { + throw new TbNodeException("Notification text template is missing"); + } + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNodeConfiguration.java new file mode 100644 index 0000000000..557c5e19b0 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNodeConfiguration.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.rule.engine.notification; + +import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.server.common.data.notification.NotificationSeverity; + +import java.util.UUID; + +@Data +public class TbNotificationNodeConfiguration implements NodeConfiguration { + + private UUID targetId; + private String notificationReason; + private String notificationTextTemplate; + private NotificationSeverity notificationSeverity; + + @Override + public TbNotificationNodeConfiguration defaultConfiguration() { + TbNotificationNodeConfiguration configuration = new TbNotificationNodeConfiguration(); + configuration.setNotificationReason("General"); + configuration.setNotificationSeverity(NotificationSeverity.NORMAL); + return configuration; + } + +} From 3734a0da49db4180045155bf90b562f6071ed4ad Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Fri, 4 Nov 2022 15:29:15 +0200 Subject: [PATCH 006/111] Notifications api permission checks, WS improvements, new target configs --- .../main/data/upgrade/3.4.2/schema_update.sql | 1 + .../server/controller/BaseController.java | 18 +- .../controller/NotificationController.java | 27 ++- .../NotificationTargetController.java | 76 +++++-- .../controller/plugin/TbWebSocketHandler.java | 3 +- .../DefaultNotificationProcessingService.java | 44 ++-- .../queue/DefaultTbCoreConsumerService.java | 6 +- .../service/security/permission/Resource.java | 3 +- .../permission/SysAdminPermissions.java | 2 + .../permission/TenantAdminPermissions.java | 3 +- .../DefaultSubscriptionManagerService.java | 9 +- .../DefaultTbLocalSubscriptionService.java | 3 +- .../SubscriptionManagerService.java | 2 +- .../subscription/TbSubscriptionType.java | 2 +- .../subscription/TbSubscriptionUtils.java | 22 +- .../telemetry/DefaultWebSocketService.java | 193 +++++++++--------- .../thingsboard/server/service/ws/WsCmd.java | 20 ++ .../DefaultNotificationCommandsHandler.java | 77 +++++-- .../NotificationCommandsHandler.java | 3 + .../cmd/MarkNotificationAsReadCmd.java | 3 +- .../cmd/NotificationCmdsWrapper.java | 10 +- .../cmd/NotificationsCountSubCmd.java | 29 +++ .../notification/cmd/NotificationsSubCmd.java | 4 +- .../cmd/NotificationsUnsubCmd.java | 7 +- .../cmd/UnreadNotificationsCountUpdate.java | 44 ++++ .../sub/NotificationsCountSubscription.java | 47 +++++ .../sub}/NotificationsSubscription.java | 11 +- .../sub/NotificationsSubscriptionUpdate.java | 1 + .../ws/telemetry/cmd/v2/CmdUpdateType.java | 3 +- .../controller/TbTestWebSocketClient.java | 1 + .../NotificationsWebSocketClient.java | 50 +++-- .../notification/NotificationsWsApiTest.java | 67 +++++- common/cluster-api/src/main/proto/queue.proto | 11 +- .../dao/notification/NotificationService.java | 6 +- .../NotificationTargetService.java | 9 +- .../server/dao/user/UserService.java | 2 + .../notification/NotificationRequest.java | 9 +- .../AllUsersNotificationTargetConfig.java | 28 +++ ...CustomerUsersNotificationTargetConfig.java | 32 +++ .../targets/NotificationTargetConfig.java | 4 +- .../targets/NotificationTargetConfigType.java | 3 + .../SingleUserNotificationTargetConfig.java | 4 +- .../UserListNotificationTargetConfig.java | 5 +- .../org/thingsboard/server/dao/DaoUtil.java | 6 +- .../DefaultNotificationService.java | 15 +- .../DefaultNotificationTargetService.java | 53 +++-- .../dao/notification/NotificationDao.java | 4 +- .../sql/notification/JpaNotificationDao.java | 9 +- .../notification/NotificationRepository.java | 9 +- .../server/dao/sql/user/JpaUserDao.java | 5 + .../thingsboard/server/dao/user/UserDao.java | 3 + .../server/dao/user/UserServiceImpl.java | 5 + .../resources/sql/schema-entities-idx.sql | 2 + 53 files changed, 752 insertions(+), 263 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/WsCmd.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java rename application/src/main/java/org/thingsboard/server/service/{subscription => ws/notification/sub}/NotificationsSubscription.java (85%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/AllUsersNotificationTargetConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/CustomerUsersNotificationTargetConfig.java diff --git a/application/src/main/data/upgrade/3.4.2/schema_update.sql b/application/src/main/data/upgrade/3.4.2/schema_update.sql index 17c75773ad..77f078e67f 100644 --- a/application/src/main/data/upgrade/3.4.2/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.2/schema_update.sql @@ -49,5 +49,6 @@ CREATE TABLE IF NOT EXISTS notification ( severity VARCHAR(32), status VARCHAR(32) ) PARTITION BY RANGE (created_time); +CREATE INDEX IF NOT EXISTS idx_notification_id ON notification(id); CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_and_created_time ON notification(recipient_id, created_time DESC); CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_and_status_and_created_time ON notification(recipient_id, status, created_time DESC); diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index ffc3fcf3cd..8f5fee218f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -43,6 +43,7 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityViewInfo; +import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.OtaPackageInfo; @@ -58,6 +59,7 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.asset.AssetProfile; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; @@ -75,6 +77,7 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.RpcId; @@ -130,8 +133,8 @@ import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.action.EntityActionService; import org.thingsboard.server.service.component.ComponentDiscoveryService; -import org.thingsboard.server.service.edge.EdgeNotificationService; import org.thingsboard.server.service.edge.rpc.EdgeRpcService; import org.thingsboard.server.service.entitiy.TbNotificationEntityService; import org.thingsboard.server.service.ota.OtaPackageStateService; @@ -284,6 +287,9 @@ public abstract class BaseController { @Autowired protected TbNotificationEntityService notificationEntityService; + @Autowired + protected EntityActionService entityActionService; + @Autowired protected QueueService queueService; @@ -912,6 +918,16 @@ public abstract class BaseController { return error != null ? (Exception.class.isInstance(error) ? (Exception) error : new Exception(error)) : null; } + protected > void logEntityAction(SecurityUser user, EntityType entityType, E savedEntity, ActionType actionType) { + logEntityAction(user, entityType, null, savedEntity, actionType, null); + } + + protected > void logEntityAction(SecurityUser user, EntityType entityType, E entity, E savedEntity, ActionType actionType, Exception e) { + EntityId entityId = savedEntity != null ? savedEntity.getId() : emptyId(entityType); + entityActionService.logEntityAction(user, entityId, savedEntity != null ? savedEntity : entity, + user.getCustomerId(), actionType, e); + } + protected void sendEntityNotificationMsg(TenantId tenantId, EntityId entityId, EdgeEventActionType action) { sendNotificationMsgToEdge(tenantId, null, entityId, null, null, action); } diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index c3cfdc24e8..1bf771218a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -28,6 +28,8 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; @@ -81,9 +83,15 @@ public class NotificationController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public NotificationRequest createNotificationRequest(@RequestBody NotificationRequest notificationRequest, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { - accessControlService.checkPermission(user, Resource.NOTIFICATION, Operation.CREATE); - // read permission for target's users - return notificationProcessingService.processNotificationRequest(user.getTenantId(), notificationRequest); + accessControlService.checkPermission(user, Resource.NOTIFICATION_REQUEST, Operation.CREATE, null, notificationRequest); + try { + NotificationRequest savedNotificationRequest = notificationProcessingService.processNotificationRequest(user.getTenantId(), notificationRequest); + logEntityAction(user, EntityType.NOTIFICATION_REQUEST, savedNotificationRequest, ActionType.ADDED); + return savedNotificationRequest; + } catch (Exception e) { + logEntityAction(user, EntityType.NOTIFICATION_REQUEST, notificationRequest, null, ActionType.ADDED, e); + throw e; + } } @GetMapping("/notification/request/{id}") @@ -102,16 +110,23 @@ public class NotificationController extends BaseController { @RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortOrder, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { - accessControlService.checkPermission(user, Resource.NOTIFICATION, Operation.CREATE); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); return notificationService.findNotificationRequestsByTenantIdAndPageLink(user.getTenantId(), pageLink); } @DeleteMapping("/notification/request/{id}") public void deleteNotificationRequest(@PathVariable UUID id, - @AuthenticationPrincipal SecurityUser user) { + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { NotificationRequestId notificationRequestId = new NotificationRequestId(id); - notificationProcessingService.deleteNotificationRequest(user.getTenantId(), notificationRequestId); + NotificationRequest notificationRequest = notificationService.findNotificationRequestById(user.getTenantId(), notificationRequestId); + accessControlService.checkPermission(user, Resource.NOTIFICATION_REQUEST, Operation.DELETE, notificationRequestId, notificationRequest); + try { + notificationProcessingService.deleteNotificationRequest(user.getTenantId(), notificationRequestId); + logEntityAction(user, EntityType.NOTIFICATION_REQUEST, notificationRequest, ActionType.DELETED); + } catch (Exception e) { + logEntityAction(user, EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationRequest, ActionType.DELETED, e); + throw e; + } } } diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java index 3eec3b6ffc..34451ddede 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java @@ -27,24 +27,26 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.NotificationTargetId; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; 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.NotificationTargetConfigType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.notification.NotificationTargetService; -import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; -import java.util.ArrayList; import java.util.List; import java.util.UUID; -import java.util.stream.Collectors; @RestController @TbCoreComponent @@ -52,37 +54,59 @@ import java.util.stream.Collectors; @RequiredArgsConstructor @Slf4j public class NotificationTargetController extends BaseController { - // fixme: permission check, log action, events private final NotificationTargetService notificationTargetService; - private final UserService userService; @PostMapping("/target") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public NotificationTarget saveNotificationTarget(@RequestBody NotificationTarget notificationTarget, - @AuthenticationPrincipal SecurityUser user) { - // fixme: read permission check for users in target - return notificationTargetService.saveNotificationTarget(user.getTenantId(), notificationTarget); + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + accessControlService.checkPermission(user, Resource.NOTIFICATION_TARGET, Operation.CREATE, null, notificationTarget); + if (!user.isSystemAdmin()) { + NotificationTargetConfig targetConfig = notificationTarget.getConfiguration(); + if (targetConfig.getType() == NotificationTargetConfigType.SINGLE_USER || + targetConfig.getType() == NotificationTargetConfigType.USER_LIST) { + PageData recipients = notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), notificationTarget.getConfiguration(), null); + for (User recipient : recipients.getData()) { + accessControlService.checkPermission(user, Resource.USER, Operation.READ, recipient.getId(), recipient); + } + } + } + + try { + NotificationTarget savedNotificationTarget = notificationTargetService.saveNotificationTarget(user.getTenantId(), notificationTarget); + logEntityAction(user, EntityType.NOTIFICATION_TARGET, savedNotificationTarget, + notificationTarget.getId() == null ? ActionType.ADDED : ActionType.UPDATED); + return savedNotificationTarget; + } catch (Exception e) { + logEntityAction(user, EntityType.NOTIFICATION_TARGET, notificationTarget, null, + notificationTarget.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); + throw e; + } } @GetMapping("/target/{id}") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public NotificationTarget getNotificationTargetById(@PathVariable UUID id, - @AuthenticationPrincipal SecurityUser user) { + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { NotificationTargetId notificationTargetId = new NotificationTargetId(id); - return notificationTargetService.findNotificationTargetById(user.getTenantId(), notificationTargetId); + NotificationTarget notificationTarget = notificationTargetService.findNotificationTargetById(user.getTenantId(), notificationTargetId); + accessControlService.checkPermission(user, Resource.NOTIFICATION_TARGET, Operation.READ, notificationTargetId, notificationTarget); + return notificationTarget; } - @GetMapping("/target/{id}/recipients") + @PostMapping("/target/recipients") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - public List getRecipientsForNotificationTarget(@PathVariable UUID id, - @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { - NotificationTargetId notificationTargetId = new NotificationTargetId(id); - // fixme: to page data - // todo: check read permission for recipients - List recipients = new ArrayList<>(); - for (UserId userId : notificationTargetService.findRecipientsForNotificationTarget(user.getTenantId(), notificationTargetId)) { - recipients.add(checkUserId(userId, Operation.READ)); + public PageData getRecipientsForNotificationTargetConfig(@RequestBody NotificationTarget notificationTarget, + @RequestParam int pageSize, + @RequestParam int page, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + PageLink pageLink = createPageLink(pageSize, page, null, null, null); + PageData recipients = notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), notificationTarget.getConfiguration(), pageLink); + if (!user.isSystemAdmin()) { + for (User recipient : recipients.getData()) { + accessControlService.checkPermission(user, Resource.USER, Operation.READ, recipient.getId(), recipient); + } } return recipients; } @@ -102,9 +126,17 @@ public class NotificationTargetController extends BaseController { @DeleteMapping("/target/{id}") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public void deleteNotificationTarget(@PathVariable UUID id, - @AuthenticationPrincipal SecurityUser user) { + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { NotificationTargetId notificationTargetId = new NotificationTargetId(id); - notificationTargetService.deleteNotificationTarget(user.getTenantId(), notificationTargetId); + NotificationTarget notificationTarget = checkNotNull(notificationTargetService.findNotificationTargetById(user.getTenantId(), notificationTargetId)); + accessControlService.checkPermission(user, Resource.NOTIFICATION_TARGET, Operation.DELETE, notificationTargetId, notificationTarget); + + try { + notificationTargetService.deleteNotificationTarget(user.getTenantId(), notificationTargetId); + logEntityAction(user, EntityType.NOTIFICATION_TARGET, notificationTarget, ActionType.DELETED); + } catch (Exception e) { + logEntityAction(user, EntityType.NOTIFICATION_TARGET, null, notificationTarget, ActionType.DELETED, e); + } } } diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index 187603afc5..7cb0a8258e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.BeanCreationNotAllowedException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import org.springframework.web.socket.CloseStatus; @@ -69,7 +70,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke private static final ConcurrentMap externalSessionMap = new ConcurrentHashMap<>(); - @Autowired + @Autowired @Lazy private WebSocketService webSocketService; @Autowired diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java index 57b295bddd..99ede1fba9 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java @@ -32,10 +32,10 @@ import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.notification.NotificationProcessingService; import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.dao.notification.NotificationTargetService; -import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.NotificationsTopicService; import org.thingsboard.server.queue.discovery.PartitionService; @@ -43,9 +43,7 @@ import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.telemetry.AbstractSubscriptionService; -import java.util.ArrayList; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -56,32 +54,23 @@ public class DefaultNotificationProcessingService extends AbstractSubscriptionSe private final NotificationTargetService notificationTargetService; private final NotificationService notificationService; - private final UserService userService; private final DbCallbackExecutorService dbCallbackExecutorService; private final NotificationsTopicService notificationsTopicService; public DefaultNotificationProcessingService(TbClusterService clusterService, PartitionService partitionService, NotificationTargetService notificationTargetService, - NotificationService notificationService, UserService userService, + NotificationService notificationService, DbCallbackExecutorService dbCallbackExecutorService, NotificationsTopicService notificationsTopicService) { super(clusterService, partitionService); this.notificationTargetService = notificationTargetService; this.notificationService = notificationService; - this.userService = userService; this.dbCallbackExecutorService = dbCallbackExecutorService; this.notificationsTopicService = notificationsTopicService; } @Override public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { - List recipientsIds = notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId()); - List recipients = new ArrayList<>(); - for (UserId recipientId : recipientsIds) { - User recipient = userService.findUserById(tenantId, recipientId); // todo: add caching - recipients.add(recipient); - } - notificationRequest.setTenantId(tenantId); NotificationRequest savedNotificationRequest = notificationService.createNotificationRequest(tenantId, notificationRequest); @@ -90,20 +79,31 @@ public class DefaultNotificationProcessingService extends AbstractSubscriptionSe // todo: delayed sending; check all delayed notification requests on start up, schedule send } - for (User recipient : recipients) { + DaoUtil.processBatches(pageLink -> { + return notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), pageLink); + }, 100, recipients -> { dbCallbackExecutorService.submit(() -> { - Notification notification = createNotification(recipient, savedNotificationRequest); - onNotificationUpdate(recipient.getTenantId(), recipient.getId(), notification); + for (User recipient : recipients) { + try { + Notification notification = createNotification(recipient, savedNotificationRequest); + onNotificationUpdate(recipient.getTenantId(), recipient.getId(), notification, true); + } catch (Exception e) { + log.error("Failed to create notification for recipient {}", recipient.getId(), e); + } + } }); - } + }); return savedNotificationRequest; } @Override public void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId) { - Notification notification = notificationService.updateNotificationStatus(tenantId, notificationId, NotificationStatus.READ); - onNotificationUpdate(tenantId, recipientId, notification); + boolean updated = notificationService.updateNotificationStatus(tenantId, recipientId, notificationId, NotificationStatus.READ); + if (updated) { + Notification notification = notificationService.findNotificationById(tenantId, notificationId); + onNotificationUpdate(tenantId, recipientId, notification, false); + } } @Override @@ -134,11 +134,11 @@ public class DefaultNotificationProcessingService extends AbstractSubscriptionSe return TbNodeUtils.processTemplate(template, context); } - private void onNotificationUpdate(TenantId tenantId, UserId recipientId, Notification notification) { + private void onNotificationUpdate(TenantId tenantId, UserId recipientId, Notification notification, boolean isNew) { forwardToSubscriptionManagerServiceOrSendToCore(tenantId, recipientId, subscriptionManagerService -> { - subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, notification, TbCallback.EMPTY); + subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, notification, isNew, TbCallback.EMPTY); }, () -> { - return TbSubscriptionUtils.notificationUpdateToProto(tenantId, recipientId, notification); + return TbSubscriptionUtils.notificationUpdateToProto(tenantId, recipientId, notification, isNew); }); } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 0a7abdab51..1ee7c3cd16 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -464,6 +464,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService subscriptions = subscriptionsByEntityId.get(recipientId); if (subscriptions != null) { NotificationsSubscriptionUpdate subscriptionUpdate = NotificationsSubscriptionUpdate.builder() .notification(notification) + .isNewNotification(isNew) .build(); subscriptions.stream() - .filter(subscription -> subscription.getType() == TbSubscriptionType.NOTIFICATIONS) + .filter(subscription -> subscription.getType() == TbSubscriptionType.NOTIFICATIONS + || subscription.getType() == TbSubscriptionType.NOTIFICATIONS_COUNT) .forEach(subscription -> { if (serviceId.equals(subscription.getServiceId())) { localSubscriptionService.onSubscriptionUpdate(subscription.getSessionId(), @@ -362,7 +364,8 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene subscriptionsByEntityId.entrySet().stream() .filter(subEntry -> subEntry.getKey().getEntityType() == EntityType.USER) .flatMap(subEntry -> subEntry.getValue().stream() - .filter(sub -> sub.getType() == TbSubscriptionType.NOTIFICATIONS) + .filter(sub -> sub.getType() == TbSubscriptionType.NOTIFICATIONS + || sub.getType() == TbSubscriptionType.NOTIFICATIONS_COUNT) .filter(sub -> sub.getServiceId().equals(serviceId))) .forEach(subscription -> { localSubscriptionService.onSubscriptionUpdate(subscription.getSessionId(), subscription.getSubscriptionId(), subscriptionUpdate, TbCallback.EMPTY); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java index e9001f3be3..5a51d0acee 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java @@ -172,7 +172,8 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer @Override public void onSubscriptionUpdate(String sessionId, int subscriptionId, NotificationsSubscriptionUpdate update, TbCallback callback) { TbSubscription subscription = subscriptionsBySessionId.getOrDefault(sessionId, Collections.emptyMap()).get(subscriptionId); - if (subscription != null && subscription.getType() == TbSubscriptionType.NOTIFICATIONS) { + if (subscription != null && (subscription.getType() == TbSubscriptionType.NOTIFICATIONS + || subscription.getType() == TbSubscriptionType.NOTIFICATIONS_COUNT)) { subscriptionUpdateExecutor.submit(() -> subscription.getUpdateProcessor().accept(subscription, update)); } callback.onSuccess(); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java index 6724674143..239f9c94f9 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java @@ -49,7 +49,7 @@ public interface SubscriptionManagerService extends ApplicationListener 0) { return new TelemetrySubscriptionUpdate(proto.getSubscriptionId(), SubscriptionErrorCode.forCode(proto.getErrorCode()), proto.getErrorMsg()); @@ -367,6 +385,7 @@ public class TbSubscriptionUtils { .setSessionId(subscription.getSessionId()) .setSubscriptionId(subscription.getSubscriptionId()) .setNotification(JacksonUtil.toString(update.getNotification())) + .setIsNewNotification(update.isNewNotification()) .build(); return TransportProtos.ToCoreNotificationMsg.newBuilder() .setToLocalSubscriptionServiceMsg(TransportProtos.LocalSubscriptionServiceMsgProto.newBuilder() @@ -375,13 +394,14 @@ public class TbSubscriptionUtils { .build(); } - public static ToCoreMsg notificationUpdateToProto(TenantId tenantId, UserId recipientId, Notification notification) { + public static ToCoreMsg notificationUpdateToProto(TenantId tenantId, UserId recipientId, Notification notification, boolean isNew) { TransportProtos.NotificationUpdateProto updateProto = TransportProtos.NotificationUpdateProto.newBuilder() .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) .setRecipientIdMSB(recipientId.getId().getMostSignificantBits()) .setRecipientIdLSB(recipientId.getId().getLeastSignificantBits()) .setNotification(JacksonUtil.toString(notification)) + .setIsNew(isNew) .build(); return ToCoreMsg.newBuilder() .setToSubscriptionMgrMsg(SubscriptionMgrMsgProto.newBuilder() diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultWebSocketService.java index f6df94b46f..e03e0fc3fb 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultWebSocketService.java @@ -22,8 +22,8 @@ import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.socket.CloseStatus; @@ -65,13 +65,10 @@ import org.thingsboard.server.service.subscription.TbTimeseriesSubscription; import org.thingsboard.server.service.ws.SessionEvent; import org.thingsboard.server.service.ws.WebSocketMsgEndpoint; import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.WsCmd; import org.thingsboard.server.service.ws.WsSessionMetaData; -import org.thingsboard.server.service.ws.notification.DefaultNotificationCommandsHandler; import org.thingsboard.server.service.ws.notification.NotificationCommandsHandler; import org.thingsboard.server.service.ws.notification.cmd.NotificationCmdsWrapper; -import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationAsReadCmd; -import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; -import org.thingsboard.server.service.ws.notification.cmd.NotificationsUnsubCmd; import org.thingsboard.server.service.ws.telemetry.WebSocketService; import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryPluginCmdsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; @@ -105,6 +102,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -114,6 +112,7 @@ import java.util.stream.Collectors; @Service @TbCoreComponent @Slf4j +@RequiredArgsConstructor public class DefaultWebSocketService implements WebSocketService { public static final int NUMBER_OF_PING_ATTEMPTS = 3; @@ -130,53 +129,57 @@ public class DefaultWebSocketService implements WebSocketService { private final ConcurrentMap wsSessionsMap = new ConcurrentHashMap<>(); - @Autowired - private TbLocalSubscriptionService oldSubService; - - @Autowired - private TbEntityDataSubscriptionService entityDataSubService; - - @Autowired - private NotificationCommandsHandler notificationCmdsHandler; - - @Autowired - private WebSocketMsgEndpoint msgEndpoint; - - @Autowired - private AccessValidator accessValidator; - - @Autowired - private AttributesService attributesService; - - @Autowired - private TimeseriesService tsService; - - @Autowired - private TbServiceInfoProvider serviceInfoProvider; - - @Autowired - private TbTenantProfileCache tenantProfileCache; + private final TbLocalSubscriptionService oldSubService; + private final TbEntityDataSubscriptionService entityDataSubService; + private final NotificationCommandsHandler notificationCmdsHandler; + private final WebSocketMsgEndpoint msgEndpoint; + private final AccessValidator accessValidator; + private final AttributesService attributesService; + private final TimeseriesService tsService; + private final TbServiceInfoProvider serviceInfoProvider; + private final TbTenantProfileCache tenantProfileCache; @Value("${server.ws.ping_timeout:30000}") private long pingTimeout; - private ConcurrentMap> tenantSubscriptionsMap = new ConcurrentHashMap<>(); - private ConcurrentMap> customerSubscriptionsMap = new ConcurrentHashMap<>(); - private ConcurrentMap> regularUserSubscriptionsMap = new ConcurrentHashMap<>(); - private ConcurrentMap> publicUserSubscriptionsMap = new ConcurrentHashMap<>(); + private final ConcurrentMap> tenantSubscriptionsMap = new ConcurrentHashMap<>(); + private final ConcurrentMap> customerSubscriptionsMap = new ConcurrentHashMap<>(); + private final ConcurrentMap> regularUserSubscriptionsMap = new ConcurrentHashMap<>(); + private final ConcurrentMap> publicUserSubscriptionsMap = new ConcurrentHashMap<>(); private ExecutorService executor; + private ScheduledExecutorService pingExecutor; private String serviceId; - private ScheduledExecutorService pingExecutor; + private List> telemetryCmdsHandlers; + private List> notificationCmdsHandlers; @PostConstruct - public void initExecutor() { + public void init() { serviceId = serviceInfoProvider.getServiceId(); executor = ThingsBoardExecutors.newWorkStealingPool(50, getClass()); pingExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("telemetry-web-socket-ping")); pingExecutor.scheduleWithFixedDelay(this::sendPing, pingTimeout / NUMBER_OF_PING_ATTEMPTS, pingTimeout / NUMBER_OF_PING_ATTEMPTS, TimeUnit.MILLISECONDS); + + telemetryCmdsHandlers = List.of( + WsCmdListHandler.of(TelemetryPluginCmdsWrapper::getAttrSubCmds, this::handleWsAttributesSubscriptionCmd), + WsCmdListHandler.of(TelemetryPluginCmdsWrapper::getTsSubCmds, this::handleWsTimeseriesSubscriptionCmd), + WsCmdListHandler.of(TelemetryPluginCmdsWrapper::getHistoryCmds, this::handleWsHistoryCmd), + WsCmdListHandler.of(TelemetryPluginCmdsWrapper::getEntityDataCmds, this::handleWsEntityDataCmd), + WsCmdListHandler.of(TelemetryPluginCmdsWrapper::getAlarmDataCmds, this::handleWsAlarmDataCmd), + WsCmdListHandler.of(TelemetryPluginCmdsWrapper::getEntityCountCmds, this::handleWsEntityCountCmd), + WsCmdListHandler.of(TelemetryPluginCmdsWrapper::getEntityDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), + WsCmdListHandler.of(TelemetryPluginCmdsWrapper::getAlarmDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), + WsCmdListHandler.of(TelemetryPluginCmdsWrapper::getAlarmDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), + WsCmdListHandler.of(TelemetryPluginCmdsWrapper::getEntityCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd) + ); + notificationCmdsHandlers = List.of( + WsCmdHandler.of(NotificationCmdsWrapper::getUnreadSubCmd, notificationCmdsHandler::handleUnreadNotificationsSubCmd), + WsCmdHandler.of(NotificationCmdsWrapper::getUnreadCountSubCmd, notificationCmdsHandler::handleUnreadNotificationsCountSubCmd), + WsCmdHandler.of(NotificationCmdsWrapper::getMarkAsReadCmd, notificationCmdsHandler::handleMarkAsReadCmd), + WsCmdHandler.of(NotificationCmdsWrapper::getUnsubCmd, notificationCmdsHandler::handleUnsubCmd) + ); } @PreDestroy @@ -231,77 +234,30 @@ public class DefaultWebSocketService implements WebSocketService { } } + private void processTelemetryCmds(WebSocketSessionRef sessionRef, String msg) throws JsonProcessingException { TelemetryPluginCmdsWrapper cmdsWrapper = jsonMapper.readValue(msg, TelemetryPluginCmdsWrapper.class); if (cmdsWrapper == null) { return; } - if (cmdsWrapper.getAttrSubCmds() != null) { - cmdsWrapper.getAttrSubCmds().forEach(cmd -> { - if (processSubscription(sessionRef, cmd)) { - handleWsAttributesSubscriptionCmd(sessionRef, cmd); - } - }); - } - if (cmdsWrapper.getTsSubCmds() != null) { - cmdsWrapper.getTsSubCmds().forEach(cmd -> { - if (processSubscription(sessionRef, cmd)) { - handleWsTimeseriesSubscriptionCmd(sessionRef, cmd); - } - }); - } - if (cmdsWrapper.getHistoryCmds() != null) { - cmdsWrapper.getHistoryCmds().forEach(cmd -> handleWsHistoryCmd(sessionRef, cmd)); - } - if (cmdsWrapper.getEntityDataCmds() != null) { - cmdsWrapper.getEntityDataCmds().forEach(cmd -> handleWsEntityDataCmd(sessionRef, cmd)); - } - if (cmdsWrapper.getAlarmDataCmds() != null) { - cmdsWrapper.getAlarmDataCmds().forEach(cmd -> handleWsAlarmDataCmd(sessionRef, cmd)); - } - if (cmdsWrapper.getEntityCountCmds() != null) { - cmdsWrapper.getEntityCountCmds().forEach(cmd -> handleWsEntityCountCmd(sessionRef, cmd)); - } - if (cmdsWrapper.getEntityDataUnsubscribeCmds() != null) { - cmdsWrapper.getEntityDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd)); - } - if (cmdsWrapper.getAlarmDataUnsubscribeCmds() != null) { - cmdsWrapper.getAlarmDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd)); - } - if (cmdsWrapper.getEntityCountUnsubscribeCmds() != null) { - cmdsWrapper.getEntityCountUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd)); + for (WsCmdListHandler cmdHandler : telemetryCmdsHandlers) { + List cmds = cmdHandler.extractCmds(cmdsWrapper); + if (cmds != null) { + cmdHandler.handle(sessionRef, cmds); + } } } private void processNotificationCmds(WebSocketSessionRef sessionRef, String msg) throws IOException { NotificationCmdsWrapper cmdsWrapper = jsonMapper.readValue(msg, NotificationCmdsWrapper.class); - if (cmdsWrapper.getUnreadSubCmd() != null) { - handleUnreadNotificationsSubCmd(sessionRef, cmdsWrapper.getUnreadSubCmd()); - } else if (cmdsWrapper.getUnreadUnsubCmd() != null) { - handleUnreadNotificationsUnsubCmd(sessionRef, cmdsWrapper.getUnreadUnsubCmd()); - } else if (cmdsWrapper.getMarkAsReadCmd() != null) { - handleMarkNotificationAsReadCmd(sessionRef, cmdsWrapper.getMarkAsReadCmd()); - } - } - - private void handleUnreadNotificationsSubCmd(WebSocketSessionRef sessionRef, NotificationsSubCmd cmd) { - String sessionId = sessionRef.getSessionId(); - if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)) { - notificationCmdsHandler.handleUnreadNotificationsSubCmd(sessionRef, cmd); - } - } - - private void handleUnreadNotificationsUnsubCmd(WebSocketSessionRef sessionRef, NotificationsUnsubCmd cmd) { - String sessionId = sessionRef.getSessionId(); - if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)) { - notificationCmdsHandler.handleUnsubCmd(sessionRef, cmd); - } - } - - private void handleMarkNotificationAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationAsReadCmd cmd) { - String sessionId = sessionRef.getSessionId(); - if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)) { - notificationCmdsHandler.handleMarkAsReadCmd(sessionRef, cmd); + for (WsCmdHandler cmdHandler : notificationCmdsHandlers) { + WsCmd cmd = cmdHandler.extractCmd(cmdsWrapper); + if (cmd != null) { + String sessionId = sessionRef.getSessionId(); + if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)) { + cmdHandler.handle(sessionRef, cmd); // todo: handle exceptions + } + } } } @@ -479,6 +435,10 @@ public class DefaultWebSocketService implements WebSocketService { } private void handleWsAttributesSubscriptionCmd(WebSocketSessionRef sessionRef, AttributesSubscriptionCmd cmd) { + if (!processSubscription(sessionRef, cmd)) { + return; + } + String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); @@ -644,6 +604,10 @@ public class DefaultWebSocketService implements WebSocketService { } private void handleWsTimeseriesSubscriptionCmd(WebSocketSessionRef sessionRef, TimeseriesSubscriptionCmd cmd) { + if (!processSubscription(sessionRef, cmd)) { + return; + } + String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); @@ -990,4 +954,37 @@ public class DefaultWebSocketService implements WebSocketService { private int getLimit(int limit) { return limit == 0 ? DEFAULT_LIMIT : limit; } + + @RequiredArgsConstructor(staticName = "of") + public static class WsCmdHandler { + private final java.util.function.Function cmdExtractor; + private final BiConsumer handler; + + public C extractCmd(W cmdsWrapper) { + return cmdExtractor.apply(cmdsWrapper); + } + + @SuppressWarnings("unchecked") + public void handle(WebSocketSessionRef sessionRef, Object cmd) { + handler.accept(sessionRef, (C) cmd); + } + } + + @RequiredArgsConstructor(staticName = "of") + public static class WsCmdListHandler { + private final java.util.function.Function> cmdExtractor; + private final BiConsumer handler; + + public List extractCmds(W cmdsWrapper) { + return cmdExtractor.apply(cmdsWrapper); + } + + @SuppressWarnings("unchecked") + public void handle(WebSocketSessionRef sessionRef, List cmds) { + cmds.forEach(cmd -> { + handler.accept(sessionRef, (C) cmd); + }); + } + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/WsCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/WsCmd.java new file mode 100644 index 0000000000..096c5930a5 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/WsCmd.java @@ -0,0 +1,20 @@ +/** + * 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.ws; + +public interface WsCmd { + int getCmdId(); +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java index ad3f9ca4b0..ee0afd3c79 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java @@ -16,6 +16,8 @@ package org.thingsboard.server.service.ws.notification; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.id.IdBased; import org.thingsboard.server.common.data.id.NotificationId; @@ -28,14 +30,16 @@ import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.dao.notification.NotificationProcessingService; import org.thingsboard.server.service.security.model.SecurityUser; -import org.thingsboard.server.service.subscription.NotificationsSubscription; +import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd; +import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscription; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.ws.WebSocketSessionRef; import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationAsReadCmd; import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; -import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationsCountSubscription; import org.thingsboard.server.service.ws.telemetry.WebSocketService; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; import java.util.Set; @@ -48,10 +52,11 @@ import java.util.stream.Collectors; public class DefaultNotificationCommandsHandler implements NotificationCommandsHandler { private final NotificationService notificationService; - private final WebSocketService wsService; private final TbLocalSubscriptionService localSubscriptionService; private final NotificationProcessingService notificationProcessingService; private final TbServiceInfoProvider serviceInfoProvider; + @Autowired @Lazy + private WebSocketService wsService; @Override public void handleUnreadNotificationsSubCmd(WebSocketSessionRef sessionRef, NotificationsSubCmd cmd) { @@ -62,7 +67,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH .subscriptionId(cmd.getCmdId()) .tenantId(user.getTenantId()) .entityId(user.getId()) - .updateProcessor(this::handleSubscriptionUpdate) + .updateProcessor(this::handleNotificationsSubscriptionUpdate) .limit(cmd.getLimit()) .build(); localSubscriptionService.addSubscription(subscription); @@ -72,14 +77,20 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH } @Override - public void handleMarkAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationAsReadCmd cmd) { - NotificationId notificationId = new NotificationId(cmd.getNotificationId()); - notificationProcessingService.markNotificationAsRead(sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), notificationId); - } + public void handleUnreadNotificationsCountSubCmd(WebSocketSessionRef sessionRef, NotificationsCountSubCmd cmd) { + SecurityUser user = sessionRef.getSecurityCtx(); + NotificationsCountSubscription subscription = NotificationsCountSubscription.builder() + .serviceId(serviceInfoProvider.getServiceId()) + .sessionId(sessionRef.getSessionId()) + .subscriptionId(cmd.getCmdId()) + .tenantId(user.getTenantId()) + .entityId(user.getId()) + .updateProcessor(this::handleNotificationsCountSubscriptionUpdate) + .build(); + localSubscriptionService.addSubscription(subscription); - @Override - public void handleUnsubCmd(WebSocketSessionRef sessionRef, UnsubscribeCmd cmd) { - localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), cmd.getCmdId()); + fetchUnreadNotificationsCount(subscription); + sendUpdate(sessionRef.getSessionId(), subscription.createUpdate()); } private void fetchUnreadNotifications(NotificationsSubscription subscription) { @@ -87,19 +98,24 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH (UserId) subscription.getEntityId(), subscription.getLimit()); subscription.getUnreadNotifications().clear(); subscription.getUnreadNotifications().putAll(notifications.getData().stream().collect(Collectors.toMap(IdBased::getUuidId, n -> n))); - subscription.getTotalUnreadCount().set((int) notifications.getTotalElements()); + subscription.getTotalUnreadCounter().set((int) notifications.getTotalElements()); + } + + private void fetchUnreadNotificationsCount(NotificationsCountSubscription subscription) { + int unreadCount = notificationService.countUnreadNotificationsByUserId(subscription.getTenantId(), (UserId) subscription.getEntityId()); + subscription.getUnreadCounter().set(unreadCount); } - private void handleSubscriptionUpdate(NotificationsSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) { + private void handleNotificationsSubscriptionUpdate(NotificationsSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) { if (subscriptionUpdate.getNotification() != null) { Notification notification = subscriptionUpdate.getNotification(); if (notification.getStatus() == NotificationStatus.READ) { fetchUnreadNotifications(subscription); sendUpdate(subscription.getSessionId(), subscription.createFullUpdate()); } else { - Notification previous = subscription.getUnreadNotifications().put(notification.getUuidId(), notification); - if (previous == null) { - subscription.getTotalUnreadCount().incrementAndGet(); + subscription.getUnreadNotifications().put(notification.getUuidId(), notification); + if (subscriptionUpdate.isNewNotification()) { + subscription.getTotalUnreadCounter().incrementAndGet(); Set beyondLimit = subscription.getUnreadNotifications().keySet().stream() .skip(subscription.getLimit()) .collect(Collectors.toSet()); @@ -116,8 +132,35 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH } } - private void sendUpdate(String sessionId, UnreadNotificationsUpdate update) { + private void handleNotificationsCountSubscriptionUpdate(NotificationsCountSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) { + if (subscriptionUpdate.getNotification() != null) { + Notification notification = subscriptionUpdate.getNotification(); + if (subscriptionUpdate.isNewNotification()) { + subscription.getUnreadCounter().incrementAndGet(); + } else if (notification.getStatus() == NotificationStatus.READ) { + // for now this can only happen when user marks notification as read + subscription.getUnreadCounter().decrementAndGet(); + } + } else if (subscriptionUpdate.isNotificationRequestDeleted()) { + fetchUnreadNotificationsCount(subscription); + } + sendUpdate(subscription.getSessionId(), subscription.createUpdate()); + } + + private void sendUpdate(String sessionId, CmdUpdate update) { wsService.sendWsMsg(sessionId, update); } + + @Override + public void handleMarkAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationAsReadCmd cmd) { + NotificationId notificationId = new NotificationId(cmd.getNotificationId()); + notificationProcessingService.markNotificationAsRead(sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), notificationId); + } + + @Override + public void handleUnsubCmd(WebSocketSessionRef sessionRef, UnsubscribeCmd cmd) { + localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), cmd.getCmdId()); + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java index b45e512045..4cf1fb3809 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java @@ -18,12 +18,15 @@ package org.thingsboard.server.service.ws.notification; import org.thingsboard.server.service.ws.WebSocketSessionRef; import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationAsReadCmd; import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; +import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; public interface NotificationCommandsHandler { void handleUnreadNotificationsSubCmd(WebSocketSessionRef sessionRef, NotificationsSubCmd cmd); + void handleUnreadNotificationsCountSubCmd(WebSocketSessionRef sessionRef, NotificationsCountSubCmd cmd); + void handleMarkAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationAsReadCmd cmd); void handleUnsubCmd(WebSocketSessionRef sessionRef, UnsubscribeCmd cmd); diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationAsReadCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationAsReadCmd.java index 50844623eb..cfc6e8e38b 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationAsReadCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationAsReadCmd.java @@ -18,13 +18,14 @@ package org.thingsboard.server.service.ws.notification.cmd; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import org.thingsboard.server.service.ws.WsCmd; import java.util.UUID; @Data @NoArgsConstructor @AllArgsConstructor -public class MarkNotificationAsReadCmd { +public class MarkNotificationAsReadCmd implements WsCmd { private int cmdId; private UUID notificationId; } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java index 367a6cc450..011e8cecca 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java @@ -16,13 +16,15 @@ package org.thingsboard.server.service.ws.notification.cmd; import lombok.Data; -import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationAsReadCmd; -import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; @Data public class NotificationCmdsWrapper { - private NotificationsSubCmd unreadSubCmd; - private NotificationsUnsubCmd unreadUnsubCmd; + private NotificationsCountSubCmd unreadCountSubCmd; + + private NotificationsSubCmd unreadSubCmd; private MarkNotificationAsReadCmd markAsReadCmd; + + private NotificationsUnsubCmd unsubCmd; + } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java new file mode 100644 index 0000000000..2722d6ae7d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.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.service.ws.notification.cmd; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.thingsboard.server.service.ws.WsCmd; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class NotificationsCountSubCmd implements WsCmd { + private int cmdId; +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java index 376b34b18e..087b250314 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java @@ -17,12 +17,14 @@ package org.thingsboard.server.service.ws.notification.cmd; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import org.thingsboard.server.service.ws.WsCmd; @Data @NoArgsConstructor @AllArgsConstructor -public class NotificationsSubCmd { +public class NotificationsSubCmd implements WsCmd { private int cmdId; private int limit; } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java index 1daeb570cc..b81e76e81d 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java @@ -15,10 +15,15 @@ */ package org.thingsboard.server.service.ws.notification.cmd; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; +import org.thingsboard.server.service.ws.WsCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; @Data -public class NotificationsUnsubCmd implements UnsubscribeCmd { +@NoArgsConstructor +@AllArgsConstructor +public class NotificationsUnsubCmd implements UnsubscribeCmd, WsCmd { private int cmdId; } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java new file mode 100644 index 0000000000..c839b36e8d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java @@ -0,0 +1,44 @@ +/** + * 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.ws.notification.cmd; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Getter; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdateType; + +@Getter +public class UnreadNotificationsCountUpdate extends CmdUpdate { + + private final int totalUnreadCount; + + @Builder + @JsonCreator + public UnreadNotificationsCountUpdate(@JsonProperty("cmdId") int cmdId, @JsonProperty("errorCode") int errorCode, + @JsonProperty("errorMsg") String errorMsg, + @JsonProperty("totalUnreadCount") int totalUnreadCount) { + super(cmdId, errorCode, errorMsg); + this.totalUnreadCount = totalUnreadCount; + } + + @Override + public CmdUpdateType getCmdUpdateType() { + return CmdUpdateType.NOTIFICATIONS_COUNT; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java new file mode 100644 index 0000000000..ab06d90001 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java @@ -0,0 +1,47 @@ +/** + * 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.ws.notification.sub; + +import lombok.Builder; +import lombok.Getter; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.service.subscription.TbSubscription; +import org.thingsboard.server.service.subscription.TbSubscriptionType; +import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsCountUpdate; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; + +@Getter +public class NotificationsCountSubscription extends TbSubscription { + + private final AtomicInteger unreadCounter = new AtomicInteger(); + + @Builder + public NotificationsCountSubscription(String serviceId, String sessionId, int subscriptionId, TenantId tenantId, EntityId entityId, + BiConsumer updateProcessor) { + super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.NOTIFICATIONS_COUNT, updateProcessor); + } + + public UnreadNotificationsCountUpdate createUpdate() { + return UnreadNotificationsCountUpdate.builder() + .cmdId(getSubscriptionId()) + .totalUnreadCount(unreadCounter.get()) + .build(); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/NotificationsSubscription.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java similarity index 85% rename from application/src/main/java/org/thingsboard/server/service/subscription/NotificationsSubscription.java rename to application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java index 00d23770e1..25d6e8725e 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/NotificationsSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java @@ -13,15 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.subscription; +package org.thingsboard.server.service.ws.notification.sub; import lombok.Builder; import lombok.Getter; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.service.subscription.TbSubscription; +import org.thingsboard.server.service.subscription.TbSubscriptionType; import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; -import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; import java.util.LinkedHashMap; import java.util.Map; @@ -34,7 +35,7 @@ public class NotificationsSubscription extends TbSubscription unreadNotifications = new LinkedHashMap<>(); private final int limit; - private final AtomicInteger totalUnreadCount = new AtomicInteger(); + private final AtomicInteger totalUnreadCounter = new AtomicInteger(); @Builder public NotificationsSubscription(String serviceId, String sessionId, int subscriptionId, TenantId tenantId, EntityId entityId, @@ -48,7 +49,7 @@ public class NotificationsSubscription extends TbSubscription currentNotifications = new LinkedHashMap<>(); @Getter - private int totalUnreadCount; + private UnreadNotificationsUpdate lastDataUpdate; @Getter - private UnreadNotificationsUpdate lastUpdate; + private UnreadNotificationsCountUpdate lastCountUpdate; public NotificationsWebSocketClient(String wsUrl, String token) throws URISyntaxException { super(new URI(wsUrl + "/api/ws/plugins/notifications?token=" + token)); @@ -47,38 +47,36 @@ public class NotificationsWebSocketClient extends TbTestWebSocketClient { public void subscribeForUnreadNotifications(int limit) { NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper(); - cmdsWrapper.setUnreadSubCmd(new NotificationsSubCmd(newCmdId(), limit)); + cmdsWrapper.setUnreadSubCmd(new NotificationsSubCmd(1, limit)); sendCmd(cmdsWrapper); } - public void markNotificationAsRead(UUID notificationId) { + public void subscribeForUnreadNotificationsCount() { NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper(); - cmdsWrapper.setMarkAsReadCmd(new MarkNotificationAsReadCmd(newCmdId(), notificationId)); + cmdsWrapper.setUnreadCountSubCmd(new NotificationsCountSubCmd(2)); sendCmd(cmdsWrapper); } - - private void handleUpdate(UnreadNotificationsUpdate update) { - totalUnreadCount = update.getTotalUnreadCount(); - if (update.getNotifications() != null) { - currentNotifications.clear(); - currentNotifications.putAll(update.getNotifications().stream().collect(Collectors.toMap(IdBased::getUuidId, n -> n))); - } else if (update.getUpdate() != null) { - Notification notification = update.getUpdate(); - currentNotifications.put(notification.getUuidId(), notification); - } + public void markNotificationAsRead(UUID notificationId) { + NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper(); + cmdsWrapper.setMarkAsReadCmd(new MarkNotificationAsReadCmd(newCmdId(), notificationId)); + sendCmd(cmdsWrapper); } - public void sendCmd(NotificationCmdsWrapper cmdsWrapper) { - send(JacksonUtil.toString(cmdsWrapper)); + String cmd = JacksonUtil.toString(cmdsWrapper); + send(cmd); } @Override public void onMessage(String s) { - UnreadNotificationsUpdate update = JacksonUtil.fromString(s, UnreadNotificationsUpdate.class); - lastUpdate = update; - handleUpdate(update); + JsonNode update = JacksonUtil.toJsonNode(s); + CmdUpdateType updateType = CmdUpdateType.valueOf(update.get("cmdUpdateType").asText()); + if (updateType == CmdUpdateType.NOTIFICATIONS) { + lastDataUpdate = JacksonUtil.treeToValue(update, UnreadNotificationsUpdate.class); + } else if (updateType == CmdUpdateType.NOTIFICATIONS_COUNT) { + lastCountUpdate = JacksonUtil.treeToValue(update, UnreadNotificationsCountUpdate.class); + } super.onMessage(s); } diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWsApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWsApiTest.java index 8ff88b7e3c..6738b37a70 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWsApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWsApiTest.java @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.notification.targets.SingleUserNotific import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.controller.TbTestWebSocketClient; import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsCountUpdate; import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; import java.net.URISyntaxException; @@ -41,6 +42,41 @@ public class NotificationsWsApiTest extends AbstractControllerTest { loginTenantAdmin(); } + @Test + public void testSubscribingToUnreadNotificationsCount() { + NotificationTarget notificationTarget = createNotificationTarget(tenantAdminUserId); + String notificationText1 = "Notification 1"; + submitNotificationRequest(notificationTarget.getId(), "Just a test", notificationText1); + String notificationText2 = "Notification 2"; + submitNotificationRequest(notificationTarget.getId(), "Just a test", notificationText2); + + getWsClient().subscribeForUnreadNotificationsCount(); + getWsClient().waitForReply(); + + UnreadNotificationsCountUpdate update = getWsClient().getLastCountUpdate(); + assertThat(update.getTotalUnreadCount()).isEqualTo(2); + } + + @Test + public void testReceivingCountUpdates_multipleSessions() { + getWsClient().subscribeForUnreadNotificationsCount(); + getAnotherWsClient().subscribeForUnreadNotificationsCount(); + getWsClient().waitForReply(); + getAnotherWsClient().waitForReply(); + assertThat(getWsClient().getLastCountUpdate().getTotalUnreadCount()).isZero(); + + getWsClient().registerWaitForUpdate(); + getAnotherWsClient().registerWaitForUpdate(); + NotificationTarget notificationTarget = createNotificationTarget(tenantAdminUserId); + String notificationText = "Notification"; + submitNotificationRequest(notificationTarget.getId(), "Just a test", notificationText); + getWsClient().waitForUpdate(); + getAnotherWsClient().waitForUpdate(); + + assertThat(getWsClient().getLastCountUpdate().getTotalUnreadCount()).isOne(); + assertThat(getAnotherWsClient().getLastCountUpdate().getTotalUnreadCount()).isOne(); + } + @Test public void testSubscribingToUnreadNotifications_multipleSessions() throws Exception { NotificationTarget notificationTarget = createNotificationTarget(tenantAdminUserId); @@ -54,8 +90,8 @@ public class NotificationsWsApiTest extends AbstractControllerTest { getWsClient().waitForReply(); getAnotherWsClient().waitForReply(); - checkFullNotificationsUpdate(getWsClient().getLastUpdate(), notificationText1, notificationText2); - checkFullNotificationsUpdate(getAnotherWsClient().getLastUpdate(), notificationText1, notificationText2); + checkFullNotificationsUpdate(getWsClient().getLastDataUpdate(), notificationText1, notificationText2); + checkFullNotificationsUpdate(getAnotherWsClient().getLastDataUpdate(), notificationText1, notificationText2); } @Test @@ -64,7 +100,7 @@ public class NotificationsWsApiTest extends AbstractControllerTest { getAnotherWsClient().subscribeForUnreadNotifications(10); getWsClient().waitForReply(); getAnotherWsClient().waitForReply(); - UnreadNotificationsUpdate notificationsUpdate = getWsClient().getLastUpdate(); + UnreadNotificationsUpdate notificationsUpdate = getWsClient().getLastDataUpdate(); assertThat(notificationsUpdate.getTotalUnreadCount()).isZero(); getWsClient().registerWaitForUpdate(); @@ -75,8 +111,8 @@ public class NotificationsWsApiTest extends AbstractControllerTest { getWsClient().waitForUpdate(); getAnotherWsClient().waitForUpdate(); - checkPartialNotificationsUpdate(getWsClient().getLastUpdate(), notificationText, 1); - checkPartialNotificationsUpdate(getAnotherWsClient().getLastUpdate(), notificationText, 1); + checkPartialNotificationsUpdate(getWsClient().getLastDataUpdate(), notificationText, 1); + checkPartialNotificationsUpdate(getAnotherWsClient().getLastDataUpdate(), notificationText, 1); } @Test @@ -85,28 +121,37 @@ public class NotificationsWsApiTest extends AbstractControllerTest { getAnotherWsClient().subscribeForUnreadNotifications(10); getWsClient().waitForReply(); getAnotherWsClient().waitForReply(); + getAnotherWsClient().subscribeForUnreadNotificationsCount(); + getAnotherWsClient().waitForReply(); NotificationTarget notificationTarget = createNotificationTarget(tenantAdminUserId); getWsClient().registerWaitForUpdate(); + getAnotherWsClient().registerWaitForUpdate(2); String notificationText1 = "Notification 1"; submitNotificationRequest(notificationTarget.getId(), "Just a test", notificationText1); getWsClient().waitForUpdate(); - Notification notification1 = getWsClient().getLastUpdate().getUpdate(); + getAnotherWsClient().waitForUpdate(); + Notification notification1 = getWsClient().getLastDataUpdate().getUpdate(); getWsClient().registerWaitForUpdate(); + getAnotherWsClient().registerWaitForUpdate(2); String notificationText2 = "Notification 2"; submitNotificationRequest(notificationTarget.getId(), "Just a test", notificationText2); getWsClient().waitForUpdate(); - assertThat(getWsClient().getLastUpdate().getTotalUnreadCount()).isEqualTo(2); + getAnotherWsClient().waitForUpdate(); + assertThat(getWsClient().getLastDataUpdate().getTotalUnreadCount()).isEqualTo(2); + assertThat(getAnotherWsClient().getLastDataUpdate().getTotalUnreadCount()).isEqualTo(2); + assertThat(getAnotherWsClient().getLastCountUpdate().getTotalUnreadCount()).isEqualTo(2); getWsClient().registerWaitForUpdate(); - getAnotherWsClient().registerWaitForUpdate(); + getAnotherWsClient().registerWaitForUpdate(2); getWsClient().markNotificationAsRead(notification1.getUuidId()); getWsClient().waitForUpdate(); getAnotherWsClient().waitForUpdate(); - checkFullNotificationsUpdate(getWsClient().getLastUpdate(), notificationText2); - checkFullNotificationsUpdate(getAnotherWsClient().getLastUpdate(), notificationText2); + checkFullNotificationsUpdate(getWsClient().getLastDataUpdate(), notificationText2); + checkFullNotificationsUpdate(getAnotherWsClient().getLastDataUpdate(), notificationText2); + assertThat(getAnotherWsClient().getLastCountUpdate().getTotalUnreadCount()).isOne(); } public void testReceivingUpdatesWhenSubscriptionAtAnotherInstance() {} @@ -127,7 +172,7 @@ public class NotificationsWsApiTest extends AbstractControllerTest { notificationTarget.setTenantId(tenantId); notificationTarget.setName("User " + userId); SingleUserNotificationTargetConfig config = new SingleUserNotificationTargetConfig(); - config.setUserId(userId); + config.setUserId(userId.getId()); notificationTarget.setConfiguration(config); return doPost("/api/notification/target", notificationTarget, NotificationTarget.class); } diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 7fb764579d..c1a6dd342e 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -552,6 +552,10 @@ message NotificationsSubscriptionProto { int32 limit = 2; } +message NotificationsCountSubscriptionProto { + TbSubscriptionProto sub = 1; +} + message TbSubscriptionUpdateProto { string sessionId = 1; int32 subscriptionId = 2; @@ -573,6 +577,7 @@ message NotificationsSubscriptionUpdateProto { string sessionId = 1; int32 subscriptionId = 2; string notification = 3; + bool isNewNotification = 4; } message NotificationUpdateProto { @@ -581,6 +586,7 @@ message NotificationUpdateProto { int64 recipientIdMSB = 3; int64 recipientIdLSB = 4; string notification = 5; + bool isNew = 6; } message NotificationRequestDeleteProto { @@ -692,8 +698,9 @@ message SubscriptionMgrMsgProto { TbAlarmDeleteProto alarmDelete = 9; TbTimeSeriesDeleteProto tsDelete = 10; NotificationsSubscriptionProto notificationsSub = 11; - NotificationUpdateProto notificationUpdate = 12; - NotificationRequestDeleteProto notificationRequestDelete = 13; + NotificationsCountSubscriptionProto notificationsCountSub = 12; + NotificationUpdateProto notificationUpdate = 13; + NotificationRequestDeleteProto notificationRequestDelete = 14; } message LocalSubscriptionServiceMsgProto { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java index c8e1a7ea08..a4bebfd135 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java @@ -38,10 +38,14 @@ public interface NotificationService { Notification createNotification(TenantId tenantId, Notification notification); - Notification updateNotificationStatus(TenantId tenantId, NotificationId notificationId, NotificationStatus status); + Notification findNotificationById(TenantId tenantId, NotificationId notificationId); + + boolean updateNotificationStatus(TenantId tenantId, UserId userId, NotificationId notificationId, NotificationStatus status); PageData findNotificationsByUserIdAndReadStatusAndPageLink(TenantId tenantId, UserId userId, boolean unreadOnly, PageLink pageLink); PageData findLatestUnreadNotificationsByUserId(TenantId tenantId, UserId userId, int limit); + int countUnreadNotificationsByUserId(TenantId tenantId, UserId userId); + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java index f6cdf780e5..10c7155d97 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java @@ -15,15 +15,14 @@ */ package org.thingsboard.server.dao.notification; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; +import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import java.util.List; - public interface NotificationTargetService { NotificationTarget saveNotificationTarget(TenantId tenantId, NotificationTarget notificationTarget); @@ -32,7 +31,9 @@ public interface NotificationTargetService { PageData findNotificationTargetsByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); - List findRecipientsForNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId); + PageData findRecipientsForNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId, PageLink pageLink); + + PageData findRecipientsForNotificationTargetConfig(TenantId tenantId, NotificationTargetConfig targetConfig, PageLink pageLink); void deleteNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java index 93da1ecd6c..41864b327d 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java @@ -57,6 +57,8 @@ public interface UserService { PageData findTenantAdmins(TenantId tenantId, PageLink pageLink); + PageData findUsers(TenantId tenantId, PageLink pageLink); + void deleteTenantAdmins(TenantId tenantId); PageData findCustomerUsers(TenantId tenantId, CustomerId customerId, PageLink pageLink); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java index 42cff008d0..dd800ef3fc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java @@ -21,11 +21,11 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; 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.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.validation.NoXss; import javax.validation.Valid; @@ -37,7 +37,7 @@ import javax.validation.constraints.NotNull; @NoArgsConstructor @AllArgsConstructor @Builder -public class NotificationRequest extends BaseData implements HasTenantId { +public class NotificationRequest extends BaseData implements HasTenantId, HasName { private TenantId tenantId; @NotNull(message = "Target is not specified") @@ -55,4 +55,9 @@ public class NotificationRequest extends BaseData impleme public static final String GENERAL_NOTIFICATION_REASON = "General"; public static final String ALARM_NOTIFICATION_REASON = "Alarm"; + @Override + public String getName() { + return notificationReason; + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/AllUsersNotificationTargetConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/AllUsersNotificationTargetConfig.java new file mode 100644 index 0000000000..8e380cafa5 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/AllUsersNotificationTargetConfig.java @@ -0,0 +1,28 @@ +/** + * 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.targets; + +import lombok.Data; + +@Data +public class AllUsersNotificationTargetConfig implements NotificationTargetConfig { + + @Override + public NotificationTargetConfigType getType() { + return NotificationTargetConfigType.ALL_USERS; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/CustomerUsersNotificationTargetConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/CustomerUsersNotificationTargetConfig.java new file mode 100644 index 0000000000..90e913c17e --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/CustomerUsersNotificationTargetConfig.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.notification.targets; + +import lombok.Data; + +import java.util.UUID; + +@Data +public class CustomerUsersNotificationTargetConfig implements NotificationTargetConfig { + + private UUID customerId; + + @Override + public NotificationTargetConfigType getType() { + return NotificationTargetConfigType.CUSTOMER_USERS; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java index 0623bf07b9..e3fb02bf4a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java @@ -24,7 +24,9 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @JsonSubTypes({ @Type(value = SingleUserNotificationTargetConfig.class, name = "SINGLE_USER"), - @Type(value = UserListNotificationTargetConfig.class, name = "USER_LIST") + @Type(value = UserListNotificationTargetConfig.class, name = "USER_LIST"), + @Type(value = CustomerUsersNotificationTargetConfig.class, name = "CUSTOMER_USERS"), + @Type(value = AllUsersNotificationTargetConfig.class, name = "ALL_USERS") }) public interface NotificationTargetConfig { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfigType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfigType.java index 2413eb9326..b7694c9de8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfigType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfigType.java @@ -19,6 +19,9 @@ public enum NotificationTargetConfigType { SINGLE_USER, USER_LIST, + CUSTOMER_USERS, + ALL_USERS + // USER_GROUP, // USERS_WITH_ROLE, // QUERY // ? diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/SingleUserNotificationTargetConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/SingleUserNotificationTargetConfig.java index 9fbb6ca600..12dc7c59ce 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/SingleUserNotificationTargetConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/SingleUserNotificationTargetConfig.java @@ -16,15 +16,15 @@ package org.thingsboard.server.common.data.notification.targets; import lombok.Data; -import org.thingsboard.server.common.data.id.UserId; import javax.validation.constraints.NotNull; +import java.util.UUID; @Data public class SingleUserNotificationTargetConfig implements NotificationTargetConfig { @NotNull - private UserId userId; + private UUID userId; @Override public NotificationTargetConfigType getType() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/UserListNotificationTargetConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/UserListNotificationTargetConfig.java index 22bc26703d..ea05948ff6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/UserListNotificationTargetConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/UserListNotificationTargetConfig.java @@ -18,12 +18,15 @@ package org.thingsboard.server.common.data.notification.targets; import lombok.Data; import org.thingsboard.server.common.data.id.UserId; +import javax.validation.constraints.NotEmpty; import java.util.List; +import java.util.UUID; @Data public class UserListNotificationTargetConfig implements NotificationTargetConfig { - private List usersIds; + @NotEmpty + private List usersIds; @Override public NotificationTargetConfigType getType() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java index 4be3f8e6ac..4e82583ba2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java +++ b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java @@ -110,13 +110,17 @@ public abstract class DaoUtil { } public static void processInBatches(Function> finder, int batchSize, Consumer processor) { + processBatches(finder, batchSize, batch -> batch.forEach(processor)); + } + + public static void processBatches(Function> finder, int batchSize, Consumer> processor) { PageLink pageLink = new PageLink(batchSize); PageData batch; boolean hasNextBatch; do { batch = finder.apply(pageLink); - batch.getData().forEach(processor); + processor.accept(batch.getData()); hasNextBatch = batch.hasNext(); pageLink = pageLink.nextPageLink(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java index e6e69fed22..c15480c747 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java @@ -81,13 +81,17 @@ public class DefaultNotificationService implements NotificationService { return notificationDao.save(tenantId, notification); } - @Transactional @Override - public Notification updateNotificationStatus(TenantId tenantId, NotificationId notificationId, NotificationStatus status) { - notificationDao.updateStatus(tenantId, notificationId, status); + public Notification findNotificationById(TenantId tenantId, NotificationId notificationId) { return notificationDao.findById(tenantId, notificationId.getId()); } + @Transactional + @Override + public boolean updateNotificationStatus(TenantId tenantId, UserId userId, NotificationId notificationId, NotificationStatus status) { + return notificationDao.updateStatusByIdAndUserId(tenantId, userId, notificationId, status); + } + @Override public PageData findNotificationsByUserIdAndReadStatusAndPageLink(TenantId tenantId, UserId userId, boolean unreadOnly, PageLink pageLink) { if (unreadOnly) { @@ -104,6 +108,11 @@ public class DefaultNotificationService implements NotificationService { return findNotificationsByUserIdAndReadStatusAndPageLink(tenantId, userId, true, pageLink); } + @Override + public int countUnreadNotificationsByUserId(TenantId tenantId, UserId userId) { + return notificationDao.countUnreadByUserId(tenantId, userId); + } + private static class NotificationRequestValidator extends DataValidator { @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java index 52f9f8b61b..00892576e4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java @@ -18,9 +18,12 @@ package org.thingsboard.server.dao.notification; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.targets.CustomerUsersNotificationTargetConfig; 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.SingleUserNotificationTargetConfig; @@ -28,9 +31,10 @@ import org.thingsboard.server.common.data.notification.targets.UserListNotificat import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.user.UserService; -import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; @Service @Slf4j @@ -38,11 +42,11 @@ import java.util.List; public class DefaultNotificationTargetService implements NotificationTargetService { private final NotificationTargetDao notificationTargetDao; + private final UserService userService; private final NotificationTargetValidator validator = new NotificationTargetValidator(); @Override public NotificationTarget saveNotificationTarget(TenantId tenantId, NotificationTarget notificationTarget) { - notificationTarget.setTenantId(tenantId); validator.validate(notificationTarget, NotificationTarget::getTenantId); return notificationTargetDao.save(tenantId, notificationTarget); } @@ -58,21 +62,42 @@ public class DefaultNotificationTargetService implements NotificationTargetServi } @Override - public List findRecipientsForNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId) { + public PageData findRecipientsForNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId, PageLink pageLink) { NotificationTarget notificationTarget = findNotificationTargetById(tenantId, notificationTargetId); NotificationTargetConfig configuration = notificationTarget.getConfiguration(); - List recipients = new ArrayList<>(); - switch (configuration.getType()) { - case SINGLE_USER: - SingleUserNotificationTargetConfig singleUserNotificationTargetConfig = (SingleUserNotificationTargetConfig) configuration; - recipients.add(singleUserNotificationTargetConfig.getUserId()); - break; - case USER_LIST: - UserListNotificationTargetConfig userListNotificationTargetConfig = (UserListNotificationTargetConfig) configuration; - recipients.addAll(userListNotificationTargetConfig.getUsersIds()); - break; + return findRecipientsForNotificationTargetConfig(tenantId, configuration, pageLink); + } + + @Override + public PageData findRecipientsForNotificationTargetConfig(TenantId tenantId, NotificationTargetConfig targetConfig, PageLink pageLink) { + switch (targetConfig.getType()) { + case SINGLE_USER: { + UserId userId = new UserId(((SingleUserNotificationTargetConfig) targetConfig).getUserId()); + User user = userService.findUserById(tenantId, userId); + return new PageData<>(List.of(user), 1, 1, false); + } + case USER_LIST: { + List users = ((UserListNotificationTargetConfig) targetConfig).getUsersIds().stream() + .map(UserId::new).map(userId -> userService.findUserById(tenantId, userId)) + .collect(Collectors.toList()); + return new PageData<>(users, 1, users.size(), false); + } + case CUSTOMER_USERS: { + if (tenantId.equals(TenantId.SYS_TENANT_ID)) { + throw new IllegalArgumentException("Customer users target is not supported for system administrator"); + } + CustomerId customerId = new CustomerId(((CustomerUsersNotificationTargetConfig) targetConfig).getCustomerId()); + return userService.findCustomerUsers(tenantId, customerId, pageLink); + } + case ALL_USERS: { + if (!tenantId.equals(TenantId.SYS_TENANT_ID)) { + return userService.findUsersByTenantId(tenantId, pageLink); + } else { + return userService.findUsers(TenantId.SYS_TENANT_ID, pageLink); + } + } } - return recipients; + return new PageData<>(); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java index b088d75c35..c16df8819a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java @@ -30,6 +30,8 @@ public interface NotificationDao extends Dao { PageData findByUserIdAndPageLink(TenantId tenantId, UserId userId, PageLink pageLink); - void updateStatus(TenantId tenantId, NotificationId notificationId, NotificationStatus status); + boolean updateStatusByIdAndUserId(TenantId tenantId, UserId userId, NotificationId notificationId, NotificationStatus status); + + int countUnreadByUserId(TenantId tenantId, UserId userId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java index bb9c30a71b..2611c32c90 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java @@ -73,8 +73,13 @@ public class JpaNotificationDao extends JpaAbstractDao :status") + int updateStatusByIdAndRecipientId(@Param("id") UUID id, + @Param("recipientId") UUID recipientId, + @Param("status") NotificationStatus status); + + int countByRecipientIdAndStatusNot(UUID recipientId, NotificationStatus status); void deleteByRequestId(UUID requestId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java index ad8cac6a28..b566395eb8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java @@ -95,6 +95,11 @@ public class JpaUserDao extends JpaAbstractSearchTextDao imple } + @Override + public PageData findAll(TenantId tenantId, PageLink pageLink) { + return DaoUtil.toPageData(userRepository.findAll(DaoUtil.toPageable(pageLink))); + } + @Override public Long countByTenantId(TenantId tenantId) { return userRepository.countByTenantId(tenantId.getId()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java index b906b3278d..081e30b525 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java @@ -69,4 +69,7 @@ public interface UserDao extends Dao, TenantEntityDao { * @return the list of user entities */ PageData findCustomerUsers(UUID tenantId, UUID customerId, PageLink pageLink); + + PageData findAll(TenantId tenantId, PageLink pageLink); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index fc0de8a4fa..dce9f772bf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -230,6 +230,11 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic return userDao.findTenantAdmins(tenantId.getId(), pageLink); } + @Override + public PageData findUsers(TenantId tenantId, PageLink pageLink) { + return userDao.findAll(tenantId, pageLink); + } + @Override public void deleteTenantAdmins(TenantId tenantId) { log.trace("Executing deleteTenantAdmins, tenantId [{}]", tenantId); diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql index 7bd4d69f5a..8f43b1ac4e 100644 --- a/dao/src/main/resources/sql/schema-entities-idx.sql +++ b/dao/src/main/resources/sql/schema-entities-idx.sql @@ -78,6 +78,8 @@ CREATE INDEX IF NOT EXISTS idx_notification_target_tenant_id_and_created_time ON CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id_and_created_time ON notification_request(tenant_id, created_time DESC); +CREATE INDEX IF NOT EXISTS idx_notification_id ON notification(id); + CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_and_created_time ON notification(recipient_id, created_time DESC); CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_and_status_and_created_time ON notification(recipient_id, status, created_time DESC); From 12ae902cfc7b982ca442e7667d88ff7d3fd5856c Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Sun, 6 Nov 2022 18:13:02 +0200 Subject: [PATCH 007/111] Notification rules initial implementation --- .../main/data/upgrade/3.4.2/schema_update.sql | 11 +- .../server/actors/ActorSystemContext.java | 7 +- .../actors/ruleChain/DefaultTbContext.java | 6 +- .../controller/NotificationController.java | 17 ++- ...aultNotificationRuleProcessingService.java | 138 ++++++++++++++++++ ...faultNotificationSubscriptionService.java} | 82 +++++++---- .../NotificationRuleProcessingService.java | 28 ++++ .../NotificationSubscriptionService.java | 6 +- .../queue/DefaultTbCoreConsumerService.java | 44 +++--- .../DefaultSubscriptionManagerService.java | 23 ++- .../SubscriptionManagerService.java | 8 +- .../subscription/TbSubscriptionUtils.java | 31 ++-- .../AbstractSubscriptionService.java | 6 +- .../DefaultAlarmSubscriptionService.java | 8 +- .../DefaultTelemetrySubscriptionService.java | 8 +- .../DefaultNotificationCommandsHandler.java | 100 +++++++++---- .../sub/NotificationRequestUpdate.java | 33 +++++ .../notification/sub/NotificationUpdate.java | 31 ++++ .../sub/NotificationsSubscriptionUpdate.java | 19 ++- common/cluster-api/src/main/proto/queue.proto | 14 +- .../notification/NotificationRuleService.java | 26 ++++ .../dao/notification/NotificationService.java | 22 ++- .../server/common/data/alarm/Alarm.java | 3 + .../device/profile/DeviceProfileAlarm.java | 3 + .../common/data/id/NotificationRuleId.java | 36 +++++ .../data/notification/NotificationInfo.java | 21 ++- .../notification/NotificationRequest.java | 11 ++ .../NotificationRequestConfig.java | 2 +- .../NotificationRequestStatus.java | 21 +++ .../NonConfirmedNotificationEscalation.java | 15 +- .../notification/rule/NotificationRule.java | 31 ++-- .../server/dao/model/ModelConstants.java | 7 + .../dao/model/sql/AbstractAlarmEntity.java | 8 + .../model/sql/NotificationRequestEntity.java | 19 +++ .../dao/model/sql/NotificationRuleEntity.java | 65 +++++++++ .../DefaultNotificationRuleService.java | 35 +++++ .../DefaultNotificationService.java | 40 ++--- .../dao/notification/NotificationDao.java | 6 + .../notification/NotificationRequestDao.java | 6 + .../dao/notification/NotificationRuleDao.java | 22 +++ .../sql/notification/JpaNotificationDao.java | 13 ++ .../JpaNotificationRequestDao.java | 8 + .../notification/JpaNotificationRuleDao.java | 46 ++++++ .../notification/NotificationRepository.java | 8 +- .../NotificationRequestRepository.java | 3 + .../NotificationRuleRepository.java | 26 ++++ .../resources/sql/schema-entities-idx.sql | 4 +- .../main/resources/sql/schema-entities.sql | 5 +- .../api/RuleEngineNotificationService.java | 25 ++++ .../rule/engine/api/TbContext.java | 3 +- .../notification/TbNotificationNode.java | 2 +- .../rule/engine/profile/AlarmState.java | 1 + .../rule/engine/profile/DeviceState.java | 1 + 53 files changed, 964 insertions(+), 200 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java rename application/src/main/java/org/thingsboard/server/service/notification/{DefaultNotificationProcessingService.java => DefaultNotificationSubscriptionService.java} (61%) create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/NotificationRuleProcessingService.java rename common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationProcessingService.java => application/src/main/java/org/thingsboard/server/service/notification/NotificationSubscriptionService.java (86%) create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationRequestUpdate.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRuleId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStatus.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineNotificationService.java diff --git a/application/src/main/data/upgrade/3.4.2/schema_update.sql b/application/src/main/data/upgrade/3.4.2/schema_update.sql index 77f078e67f..7294916445 100644 --- a/application/src/main/data/upgrade/3.4.2/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.2/schema_update.sql @@ -24,6 +24,10 @@ CREATE TABLE IF NOT EXISTS notification_target ( ); CREATE INDEX IF NOT EXISTS idx_notification_target_tenant_id_and_created_time ON notification_target(tenant_id, created_time DESC); +CREATE TABLE IF NOT EXISTS notification_rule ( + id UUID NOT NULL CONSTRAINT notification_rule_pkey PRIMARY KEY +); + CREATE TABLE IF NOT EXISTS notification_request ( id UUID NOT NULL CONSTRAINT notification_request_pkey PRIMARY KEY, created_time BIGINT NOT NULL, @@ -33,7 +37,10 @@ CREATE TABLE IF NOT EXISTS notification_request ( text_template VARCHAR NOT NULL, notification_info VARCHAR(1000), notification_severity VARCHAR(32), - additional_config VARCHAR(1000) + additional_config VARCHAR(1000), + status VARCHAR(32), + rule_id UUID NULL CONSTRAINT fk_notification_request_rule_id REFERENCES notification_rule(id), + alarm_id UUID ); CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id_and_created_time ON notification_request(tenant_id, created_time DESC); @@ -50,5 +57,5 @@ CREATE TABLE IF NOT EXISTS notification ( status VARCHAR(32) ) PARTITION BY RANGE (created_time); CREATE INDEX IF NOT EXISTS idx_notification_id ON notification(id); +CREATE INDEX IF NOT EXISTS idx_notification_notification_request_id ON notification(request_id); CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_and_created_time ON notification(recipient_id, created_time DESC); -CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_and_status_and_created_time ON notification(recipient_id, status, created_time DESC); diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 2a574f5788..27d3d401ff 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -15,9 +15,7 @@ */ package org.thingsboard.server.actors; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -32,6 +30,7 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.MailService; +import org.thingsboard.rule.engine.api.RuleEngineNotificationService; import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.server.actors.service.ActorService; @@ -64,7 +63,6 @@ import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor; import org.thingsboard.server.dao.nosql.CassandraBufferedRateWriteExecutor; -import org.thingsboard.server.dao.notification.NotificationProcessingService; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; @@ -106,7 +104,6 @@ import javax.annotation.PostConstruct; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; -import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ScheduledExecutorService; @@ -307,7 +304,7 @@ public class ActorSystemContext { @Autowired @Getter - private NotificationProcessingService notificationProcessingService; + private RuleEngineNotificationService notificationService; @Lazy @Autowired(required = false) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index c0197a0f89..82010dc984 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -27,6 +27,7 @@ import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.RuleEngineAlarmService; import org.thingsboard.rule.engine.api.RuleEngineAssetProfileCache; import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; +import org.thingsboard.rule.engine.api.RuleEngineNotificationService; import org.thingsboard.rule.engine.api.RuleEngineRpcService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.ScriptEngine; @@ -79,7 +80,6 @@ import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.nosql.CassandraStatementTask; import org.thingsboard.server.dao.nosql.TbResultSetFuture; -import org.thingsboard.server.dao.notification.NotificationProcessingService; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; @@ -632,8 +632,8 @@ class DefaultTbContext implements TbContext { } @Override - public NotificationProcessingService getNotificationProcessingService() { - return mainCtx.getNotificationProcessingService(); + public RuleEngineNotificationService getNotificationService() { + return mainCtx.getNotificationService(); } @Override diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index 1bf771218a..a4f44b589d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -39,7 +39,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.dao.notification.NotificationProcessingService; +import org.thingsboard.server.service.notification.NotificationSubscriptionService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -54,7 +54,7 @@ import java.util.UUID; public class NotificationController extends BaseController { private final NotificationService notificationService; - private final NotificationProcessingService notificationProcessingService; + private final NotificationSubscriptionService notificationSubscriptionService; @GetMapping("/notifications") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @@ -66,7 +66,7 @@ public class NotificationController extends BaseController { @RequestParam(defaultValue = "false") boolean unreadOnly, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return notificationService.findNotificationsByUserIdAndReadStatusAndPageLink(user.getTenantId(), user.getId(), unreadOnly, pageLink); + return notificationService.findNotificationsByUserIdAndReadStatus(user.getTenantId(), user.getId(), unreadOnly, pageLink); } @PutMapping("/notification/{id}/read") // or maybe to NotificationUpdateRequest for the future @@ -74,7 +74,7 @@ public class NotificationController extends BaseController { public void markNotificationAsRead(@PathVariable UUID id, @AuthenticationPrincipal SecurityUser user) { NotificationId notificationId = new NotificationId(id); - notificationProcessingService.markNotificationAsRead(user.getTenantId(), user.getId(), notificationId); + notificationSubscriptionService.markNotificationAsRead(user.getTenantId(), user.getId(), notificationId); } // delete notification? @@ -84,8 +84,11 @@ public class NotificationController extends BaseController { public NotificationRequest createNotificationRequest(@RequestBody NotificationRequest notificationRequest, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { accessControlService.checkPermission(user, Resource.NOTIFICATION_REQUEST, Operation.CREATE, null, notificationRequest); + if (notificationRequest.getId() != null) { + throw new IllegalArgumentException("Notification request cannot be changed. You can delete it and create a new one"); + } try { - NotificationRequest savedNotificationRequest = notificationProcessingService.processNotificationRequest(user.getTenantId(), notificationRequest); + NotificationRequest savedNotificationRequest = notificationSubscriptionService.processNotificationRequest(user.getTenantId(), notificationRequest); logEntityAction(user, EntityType.NOTIFICATION_REQUEST, savedNotificationRequest, ActionType.ADDED); return savedNotificationRequest; } catch (Exception e) { @@ -111,7 +114,7 @@ public class NotificationController extends BaseController { @RequestParam(required = false) String sortOrder, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return notificationService.findNotificationRequestsByTenantIdAndPageLink(user.getTenantId(), pageLink); + return notificationService.findNotificationRequestsByTenantId(user.getTenantId(), pageLink); } @DeleteMapping("/notification/request/{id}") @@ -121,7 +124,7 @@ public class NotificationController extends BaseController { NotificationRequest notificationRequest = notificationService.findNotificationRequestById(user.getTenantId(), notificationRequestId); accessControlService.checkPermission(user, Resource.NOTIFICATION_REQUEST, Operation.DELETE, notificationRequestId, notificationRequest); try { - notificationProcessingService.deleteNotificationRequest(user.getTenantId(), notificationRequestId); + notificationSubscriptionService.deleteNotificationRequest(user.getTenantId(), notificationRequestId); logEntityAction(user, EntityType.NOTIFICATION_REQUEST, notificationRequest, ActionType.DELETED); } catch (Exception e) { logEntityAction(user, EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationRequest, ActionType.DELETED, e); diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java new file mode 100644 index 0000000000..7c459b2e80 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java @@ -0,0 +1,138 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.id.NotificationRuleId; +import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.NotificationInfo; +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationRequestConfig; +import org.thingsboard.server.common.data.notification.NotificationRequestStatus; +import org.thingsboard.server.common.data.notification.NotificationSeverity; +import org.thingsboard.server.common.data.notification.rule.NonConfirmedNotificationEscalation; +import org.thingsboard.server.common.data.notification.rule.NotificationRule; +import org.thingsboard.server.dao.notification.NotificationRuleService; +import org.thingsboard.server.dao.notification.NotificationService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.executors.DbCallbackExecutorService; + +import java.util.List; + +@Service +@TbCoreComponent +@RequiredArgsConstructor +public class DefaultNotificationRuleProcessingService implements NotificationRuleProcessingService { + + private final NotificationRuleService notificationRuleService; + private final NotificationService notificationService; + private final NotificationSubscriptionService notificationSubscriptionService; + private final DbCallbackExecutorService dbCallbackExecutorService; + + @Override + public ListenableFuture onAlarmCreatedOrUpdated(TenantId tenantId, Alarm alarm) { + return processAlarmUpdate(tenantId, alarm); + } + + @Override + public ListenableFuture onAlarmAcknowledged(TenantId tenantId, Alarm alarm) { + return processAlarmUpdate(tenantId, alarm); + } + + private ListenableFuture processAlarmUpdate(TenantId tenantId, Alarm alarm) { + if (alarm.getNotificationRuleId() == null) return Futures.immediateFuture(null); + return dbCallbackExecutorService.submit(() -> { + onAlarmUpdate(tenantId, alarm.getNotificationRuleId(), alarm); + }); + } + + private void onAlarmUpdate(TenantId tenantId, NotificationRuleId notificationRuleId, Alarm alarm) { + List notificationRequests = notificationService.findNotificationRequestsByRuleIdAndAlarmId(tenantId, notificationRuleId, alarm.getId()); + NotificationRule notificationRule = notificationRuleService.findNotificationRuleById(tenantId, notificationRuleId); + + if (notificationRequests.isEmpty()) { // in case it is first notification for alarm, or it was previously acked and now we need to send notifications again + NotificationTargetId initialNotificationTargetId = notificationRule.getInitialNotificationTargetId(); + submitNotificationRequest(tenantId, initialNotificationTargetId, notificationRule, alarm, 0); + + for (NonConfirmedNotificationEscalation escalation : notificationRule.getEscalations()) { + submitNotificationRequest(tenantId, escalation.getNotificationTargetId(), notificationRule, alarm, escalation.getDelayInMinutes()); + } + } else { + if (alarmAcknowledged(alarm)) { + for (NotificationRequest notificationRequest : notificationRequests) { + if (notificationRequest.getStatus() == NotificationRequestStatus.SCHEDULED) { + // using regular service due to no need to send an update to subscription manager + notificationService.deleteNotificationRequestById(tenantId, notificationRequest.getId()); + } else { + notificationSubscriptionService.deleteNotificationRequest(tenantId, notificationRequest.getId()); + // todo: or should we mark already sent notifications as read? + } + } + } else { + NotificationInfo newNotificationInfo = constructNotificationInfo(alarm, notificationRule); + for (NotificationRequest notificationRequest : notificationRequests) { + NotificationInfo previousNotificationInfo = notificationRequest.getNotificationInfo(); + if (!previousNotificationInfo.equals(newNotificationInfo)) { + notificationRequest.setNotificationInfo(newNotificationInfo); + notificationSubscriptionService.updateNotificationRequest(tenantId, notificationRequest); + } + // fixme: no need to send an update event for scheduled requests, only for sent + } + } + } + } + + private boolean alarmAcknowledged(Alarm alarm) { // todo: decide when to consider the alarm processed by notification target (not to escalate then) + return alarm.getStatus().isAck() && alarm.getStatus().isCleared(); + } + + private void submitNotificationRequest(TenantId tenantId, NotificationTargetId targetId, NotificationRule notificationRule, Alarm alarm, int delayInMinutes) { + NotificationRequestConfig config = new NotificationRequestConfig(); + if (delayInMinutes > 0) { + config.setSendingDelayInMinutes(delayInMinutes); + } + NotificationInfo notificationInfo = constructNotificationInfo(alarm, notificationRule); + + NotificationRequest notificationRequest = NotificationRequest.builder() + .tenantId(tenantId) + .targetId(targetId) + .notificationReason("Alarm") + .textTemplate(notificationRule.getNotificationTextTemplate()) // todo: format with alarm vars + .notificationInfo(notificationInfo) + .notificationSeverity(NotificationSeverity.NORMAL) // todo: from alarm severity + .additionalConfig(config) + .ruleId(notificationRule.getId()) + .alarmId(alarm.getId()) + .build(); + notificationSubscriptionService.processNotificationRequest(tenantId, notificationRequest); + } + + private NotificationInfo constructNotificationInfo(Alarm alarm, NotificationRule notificationRule) { + return NotificationInfo.builder() + .alarmId(alarm.getId()) + .alarmType(alarm.getType()) + .alarmOriginator(alarm.getOriginator()) + .alarmSeverity(alarm.getSeverity()) + .alarmStatus(alarm.getStatus()) + .build(); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSubscriptionService.java similarity index 61% rename from application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java rename to application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSubscriptionService.java index 99ede1fba9..8709f76179 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSubscriptionService.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.notification; import com.google.common.base.Strings; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.RuleEngineNotificationService; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.User; @@ -28,12 +29,12 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; +import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.notification.NotificationProcessingService; import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.gen.transport.TransportProtos; @@ -42,6 +43,8 @@ import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.telemetry.AbstractSubscriptionService; +import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; import java.util.HashSet; import java.util.Map; @@ -50,18 +53,18 @@ import java.util.UUID; @Service @Slf4j -public class DefaultNotificationProcessingService extends AbstractSubscriptionService implements NotificationProcessingService { +public class DefaultNotificationSubscriptionService extends AbstractSubscriptionService implements NotificationSubscriptionService, RuleEngineNotificationService { private final NotificationTargetService notificationTargetService; private final NotificationService notificationService; private final DbCallbackExecutorService dbCallbackExecutorService; private final NotificationsTopicService notificationsTopicService; - public DefaultNotificationProcessingService(TbClusterService clusterService, PartitionService partitionService, - NotificationTargetService notificationTargetService, - NotificationService notificationService, - DbCallbackExecutorService dbCallbackExecutorService, - NotificationsTopicService notificationsTopicService) { + public DefaultNotificationSubscriptionService(TbClusterService clusterService, PartitionService partitionService, + NotificationTargetService notificationTargetService, + NotificationService notificationService, + DbCallbackExecutorService dbCallbackExecutorService, + NotificationsTopicService notificationsTopicService) { super(clusterService, partitionService); this.notificationTargetService = notificationTargetService; this.notificationService = notificationService; @@ -72,13 +75,20 @@ public class DefaultNotificationProcessingService extends AbstractSubscriptionSe @Override public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { notificationRequest.setTenantId(tenantId); - NotificationRequest savedNotificationRequest = notificationService.createNotificationRequest(tenantId, notificationRequest); - if (notificationRequest.getAdditionalConfig() != null) { NotificationRequestConfig config = notificationRequest.getAdditionalConfig(); - // todo: delayed sending; check all delayed notification requests on start up, schedule send + if (config.getSendingDelayInMinutes() > 0) { + notificationRequest.setStatus(NotificationRequestStatus.SCHEDULED); + + // todo: delayed sending; check all delayed notification requests on start up, schedule send + } + } + if (notificationRequest.getStatus() == null) { + notificationRequest.setStatus(NotificationRequestStatus.PROCESSED); } + NotificationRequest savedNotificationRequest = notificationService.saveNotificationRequest(tenantId, notificationRequest); + DaoUtil.processBatches(pageLink -> { return notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), pageLink); }, 100, recipients -> { @@ -99,7 +109,7 @@ public class DefaultNotificationProcessingService extends AbstractSubscriptionSe @Override public void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId) { - boolean updated = notificationService.updateNotificationStatus(tenantId, recipientId, notificationId, NotificationStatus.READ); + boolean updated = notificationService.markNotificationAsRead(tenantId, recipientId, notificationId); if (updated) { Notification notification = notificationService.findNotificationById(tenantId, notificationId); onNotificationUpdate(tenantId, recipientId, notification, false); @@ -108,8 +118,22 @@ public class DefaultNotificationProcessingService extends AbstractSubscriptionSe @Override public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId) { - notificationService.deleteNotificationRequest(tenantId, notificationRequestId); - onNotificationRequestDeleted(tenantId, notificationRequestId); + notificationService.deleteNotificationRequestById(tenantId, notificationRequestId); + onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder() + .notificationRequestId(notificationRequestId) + .deleted(true) + .build()); + } + + @Override + public void updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { + notificationService.saveNotificationRequest(tenantId, notificationRequest); + notificationService.updateNotificationsInfosByRequestId(tenantId, notificationRequest.getId(), notificationRequest.getNotificationInfo()); + onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder() + .notificationRequestId(notificationRequest.getId()) + .notificationInfo(notificationRequest.getNotificationInfo()) + .deleted(false) + .build()); } private Notification createNotification(User recipient, NotificationRequest notificationRequest) { @@ -122,7 +146,7 @@ public class DefaultNotificationProcessingService extends AbstractSubscriptionSe .severity(notificationRequest.getNotificationSeverity()) .status(NotificationStatus.SENT) .build(); - return notificationService.createNotification(recipient.getTenantId(), notification); + return notificationService.saveNotification(recipient.getTenantId(), notification); } private String formatNotificationText(String template, User recipient) { @@ -135,20 +159,28 @@ public class DefaultNotificationProcessingService extends AbstractSubscriptionSe } private void onNotificationUpdate(TenantId tenantId, UserId recipientId, Notification notification, boolean isNew) { - forwardToSubscriptionManagerServiceOrSendToCore(tenantId, recipientId, subscriptionManagerService -> { - subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, notification, isNew, TbCallback.EMPTY); - }, () -> { - return TbSubscriptionUtils.notificationUpdateToProto(tenantId, recipientId, notification, isNew); + NotificationUpdate notificationUpdate = NotificationUpdate.builder() + .notification(notification) + .isNew(isNew) + .build(); + wsCallBackExecutor.submit(() -> { + forwardToSubscriptionManagerService(tenantId, recipientId, subscriptionManagerService -> { + subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, notificationUpdate, TbCallback.EMPTY); + }, () -> { + return TbSubscriptionUtils.notificationUpdateToProto(tenantId, recipientId, notificationUpdate); + }); }); } - public void onNotificationRequestDeleted(TenantId tenantId, NotificationRequestId notificationRequestId) { - TransportProtos.ToCoreMsg notificationRequestDeletedProto = TbSubscriptionUtils.notificationRequestDeletedToProto(tenantId, notificationRequestId); - Set coreServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_CORE)); - for (String serviceId : coreServices) { - TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, serviceId); - clusterService.pushMsgToCore(tpi, UUID.randomUUID(), notificationRequestDeletedProto, null); - } + private void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate update) { + wsCallBackExecutor.submit(() -> { + TransportProtos.ToCoreMsg notificationRequestDeletedProto = TbSubscriptionUtils.notificationRequestUpdateToProto(tenantId, update); + Set coreServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_CORE)); + for (String serviceId : coreServices) { + TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, serviceId); + clusterService.pushMsgToCore(tpi, UUID.randomUUID(), notificationRequestDeletedProto, null); + } + }); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationRuleProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationRuleProcessingService.java new file mode 100644 index 0000000000..4f3d0f7d9e --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationRuleProcessingService.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification; + +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.id.TenantId; + +public interface NotificationRuleProcessingService { + + ListenableFuture onAlarmCreatedOrUpdated(TenantId tenantId, Alarm alarm); + + ListenableFuture onAlarmAcknowledged(TenantId tenantId, Alarm alarm); + +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationSubscriptionService.java similarity index 86% rename from common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationProcessingService.java rename to application/src/main/java/org/thingsboard/server/service/notification/NotificationSubscriptionService.java index b25d4943b7..902ad39470 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationSubscriptionService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.notification; +package org.thingsboard.server.service.notification; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; @@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.NotificationRequest; -public interface NotificationProcessingService { +public interface NotificationSubscriptionService { NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); @@ -29,4 +29,6 @@ public interface NotificationProcessingService { void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId); + void updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); + } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 1ee7c3cd16..cf93ea7f1d 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.queue; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.scheduling.annotation.Scheduled; @@ -31,6 +32,7 @@ import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.TbActorMsg; @@ -80,6 +82,8 @@ import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.sync.vc.GitVersionControlQueueService; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; +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.NotificationsSubscriptionUpdate; import javax.annotation.PostConstruct; @@ -461,13 +465,17 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> subscriptionsByEntityId = new ConcurrentHashMap<>(); private final Map> subscriptionsByWsSessionId = new ConcurrentHashMap<>(); private final ConcurrentMap> partitionedSubscriptions = new ConcurrentHashMap<>(); @@ -309,6 +313,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene s -> alarm, false ); + notificationRuleProcessingService.onAlarmCreatedOrUpdated(tenantId, alarm); callback.onSuccess(); } @@ -330,13 +335,10 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene } @Override - public void onNotificationUpdate(TenantId tenantId, UserId recipientId, Notification notification, boolean isNew, TbCallback callback) { + public void onNotificationUpdate(TenantId tenantId, UserId recipientId, NotificationUpdate notificationUpdate, TbCallback callback) { Set subscriptions = subscriptionsByEntityId.get(recipientId); if (subscriptions != null) { - NotificationsSubscriptionUpdate subscriptionUpdate = NotificationsSubscriptionUpdate.builder() - .notification(notification) - .isNewNotification(isNew) - .build(); + NotificationsSubscriptionUpdate subscriptionUpdate = new NotificationsSubscriptionUpdate(notificationUpdate); subscriptions.stream() .filter(subscription -> subscription.getType() == TbSubscriptionType.NOTIFICATIONS || subscription.getType() == TbSubscriptionType.NOTIFICATIONS_COUNT) @@ -356,11 +358,8 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene } @Override - public void onNotificationRequestDeleted(TenantId tenantId, NotificationRequestId notificationRequestId, TbCallback callback) { - NotificationsSubscriptionUpdate subscriptionUpdate = NotificationsSubscriptionUpdate.builder() - .notificationRequestDeleted(true) - .notificationRequestId(notificationRequestId) - .build(); + public void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate notificationRequestUpdate, TbCallback callback) { + NotificationsSubscriptionUpdate subscriptionUpdate = new NotificationsSubscriptionUpdate(notificationRequestUpdate); subscriptionsByEntityId.entrySet().stream() .filter(subEntry -> subEntry.getKey().getEntityType() == EntityType.USER) .flatMap(subEntry -> subEntry.getValue().stream() diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java index 239f9c94f9..8079bdbcce 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java @@ -18,14 +18,14 @@ package org.thingsboard.server.service.subscription; import org.springframework.context.ApplicationListener; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; -import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; +import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; import java.util.List; @@ -49,8 +49,8 @@ public interface SubscriptionManagerService extends ApplicationListener toSubscriptionManagerService, - Supplier toCore) { + protected void forwardToSubscriptionManagerService(TenantId tenantId, EntityId entityId, + Consumer toSubscriptionManagerService, + Supplier toCore) { TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); if (currentPartitions.contains(tpi)) { if (subscriptionManagerService.isPresent()) { diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index 386904918f..0e455bf9d5 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -37,12 +37,9 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmDataQuery; -import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; -import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.alarm.AlarmOperationResult; import org.thingsboard.server.dao.alarm.AlarmService; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; @@ -164,13 +161,14 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService Alarm alarm = result.getAlarm(); TenantId tenantId = result.getAlarm().getTenantId(); for (EntityId entityId : result.getPropagatedEntitiesList()) { - forwardToSubscriptionManagerServiceOrSendToCore(tenantId, entityId, subscriptionManagerService -> { + forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> { subscriptionManagerService.onAlarmUpdate(tenantId, entityId, alarm, TbCallback.EMPTY); }, () -> { return TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm); }); } }); + // todo: handle notification rule } private void onAlarmDeleted(AlarmOperationResult result) { @@ -178,7 +176,7 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService Alarm alarm = result.getAlarm(); TenantId tenantId = result.getAlarm().getTenantId(); for (EntityId entityId : result.getPropagatedEntitiesList()) { - forwardToSubscriptionManagerServiceOrSendToCore(tenantId, entityId, subscriptionManagerService -> { + forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> { subscriptionManagerService.onAlarmDeleted(tenantId, entityId, alarm, TbCallback.EMPTY); }, () -> { return TbSubscriptionUtils.toAlarmDeletedProto(tenantId, entityId, alarm); diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index 567f5cb9ff..9521805337 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -366,7 +366,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer } private void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List attributes, boolean notifyDevice) { - forwardToSubscriptionManagerServiceOrSendToCore(tenantId, entityId, subscriptionManagerService -> { + forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> { subscriptionManagerService.onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice, TbCallback.EMPTY); }, () -> { return TbSubscriptionUtils.toAttributesUpdateProto(tenantId, entityId, scope, attributes); @@ -374,7 +374,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer } private void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List keys) { - forwardToSubscriptionManagerServiceOrSendToCore(tenantId, entityId, subscriptionManagerService -> { + forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> { subscriptionManagerService.onAttributesDelete(tenantId, entityId, scope, keys, TbCallback.EMPTY); }, () -> { return TbSubscriptionUtils.toAttributesDeleteProto(tenantId, entityId, scope, keys); @@ -382,7 +382,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer } private void onTimeSeriesUpdate(TenantId tenantId, EntityId entityId, List ts) { - forwardToSubscriptionManagerServiceOrSendToCore(tenantId, entityId, subscriptionManagerService -> { + forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> { subscriptionManagerService.onTimeSeriesUpdate(tenantId, entityId, ts, TbCallback.EMPTY); }, () -> { return TbSubscriptionUtils.toTimeseriesUpdateProto(tenantId, entityId, ts); @@ -390,7 +390,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer } private void onTimeSeriesDelete(TenantId tenantId, EntityId entityId, List keys, List ts) { - forwardToSubscriptionManagerServiceOrSendToCore(tenantId, entityId, subscriptionManagerService -> { + forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> { List updated = new ArrayList<>(); List deleted = new ArrayList<>(); diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java index ee0afd3c79..6e4ff69a88 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java @@ -21,16 +21,20 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.id.IdBased; import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.dao.notification.NotificationProcessingService; +import org.thingsboard.server.service.notification.NotificationSubscriptionService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd; +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.NotificationsSubscription; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.ws.WebSocketSessionRef; @@ -53,7 +57,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH private final NotificationService notificationService; private final TbLocalSubscriptionService localSubscriptionService; - private final NotificationProcessingService notificationProcessingService; + private final NotificationSubscriptionService notificationSubscriptionService; private final TbServiceInfoProvider serviceInfoProvider; @Autowired @Lazy private WebSocketService wsService; @@ -106,47 +110,83 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH subscription.getUnreadCounter().set(unreadCount); } + + /* Notifications subscription update handling */ private void handleNotificationsSubscriptionUpdate(NotificationsSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) { - if (subscriptionUpdate.getNotification() != null) { - Notification notification = subscriptionUpdate.getNotification(); - if (notification.getStatus() == NotificationStatus.READ) { - fetchUnreadNotifications(subscription); - sendUpdate(subscription.getSessionId(), subscription.createFullUpdate()); - } else { - subscription.getUnreadNotifications().put(notification.getUuidId(), notification); - if (subscriptionUpdate.isNewNotification()) { - subscription.getTotalUnreadCounter().incrementAndGet(); - Set beyondLimit = subscription.getUnreadNotifications().keySet().stream() - .skip(subscription.getLimit()) - .collect(Collectors.toSet()); - beyondLimit.forEach(notificationId -> subscription.getUnreadNotifications().remove(notificationId)); - } - sendUpdate(subscription.getSessionId(), subscription.createPartialUpdate(notification)); + if (subscriptionUpdate.getNotificationUpdate() != null) { + handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate()); + } else if (subscriptionUpdate.getNotificationRequestUpdate() != null) { + handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate()); + } + } + + private void handleNotificationUpdate(NotificationsSubscription subscription, NotificationUpdate update) { + Notification notification = update.getNotification(); + if (notification.getStatus() == NotificationStatus.READ) { + fetchUnreadNotifications(subscription); + sendUpdate(subscription.getSessionId(), subscription.createFullUpdate()); + } else { + subscription.getUnreadNotifications().put(notification.getUuidId(), notification); + if (update.isNew()) { + subscription.getTotalUnreadCounter().incrementAndGet(); + Set beyondLimit = subscription.getUnreadNotifications().keySet().stream() + .skip(subscription.getLimit()) + .collect(Collectors.toSet()); + beyondLimit.forEach(notificationId -> subscription.getUnreadNotifications().remove(notificationId)); } - } else if (subscriptionUpdate.isNotificationRequestDeleted()) { + sendUpdate(subscription.getSessionId(), subscription.createPartialUpdate(notification)); + } + } + + private void handleNotificationRequestUpdate(NotificationsSubscription subscription, NotificationRequestUpdate update) { + NotificationRequestId notificationRequestId = update.getNotificationRequestId(); + if (update.isDeleted()) { if (subscription.getUnreadNotifications().values().stream() - .anyMatch(notification -> notification.getRequestId().equals(subscriptionUpdate.getNotificationRequestId()))) { + .anyMatch(notification -> notification.getRequestId().equals(notificationRequestId))) { fetchUnreadNotifications(subscription); sendUpdate(subscription.getSessionId(), subscription.createFullUpdate()); } + } else { + NotificationInfo notificationInfo = update.getNotificationInfo(); + subscription.getUnreadNotifications().values().stream() + .filter(notification -> notification.getRequestId().equals(notificationRequestId)) + .forEach(notification -> { + notification.setInfo(notificationInfo); + sendUpdate(subscription.getSessionId(), subscription.createPartialUpdate(notification)); + }); } } + + /* Notifications count subscription update handling */ private void handleNotificationsCountSubscriptionUpdate(NotificationsCountSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) { - if (subscriptionUpdate.getNotification() != null) { - Notification notification = subscriptionUpdate.getNotification(); - if (subscriptionUpdate.isNewNotification()) { - subscription.getUnreadCounter().incrementAndGet(); - } else if (notification.getStatus() == NotificationStatus.READ) { - // for now this can only happen when user marks notification as read - subscription.getUnreadCounter().decrementAndGet(); - } - } else if (subscriptionUpdate.isNotificationRequestDeleted()) { - fetchUnreadNotificationsCount(subscription); + if (subscriptionUpdate.getNotificationUpdate() != null) { + handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate()); + } else if (subscriptionUpdate.getNotificationRequestUpdate() != null) { + handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate()); + } + sendUpdate(subscription.getSessionId(), subscription.createUpdate()); + } + + private void handleNotificationUpdate(NotificationsCountSubscription subscription, NotificationUpdate update) { + Notification notification = update.getNotification(); + if (update.isNew()) { + subscription.getUnreadCounter().incrementAndGet(); + } else if (notification.getStatus() == NotificationStatus.READ) { + // for now this can only happen when user marks notification as read + subscription.getUnreadCounter().decrementAndGet(); } sendUpdate(subscription.getSessionId(), subscription.createUpdate()); } + private void handleNotificationRequestUpdate(NotificationsCountSubscription subscription, NotificationRequestUpdate update) { + if (update.isDeleted()) { + fetchUnreadNotificationsCount(subscription); + sendUpdate(subscription.getSessionId(), subscription.createUpdate()); + } + } + + private void sendUpdate(String sessionId, CmdUpdate update) { wsService.sendWsMsg(sessionId, update); } @@ -155,7 +195,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH @Override public void handleMarkAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationAsReadCmd cmd) { NotificationId notificationId = new NotificationId(cmd.getNotificationId()); - notificationProcessingService.markNotificationAsRead(sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), notificationId); + notificationSubscriptionService.markNotificationAsRead(sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), notificationId); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationRequestUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationRequestUpdate.java new file mode 100644 index 0000000000..36c3b2a441 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationRequestUpdate.java @@ -0,0 +1,33 @@ +/** + * 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.ws.notification.sub; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.notification.NotificationInfo; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class NotificationRequestUpdate { + private NotificationRequestId notificationRequestId; + private NotificationInfo notificationInfo; + private boolean deleted; +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java new file mode 100644 index 0000000000..0e7db3e8cf --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.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.ws.notification.sub; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.notification.Notification; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class NotificationUpdate { + private Notification notification; + private boolean isNew; +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionUpdate.java index 4ed7ca7341..cedd6041f7 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionUpdate.java @@ -15,19 +15,22 @@ */ package org.thingsboard.server.service.ws.notification.sub; -import lombok.Builder; import lombok.Data; -import org.thingsboard.server.common.data.id.NotificationRequestId; -import org.thingsboard.server.common.data.notification.Notification; @Data -@Builder public class NotificationsSubscriptionUpdate { - private final Notification notification; - private final boolean isNewNotification; + private final NotificationUpdate notificationUpdate; + private final NotificationRequestUpdate notificationRequestUpdate; - private final boolean notificationRequestDeleted; - private final NotificationRequestId notificationRequestId; + public NotificationsSubscriptionUpdate(NotificationUpdate notificationUpdate) { + this.notificationUpdate = notificationUpdate; + this.notificationRequestUpdate = null; + } + + public NotificationsSubscriptionUpdate(NotificationRequestUpdate notificationRequestUpdate) { + this.notificationUpdate = null; + this.notificationRequestUpdate = notificationRequestUpdate; + } } diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index c1a6dd342e..74223de8c7 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -576,8 +576,8 @@ message TbAlarmSubscriptionUpdateProto { message NotificationsSubscriptionUpdateProto { string sessionId = 1; int32 subscriptionId = 2; - string notification = 3; - bool isNewNotification = 4; + string notificationUpdate = 3; + string notificationRequestUpdate = 4; } message NotificationUpdateProto { @@ -585,15 +585,13 @@ message NotificationUpdateProto { int64 tenantIdLSB = 2; int64 recipientIdMSB = 3; int64 recipientIdLSB = 4; - string notification = 5; - bool isNew = 6; + string update = 5; } -message NotificationRequestDeleteProto { +message NotificationRequestUpdateProto { int64 tenantIdMSB = 1; int64 tenantIdLSB = 2; - int64 notificationRequestIdMSB = 3; - int64 notificationRequestIdLSB = 4; + string update = 6; } message TbAttributeUpdateProto { @@ -700,7 +698,7 @@ message SubscriptionMgrMsgProto { NotificationsSubscriptionProto notificationsSub = 11; NotificationsCountSubscriptionProto notificationsCountSub = 12; NotificationUpdateProto notificationUpdate = 13; - NotificationRequestDeleteProto notificationRequestDelete = 14; + NotificationRequestUpdateProto notificationRequestUpdate = 14; } message LocalSubscriptionServiceMsgProto { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.java new file mode 100644 index 0000000000..291688a422 --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.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.notification; + +import org.thingsboard.server.common.data.id.NotificationRuleId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.rule.NotificationRule; + +public interface NotificationRuleService { + + NotificationRule findNotificationRuleById(TenantId tenantId, NotificationRuleId notificationRuleId); + +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java index a4bebfd135..6b7f6267db 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java @@ -15,37 +15,45 @@ */ package org.thingsboard.server.dao.notification; +import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationRequest; -import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import java.util.List; + public interface NotificationService { - NotificationRequest createNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); + NotificationRequest saveNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); NotificationRequest findNotificationRequestById(TenantId tenantId, NotificationRequestId id); - PageData findNotificationRequestsByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); + PageData findNotificationRequestsByTenantId(TenantId tenantId, PageLink pageLink); + + List findNotificationRequestsByRuleIdAndAlarmId(TenantId tenantId, NotificationRuleId ruleId, AlarmId alarmId); - void deleteNotificationRequest(TenantId tenantId, NotificationRequestId id); + void deleteNotificationRequestById(TenantId tenantId, NotificationRequestId id); - Notification createNotification(TenantId tenantId, Notification notification); + Notification saveNotification(TenantId tenantId, Notification notification); Notification findNotificationById(TenantId tenantId, NotificationId notificationId); - boolean updateNotificationStatus(TenantId tenantId, UserId userId, NotificationId notificationId, NotificationStatus status); + boolean markNotificationAsRead(TenantId tenantId, UserId userId, NotificationId notificationId); - PageData findNotificationsByUserIdAndReadStatusAndPageLink(TenantId tenantId, UserId userId, boolean unreadOnly, PageLink pageLink); + PageData findNotificationsByUserIdAndReadStatus(TenantId tenantId, UserId userId, boolean unreadOnly, PageLink pageLink); PageData findLatestUnreadNotificationsByUserId(TenantId tenantId, UserId userId, int limit); int countUnreadNotificationsByUserId(TenantId tenantId, UserId userId); + int updateNotificationsInfosByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, NotificationInfo notificationInfo); + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java index f13c84bc82..ba7d30fb85 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.Length; @@ -78,6 +79,7 @@ public class Alarm extends BaseData implements HasName, HasTenantId, Ha "By default, 'propagateRelationTypes' array is empty which means that the alarm will be propagated based on any relation type to parent entities. " + "This parameter should be used only in case when 'propagate' parameter is set to true, otherwise, 'propagateRelationTypes' array will be ignored.") private List propagateRelationTypes; + private NotificationRuleId notificationRuleId; public Alarm() { super(); @@ -105,6 +107,7 @@ public class Alarm extends BaseData implements HasName, HasTenantId, Ha this.propagateToOwner = alarm.isPropagateToOwner(); this.propagateToTenant = alarm.isPropagateToTenant(); this.propagateRelationTypes = alarm.getPropagateRelationTypes(); + this.notificationRuleId = alarm.getNotificationRuleId(); } @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java index 71e7b5f415..33e5cf1391 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java @@ -19,6 +19,7 @@ import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; @@ -59,4 +60,6 @@ public class DeviceProfileAlarm implements Serializable { "This parameter should be used only in case when 'propagate' parameter is set to true, otherwise, 'propagateRelationTypes' array will be ignored.") private List propagateRelationTypes; + private NotificationRuleId notificationRuleId; + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRuleId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRuleId.java new file mode 100644 index 0000000000..2b963c5b88 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRuleId.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; + +import java.util.UUID; + +public class NotificationRuleId extends UUIDBased { + + @JsonCreator + public NotificationRuleId(@JsonProperty("id") UUID id) { + super(id); + } + +// @Override +// public EntityType getEntityType() { +// return EntityType.NOTIFICATION_TARGET; +// } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationInfo.java index 4831e056cd..298998f8d7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationInfo.java @@ -15,14 +15,22 @@ */ package org.thingsboard.server.common.data.notification; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.validation.NoXss; @Data +@AllArgsConstructor +@NoArgsConstructor +@Builder //@JsonIgnoreProperties(ignoreUnknown = true) //@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "notificationType", visible = true, defaultImpl = NotificationInfo.class) //@JsonSubTypes({ @@ -31,7 +39,12 @@ import org.thingsboard.server.common.data.validation.NoXss; public class NotificationInfo { @NoXss private String description; - - private ObjectNode alarmDetails; // move to child class private DashboardId dashboardId; + + private AlarmId alarmId; + private String alarmType; + private EntityId alarmOriginator; + private AlarmSeverity alarmSeverity; + private AlarmStatus alarmStatus; + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java index dd800ef3fc..af90eab7ee 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.common.data.notification; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -23,7 +25,9 @@ import lombok.NoArgsConstructor; 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.AlarmId; import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.NoXss; @@ -51,6 +55,13 @@ public class NotificationRequest extends BaseData impleme private NotificationInfo notificationInfo; private NotificationSeverity notificationSeverity; private NotificationRequestConfig additionalConfig; + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private NotificationRequestStatus status; + + @JsonIgnore + private NotificationRuleId ruleId; // maybe move to child class + @JsonIgnore + private AlarmId alarmId; public static final String GENERAL_NOTIFICATION_REASON = "General"; public static final String ALARM_NOTIFICATION_REASON = "Alarm"; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java index 8f65427f21..fd01c91255 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java @@ -19,5 +19,5 @@ import lombok.Data; @Data public class NotificationRequestConfig { - private Long sendingDelayMs; + private int sendingDelayInMinutes; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStatus.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStatus.java new file mode 100644 index 0000000000..1c3eede15e --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStatus.java @@ -0,0 +1,21 @@ +/** + * 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; + +public enum NotificationRequestStatus { + PROCESSED, + SCHEDULED +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NonConfirmedNotificationEscalation.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NonConfirmedNotificationEscalation.java index befc666281..1174cedb06 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NonConfirmedNotificationEscalation.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NonConfirmedNotificationEscalation.java @@ -15,9 +15,18 @@ */ package org.thingsboard.server.common.data.notification.rule; -import java.util.UUID; +import lombok.Data; +import org.thingsboard.server.common.data.id.NotificationTargetId; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; + +@Data public class NonConfirmedNotificationEscalation { - private long delayMs; // delay since initial notification request // if no one from previous escalation item has read the notification, send notifications after this time to other recipients - private UUID notificationTargetId; + + @Min(1) + private int delayInMinutes; // delay since initial notification request // if no one from previous escalation item has read the notification, send notifications after this time to other recipients + @NotNull + private NotificationTargetId notificationTargetId; + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java index 340a49c54d..4d2a8ed184 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java @@ -16,23 +16,36 @@ package org.thingsboard.server.common.data.notification.rule; import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.id.NotificationRuleId; +import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.TenantId; +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; import java.util.List; import java.util.Map; +import java.util.TreeMap; import java.util.UUID; @Data -public class NotificationRule { +@EqualsAndHashCode(callSuper = true) +public class NotificationRule extends BaseData implements HasTenantId, HasName { - // we may choose it in the alarm rule config, or maybe it's better to configure evrth in the triggers? - - private UUID id; // NotificationRuleId id; + @NotNull + private TenantId tenantId; + @NotBlank private String name; - private Map triggers; // or maybe bad idea - // Map - concrete alarmRule or alarm rule search (e.g. alarm rule of device profiles of particular transport type with certain severity) - // triggerConfiguration (??) - alarm filter: severity, specific device profile, alarm rule - - private UUID initialNotificationTargetId; + @NotBlank + private String notificationTextTemplate; + @NotNull + private NotificationTargetId initialNotificationTargetId; + @NotNull + @Valid private List escalations; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 2ae524f1e2..27b17164ea 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -295,6 +295,7 @@ public class ModelConstants { public static final String ALARM_PROPAGATE_TO_OWNER_PROPERTY = "propagate_to_owner"; public static final String ALARM_PROPAGATE_TO_TENANT_PROPERTY = "propagate_to_tenant"; public static final String ALARM_PROPAGATE_RELATION_TYPES = "propagate_relation_types"; + public static final String ALARM_NOTIFICATION_RULE_ID = "notification_rule_id"; public static final String ALARM_BY_ID_VIEW_NAME = "alarm_by_id"; @@ -668,6 +669,12 @@ public class ModelConstants { public static final String NOTIFICATION_REQUEST_NOTIFICATION_INFO_PROPERTY = "notification_info"; public static final String NOTIFICATION_REQUEST_NOTIFICATION_SEVERITY_PROPERTY = "notification_severity"; public static final String NOTIFICATION_REQUEST_ADDITIONAL_CONFIG_PROPERTY = "additional_config"; + public static final String NOTIFICATION_REQUEST_STATUS_PROPERTY = "status"; + public static final String NOTIFICATION_REQUEST_RULE_ID_PROPERTY = "rule_id"; + public static final String NOTIFICATION_REQUEST_ALARM_ID_PROPERTY = "alarm_id"; + + public static final String NOTIFICATION_RULE_TABLE_NAME = "notification_rule"; + // ... 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}; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java index 56adfae6de..8bdb90d1b4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseSqlEntity; @@ -47,6 +48,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPE import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CLEAR_TS_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CUSTOMER_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_END_TS_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_NOTIFICATION_RULE_ID; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_PROPERTY; @@ -116,6 +118,9 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity @Column(name = ALARM_PROPAGATE_RELATION_TYPES) private String propagateRelationTypes; + @Column(name = ALARM_NOTIFICATION_RULE_ID) + private UUID notificationRuleId; + public AbstractAlarmEntity() { super(); } @@ -150,6 +155,7 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity } else { this.propagateRelationTypes = null; } + this.notificationRuleId = getUuid(alarm.getNotificationRuleId()); } public AbstractAlarmEntity(AlarmEntity alarmEntity) { @@ -172,6 +178,7 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity this.clearTs = alarmEntity.getClearTs(); this.details = alarmEntity.getDetails(); this.propagateRelationTypes = alarmEntity.getPropagateRelationTypes(); + this.notificationRuleId = alarmEntity.getNotificationRuleId(); } protected Alarm toAlarm() { @@ -200,6 +207,7 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity } else { alarm.setPropagateRelationTypes(Collections.emptyList()); } + alarm.setNotificationRuleId(createId(notificationRuleId, NotificationRuleId::new)); return alarm; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java index 65e1fe8214..cd0bbd435a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java @@ -21,13 +21,16 @@ import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; +import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.NotificationSeverity; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; @@ -71,6 +74,16 @@ public class NotificationRequestEntity extends BaseSqlEntity { + + @Column(name = ModelConstants.TENANT_ID_PROPERTY, nullable = false) + private UUID tenantId; + + @Column(name = ModelConstants.NAME_PROPERTY, nullable = false) + private String name; + + + public NotificationRuleEntity() {} + + public NotificationRuleEntity(NotificationRule notificationRule) { + setId(notificationRule.getUuidId()); + setCreatedTime(notificationRule.getCreatedTime()); + setTenantId(getUuid(notificationRule.getTenantId())); + setName(notificationRule.getName()); + } + + @Override + public NotificationRule toData() { + NotificationRule notificationRule = new NotificationRule(); + notificationRule.setId(new NotificationRuleId(id)); + notificationRule.setCreatedTime(createdTime); + notificationRule.setTenantId(createId(tenantId, TenantId::fromUUID)); + notificationRule.setName(name); + return notificationRule; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java new file mode 100644 index 0000000000..7c9e404b3b --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.notification; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.id.NotificationRuleId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.rule.NotificationRule; + +@Service +@RequiredArgsConstructor +public class DefaultNotificationRuleService implements NotificationRuleService { + + private final NotificationRuleDao notificationRuleDao; + + @Override + public NotificationRule findNotificationRuleById(TenantId tenantId, NotificationRuleId notificationRuleId) { + return notificationRuleDao.findById(tenantId, notificationRuleId.getId()); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java index c15480c747..a3ef857233 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java @@ -19,22 +19,25 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationSeverity; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; -import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.sql.query.EntityKeyMapping; +import java.util.List; + @Service @Slf4j @RequiredArgsConstructor @@ -46,7 +49,7 @@ public class DefaultNotificationService implements NotificationService { private final NotificationRequestValidator notificationRequestValidator = new NotificationRequestValidator(); @Override - public NotificationRequest createNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { + public NotificationRequest saveNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { if (StringUtils.isBlank(notificationRequest.getNotificationReason())) { notificationRequest.setNotificationReason(NotificationRequest.GENERAL_NOTIFICATION_REASON); } @@ -63,21 +66,23 @@ public class DefaultNotificationService implements NotificationService { } @Override - public PageData findNotificationRequestsByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) { + public PageData findNotificationRequestsByTenantId(TenantId tenantId, PageLink pageLink) { return notificationRequestDao.findByTenantIdAndPageLink(tenantId, pageLink); } + @Override + public List findNotificationRequestsByRuleIdAndAlarmId(TenantId tenantId, NotificationRuleId ruleId, AlarmId alarmId) { + return notificationRequestDao.findByRuleIdAndAlarmId(tenantId, ruleId, alarmId); + } + // ON DELETE CASCADE is used: notifications for request are deleted as well @Override - public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId id) { + public void deleteNotificationRequestById(TenantId tenantId, NotificationRequestId id) { notificationRequestDao.removeById(tenantId, id.getId()); } @Override - public Notification createNotification(TenantId tenantId, Notification notification) { - if (notification.getId() != null) { - throw new DataValidationException("Notification cannot be updated"); // tmp ? - } + public Notification saveNotification(TenantId tenantId, Notification notification) { return notificationDao.save(tenantId, notification); } @@ -86,14 +91,13 @@ public class DefaultNotificationService implements NotificationService { return notificationDao.findById(tenantId, notificationId.getId()); } - @Transactional @Override - public boolean updateNotificationStatus(TenantId tenantId, UserId userId, NotificationId notificationId, NotificationStatus status) { - return notificationDao.updateStatusByIdAndUserId(tenantId, userId, notificationId, status); + public boolean markNotificationAsRead(TenantId tenantId, UserId userId, NotificationId notificationId) { + return notificationDao.updateStatusByIdAndUserId(tenantId, userId, notificationId, NotificationStatus.READ); } @Override - public PageData findNotificationsByUserIdAndReadStatusAndPageLink(TenantId tenantId, UserId userId, boolean unreadOnly, PageLink pageLink) { + public PageData findNotificationsByUserIdAndReadStatus(TenantId tenantId, UserId userId, boolean unreadOnly, PageLink pageLink) { if (unreadOnly) { return notificationDao.findUnreadByUserIdAndPageLink(tenantId, userId, pageLink); } else { @@ -105,7 +109,7 @@ public class DefaultNotificationService implements NotificationService { public PageData findLatestUnreadNotificationsByUserId(TenantId tenantId, UserId userId, int limit) { SortOrder sortOrder = new SortOrder(EntityKeyMapping.CREATED_TIME, SortOrder.Direction.DESC); PageLink pageLink = new PageLink(limit, 0, null, sortOrder); - return findNotificationsByUserIdAndReadStatusAndPageLink(tenantId, userId, true, pageLink); + return findNotificationsByUserIdAndReadStatus(tenantId, userId, true, pageLink); } @Override @@ -113,13 +117,15 @@ public class DefaultNotificationService implements NotificationService { return notificationDao.countUnreadByUserId(tenantId, userId); } + @Override + public int updateNotificationsInfosByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, NotificationInfo notificationInfo) { + return notificationDao.updateInfosByRequestId(tenantId, notificationRequestId, notificationInfo); + } + private static class NotificationRequestValidator extends DataValidator { @Override protected void validateDataImpl(TenantId tenantId, NotificationRequest notificationRequest) { - if (notificationRequest.getId() != null) { - throw new DataValidationException("Notification request cannot be changed once created"); - } } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java index c16df8819a..dcfba4d704 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java @@ -16,9 +16,11 @@ package org.thingsboard.server.dao.notification; import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -34,4 +36,8 @@ public interface NotificationDao extends Dao { int countUnreadByUserId(TenantId tenantId, UserId userId); + PageData findByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, PageLink pageLink); + + int updateInfosByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, NotificationInfo notificationInfo); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java index d518a4e245..1848e826e0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java @@ -15,14 +15,20 @@ */ package org.thingsboard.server.dao.notification; +import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; +import java.util.List; + public interface NotificationRequestDao extends Dao { PageData findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); + List findByRuleIdAndAlarmId(TenantId tenantId, NotificationRuleId ruleId, AlarmId alarmId); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java new file mode 100644 index 0000000000..3a7cbb777b --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.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.rule.NotificationRule; +import org.thingsboard.server.dao.Dao; + +public interface NotificationRuleDao extends Dao { +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java index 2611c32c90..cd98941011 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java @@ -20,10 +20,13 @@ import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -82,6 +85,16 @@ public class JpaNotificationDao extends JpaAbstractDao findByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, PageLink pageLink) { + return DaoUtil.toPageData(notificationRepository.findByRequestId(notificationRequestId.getId(), DaoUtil.toPageable(pageLink))); + } + + @Override + public int updateInfosByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, NotificationInfo notificationInfo) { + return notificationRepository.updateInfosByRequestId(notificationRequestId.getId(), JacksonUtil.valueToTree(notificationInfo)); + } + @Override protected Class getEntityClass() { return NotificationEntity.class; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java index 099218abcc..5e1ca5d3f7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java @@ -19,6 +19,8 @@ import com.google.common.base.Strings; import lombok.RequiredArgsConstructor; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.page.PageData; @@ -29,6 +31,7 @@ import org.thingsboard.server.dao.notification.NotificationRequestDao; import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.util.SqlDao; +import java.util.List; import java.util.UUID; @Component @@ -44,6 +47,11 @@ public class JpaNotificationRequestDao extends JpaAbstractDao findByRuleIdAndAlarmId(TenantId tenantId, NotificationRuleId ruleId, AlarmId alarmId) { + return DaoUtil.convertDataList(notificationRequestRepository.findAllByRuleIdAndAlarmId(ruleId.getId(), alarmId.getId())); + } + @Override protected Class getEntityClass() { return NotificationRequestEntity.class; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java new file mode 100644 index 0000000000..365a439bd5 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.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.rule.NotificationRule; +import org.thingsboard.server.dao.model.sql.NotificationRuleEntity; +import org.thingsboard.server.dao.notification.NotificationRuleDao; +import org.thingsboard.server.dao.sql.JpaAbstractDao; +import org.thingsboard.server.dao.util.SqlDao; + +import java.util.UUID; + +@Component +@SqlDao +@RequiredArgsConstructor +public class JpaNotificationRuleDao extends JpaAbstractDao implements NotificationRuleDao { + + private final NotificationRuleRepository notificationRuleRepository; + + @Override + protected Class getEntityClass() { + return NotificationRuleEntity.class; + } + + @Override + protected JpaRepository getRepository() { + return notificationRuleRepository; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java index e264689ce7..3f46a3dfa9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.sql.notification; +import com.fasterxml.jackson.databind.JsonNode; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -45,6 +46,11 @@ public interface NotificationRepository extends JpaRepository findByRequestId(UUID requestId, Pageable pageable); + + @Modifying + @Transactional + @Query("UPDATE NotificationEntity n SET n.info = :info WHERE n.requestId = :requestId") + int updateInfosByRequestId(@Param("requestId") UUID requestId, @Param("info") JsonNode info); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java index 1303c99034..4ff69f8797 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java @@ -23,6 +23,7 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import org.thingsboard.server.dao.model.sql.NotificationRequestEntity; +import java.util.List; import java.util.UUID; @Repository @@ -34,4 +35,6 @@ public interface NotificationRequestRepository extends JpaRepository findByTenantIdAndSearchText(@Param("tenantId") UUID tenantId, @Param("searchText") String searchText, Pageable pageable); + List findAllByRuleIdAndAlarmId(UUID ruleId, UUID alarmId); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java new file mode 100644 index 0000000000..a991407ab2 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.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.NotificationRuleEntity; + +import java.util.UUID; + +@Repository +public interface NotificationRuleRepository extends JpaRepository { +} diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql index 8f43b1ac4e..019a8b1771 100644 --- a/dao/src/main/resources/sql/schema-entities-idx.sql +++ b/dao/src/main/resources/sql/schema-entities-idx.sql @@ -80,6 +80,6 @@ CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id_and_created_time O CREATE INDEX IF NOT EXISTS idx_notification_id ON notification(id); -CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_and_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_recipient_id_and_status_and_created_time ON notification(recipient_id, status, created_time DESC); +CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_and_created_time ON notification(recipient_id, created_time DESC); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index cae28a049a..1018abe42a 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -795,7 +795,10 @@ CREATE TABLE IF NOT EXISTS notification_request ( text_template VARCHAR NOT NULL, notification_info VARCHAR(1000), notification_severity VARCHAR(32), - additional_config VARCHAR(1000) + additional_config VARCHAR(1000), + status VARCHAR(32), + rule_id UUID NULL CONSTRAINT fk_notification_request_rule_id REFERENCES notification_rule(id), + alarm_id UUID ); CREATE TABLE IF NOT EXISTS notification ( diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineNotificationService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineNotificationService.java new file mode 100644 index 0000000000..e7b87cf25d --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineNotificationService.java @@ -0,0 +1,25 @@ +/** + * 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.rule.engine.api; + +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.NotificationRequest; + +public interface RuleEngineNotificationService { + + NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 6f6ebcd233..0978296ba5 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -53,7 +53,6 @@ import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.nosql.CassandraStatementTask; import org.thingsboard.server.dao.nosql.TbResultSetFuture; -import org.thingsboard.server.dao.notification.NotificationProcessingService; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; @@ -267,7 +266,7 @@ public interface TbContext { SmsSenderFactory getSmsSenderFactory(); - NotificationProcessingService getNotificationProcessingService(); + RuleEngineNotificationService getNotificationService(); ScriptEngine createJsScriptEngine(String script, String... argNames); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java index 73ec3383fe..06fc0b110d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java @@ -60,7 +60,7 @@ public class TbNotificationNode implements TbNode { .notificationSeverity(config.getNotificationSeverity()) .build(); withCallback(ctx.getDbCallbackExecutor().executeAsync(() -> { - return ctx.getNotificationProcessingService().processNotificationRequest(ctx.getTenantId(), notificationRequest); + return ctx.getNotificationService().processNotificationRequest(ctx.getTenantId(), notificationRequest); }), r -> { TbMsgMetaData msgMetaData = msg.getMetaData().copy(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 1f90b4e6c1..29bd7d6177 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -272,6 +272,7 @@ class AlarmState { if (alarmDefinition.getPropagateRelationTypes() != null) { currentAlarm.setPropagateRelationTypes(alarmDefinition.getPropagateRelationTypes()); } + currentAlarm.setNotificationRuleId(alarmDefinition.getNotificationRuleId()); currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm); boolean updated = currentAlarm.getStartTs() != currentAlarm.getEndTs(); return new TbAlarmResult(!updated, updated, false, false, currentAlarm); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java index 5ab5adc7fc..1ef06547ba 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java @@ -191,6 +191,7 @@ class DeviceState { AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm), dynamicPredicateValueCtx)); alarmState.processAckAlarm(alarmNf); + // todo: process notification rule } ctx.tellSuccess(msg); } From a545ace4ecc4bbb98af41670c40af9911b96f628 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Sun, 6 Nov 2022 18:51:49 +0200 Subject: [PATCH 008/111] Add logging for notification services --- .../DefaultNotificationSubscriptionService.java | 14 +++++++++++--- .../DefaultNotificationCommandsHandler.java | 12 +++++++++++- .../cmd/UnreadNotificationsCountUpdate.java | 2 ++ .../cmd/UnreadNotificationsUpdate.java | 3 ++- dao/src/main/resources/sql/schema-entities.sql | 4 ++++ 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSubscriptionService.java index 8709f76179..74fb400fb2 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSubscriptionService.java @@ -74,6 +74,7 @@ public class DefaultNotificationSubscriptionService extends AbstractSubscription @Override public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { + log.info("Processing notification request (tenant id: {}, notification target id: {})", tenantId, notificationRequest.getTargetId()); notificationRequest.setTenantId(tenantId); if (notificationRequest.getAdditionalConfig() != null) { NotificationRequestConfig config = notificationRequest.getAdditionalConfig(); @@ -93,6 +94,7 @@ public class DefaultNotificationSubscriptionService extends AbstractSubscription return notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), pageLink); }, 100, recipients -> { dbCallbackExecutorService.submit(() -> { + log.debug("Sending notifications for request {} to recipients batch", savedNotificationRequest.getId()); for (User recipient : recipients) { try { Notification notification = createNotification(recipient, savedNotificationRequest); @@ -111,6 +113,7 @@ public class DefaultNotificationSubscriptionService extends AbstractSubscription public void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId) { boolean updated = notificationService.markNotificationAsRead(tenantId, recipientId, notificationId); if (updated) { + log.debug("Marking notification {} as read (recipient id: {}, tenant id: {})", notificationId, recipientId, tenantId); Notification notification = notificationService.findNotificationById(tenantId, notificationId); onNotificationUpdate(tenantId, recipientId, notification, false); } @@ -118,6 +121,7 @@ public class DefaultNotificationSubscriptionService extends AbstractSubscription @Override public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId) { + log.debug("Deleting notification request {}", notificationRequestId); notificationService.deleteNotificationRequestById(tenantId, notificationRequestId); onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder() .notificationRequestId(notificationRequestId) @@ -127,6 +131,7 @@ public class DefaultNotificationSubscriptionService extends AbstractSubscription @Override public void updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { + log.debug("Updating notification request {}", notificationRequest.getId()); notificationService.saveNotificationRequest(tenantId, notificationRequest); notificationService.updateNotificationsInfosByRequestId(tenantId, notificationRequest.getId(), notificationRequest.getNotificationInfo()); onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder() @@ -137,6 +142,7 @@ public class DefaultNotificationSubscriptionService extends AbstractSubscription } private Notification createNotification(User recipient, NotificationRequest notificationRequest) { + log.trace("Creating notification for recipient {} (notification request id: {})", recipient.getId(), notificationRequest.getId()); Notification notification = Notification.builder() .requestId(notificationRequest.getId()) .recipientId(recipient.getId()) @@ -159,20 +165,22 @@ public class DefaultNotificationSubscriptionService extends AbstractSubscription } private void onNotificationUpdate(TenantId tenantId, UserId recipientId, Notification notification, boolean isNew) { - NotificationUpdate notificationUpdate = NotificationUpdate.builder() + NotificationUpdate update = NotificationUpdate.builder() .notification(notification) .isNew(isNew) .build(); + log.trace("Submitting notification update for recipient {}: {}", recipientId, update); wsCallBackExecutor.submit(() -> { forwardToSubscriptionManagerService(tenantId, recipientId, subscriptionManagerService -> { - subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, notificationUpdate, TbCallback.EMPTY); + subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, update, TbCallback.EMPTY); }, () -> { - return TbSubscriptionUtils.notificationUpdateToProto(tenantId, recipientId, notificationUpdate); + return TbSubscriptionUtils.notificationUpdateToProto(tenantId, recipientId, update); }); }); } private void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate update) { + log.trace("Submitting notification request update: {}", update); wsCallBackExecutor.submit(() -> { TransportProtos.ToCoreMsg notificationRequestDeletedProto = TbSubscriptionUtils.notificationRequestUpdateToProto(tenantId, update); Set coreServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_CORE)); diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java index 6e4ff69a88..6af03caeb5 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.ws.notification; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -53,6 +54,7 @@ import java.util.stream.Collectors; @Service @TbCoreComponent @RequiredArgsConstructor +@Slf4j public class DefaultNotificationCommandsHandler implements NotificationCommandsHandler { private final NotificationService notificationService; @@ -64,6 +66,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH @Override public void handleUnreadNotificationsSubCmd(WebSocketSessionRef sessionRef, NotificationsSubCmd cmd) { + log.debug("[{}] Handling unread notifications subscription cmd (cmdId: {})", sessionRef.getSessionId(), cmd.getCmdId()); SecurityUser user = sessionRef.getSecurityCtx(); NotificationsSubscription subscription = NotificationsSubscription.builder() .serviceId(serviceInfoProvider.getServiceId()) @@ -82,6 +85,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH @Override public void handleUnreadNotificationsCountSubCmd(WebSocketSessionRef sessionRef, NotificationsCountSubCmd cmd) { + log.debug("[{}] Handling unread notifications count subscription cmd (cmdId: {})", sessionRef.getSessionId(), cmd.getCmdId()); SecurityUser user = sessionRef.getSecurityCtx(); NotificationsCountSubscription subscription = NotificationsCountSubscription.builder() .serviceId(serviceInfoProvider.getServiceId()) @@ -98,6 +102,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH } private void fetchUnreadNotifications(NotificationsSubscription subscription) { + log.trace("[{}, subId: {}] Fetching unread notifications from DB", subscription.getSessionId(), subscription.getSubscriptionId()); PageData notifications = notificationService.findLatestUnreadNotificationsByUserId(subscription.getTenantId(), (UserId) subscription.getEntityId(), subscription.getLimit()); subscription.getUnreadNotifications().clear(); @@ -106,6 +111,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH } private void fetchUnreadNotificationsCount(NotificationsCountSubscription subscription) { + log.trace("[{}, subId: {}] Fetching unread notifications count from DB", subscription.getSessionId(), subscription.getSubscriptionId()); int unreadCount = notificationService.countUnreadNotificationsByUserId(subscription.getTenantId(), (UserId) subscription.getEntityId()); subscription.getUnreadCounter().set(unreadCount); } @@ -121,6 +127,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH } private void handleNotificationUpdate(NotificationsSubscription subscription, NotificationUpdate update) { + log.trace("[{}, subId: {}] Handling notification update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); Notification notification = update.getNotification(); if (notification.getStatus() == NotificationStatus.READ) { fetchUnreadNotifications(subscription); @@ -139,6 +146,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH } private void handleNotificationRequestUpdate(NotificationsSubscription subscription, NotificationRequestUpdate update) { + log.trace("[{}, subId: {}] Handling notification request update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); NotificationRequestId notificationRequestId = update.getNotificationRequestId(); if (update.isDeleted()) { if (subscription.getUnreadNotifications().values().stream() @@ -165,10 +173,10 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH } else if (subscriptionUpdate.getNotificationRequestUpdate() != null) { handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate()); } - sendUpdate(subscription.getSessionId(), subscription.createUpdate()); } private void handleNotificationUpdate(NotificationsCountSubscription subscription, NotificationUpdate update) { + log.trace("[{}, subId: {}] Handling notification update for count sub: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); Notification notification = update.getNotification(); if (update.isNew()) { subscription.getUnreadCounter().incrementAndGet(); @@ -180,6 +188,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH } private void handleNotificationRequestUpdate(NotificationsCountSubscription subscription, NotificationRequestUpdate update) { + log.trace("[{}, subId: {}] Handling notification request update for count sub: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); if (update.isDeleted()) { fetchUnreadNotificationsCount(subscription); sendUpdate(subscription.getSessionId(), subscription.createUpdate()); @@ -188,6 +197,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH private void sendUpdate(String sessionId, CmdUpdate update) { + log.trace("[{}] Sending WS update: {}", sessionId, update); wsService.sendWsMsg(sessionId, update); } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java index c839b36e8d..4f99414be7 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java @@ -19,10 +19,12 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Builder; import lombok.Getter; +import lombok.ToString; import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdateType; @Getter +@ToString public class UnreadNotificationsCountUpdate extends CmdUpdate { private final int totalUnreadCount; diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java index 0597612569..d6da5f9e7e 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java @@ -19,14 +19,15 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Builder; import lombok.Getter; +import lombok.ToString; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdateType; import java.util.Collection; -import java.util.List; @Getter +@ToString(exclude = "notifications") public class UnreadNotificationsUpdate extends CmdUpdate { private final Collection notifications; diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 1018abe42a..1ef732b8b6 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -786,6 +786,10 @@ CREATE TABLE IF NOT EXISTS notification_target ( configuration varchar(1000) NOT NULL ); +CREATE TABLE IF NOT EXISTS notification_rule ( + id UUID NOT NULL CONSTRAINT notification_rule_pkey PRIMARY KEY +); + CREATE TABLE IF NOT EXISTS notification_request ( id UUID NOT NULL CONSTRAINT notification_request_pkey PRIMARY KEY, created_time BIGINT NOT NULL, From c4d8354fa77ab0731dee14b3eb8d086be68c61a1 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Mon, 7 Nov 2022 11:42:58 +0200 Subject: [PATCH 009/111] Notification request scheduling --- .../controller/NotificationController.java | 8 +- ...aultNotificationRuleProcessingService.java | 8 +- .../DefaultNotificationSchedulerService.java | 129 ++++++++++++++++++ ...efaultNotificationSubscriptionService.java | 37 +++-- .../NotificationSchedulerService.java | 27 ++++ .../AbstractPartitionBasedService.java | 5 +- .../queue/DefaultTbCoreConsumerService.java | 28 +++- common/cluster-api/src/main/proto/queue.proto | 10 +- .../NotificationRequestService.java | 42 ++++++ .../dao/notification/NotificationService.java | 16 --- .../server/dao/model/BaseSqlEntity.java | 4 +- .../model/sql/NotificationRequestEntity.java | 4 +- .../DefaultNotificationRequestService.java | 87 ++++++++++++ .../DefaultNotificationService.java | 45 +----- .../notification/NotificationRequestDao.java | 3 + .../JpaNotificationRequestDao.java | 6 + .../NotificationRequestRepository.java | 3 + 17 files changed, 377 insertions(+), 85 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/NotificationSchedulerService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index a4f44b589d..5c621477a1 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.notification.NotificationSubscriptionService; @@ -54,6 +55,7 @@ import java.util.UUID; public class NotificationController extends BaseController { private final NotificationService notificationService; + private final NotificationRequestService notificationRequestService; private final NotificationSubscriptionService notificationSubscriptionService; @GetMapping("/notifications") @@ -102,7 +104,7 @@ public class NotificationController extends BaseController { public NotificationRequest getNotificationRequestById(@PathVariable UUID id, @AuthenticationPrincipal SecurityUser user) { NotificationRequestId notificationRequestId = new NotificationRequestId(id); - return notificationService.findNotificationRequestById(user.getTenantId(), notificationRequestId); + return notificationRequestService.findNotificationRequestById(user.getTenantId(), notificationRequestId); } @GetMapping("/notification/requests") @@ -114,14 +116,14 @@ public class NotificationController extends BaseController { @RequestParam(required = false) String sortOrder, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return notificationService.findNotificationRequestsByTenantId(user.getTenantId(), pageLink); + return notificationRequestService.findNotificationRequestsByTenantId(user.getTenantId(), pageLink); } @DeleteMapping("/notification/request/{id}") public void deleteNotificationRequest(@PathVariable UUID id, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { NotificationRequestId notificationRequestId = new NotificationRequestId(id); - NotificationRequest notificationRequest = notificationService.findNotificationRequestById(user.getTenantId(), notificationRequestId); + NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(user.getTenantId(), notificationRequestId); accessControlService.checkPermission(user, Resource.NOTIFICATION_REQUEST, Operation.DELETE, notificationRequestId, notificationRequest); try { notificationSubscriptionService.deleteNotificationRequest(user.getTenantId(), notificationRequestId); diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java index 7c459b2e80..a36b5cc8c2 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java @@ -30,8 +30,8 @@ import org.thingsboard.server.common.data.notification.NotificationRequestStatus import org.thingsboard.server.common.data.notification.NotificationSeverity; import org.thingsboard.server.common.data.notification.rule.NonConfirmedNotificationEscalation; import org.thingsboard.server.common.data.notification.rule.NotificationRule; +import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationRuleService; -import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.executors.DbCallbackExecutorService; @@ -43,7 +43,7 @@ import java.util.List; public class DefaultNotificationRuleProcessingService implements NotificationRuleProcessingService { private final NotificationRuleService notificationRuleService; - private final NotificationService notificationService; + private final NotificationRequestService notificationRequestService; private final NotificationSubscriptionService notificationSubscriptionService; private final DbCallbackExecutorService dbCallbackExecutorService; @@ -65,7 +65,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul } private void onAlarmUpdate(TenantId tenantId, NotificationRuleId notificationRuleId, Alarm alarm) { - List notificationRequests = notificationService.findNotificationRequestsByRuleIdAndAlarmId(tenantId, notificationRuleId, alarm.getId()); + List notificationRequests = notificationRequestService.findNotificationRequestsByRuleIdAndAlarmId(tenantId, notificationRuleId, alarm.getId()); NotificationRule notificationRule = notificationRuleService.findNotificationRuleById(tenantId, notificationRuleId); if (notificationRequests.isEmpty()) { // in case it is first notification for alarm, or it was previously acked and now we need to send notifications again @@ -80,7 +80,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul for (NotificationRequest notificationRequest : notificationRequests) { if (notificationRequest.getStatus() == NotificationRequestStatus.SCHEDULED) { // using regular service due to no need to send an update to subscription manager - notificationService.deleteNotificationRequestById(tenantId, notificationRequest.getId()); + notificationRequestService.deleteNotificationRequestById(tenantId, notificationRequest.getId()); } else { notificationSubscriptionService.deleteNotificationRequest(tenantId, notificationRequest.getId()); // todo: or should we mark already sent notifications as read? diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java new file mode 100644 index 0000000000..903444529b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java @@ -0,0 +1,129 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListenableScheduledFuture; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationRequestConfig; +import org.thingsboard.server.common.data.page.PageDataIterable; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.dao.notification.NotificationRequestService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.partition.AbstractPartitionBasedService; + +import javax.annotation.PostConstruct; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +@TbCoreComponent +@Service +@RequiredArgsConstructor +@Slf4j +@SuppressWarnings("UnstableApiUsage") +public class DefaultNotificationSchedulerService extends AbstractPartitionBasedService implements NotificationSchedulerService { + + private final NotificationSubscriptionService notificationSubscriptionService; + private final NotificationRequestService notificationRequestService; + + private final Map> scheduledNotificationRequests = new ConcurrentHashMap<>(); + + @PostConstruct + public void init() { + super.init(); + } + + @Override + protected Map>> onAddedPartitions(Set addedPartitions) { + PageDataIterable notificationRequests = new PageDataIterable<>(pageLink -> { + return notificationRequestService.findScheduledNotificationRequests(pageLink); + }, 1000); + for (NotificationRequest notificationRequest : notificationRequests) { + TopicPartitionInfo requestPartition = partitionService.resolve(ServiceType.TB_CORE, notificationRequest.getTenantId(), notificationRequest.getId()); + if (addedPartitions.contains(requestPartition)) { + partitionedEntities.computeIfAbsent(requestPartition, k -> ConcurrentHashMap.newKeySet()).add(notificationRequest.getId()); + if (!scheduledNotificationRequests.containsKey(notificationRequest.getId())) { + scheduleNotificationRequest(notificationRequest.getTenantId(), notificationRequest, notificationRequest.getCreatedTime()); + } + } + } + return Collections.emptyMap(); + } + + @Override + public void scheduleNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId, long requestTs) { + NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(tenantId, notificationRequestId); + scheduleNotificationRequest(tenantId, notificationRequest, requestTs); + } + + private void scheduleNotificationRequest(TenantId tenantId, NotificationRequest request, long requestTs) { + int delayInMinutes = Optional.ofNullable(request) + .map(NotificationRequest::getAdditionalConfig) + .map(NotificationRequestConfig::getSendingDelayInMinutes) + .orElse(0); + if (delayInMinutes <= 0) return; // todo: think about: if server was down for some time and delayMs will be negative - need to send these requests as well (but when the value is within some range) + long delayMs = TimeUnit.MINUTES.toMillis(delayInMinutes) - (System.currentTimeMillis() - requestTs); + + ListenableScheduledFuture scheduledTask = scheduledExecutor.schedule(() -> { + NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(tenantId, request.getId()); + if (notificationRequest == null) return; + + notificationSubscriptionService.processNotificationRequest(tenantId, notificationRequest); + scheduledNotificationRequests.remove(notificationRequest.getId()); + }, delayMs, TimeUnit.MILLISECONDS); + scheduledNotificationRequests.put(request.getId(), scheduledTask); + } + + @Override + public void onNotificationRequestDeleted(TenantId tenantId, NotificationRequestId notificationRequestId) { + removeAndCancel(notificationRequestId); + } + + @Override + protected void cleanupEntityOnPartitionRemoval(NotificationRequestId notificationRequestId) { + removeAndCancel(notificationRequestId); + } + + private void removeAndCancel(NotificationRequestId notificationRequestId) { + ScheduledFuture scheduledTask = scheduledNotificationRequests.remove(notificationRequestId); + if (scheduledTask != null) { + scheduledTask.cancel(false); + } + } + + @Override + protected String getServiceName() { + return "Notifications scheduler"; + } + + @Override + protected String getSchedulerExecutorName() { + return "notifications-scheduler"; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSubscriptionService.java index 74fb400fb2..8c786f33c9 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSubscriptionService.java @@ -35,6 +35,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.gen.transport.TransportProtos; @@ -56,17 +57,20 @@ import java.util.UUID; public class DefaultNotificationSubscriptionService extends AbstractSubscriptionService implements NotificationSubscriptionService, RuleEngineNotificationService { private final NotificationTargetService notificationTargetService; + private final NotificationRequestService notificationRequestService; private final NotificationService notificationService; private final DbCallbackExecutorService dbCallbackExecutorService; private final NotificationsTopicService notificationsTopicService; public DefaultNotificationSubscriptionService(TbClusterService clusterService, PartitionService partitionService, NotificationTargetService notificationTargetService, + NotificationRequestService notificationRequestService, NotificationService notificationService, DbCallbackExecutorService dbCallbackExecutorService, NotificationsTopicService notificationsTopicService) { super(clusterService, partitionService); this.notificationTargetService = notificationTargetService; + this.notificationRequestService = notificationRequestService; this.notificationService = notificationService; this.dbCallbackExecutorService = dbCallbackExecutorService; this.notificationsTopicService = notificationsTopicService; @@ -77,18 +81,18 @@ public class DefaultNotificationSubscriptionService extends AbstractSubscription log.info("Processing notification request (tenant id: {}, notification target id: {})", tenantId, notificationRequest.getTargetId()); notificationRequest.setTenantId(tenantId); if (notificationRequest.getAdditionalConfig() != null) { + // TODO: think about notification request update NotificationRequestConfig config = notificationRequest.getAdditionalConfig(); - if (config.getSendingDelayInMinutes() > 0) { + if (config.getSendingDelayInMinutes() > 0 && notificationRequest.getId() == null) { notificationRequest.setStatus(NotificationRequestStatus.SCHEDULED); - - // todo: delayed sending; check all delayed notification requests on start up, schedule send + NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); + forwardToNotificationSchedulerService(tenantId, savedNotificationRequest.getId(), false); + return savedNotificationRequest; } } - if (notificationRequest.getStatus() == null) { - notificationRequest.setStatus(NotificationRequestStatus.PROCESSED); - } - NotificationRequest savedNotificationRequest = notificationService.saveNotificationRequest(tenantId, notificationRequest); + notificationRequest.setStatus(NotificationRequestStatus.PROCESSED); + NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); DaoUtil.processBatches(pageLink -> { return notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), pageLink); @@ -109,6 +113,20 @@ public class DefaultNotificationSubscriptionService extends AbstractSubscription return savedNotificationRequest; } + private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId, boolean deleted) { + TransportProtos.NotificationSchedulerServiceMsg.Builder msg = TransportProtos.NotificationSchedulerServiceMsg.newBuilder() + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setRequestIdMSB(notificationRequestId.getId().getMostSignificantBits()) + .setRequestIdLSB(notificationRequestId.getId().getLeastSignificantBits()) + .setTs(System.currentTimeMillis()) + .setDeleted(deleted); + TransportProtos.ToCoreMsg toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setNotificationSchedulerServiceMsg(msg) + .build(); + clusterService.pushMsgToCore(tenantId, notificationRequestId, toCoreMsg, null); + } + @Override public void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId) { boolean updated = notificationService.markNotificationAsRead(tenantId, recipientId, notificationId); @@ -122,17 +140,18 @@ public class DefaultNotificationSubscriptionService extends AbstractSubscription @Override public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId) { log.debug("Deleting notification request {}", notificationRequestId); - notificationService.deleteNotificationRequestById(tenantId, notificationRequestId); + notificationRequestService.deleteNotificationRequestById(tenantId, notificationRequestId); onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder() .notificationRequestId(notificationRequestId) .deleted(true) .build()); + forwardToNotificationSchedulerService(tenantId, notificationRequestId, true); } @Override public void updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { log.debug("Updating notification request {}", notificationRequest.getId()); - notificationService.saveNotificationRequest(tenantId, notificationRequest); + notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); notificationService.updateNotificationsInfosByRequestId(tenantId, notificationRequest.getId(), notificationRequest.getNotificationInfo()); onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder() .notificationRequestId(notificationRequest.getId()) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationSchedulerService.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationSchedulerService.java new file mode 100644 index 0000000000..ce018f171c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationSchedulerService.java @@ -0,0 +1,27 @@ +/** + * 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 org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.TenantId; + +public interface NotificationSchedulerService { + + void scheduleNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId, long requestTs); + + void onNotificationRequestDeleted(TenantId tenantId, NotificationRequestId notificationRequestId); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java b/application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java index 305696662b..489b1dcb48 100644 --- a/application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java +++ b/application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java @@ -20,16 +20,17 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningScheduledExecutorService; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import java.util.ArrayList; -import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -48,6 +49,8 @@ public abstract class AbstractPartitionBasedService extends protected final ConcurrentMap>> partitionedFetchTasks = new ConcurrentHashMap<>(); final Queue> subscribeQueue = new ConcurrentLinkedQueue<>(); + @Autowired + protected PartitionService partitionService; protected ListeningScheduledExecutorService scheduledExecutor; abstract protected String getServiceName(); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index cf93ea7f1d..60a9567488 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -31,8 +31,6 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.notification.Notification; -import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.TbActorMsg; @@ -69,6 +67,7 @@ import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.edge.EdgeNotificationService; +import org.thingsboard.server.service.notification.NotificationSchedulerService; import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache; @@ -127,6 +126,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> usageStatsConsumer; private final TbQueueConsumer> firmwareStatesConsumer; @@ -151,7 +151,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService findNotificationRequestsByTenantId(TenantId tenantId, PageLink pageLink); + + List findNotificationRequestsByRuleIdAndAlarmId(TenantId tenantId, NotificationRuleId ruleId, AlarmId alarmId); + + void deleteNotificationRequestById(TenantId tenantId, NotificationRequestId id); + + PageData findScheduledNotificationRequests(PageLink pageLink); + +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java index 6b7f6267db..498e3dfa3f 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java @@ -15,33 +15,17 @@ */ package org.thingsboard.server.dao.notification; -import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; -import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationInfo; -import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import java.util.List; - public interface NotificationService { - NotificationRequest saveNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); - - NotificationRequest findNotificationRequestById(TenantId tenantId, NotificationRequestId id); - - PageData findNotificationRequestsByTenantId(TenantId tenantId, PageLink pageLink); - - List findNotificationRequestsByRuleIdAndAlarmId(TenantId tenantId, NotificationRuleId ruleId, AlarmId alarmId); - - void deleteNotificationRequestById(TenantId tenantId, NotificationRequestId id); - - Notification saveNotification(TenantId tenantId, Notification notification); Notification findNotificationById(TenantId tenantId, NotificationId notificationId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java index 5256eee7db..d639192917 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java @@ -86,8 +86,8 @@ public abstract class BaseSqlEntity implements BaseEntity { } } - protected T fromJson(JsonNode json) { - return JacksonUtil.convertValue(json, new TypeReference() {}); + protected T fromJson(JsonNode json, Class type) { + return JacksonUtil.convertValue(json, type); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java index cd0bbd435a..3c7b525628 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java @@ -110,9 +110,9 @@ public class NotificationRequestEntity extends BaseSqlEntity findNotificationRequestsByTenantId(TenantId tenantId, PageLink pageLink) { + return notificationRequestDao.findByTenantIdAndPageLink(tenantId, pageLink); + } + + @Override + public List findNotificationRequestsByRuleIdAndAlarmId(TenantId tenantId, NotificationRuleId ruleId, AlarmId alarmId) { + return notificationRequestDao.findByRuleIdAndAlarmId(tenantId, ruleId, alarmId); + } + + // ON DELETE CASCADE is used: notifications for request are deleted as well + @Override + public void deleteNotificationRequestById(TenantId tenantId, NotificationRequestId id) { + notificationRequestDao.removeById(tenantId, id.getId()); + } + + @Override + public PageData findScheduledNotificationRequests(PageLink pageLink) { + return notificationRequestDao.findAllByStatus(NotificationRequestStatus.SCHEDULED, pageLink); + } + + + private static class NotificationRequestValidator extends DataValidator { + + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java index a3ef857233..37a45a3a72 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.NotificationSeverity; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.page.PageData; @@ -43,44 +44,8 @@ import java.util.List; @RequiredArgsConstructor public class DefaultNotificationService implements NotificationService { - private final NotificationRequestDao notificationRequestDao; private final NotificationDao notificationDao; - private final NotificationRequestValidator notificationRequestValidator = new NotificationRequestValidator(); - - @Override - public NotificationRequest saveNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { - if (StringUtils.isBlank(notificationRequest.getNotificationReason())) { - notificationRequest.setNotificationReason(NotificationRequest.GENERAL_NOTIFICATION_REASON); - } - if (notificationRequest.getNotificationSeverity() == null) { - notificationRequest.setNotificationSeverity(NotificationSeverity.NORMAL); - } - notificationRequestValidator.validate(notificationRequest, NotificationRequest::getTenantId); - return notificationRequestDao.save(tenantId, notificationRequest); - } - - @Override - public NotificationRequest findNotificationRequestById(TenantId tenantId, NotificationRequestId id) { - return notificationRequestDao.findById(tenantId, id.getId()); - } - - @Override - public PageData findNotificationRequestsByTenantId(TenantId tenantId, PageLink pageLink) { - return notificationRequestDao.findByTenantIdAndPageLink(tenantId, pageLink); - } - - @Override - public List findNotificationRequestsByRuleIdAndAlarmId(TenantId tenantId, NotificationRuleId ruleId, AlarmId alarmId) { - return notificationRequestDao.findByRuleIdAndAlarmId(tenantId, ruleId, alarmId); - } - - // ON DELETE CASCADE is used: notifications for request are deleted as well - @Override - public void deleteNotificationRequestById(TenantId tenantId, NotificationRequestId id) { - notificationRequestDao.removeById(tenantId, id.getId()); - } - @Override public Notification saveNotification(TenantId tenantId, Notification notification) { return notificationDao.save(tenantId, notification); @@ -122,12 +87,4 @@ public class DefaultNotificationService implements NotificationService { return notificationDao.updateInfosByRequestId(tenantId, notificationRequestId, notificationInfo); } - private static class NotificationRequestValidator extends DataValidator { - - @Override - protected void validateDataImpl(TenantId tenantId, NotificationRequest notificationRequest) { - } - - } - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java index 1848e826e0..76778c64b5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java @@ -19,6 +19,7 @@ import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; @@ -31,4 +32,6 @@ public interface NotificationRequestDao extends Dao { List findByRuleIdAndAlarmId(TenantId tenantId, NotificationRuleId ruleId, AlarmId alarmId); + PageData findAllByStatus(NotificationRequestStatus status, PageLink pageLink); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java index 5e1ca5d3f7..7684ed5865 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.DaoUtil; @@ -52,6 +53,11 @@ public class JpaNotificationRequestDao extends JpaAbstractDao findAllByStatus(NotificationRequestStatus status, PageLink pageLink) { + return DaoUtil.toPageData(notificationRequestRepository.findAllByStatus(status, DaoUtil.toPageable(pageLink))); + } + @Override protected Class getEntityClass() { return NotificationRequestEntity.class; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java index 4ff69f8797..f892767a83 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java @@ -21,6 +21,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.dao.model.sql.NotificationRequestEntity; import java.util.List; @@ -37,4 +38,6 @@ public interface NotificationRequestRepository extends JpaRepository findAllByRuleIdAndAlarmId(UUID ruleId, UUID alarmId); + Page findAllByStatus(NotificationRequestStatus status, Pageable pageable); + } From 48023495a850801516345721db467220975fc217 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Mon, 7 Nov 2022 13:50:16 +0200 Subject: [PATCH 010/111] Notification api refactoring --- .../main/data/upgrade/3.4.2/schema_update.sql | 18 +++--- .../server/actors/ActorSystemContext.java | 4 +- .../actors/ruleChain/DefaultTbContext.java | 6 +- .../controller/NotificationController.java | 29 ++++++++-- .../controller/plugin/TbWebSocketHandler.java | 4 +- ...e.java => DefaultNotificationManager.java} | 18 +++--- ...aultNotificationRuleProcessingService.java | 20 ++++--- .../DefaultNotificationSchedulerService.java | 5 +- ...efaultTbEntityDataSubscriptionService.java | 2 +- .../subscription/TbAbstractDataSubCtx.java | 2 +- .../subscription/TbAbstractSubCtx.java | 2 +- .../subscription/TbAlarmDataSubCtx.java | 2 +- .../subscription/TbEntityCountSubCtx.java | 2 +- .../subscription/TbEntityDataSubCtx.java | 2 +- .../DefaultWebSocketService.java | 55 ++++++++++--------- .../ws/{telemetry => }/WebSocketService.java | 2 +- .../DefaultNotificationCommandsHandler.java | 31 ++++++----- .../NotificationCommandsHandler.java | 4 +- ...d.java => MarkNotificationsAsReadCmd.java} | 6 +- .../cmd/NotificationCmdsWrapper.java | 3 +- .../cmd/NotificationsCountSubCmd.java | 2 - .../notification/cmd/NotificationsSubCmd.java | 2 - .../cmd/NotificationsUnsubCmd.java | 1 - .../ws/{ => notification/cmd}/WsCmd.java | 2 +- ...sApiTest.java => NotificationApiTest.java} | 14 ++--- ...ient.java => NotificationApiWsClient.java} | 11 ++-- .../NotificationRequestService.java | 4 +- .../AlarmOriginatedNotificationInfo.java | 46 ++++++++++++++++ .../data/notification/Notification.java | 2 +- .../data/notification/NotificationInfo.java | 27 +++++---- .../NotificationOriginatorType.java | 13 ++--- .../notification/NotificationRequest.java | 22 ++++---- .../server/dao/model/ModelConstants.java | 5 +- .../dao/model/sql/NotificationEntity.java | 7 +++ .../model/sql/NotificationRequestEntity.java | 38 +++++++++---- .../DefaultNotificationRequestService.java | 14 +---- .../notification/NotificationRequestDao.java | 4 +- .../JpaNotificationRequestDao.java | 6 +- .../NotificationRequestRepository.java | 3 +- .../dao/sql/query/AlarmDataAdapter.java | 4 ++ .../query/DefaultAlarmQueryRepository.java | 1 + .../resources/sql/schema-entities-idx.sql | 8 +-- .../main/resources/sql/schema-entities.sql | 12 ++-- .../rule/engine/api/NotificationManager.java | 4 +- .../rule/engine/api/TbContext.java | 2 +- .../notification/TbNotificationNode.java | 5 +- 46 files changed, 291 insertions(+), 185 deletions(-) rename application/src/main/java/org/thingsboard/server/service/notification/{DefaultNotificationSubscriptionService.java => DefaultNotificationManager.java} (92%) rename application/src/main/java/org/thingsboard/server/service/{telemetry => ws}/DefaultWebSocketService.java (95%) rename application/src/main/java/org/thingsboard/server/service/ws/{telemetry => }/WebSocketService.java (96%) rename application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/{MarkNotificationAsReadCmd.java => MarkNotificationsAsReadCmd.java} (86%) rename application/src/main/java/org/thingsboard/server/service/ws/{ => notification/cmd}/WsCmd.java (91%) rename application/src/test/java/org/thingsboard/server/service/notification/{NotificationsWsApiTest.java => NotificationApiTest.java} (94%) rename application/src/test/java/org/thingsboard/server/service/notification/{NotificationsWebSocketClient.java => NotificationApiWsClient.java} (89%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/AlarmOriginatedNotificationInfo.java rename rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineNotificationService.java => common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationOriginatorType.java (64%) rename application/src/main/java/org/thingsboard/server/service/notification/NotificationSubscriptionService.java => rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationManager.java (92%) diff --git a/application/src/main/data/upgrade/3.4.2/schema_update.sql b/application/src/main/data/upgrade/3.4.2/schema_update.sql index 7294916445..01113f2324 100644 --- a/application/src/main/data/upgrade/3.4.2/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.2/schema_update.sql @@ -14,7 +14,6 @@ -- limitations under the License. -- - CREATE TABLE IF NOT EXISTS notification_target ( id UUID NOT NULL CONSTRAINT notification_target_pkey PRIMARY KEY, created_time BIGINT NOT NULL, @@ -22,12 +21,14 @@ CREATE TABLE IF NOT EXISTS notification_target ( name VARCHAR(255) NOT NULL, configuration varchar(1000) NOT NULL ); -CREATE INDEX IF NOT EXISTS idx_notification_target_tenant_id_and_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_rule ( id UUID NOT NULL CONSTRAINT notification_rule_pkey PRIMARY KEY ); +ALTER TABLE alarm ADD COLUMN IF NOT EXISTS notification_rule_id UUID; + CREATE TABLE IF NOT EXISTS notification_request ( id UUID NOT NULL CONSTRAINT notification_request_pkey PRIMARY KEY, created_time BIGINT NOT NULL, @@ -37,12 +38,14 @@ CREATE TABLE IF NOT EXISTS notification_request ( text_template VARCHAR NOT NULL, notification_info VARCHAR(1000), notification_severity VARCHAR(32), - additional_config VARCHAR(1000), - status VARCHAR(32), + originator_type VARCHAR(32) NOT NULL, + originator_entity_id UUID, + originator_entity_type VARCHAR(32), rule_id UUID NULL CONSTRAINT fk_notification_request_rule_id REFERENCES notification_rule(id), - alarm_id UUID + additional_config VARCHAR(1000), + status VARCHAR(32) ); -CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id_and_created_time ON notification_request(tenant_id, 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); CREATE TABLE IF NOT EXISTS notification ( id UUID NOT NULL, @@ -54,8 +57,9 @@ CREATE TABLE IF NOT EXISTS notification ( text VARCHAR NOT NULL, info VARCHAR(1000), severity VARCHAR(32), + originator_type VARCHAR(32) NOT NULL, status VARCHAR(32) ) PARTITION BY RANGE (created_time); CREATE INDEX IF NOT EXISTS idx_notification_id ON notification(id); +CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_created_time ON notification(recipient_id, created_time DESC); CREATE INDEX IF NOT EXISTS idx_notification_notification_request_id ON notification(request_id); -CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_and_created_time ON notification(recipient_id, created_time DESC); diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 1bfb515811..52cd33a885 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -30,7 +30,7 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.MailService; -import org.thingsboard.rule.engine.api.RuleEngineNotificationService; +import org.thingsboard.rule.engine.api.NotificationManager; import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.script.api.js.JsInvokeService; @@ -309,7 +309,7 @@ public class ActorSystemContext { @Autowired @Getter - private RuleEngineNotificationService notificationService; + private NotificationManager notificationManager; @Lazy @Autowired(required = false) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index f623a9ad4c..bc43e69284 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -25,10 +25,10 @@ import org.bouncycastle.util.Arrays; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.MailService; +import org.thingsboard.rule.engine.api.NotificationManager; import org.thingsboard.rule.engine.api.RuleEngineAlarmService; import org.thingsboard.rule.engine.api.RuleEngineAssetProfileCache; import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; -import org.thingsboard.rule.engine.api.RuleEngineNotificationService; import org.thingsboard.rule.engine.api.RuleEngineRpcService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.ScriptEngine; @@ -665,8 +665,8 @@ class DefaultTbContext implements TbContext { } @Override - public RuleEngineNotificationService getNotificationService() { - return mainCtx.getNotificationService(); + public NotificationManager getNotificationManager() { + return mainCtx.getNotificationManager(); } @Override diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index 5c621477a1..a392e6ec74 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -17,6 +17,7 @@ package org.thingsboard.server.controller; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; @@ -34,13 +35,15 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationOriginatorType; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationSeverity; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.notification.NotificationSubscriptionService; +import org.thingsboard.rule.engine.api.NotificationManager; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -56,7 +59,7 @@ public class NotificationController extends BaseController { private final NotificationService notificationService; private final NotificationRequestService notificationRequestService; - private final NotificationSubscriptionService notificationSubscriptionService; + private final NotificationManager notificationManager; @GetMapping("/notifications") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @@ -76,7 +79,7 @@ public class NotificationController extends BaseController { public void markNotificationAsRead(@PathVariable UUID id, @AuthenticationPrincipal SecurityUser user) { NotificationId notificationId = new NotificationId(id); - notificationSubscriptionService.markNotificationAsRead(user.getTenantId(), user.getId(), notificationId); + notificationManager.markNotificationAsRead(user.getTenantId(), user.getId(), notificationId); } // delete notification? @@ -86,11 +89,27 @@ public class NotificationController extends BaseController { public NotificationRequest createNotificationRequest(@RequestBody NotificationRequest notificationRequest, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { accessControlService.checkPermission(user, Resource.NOTIFICATION_REQUEST, Operation.CREATE, null, notificationRequest); + // todo: check permission for notification target if (notificationRequest.getId() != null) { + // TODO: think about notification request update throw new IllegalArgumentException("Notification request cannot be changed. You can delete it and create a new one"); } + notificationRequest.setOriginatorType(NotificationOriginatorType.USER); + notificationRequest.setOriginatorEntityId(user.getId()); + if (StringUtils.isBlank(notificationRequest.getNotificationReason())) { + notificationRequest.setNotificationReason(NotificationRequest.GENERAL_NOTIFICATION_REASON); + } + 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); + try { - NotificationRequest savedNotificationRequest = notificationSubscriptionService.processNotificationRequest(user.getTenantId(), notificationRequest); + NotificationRequest savedNotificationRequest = notificationManager.processNotificationRequest(user.getTenantId(), notificationRequest); logEntityAction(user, EntityType.NOTIFICATION_REQUEST, savedNotificationRequest, ActionType.ADDED); return savedNotificationRequest; } catch (Exception e) { @@ -126,7 +145,7 @@ public class NotificationController extends BaseController { NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(user.getTenantId(), notificationRequestId); accessControlService.checkPermission(user, Resource.NOTIFICATION_REQUEST, Operation.DELETE, notificationRequestId, notificationRequest); try { - notificationSubscriptionService.deleteNotificationRequest(user.getTenantId(), notificationRequestId); + notificationManager.deleteNotificationRequest(user.getTenantId(), notificationRequestId); logEntityAction(user, EntityType.NOTIFICATION_REQUEST, notificationRequest, ActionType.DELETED); } catch (Exception e) { logEntityAction(user, EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationRequest, ActionType.DELETED, e); diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index 7cb0a8258e..edd6843c2f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -42,7 +42,7 @@ import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.ws.SessionEvent; import org.thingsboard.server.service.ws.WebSocketMsgEndpoint; import org.thingsboard.server.service.ws.WebSocketSessionType; -import org.thingsboard.server.service.ws.telemetry.WebSocketService; +import org.thingsboard.server.service.ws.WebSocketService; import org.thingsboard.server.service.ws.WebSocketSessionRef; import javax.websocket.RemoteEndpoint; @@ -59,7 +59,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.LinkedBlockingQueue; -import static org.thingsboard.server.service.telemetry.DefaultWebSocketService.NUMBER_OF_PING_ATTEMPTS; +import static org.thingsboard.server.service.ws.DefaultWebSocketService.NUMBER_OF_PING_ATTEMPTS; @Service @TbCoreComponent diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java similarity index 92% rename from application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSubscriptionService.java rename to application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java index 8c786f33c9..4bb8afc61d 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java @@ -18,7 +18,7 @@ package org.thingsboard.server.service.notification; import com.google.common.base.Strings; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import org.thingsboard.rule.engine.api.RuleEngineNotificationService; +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; @@ -54,7 +54,7 @@ import java.util.UUID; @Service @Slf4j -public class DefaultNotificationSubscriptionService extends AbstractSubscriptionService implements NotificationSubscriptionService, RuleEngineNotificationService { +public class DefaultNotificationManager extends AbstractSubscriptionService implements NotificationManager { private final NotificationTargetService notificationTargetService; private final NotificationRequestService notificationRequestService; @@ -62,12 +62,12 @@ public class DefaultNotificationSubscriptionService extends AbstractSubscription private final DbCallbackExecutorService dbCallbackExecutorService; private final NotificationsTopicService notificationsTopicService; - public DefaultNotificationSubscriptionService(TbClusterService clusterService, PartitionService partitionService, - NotificationTargetService notificationTargetService, - NotificationRequestService notificationRequestService, - NotificationService notificationService, - DbCallbackExecutorService dbCallbackExecutorService, - NotificationsTopicService notificationsTopicService) { + public DefaultNotificationManager(TbClusterService clusterService, PartitionService partitionService, + NotificationTargetService notificationTargetService, + NotificationRequestService notificationRequestService, + NotificationService notificationService, + DbCallbackExecutorService dbCallbackExecutorService, + NotificationsTopicService notificationsTopicService) { super(clusterService, partitionService); this.notificationTargetService = notificationTargetService; this.notificationRequestService = notificationRequestService; @@ -81,7 +81,6 @@ public class DefaultNotificationSubscriptionService extends AbstractSubscription log.info("Processing notification request (tenant id: {}, notification target id: {})", tenantId, notificationRequest.getTargetId()); notificationRequest.setTenantId(tenantId); if (notificationRequest.getAdditionalConfig() != null) { - // TODO: think about notification request update NotificationRequestConfig config = notificationRequest.getAdditionalConfig(); if (config.getSendingDelayInMinutes() > 0 && notificationRequest.getId() == null) { notificationRequest.setStatus(NotificationRequestStatus.SCHEDULED); @@ -169,6 +168,7 @@ public class DefaultNotificationSubscriptionService extends AbstractSubscription .text(formatNotificationText(notificationRequest.getTextTemplate(), recipient)) .info(notificationRequest.getNotificationInfo()) .severity(notificationRequest.getNotificationSeverity()) + .originatorType(notificationRequest.getOriginatorType()) .status(NotificationStatus.SENT) .build(); return notificationService.saveNotification(recipient.getTenantId(), notification); diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java index a36b5cc8c2..ac7e875e03 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java @@ -19,11 +19,14 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.NotificationManager; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.AlarmOriginatedNotificationInfo; import org.thingsboard.server.common.data.notification.NotificationInfo; +import org.thingsboard.server.common.data.notification.NotificationOriginatorType; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; import org.thingsboard.server.common.data.notification.NotificationRequestStatus; @@ -44,7 +47,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul private final NotificationRuleService notificationRuleService; private final NotificationRequestService notificationRequestService; - private final NotificationSubscriptionService notificationSubscriptionService; + private final NotificationManager notificationManager; private final DbCallbackExecutorService dbCallbackExecutorService; @Override @@ -65,7 +68,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul } private void onAlarmUpdate(TenantId tenantId, NotificationRuleId notificationRuleId, Alarm alarm) { - List notificationRequests = notificationRequestService.findNotificationRequestsByRuleIdAndAlarmId(tenantId, notificationRuleId, alarm.getId()); + List notificationRequests = notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityId(tenantId, notificationRuleId, alarm.getId()); NotificationRule notificationRule = notificationRuleService.findNotificationRuleById(tenantId, notificationRuleId); if (notificationRequests.isEmpty()) { // in case it is first notification for alarm, or it was previously acked and now we need to send notifications again @@ -82,7 +85,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul // using regular service due to no need to send an update to subscription manager notificationRequestService.deleteNotificationRequestById(tenantId, notificationRequest.getId()); } else { - notificationSubscriptionService.deleteNotificationRequest(tenantId, notificationRequest.getId()); + notificationManager.deleteNotificationRequest(tenantId, notificationRequest.getId()); // todo: or should we mark already sent notifications as read? } } @@ -92,7 +95,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul NotificationInfo previousNotificationInfo = notificationRequest.getNotificationInfo(); if (!previousNotificationInfo.equals(newNotificationInfo)) { notificationRequest.setNotificationInfo(newNotificationInfo); - notificationSubscriptionService.updateNotificationRequest(tenantId, notificationRequest); + notificationManager.updateNotificationRequest(tenantId, notificationRequest); } // fixme: no need to send an update event for scheduled requests, only for sent } @@ -118,15 +121,16 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul .textTemplate(notificationRule.getNotificationTextTemplate()) // todo: format with alarm vars .notificationInfo(notificationInfo) .notificationSeverity(NotificationSeverity.NORMAL) // todo: from alarm severity - .additionalConfig(config) + .originatorType(NotificationOriginatorType.ALARM) + .originatorEntityId(alarm.getId()) .ruleId(notificationRule.getId()) - .alarmId(alarm.getId()) + .additionalConfig(config) .build(); - notificationSubscriptionService.processNotificationRequest(tenantId, notificationRequest); + notificationManager.processNotificationRequest(tenantId, notificationRequest); } private NotificationInfo constructNotificationInfo(Alarm alarm, NotificationRule notificationRule) { - return NotificationInfo.builder() + return AlarmOriginatedNotificationInfo.builder() .alarmId(alarm.getId()) .alarmType(alarm.getType()) .alarmOriginator(alarm.getOriginator()) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java index 903444529b..36faeeee87 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java @@ -20,6 +20,7 @@ import com.google.common.util.concurrent.ListenableScheduledFuture; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.NotificationManager; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationRequest; @@ -48,7 +49,7 @@ import java.util.concurrent.TimeUnit; @SuppressWarnings("UnstableApiUsage") public class DefaultNotificationSchedulerService extends AbstractPartitionBasedService implements NotificationSchedulerService { - private final NotificationSubscriptionService notificationSubscriptionService; + private final NotificationManager notificationManager; private final NotificationRequestService notificationRequestService; private final Map> scheduledNotificationRequests = new ConcurrentHashMap<>(); @@ -93,7 +94,7 @@ public class DefaultNotificationSchedulerService extends AbstractPartitionBasedS NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(tenantId, request.getId()); if (notificationRequest == null) return; - notificationSubscriptionService.processNotificationRequest(tenantId, notificationRequest); + notificationManager.processNotificationRequest(tenantId, notificationRequest); scheduledNotificationRequests.remove(notificationRequest.getId()); }, delayMs, TimeUnit.MILLISECONDS); scheduledNotificationRequests.put(request.getId(), scheduledTask); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java index eed12acf3e..4661730f9c 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java @@ -48,7 +48,7 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.executors.DbCallbackExecutorService; -import org.thingsboard.server.service.ws.telemetry.WebSocketService; +import org.thingsboard.server.service.ws.WebSocketService; import org.thingsboard.server.service.ws.WebSocketSessionRef; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggHistoryCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggKey; diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java index 7ff8bf931e..11fa6b4c35 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java @@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.service.ws.WebSocketSessionRef; -import org.thingsboard.server.service.ws.telemetry.WebSocketService; +import org.thingsboard.server.service.ws.WebSocketService; import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; import java.util.ArrayList; diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java index d7fde23b31..8dd90da5dc 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java @@ -39,7 +39,7 @@ import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.service.ws.WebSocketSessionRef; -import org.thingsboard.server.service.ws.telemetry.WebSocketService; +import org.thingsboard.server.service.ws.WebSocketService; import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java index c66cdc4828..a952a01ee9 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java @@ -38,7 +38,7 @@ import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.service.ws.telemetry.WebSocketService; +import org.thingsboard.server.service.ws.WebSocketService; import org.thingsboard.server.service.ws.WebSocketSessionRef; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataUpdate; import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate; diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java index f0cc2260b6..573016c20c 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java @@ -19,7 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.query.EntityCountQuery; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entity.EntityService; -import org.thingsboard.server.service.ws.telemetry.WebSocketService; +import org.thingsboard.server.service.ws.WebSocketService; import org.thingsboard.server.service.ws.WebSocketSessionRef; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate; diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java index 975ecea1c2..f5c701916d 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java @@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entity.EntityService; -import org.thingsboard.server.service.ws.telemetry.WebSocketService; +import org.thingsboard.server.service.ws.WebSocketService; import org.thingsboard.server.service.ws.WebSocketSessionRef; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java similarity index 95% rename from application/src/main/java/org/thingsboard/server/service/telemetry/DefaultWebSocketService.java rename to application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java index e03e0fc3fb..2598429672 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry; +package org.thingsboard.server.service.ws; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -62,14 +62,9 @@ import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope; import org.thingsboard.server.service.subscription.TbEntityDataSubscriptionService; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.subscription.TbTimeseriesSubscription; -import org.thingsboard.server.service.ws.SessionEvent; -import org.thingsboard.server.service.ws.WebSocketMsgEndpoint; -import org.thingsboard.server.service.ws.WebSocketSessionRef; -import org.thingsboard.server.service.ws.WsCmd; -import org.thingsboard.server.service.ws.WsSessionMetaData; import org.thingsboard.server.service.ws.notification.NotificationCommandsHandler; import org.thingsboard.server.service.ws.notification.cmd.NotificationCmdsWrapper; -import org.thingsboard.server.service.ws.telemetry.WebSocketService; +import org.thingsboard.server.service.ws.notification.cmd.WsCmd; import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryPluginCmdsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.GetHistoryCmd; @@ -163,22 +158,22 @@ public class DefaultWebSocketService implements WebSocketService { pingExecutor.scheduleWithFixedDelay(this::sendPing, pingTimeout / NUMBER_OF_PING_ATTEMPTS, pingTimeout / NUMBER_OF_PING_ATTEMPTS, TimeUnit.MILLISECONDS); telemetryCmdsHandlers = List.of( - WsCmdListHandler.of(TelemetryPluginCmdsWrapper::getAttrSubCmds, this::handleWsAttributesSubscriptionCmd), - WsCmdListHandler.of(TelemetryPluginCmdsWrapper::getTsSubCmds, this::handleWsTimeseriesSubscriptionCmd), - WsCmdListHandler.of(TelemetryPluginCmdsWrapper::getHistoryCmds, this::handleWsHistoryCmd), - WsCmdListHandler.of(TelemetryPluginCmdsWrapper::getEntityDataCmds, this::handleWsEntityDataCmd), - WsCmdListHandler.of(TelemetryPluginCmdsWrapper::getAlarmDataCmds, this::handleWsAlarmDataCmd), - WsCmdListHandler.of(TelemetryPluginCmdsWrapper::getEntityCountCmds, this::handleWsEntityCountCmd), - WsCmdListHandler.of(TelemetryPluginCmdsWrapper::getEntityDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), - WsCmdListHandler.of(TelemetryPluginCmdsWrapper::getAlarmDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), - WsCmdListHandler.of(TelemetryPluginCmdsWrapper::getAlarmDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), - WsCmdListHandler.of(TelemetryPluginCmdsWrapper::getEntityCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd) + newCmdsHandler(TelemetryPluginCmdsWrapper::getAttrSubCmds, this::handleWsAttributesSubscriptionCmd), + newCmdsHandler(TelemetryPluginCmdsWrapper::getTsSubCmds, this::handleWsTimeseriesSubscriptionCmd), + newCmdsHandler(TelemetryPluginCmdsWrapper::getHistoryCmds, this::handleWsHistoryCmd), + newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityDataCmds, this::handleWsEntityDataCmd), + newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmDataCmds, this::handleWsAlarmDataCmd), + newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityCountCmds, this::handleWsEntityCountCmd), + newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), + newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), + newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), + newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd) ); notificationCmdsHandlers = List.of( - WsCmdHandler.of(NotificationCmdsWrapper::getUnreadSubCmd, notificationCmdsHandler::handleUnreadNotificationsSubCmd), - WsCmdHandler.of(NotificationCmdsWrapper::getUnreadCountSubCmd, notificationCmdsHandler::handleUnreadNotificationsCountSubCmd), - WsCmdHandler.of(NotificationCmdsWrapper::getMarkAsReadCmd, notificationCmdsHandler::handleMarkAsReadCmd), - WsCmdHandler.of(NotificationCmdsWrapper::getUnsubCmd, notificationCmdsHandler::handleUnsubCmd) + newCmdHandler(NotificationCmdsWrapper::getUnreadSubCmd, notificationCmdsHandler::handleUnreadNotificationsSubCmd), + newCmdHandler(NotificationCmdsWrapper::getUnreadCountSubCmd, notificationCmdsHandler::handleUnreadNotificationsCountSubCmd), + newCmdHandler(NotificationCmdsWrapper::getMarkAsReadCmd, notificationCmdsHandler::handleMarkAsReadCmd), + newCmdHandler(NotificationCmdsWrapper::getUnsubCmd, notificationCmdsHandler::handleUnsubCmd) ); } @@ -955,7 +950,17 @@ public class DefaultWebSocketService implements WebSocketService { return limit == 0 ? DEFAULT_LIMIT : limit; } - @RequiredArgsConstructor(staticName = "of") + public static WsCmdHandler newCmdHandler(java.util.function.Function cmdExtractor, + BiConsumer handler) { + return new WsCmdHandler<>(cmdExtractor, handler); + } + + public static WsCmdListHandler newCmdsHandler(java.util.function.Function> cmdsExtractor, + BiConsumer handler) { + return new WsCmdListHandler<>(cmdsExtractor, handler); + } + + @RequiredArgsConstructor public static class WsCmdHandler { private final java.util.function.Function cmdExtractor; private final BiConsumer handler; @@ -970,13 +975,13 @@ public class DefaultWebSocketService implements WebSocketService { } } - @RequiredArgsConstructor(staticName = "of") + @RequiredArgsConstructor public static class WsCmdListHandler { - private final java.util.function.Function> cmdExtractor; + private final java.util.function.Function> cmdsExtractor; private final BiConsumer handler; public List extractCmds(W cmdsWrapper) { - return cmdExtractor.apply(cmdsWrapper); + return cmdsExtractor.apply(cmdsWrapper); } @SuppressWarnings("unchecked") diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/WebSocketService.java b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java similarity index 96% rename from application/src/main/java/org/thingsboard/server/service/ws/telemetry/WebSocketService.java rename to application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java index d0d8dab59e..ddd08590b5 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/WebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.ws.telemetry; +package org.thingsboard.server.service.ws; import org.springframework.web.socket.CloseStatus; import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java index 6af03caeb5..5e31924e45 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java @@ -31,7 +31,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.notification.NotificationSubscriptionService; +import org.thingsboard.rule.engine.api.NotificationManager; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd; import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate; @@ -39,11 +39,11 @@ import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscription; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.ws.WebSocketSessionRef; -import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationAsReadCmd; +import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd; import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationsCountSubscription; -import org.thingsboard.server.service.ws.telemetry.WebSocketService; +import org.thingsboard.server.service.ws.WebSocketService; import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; @@ -59,7 +59,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH private final NotificationService notificationService; private final TbLocalSubscriptionService localSubscriptionService; - private final NotificationSubscriptionService notificationSubscriptionService; + private final NotificationManager notificationManager; private final TbServiceInfoProvider serviceInfoProvider; @Autowired @Lazy private WebSocketService wsService; @@ -67,13 +67,13 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH @Override public void handleUnreadNotificationsSubCmd(WebSocketSessionRef sessionRef, NotificationsSubCmd cmd) { log.debug("[{}] Handling unread notifications subscription cmd (cmdId: {})", sessionRef.getSessionId(), cmd.getCmdId()); - SecurityUser user = sessionRef.getSecurityCtx(); + SecurityUser securityCtx = sessionRef.getSecurityCtx(); NotificationsSubscription subscription = NotificationsSubscription.builder() .serviceId(serviceInfoProvider.getServiceId()) .sessionId(sessionRef.getSessionId()) .subscriptionId(cmd.getCmdId()) - .tenantId(user.getTenantId()) - .entityId(user.getId()) + .tenantId(securityCtx.getTenantId()) + .entityId(securityCtx.getId()) .updateProcessor(this::handleNotificationsSubscriptionUpdate) .limit(cmd.getLimit()) .build(); @@ -86,13 +86,13 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH @Override public void handleUnreadNotificationsCountSubCmd(WebSocketSessionRef sessionRef, NotificationsCountSubCmd cmd) { log.debug("[{}] Handling unread notifications count subscription cmd (cmdId: {})", sessionRef.getSessionId(), cmd.getCmdId()); - SecurityUser user = sessionRef.getSecurityCtx(); + SecurityUser securityCtx = sessionRef.getSecurityCtx(); NotificationsCountSubscription subscription = NotificationsCountSubscription.builder() .serviceId(serviceInfoProvider.getServiceId()) .sessionId(sessionRef.getSessionId()) .subscriptionId(cmd.getCmdId()) - .tenantId(user.getTenantId()) - .entityId(user.getId()) + .tenantId(securityCtx.getTenantId()) + .entityId(securityCtx.getId()) .updateProcessor(this::handleNotificationsCountSubscriptionUpdate) .build(); localSubscriptionService.addSubscription(subscription); @@ -203,9 +203,14 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH @Override - public void handleMarkAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationAsReadCmd cmd) { - NotificationId notificationId = new NotificationId(cmd.getNotificationId()); - notificationSubscriptionService.markNotificationAsRead(sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), notificationId); + public void handleMarkAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationsAsReadCmd cmd) { + SecurityUser securityCtx = sessionRef.getSecurityCtx(); + cmd.getNotifications().stream() + .map(NotificationId::new) + .forEach(notificationId -> { + notificationManager.markNotificationAsRead(securityCtx.getTenantId(), securityCtx.getId(), notificationId); + // fixme: should send bulk update event, not a separate event for each notification + }); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java index 4cf1fb3809..0f57730601 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java @@ -16,7 +16,7 @@ package org.thingsboard.server.service.ws.notification; import org.thingsboard.server.service.ws.WebSocketSessionRef; -import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationAsReadCmd; +import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd; import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; @@ -27,7 +27,7 @@ public interface NotificationCommandsHandler { void handleUnreadNotificationsCountSubCmd(WebSocketSessionRef sessionRef, NotificationsCountSubCmd cmd); - void handleMarkAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationAsReadCmd cmd); + void handleMarkAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationsAsReadCmd cmd); void handleUnsubCmd(WebSocketSessionRef sessionRef, UnsubscribeCmd cmd); diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationAsReadCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationsAsReadCmd.java similarity index 86% rename from application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationAsReadCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationsAsReadCmd.java index cfc6e8e38b..6ed49fc4b6 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationAsReadCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationsAsReadCmd.java @@ -18,14 +18,14 @@ package org.thingsboard.server.service.ws.notification.cmd; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import org.thingsboard.server.service.ws.WsCmd; +import java.util.List; import java.util.UUID; @Data @NoArgsConstructor @AllArgsConstructor -public class MarkNotificationAsReadCmd implements WsCmd { +public class MarkNotificationsAsReadCmd implements WsCmd { private int cmdId; - private UUID notificationId; + private List notifications; } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java index 011e8cecca..1c56d49a6f 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java @@ -23,7 +23,8 @@ public class NotificationCmdsWrapper { private NotificationsCountSubCmd unreadCountSubCmd; private NotificationsSubCmd unreadSubCmd; - private MarkNotificationAsReadCmd markAsReadCmd; + + private MarkNotificationsAsReadCmd markAsReadCmd; private NotificationsUnsubCmd unsubCmd; diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java index 2722d6ae7d..985e0c9aa0 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java @@ -17,9 +17,7 @@ package org.thingsboard.server.service.ws.notification.cmd; import lombok.AllArgsConstructor; import lombok.Data; -import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; -import org.thingsboard.server.service.ws.WsCmd; @Data @NoArgsConstructor diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java index 087b250314..c772635176 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java @@ -17,9 +17,7 @@ package org.thingsboard.server.service.ws.notification.cmd; import lombok.AllArgsConstructor; import lombok.Data; -import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; -import org.thingsboard.server.service.ws.WsCmd; @Data @NoArgsConstructor diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java index b81e76e81d..d8e825e21d 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java @@ -18,7 +18,6 @@ package org.thingsboard.server.service.ws.notification.cmd; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import org.thingsboard.server.service.ws.WsCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; @Data diff --git a/application/src/main/java/org/thingsboard/server/service/ws/WsCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/WsCmd.java similarity index 91% rename from application/src/main/java/org/thingsboard/server/service/ws/WsCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/WsCmd.java index 096c5930a5..daa59abcba 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/WsCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/WsCmd.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.ws; +package org.thingsboard.server.service.ws.notification.cmd; public interface WsCmd { int getCmdId(); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWsApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java similarity index 94% rename from application/src/test/java/org/thingsboard/server/service/notification/NotificationsWsApiTest.java rename to application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java index 6738b37a70..aca74f9a07 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWsApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java @@ -35,7 +35,7 @@ import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; @DaoSqlTest -public class NotificationsWsApiTest extends AbstractControllerTest { +public class NotificationApiTest extends AbstractControllerTest { @Before public void beforeEach() throws Exception { @@ -154,8 +154,6 @@ public class NotificationsWsApiTest extends AbstractControllerTest { assertThat(getAnotherWsClient().getLastCountUpdate().getTotalUnreadCount()).isOne(); } - public void testReceivingUpdatesWhenSubscriptionAtAnotherInstance() {} - private void checkFullNotificationsUpdate(UnreadNotificationsUpdate notificationsUpdate, String... expectedNotifications) { assertThat(notificationsUpdate.getNotifications()).extracting(Notification::getText).containsOnly(expectedNotifications); @@ -189,19 +187,19 @@ public class NotificationsWsApiTest extends AbstractControllerTest { @Override protected TbTestWebSocketClient buildAndConnectWebSocketClient() throws URISyntaxException, InterruptedException { - NotificationsWebSocketClient wsClient = new NotificationsWebSocketClient(WS_URL + wsPort, token); + NotificationApiWsClient wsClient = new NotificationApiWsClient(WS_URL + wsPort, token); assertThat(wsClient.connectBlocking(TIMEOUT, TimeUnit.SECONDS)).isTrue(); return wsClient; } @Override - public NotificationsWebSocketClient getWsClient() { - return (NotificationsWebSocketClient) super.getWsClient(); + public NotificationApiWsClient getWsClient() { + return (NotificationApiWsClient) super.getWsClient(); } @Override - public NotificationsWebSocketClient getAnotherWsClient() { - return (NotificationsWebSocketClient) super.getAnotherWsClient(); + public NotificationApiWsClient getAnotherWsClient() { + return (NotificationApiWsClient) super.getAnotherWsClient(); } } diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWebSocketClient.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java similarity index 89% rename from application/src/test/java/org/thingsboard/server/service/notification/NotificationsWebSocketClient.java rename to application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java index 5da35b91fc..e5b9d4b887 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationsWebSocketClient.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java @@ -21,7 +21,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.controller.TbTestWebSocketClient; -import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationAsReadCmd; +import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd; import org.thingsboard.server.service.ws.notification.cmd.NotificationCmdsWrapper; import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd; import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; @@ -31,17 +31,18 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdateType; import java.net.URI; import java.net.URISyntaxException; +import java.util.Arrays; import java.util.UUID; @Slf4j -public class NotificationsWebSocketClient extends TbTestWebSocketClient { +public class NotificationApiWsClient extends TbTestWebSocketClient { @Getter private UnreadNotificationsUpdate lastDataUpdate; @Getter private UnreadNotificationsCountUpdate lastCountUpdate; - public NotificationsWebSocketClient(String wsUrl, String token) throws URISyntaxException { + public NotificationApiWsClient(String wsUrl, String token) throws URISyntaxException { super(new URI(wsUrl + "/api/ws/plugins/notifications?token=" + token)); } @@ -57,9 +58,9 @@ public class NotificationsWebSocketClient extends TbTestWebSocketClient { sendCmd(cmdsWrapper); } - public void markNotificationAsRead(UUID notificationId) { + public void markNotificationAsRead(UUID... notifications) { NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper(); - cmdsWrapper.setMarkAsReadCmd(new MarkNotificationAsReadCmd(newCmdId(), notificationId)); + cmdsWrapper.setMarkAsReadCmd(new MarkNotificationsAsReadCmd(newCmdId(), Arrays.asList(notifications))); sendCmd(cmdsWrapper); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java index 4ded8cbd9f..7ae96ada79 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.dao.notification; -import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; @@ -33,7 +33,7 @@ public interface NotificationRequestService { PageData findNotificationRequestsByTenantId(TenantId tenantId, PageLink pageLink); - List findNotificationRequestsByRuleIdAndAlarmId(TenantId tenantId, NotificationRuleId ruleId, AlarmId alarmId); + List findNotificationRequestsByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId); void deleteNotificationRequestById(TenantId tenantId, NotificationRequestId id); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/AlarmOriginatedNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/AlarmOriginatedNotificationInfo.java new file mode 100644 index 0000000000..19d3dc99ad --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/AlarmOriginatedNotificationInfo.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.common.data.notification; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.EntityId; + +@Data +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AlarmOriginatedNotificationInfo extends NotificationInfo { + + private AlarmId alarmId; + private String alarmType; + private EntityId alarmOriginator; + private AlarmSeverity alarmSeverity; + private AlarmStatus alarmStatus; + + @Override + public NotificationOriginatorType getOriginatorType() { + return NotificationOriginatorType.ALARM; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java index 827bddc980..a39d858fcc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java @@ -38,7 +38,7 @@ public class Notification extends BaseData { private String text; private NotificationInfo info; private NotificationSeverity severity; + private NotificationOriginatorType originatorType; private NotificationStatus status; -// private UserId senderId; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationInfo.java index 298998f8d7..e7b2873d99 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationInfo.java @@ -15,6 +15,10 @@ */ package org.thingsboard.server.common.data.notification; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; +import com.fasterxml.jackson.annotation.JsonTypeInfo; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -28,23 +32,18 @@ import org.thingsboard.server.common.data.validation.NoXss; @Data -@AllArgsConstructor -@NoArgsConstructor -@Builder -//@JsonIgnoreProperties(ignoreUnknown = true) -//@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "notificationType", visible = true, defaultImpl = NotificationInfo.class) -//@JsonSubTypes({ -// @Type(name = "ALARM", value = DeviceExportData.class), -//}) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "originatorType", defaultImpl = NotificationInfo.class) +@JsonSubTypes({ + @Type(name = "ALARM", value = AlarmOriginatedNotificationInfo.class), +}) public class NotificationInfo { - @NoXss + private String description; private DashboardId dashboardId; - private AlarmId alarmId; - private String alarmType; - private EntityId alarmOriginator; - private AlarmSeverity alarmSeverity; - private AlarmStatus alarmStatus; + public NotificationOriginatorType getOriginatorType() { + return null; + } } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineNotificationService.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationOriginatorType.java similarity index 64% rename from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineNotificationService.java rename to common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationOriginatorType.java index e7b87cf25d..44aa879a00 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineNotificationService.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationOriginatorType.java @@ -13,13 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.api; - -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.notification.NotificationRequest; - -public interface RuleEngineNotificationService { - - NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); +package org.thingsboard.server.common.data.notification; +public enum NotificationOriginatorType { + USER, + ALARM, + RULE_NODE } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java index af90eab7ee..f962db97e4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.common.data.notification; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -25,7 +24,7 @@ import lombok.NoArgsConstructor; 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.AlarmId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationTargetId; @@ -46,26 +45,25 @@ public class NotificationRequest extends BaseData impleme private TenantId tenantId; @NotNull(message = "Target is not specified") private NotificationTargetId targetId; + @NoXss - private String notificationReason; // "Alarm", "Scheduled event". "General" by default - // @NoXss + private String notificationReason; @NotBlank(message = "Notification text template is missing") - private String textTemplate; + private String textTemplate; // fixme: xss @Valid private NotificationInfo notificationInfo; private NotificationSeverity notificationSeverity; - private NotificationRequestConfig additionalConfig; - @JsonProperty(access = JsonProperty.Access.READ_ONLY) - private NotificationRequestStatus status; - @JsonIgnore + private NotificationOriginatorType originatorType; + private EntityId originatorEntityId; // userId, alarmId or tenantId private NotificationRuleId ruleId; // maybe move to child class - @JsonIgnore - private AlarmId alarmId; + + private NotificationRequestConfig additionalConfig; + private NotificationRequestStatus status; public static final String GENERAL_NOTIFICATION_REASON = "General"; - public static final String ALARM_NOTIFICATION_REASON = "Alarm"; + @JsonIgnore @Override public String getName() { return notificationReason; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 27b17164ea..9d477059d6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -660,6 +660,7 @@ public class ModelConstants { public static final String NOTIFICATION_TEXT_PROPERTY = "text"; public static final String NOTIFICATION_INFO_PROPERTY = "info"; public static final String NOTIFICATION_SEVERITY_PROPERTY = "severity"; + public static final String NOTIFICATION_ORIGINATOR_TYPE_PROPERTY = "originator_type"; public static final String NOTIFICATION_STATUS_PROPERTY = "status"; public static final String NOTIFICATION_REQUEST_TABLE_NAME = "notification_request"; @@ -668,10 +669,12 @@ public class ModelConstants { public static final String NOTIFICATION_REQUEST_NOTIFICATION_REASON_PROPERTY = "notification_reason"; public static final String NOTIFICATION_REQUEST_NOTIFICATION_INFO_PROPERTY = "notification_info"; public static final String NOTIFICATION_REQUEST_NOTIFICATION_SEVERITY_PROPERTY = "notification_severity"; + public static final String NOTIFICATION_REQUEST_ORIGINATOR_TYPE_PROPERTY = "originator_type"; + public static final String NOTIFICATION_REQUEST_ORIGINATOR_ENTITY_ID_PROPERTY = "originator_entity_id"; + public static final String NOTIFICATION_REQUEST_ORIGINATOR_ENTITY_TYPE_PROPERTY = "originator_entity_type"; public static final String NOTIFICATION_REQUEST_ADDITIONAL_CONFIG_PROPERTY = "additional_config"; public static final String NOTIFICATION_REQUEST_STATUS_PROPERTY = "status"; public static final String NOTIFICATION_REQUEST_RULE_ID_PROPERTY = "rule_id"; - public static final String NOTIFICATION_REQUEST_ALARM_ID_PROPERTY = "alarm_id"; public static final String NOTIFICATION_RULE_TABLE_NAME = "notification_rule"; // ... diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java index c79ba6f30b..c94ec80016 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationInfo; +import org.thingsboard.server.common.data.notification.NotificationOriginatorType; import org.thingsboard.server.common.data.notification.NotificationSeverity; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.dao.model.BaseSqlEntity; @@ -66,6 +67,10 @@ public class NotificationEntity extends BaseSqlEntity { @Column(name = ModelConstants.NOTIFICATION_SEVERITY_PROPERTY) private NotificationSeverity severity; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.NOTIFICATION_ORIGINATOR_TYPE_PROPERTY) + private NotificationOriginatorType originatorType; + @Enumerated(EnumType.STRING) @Column(name = ModelConstants.NOTIFICATION_STATUS_PROPERTY) private NotificationStatus status; @@ -83,6 +88,7 @@ public class NotificationEntity extends BaseSqlEntity { setInfo(JacksonUtil.valueToTree(notification.getInfo())); } setSeverity(notification.getSeverity()); + setOriginatorType(notification.getOriginatorType()); setStatus(notification.getStatus()); } @@ -99,6 +105,7 @@ public class NotificationEntity extends BaseSqlEntity { notification.setInfo(JacksonUtil.treeToValue(info, NotificationInfo.class)); } notification.setSeverity(severity); + notification.setOriginatorType(originatorType); notification.setStatus(status); return notification; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java index 3c7b525628..52b5aee2b9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java @@ -21,13 +21,16 @@ import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.NotificationInfo; +import org.thingsboard.server.common.data.notification.NotificationOriginatorType; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; import org.thingsboard.server.common.data.notification.NotificationRequestStatus; @@ -70,6 +73,20 @@ public class NotificationRequestEntity extends BaseSqlEntity findNotificationRequestsByRuleIdAndAlarmId(TenantId tenantId, NotificationRuleId ruleId, AlarmId alarmId) { - return notificationRequestDao.findByRuleIdAndAlarmId(tenantId, ruleId, alarmId); + public List findNotificationRequestsByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId) { + return notificationRequestDao.findByRuleIdAndOriginatorEntityId(tenantId, ruleId, originatorEntityId); } // ON DELETE CASCADE is used: notifications for request are deleted as well diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java index 76778c64b5..2036105ffb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.dao.notification; -import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationRequest; @@ -30,7 +30,7 @@ public interface NotificationRequestDao extends Dao { PageData findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); - List findByRuleIdAndAlarmId(TenantId tenantId, NotificationRuleId ruleId, AlarmId alarmId); + List findByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId); PageData findAllByStatus(NotificationRequestStatus status, PageLink pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java index 7684ed5865..b58fc198a7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java @@ -19,7 +19,7 @@ import com.google.common.base.Strings; import lombok.RequiredArgsConstructor; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationRequest; @@ -49,8 +49,8 @@ public class JpaNotificationRequestDao extends JpaAbstractDao findByRuleIdAndAlarmId(TenantId tenantId, NotificationRuleId ruleId, AlarmId alarmId) { - return DaoUtil.convertDataList(notificationRequestRepository.findAllByRuleIdAndAlarmId(ruleId.getId(), alarmId.getId())); + public List findByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId) { + return DaoUtil.convertDataList(notificationRequestRepository.findAllByRuleIdAndOriginatorEntityTypeAndOriginatorEntityId(ruleId.getId(), originatorEntityId.getEntityType(), originatorEntityId.getId())); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java index f892767a83..40ae696979 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java @@ -21,6 +21,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.dao.model.sql.NotificationRequestEntity; @@ -36,7 +37,7 @@ public interface NotificationRequestRepository extends JpaRepository findByTenantIdAndSearchText(@Param("tenantId") UUID tenantId, @Param("searchText") String searchText, Pageable pageable); - List findAllByRuleIdAndAlarmId(UUID ruleId, UUID alarmId); + List findAllByRuleIdAndOriginatorEntityTypeAndOriginatorEntityId(UUID ruleId, EntityType originatorEntityType, UUID originatorEntityId); Page findAllByStatus(NotificationRequestStatus status, Pageable pageable); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java index ff156eb51c..b825d36892 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.AlarmData; @@ -101,6 +102,9 @@ public class AlarmDataAdapter { } else { alarm.setPropagateRelationTypes(Collections.emptyList()); } + if (row.get(ModelConstants.ALARM_NOTIFICATION_RULE_ID) != null) { + alarm.setNotificationRuleId(new NotificationRuleId((UUID) row.get(ModelConstants.ALARM_NOTIFICATION_RULE_ID))); + } UUID entityUuid = (UUID) row.get(ModelConstants.ENTITY_ID_COLUMN); EntityId entityId = entityIdMap.get(entityUuid); Object originatorNameObj = row.get(ModelConstants.ALARM_ORIGINATOR_NAME_PROPERTY); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java index 69cb4a2d86..94eebc7f6b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java @@ -104,6 +104,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { " a.tenant_id as tenant_id, " + " a.customer_id as customer_id, " + " a.propagate_relation_types as propagate_relation_types, " + + " a.notification_rule_id as notification_rule_id, " + " a.type as type," + SELECT_ORIGINATOR_NAME + ", "; private static final String JOIN_ENTITY_ALARMS = "inner join entity_alarm ea on a.id = ea.alarm_id"; diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql index 019a8b1771..0b95809b91 100644 --- a/dao/src/main/resources/sql/schema-entities-idx.sql +++ b/dao/src/main/resources/sql/schema-entities-idx.sql @@ -74,12 +74,12 @@ CREATE INDEX IF NOT EXISTS idx_rule_node_type ON rule_node(type); CREATE INDEX IF NOT EXISTS idx_api_usage_state_entity_id ON api_usage_state(entity_id); -CREATE INDEX IF NOT EXISTS idx_notification_target_tenant_id_and_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 INDEX IF NOT EXISTS idx_notification_request_tenant_id_and_created_time ON notification_request(tenant_id, 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); CREATE INDEX IF NOT EXISTS idx_notification_id ON notification(id); -CREATE INDEX IF NOT EXISTS idx_notification_notification_request_id ON notification(request_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_and_created_time ON notification(recipient_id, created_time DESC); +CREATE INDEX IF NOT EXISTS idx_notification_notification_request_id ON notification(request_id); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 1ef732b8b6..885608c7f5 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -58,7 +58,8 @@ CREATE TABLE IF NOT EXISTS alarm ( propagate_relation_types varchar, type varchar(255), propagate_to_owner boolean, - propagate_to_tenant boolean + propagate_to_tenant boolean, + notification_rule_id uuid ); CREATE TABLE IF NOT EXISTS entity_alarm ( @@ -799,10 +800,12 @@ CREATE TABLE IF NOT EXISTS notification_request ( text_template VARCHAR NOT NULL, notification_info VARCHAR(1000), notification_severity VARCHAR(32), - additional_config VARCHAR(1000), - status VARCHAR(32), + originator_type VARCHAR(32) NOT NULL, + originator_entity_id UUID, + originator_entity_type VARCHAR(32), rule_id UUID NULL CONSTRAINT fk_notification_request_rule_id REFERENCES notification_rule(id), - alarm_id UUID + additional_config VARCHAR(1000), + status VARCHAR(32) ); CREATE TABLE IF NOT EXISTS notification ( @@ -815,5 +818,6 @@ CREATE TABLE IF NOT EXISTS notification ( text VARCHAR NOT NULL, info VARCHAR(1000), severity VARCHAR(32), + originator_type VARCHAR(32) NOT NULL, status VARCHAR(32) ) PARTITION BY RANGE (created_time); diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationSubscriptionService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationManager.java similarity index 92% rename from application/src/main/java/org/thingsboard/server/service/notification/NotificationSubscriptionService.java rename to rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationManager.java index 902ad39470..ac336e9fe4 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/NotificationSubscriptionService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationManager.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.notification; +package org.thingsboard.rule.engine.api; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; @@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.NotificationRequest; -public interface NotificationSubscriptionService { +public interface NotificationManager { NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 7ca31da24e..ede2f716e5 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -267,7 +267,7 @@ public interface TbContext { SmsSenderFactory getSmsSenderFactory(); - RuleEngineNotificationService getNotificationService(); + NotificationManager getNotificationManager(); /** * Creates JS Script Engine diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java index 06fc0b110d..f87debd4dc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java @@ -23,6 +23,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.notification.NotificationOriginatorType; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -58,9 +59,11 @@ public class TbNotificationNode implements TbNode { .notificationReason(config.getNotificationReason()) .textTemplate(TbNodeUtils.processPattern(config.getNotificationTextTemplate(), msg)) .notificationSeverity(config.getNotificationSeverity()) + .originatorType(NotificationOriginatorType.RULE_NODE) + .originatorEntityId(ctx.getTenantId()) .build(); withCallback(ctx.getDbCallbackExecutor().executeAsync(() -> { - return ctx.getNotificationService().processNotificationRequest(ctx.getTenantId(), notificationRequest); + return ctx.getNotificationManager().processNotificationRequest(ctx.getTenantId(), notificationRequest); }), r -> { TbMsgMetaData msgMetaData = msg.getMetaData().copy(); From 165e86c82b18f59001035cdbf4a8eafdaed9641f Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Tue, 8 Nov 2022 16:04:50 +0200 Subject: [PATCH 011/111] Notification rules processing; refactoring --- .../main/data/upgrade/3.4.2/schema_update.sql | 10 ++- .../NotificationRuleController.java | 81 +++++++++++++++++++ .../NotificationTargetController.java | 5 +- .../controller/plugin/TbWebSocketHandler.java | 14 ++-- .../DefaultNotificationManager.java | 13 ++- ...aultNotificationRuleProcessingService.java | 73 +++++++++-------- .../NotificationRuleProcessingService.java | 7 +- .../queue/DefaultTbCoreConsumerService.java | 2 + .../service/security/permission/Resource.java | 3 +- .../permission/TenantAdminPermissions.java | 1 + .../DefaultSubscriptionManagerService.java | 1 + .../subscription/TbSubscriptionUtils.java | 9 ++- .../DefaultAlarmSubscriptionService.java | 1 - .../DefaultNotificationCommandsHandler.java | 45 +++++++---- .../sub/NotificationsSubscription.java | 23 +++++- .../controller/TbTestWebSocketClient.java | 12 ++- .../notification/NotificationApiTest.java | 16 ++-- .../notification/NotificationApiWsClient.java | 33 +++++++- common/cluster-api/src/main/proto/queue.proto | 1 + .../notification/NotificationRuleService.java | 6 ++ .../NotificationTargetService.java | 2 +- .../server/common/data/EntityType.java | 2 +- .../common/data/id/EntityIdFactory.java | 2 + .../common/data/id/NotificationRuleId.java | 10 +-- .../notification/NotificationRequest.java | 2 +- .../rule/NotificationEscalationConfig.java | 31 +++++++ .../notification/rule/NotificationRule.java | 7 +- .../server/dao/model/ModelConstants.java | 4 +- .../dao/model/sql/NotificationRuleEntity.java | 19 +++++ .../DefaultNotificationRuleService.java | 19 +++++ .../DefaultNotificationTargetService.java | 2 +- .../dao/notification/NotificationRuleDao.java | 6 ++ .../notification/JpaNotificationRuleDao.java | 11 +++ .../NotificationRuleRepository.java | 5 ++ .../main/resources/sql/schema-entities.sql | 8 +- 35 files changed, 379 insertions(+), 107 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationEscalationConfig.java diff --git a/application/src/main/data/upgrade/3.4.2/schema_update.sql b/application/src/main/data/upgrade/3.4.2/schema_update.sql index 01113f2324..3dcc7c3a07 100644 --- a/application/src/main/data/upgrade/3.4.2/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.2/schema_update.sql @@ -19,12 +19,18 @@ CREATE TABLE IF NOT EXISTS notification_target ( created_time BIGINT NOT NULL, tenant_id UUID NOT NULL, name VARCHAR(255) NOT NULL, - configuration varchar(1000) NOT NULL + configuration VARCHAR(1000) NOT NULL ); 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_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, + tenant_id UUID NOT NULL, + name VARCHAR(255) NOT NULL, + notification_text_template VARCHAR NOT NULL, + initial_notification_target_id UUID NULL CONSTRAINT fk_notification_rule_target_id REFERENCES notification_target(id), + escalation_config VARCHAR(500) ); ALTER TABLE alarm ADD COLUMN IF NOT EXISTS notification_rule_id UUID; diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java new file mode 100644 index 0000000000..78f5827c6d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java @@ -0,0 +1,81 @@ +/** + * 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.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.NotificationRuleId; +import org.thingsboard.server.common.data.notification.rule.NotificationRule; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.notification.NotificationRuleService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; + +import java.util.UUID; + +@RestController +@TbCoreComponent +@RequestMapping("/api") +@RequiredArgsConstructor +@Slf4j +public class NotificationRuleController extends BaseController { + // todo: logEntityAction + private final NotificationRuleService notificationRuleService; + + @PostMapping("/notification/rule") + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + public NotificationRule saveNotificationRule(@RequestBody NotificationRule notificationRule, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { +// accessControlService.checkPermission(user, Resource.NOTIFICATION_RULE, notificationRule.getId() == null ? Operation.CREATE : Operation.WRITE, notificationRule.getId(), ); + return notificationRuleService.saveNotificationRule(user.getTenantId(), notificationRule); + } + + @GetMapping("/notification/rule/{id}") + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + public NotificationRule getNotificationRuleById(@PathVariable UUID id, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + NotificationRuleId notificationRuleId = new NotificationRuleId(id); + NotificationRule notificationRule = notificationRuleService.findNotificationRuleById(user.getTenantId(), notificationRuleId); + accessControlService.checkPermission(user, Resource.NOTIFICATION_RULE, Operation.READ, notificationRuleId, notificationRule); + return notificationRule; + } + + @GetMapping("/notification/rules") + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + public PageData getNotificationRules(@RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return notificationRuleService.findNotificationRulesByTenantId(user.getTenantId(), pageLink); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java index 34451ddede..22ab0475c2 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java @@ -32,20 +32,17 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.NotificationTargetId; -import org.thingsboard.server.common.data.id.UserId; 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.NotificationTargetConfigType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; -import java.util.List; import java.util.UUID; @RestController @@ -120,7 +117,7 @@ public class NotificationTargetController extends BaseController { @RequestParam(required = false) String sortOrder, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return notificationTargetService.findNotificationTargetsByTenantIdAndPageLink(user.getTenantId(), pageLink); + return notificationTargetService.findNotificationTargetsByTenantId(user.getTenantId(), pageLink); } @DeleteMapping("/target/{id}") diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index edd6843c2f..00f3be68c4 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -41,9 +41,9 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.ws.SessionEvent; import org.thingsboard.server.service.ws.WebSocketMsgEndpoint; -import org.thingsboard.server.service.ws.WebSocketSessionType; import org.thingsboard.server.service.ws.WebSocketService; import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.WebSocketSessionType; import javax.websocket.RemoteEndpoint; import javax.websocket.SendHandler; @@ -58,6 +58,7 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; import static org.thingsboard.server.service.ws.DefaultWebSocketService.NUMBER_OF_PING_ATTEMPTS; @@ -215,7 +216,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke private final RemoteEndpoint.Async asyncRemote; private final WebSocketSessionRef sessionRef; - private volatile boolean isSending = false; + private final AtomicBoolean isSending = new AtomicBoolean(false); private final Queue> msgQueue; private volatile long lastActivityTime; @@ -262,7 +263,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } synchronized void sendMsg(TbWebSocketMsg msg) { - if (isSending) { + if (isSending.compareAndSet(false, true)) { + sendMsgInternal(msg); + } else { try { msgQueue.add(msg); } catch (RuntimeException e) { @@ -273,9 +276,6 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } closeSession(CloseStatus.POLICY_VIOLATION.withReason("Max pending updates limit reached!")); } - } else { - isSending = true; - sendMsgInternal(msg); } } @@ -310,7 +310,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke if (msg != null) { sendMsgInternal(msg); } else { - isSending = false; + isSending.set(false); } } } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java index 4bb8afc61d..f4232fb72d 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java @@ -39,8 +39,10 @@ import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.NotificationsTopicService; import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.telemetry.AbstractSubscriptionService; @@ -61,24 +63,27 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl private final NotificationService notificationService; private final DbCallbackExecutorService dbCallbackExecutorService; private final NotificationsTopicService notificationsTopicService; + private final TbQueueProducerProvider producerProvider; public DefaultNotificationManager(TbClusterService clusterService, PartitionService partitionService, NotificationTargetService notificationTargetService, NotificationRequestService notificationRequestService, NotificationService notificationService, DbCallbackExecutorService dbCallbackExecutorService, - NotificationsTopicService notificationsTopicService) { + NotificationsTopicService notificationsTopicService, + TbQueueProducerProvider producerProvider) { super(clusterService, partitionService); this.notificationTargetService = notificationTargetService; this.notificationRequestService = notificationRequestService; this.notificationService = notificationService; this.dbCallbackExecutorService = dbCallbackExecutorService; this.notificationsTopicService = notificationsTopicService; + this.producerProvider = producerProvider; } @Override public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { - log.info("Processing notification request (tenant id: {}, notification target id: {})", tenantId, notificationRequest.getTargetId()); + log.debug("Processing notification request (tenant id: {}, notification target id: {})", tenantId, notificationRequest.getTargetId()); notificationRequest.setTenantId(tenantId); if (notificationRequest.getAdditionalConfig() != null) { NotificationRequestConfig config = notificationRequest.getAdditionalConfig(); @@ -201,11 +206,11 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl private void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate update) { log.trace("Submitting notification request update: {}", update); wsCallBackExecutor.submit(() -> { - TransportProtos.ToCoreMsg notificationRequestDeletedProto = TbSubscriptionUtils.notificationRequestUpdateToProto(tenantId, update); + TransportProtos.ToCoreNotificationMsg notificationRequestUpdateProto = TbSubscriptionUtils.notificationRequestUpdateToProto(tenantId, update); Set coreServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_CORE)); for (String serviceId : coreServices) { TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, serviceId); - clusterService.pushMsgToCore(tpi, UUID.randomUUID(), notificationRequestDeletedProto, null); + producerProvider.getTbCoreNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), notificationRequestUpdateProto), null); } }); } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java index ac7e875e03..c8e472a8cc 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java @@ -29,7 +29,6 @@ import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationOriginatorType; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; -import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.NotificationSeverity; import org.thingsboard.server.common.data.notification.rule.NonConfirmedNotificationEscalation; import org.thingsboard.server.common.data.notification.rule.NotificationRule; @@ -51,53 +50,63 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul private final DbCallbackExecutorService dbCallbackExecutorService; @Override - public ListenableFuture onAlarmCreatedOrUpdated(TenantId tenantId, Alarm alarm) { - return processAlarmUpdate(tenantId, alarm); + public ListenableFuture onAlarmCreatedOrUpdated(TenantId tenantId, Alarm alarm) { + return processAlarmUpdate(tenantId, alarm, false); } @Override - public ListenableFuture onAlarmAcknowledged(TenantId tenantId, Alarm alarm) { - return processAlarmUpdate(tenantId, alarm); + public ListenableFuture onAlarmDeleted(TenantId tenantId, Alarm alarm) { + return processAlarmUpdate(tenantId, alarm, true); } - private ListenableFuture processAlarmUpdate(TenantId tenantId, Alarm alarm) { + private ListenableFuture processAlarmUpdate(TenantId tenantId, Alarm alarm, boolean deleted) { if (alarm.getNotificationRuleId() == null) return Futures.immediateFuture(null); return dbCallbackExecutorService.submit(() -> { - onAlarmUpdate(tenantId, alarm.getNotificationRuleId(), alarm); + onAlarmUpdate(tenantId, alarm.getNotificationRuleId(), alarm, deleted); + return null; }); } - private void onAlarmUpdate(TenantId tenantId, NotificationRuleId notificationRuleId, Alarm alarm) { + @Override + public ListenableFuture onNotificationRuleDeleted(TenantId tenantId, NotificationRuleId ruleId) { + return dbCallbackExecutorService.submit(() -> { + // need to remove fk constraint in notificationRequest to rule + // todo: do we need to remove all notifications when notification request is deleted? + return null; + }); + } + + // todo: think about: what if notification rule was updated? + private void onAlarmUpdate(TenantId tenantId, NotificationRuleId notificationRuleId, Alarm alarm, boolean deleted) { List notificationRequests = notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityId(tenantId, notificationRuleId, alarm.getId()); NotificationRule notificationRule = notificationRuleService.findNotificationRuleById(tenantId, notificationRuleId); + if (notificationRule == null) return; - if (notificationRequests.isEmpty()) { // in case it is first notification for alarm, or it was previously acked and now we need to send notifications again - NotificationTargetId initialNotificationTargetId = notificationRule.getInitialNotificationTargetId(); - submitNotificationRequest(tenantId, initialNotificationTargetId, notificationRule, alarm, 0); + if (alarmAcknowledged(alarm) || deleted) { + for (NotificationRequest notificationRequest : notificationRequests) { + notificationManager.deleteNotificationRequest(tenantId, notificationRequest.getId()); + // todo: or should we mark already sent notifications as read and delete only scheduled? + } + return; + } - for (NonConfirmedNotificationEscalation escalation : notificationRule.getEscalations()) { - submitNotificationRequest(tenantId, escalation.getNotificationTargetId(), notificationRule, alarm, escalation.getDelayInMinutes()); + if (notificationRequests.isEmpty()) { + NotificationTargetId initialNotificationTargetId = notificationRule.getInitialNotificationTargetId(); + if (initialNotificationTargetId != null) { + submitNotificationRequest(tenantId, initialNotificationTargetId, notificationRule, alarm, 0); } - } else { - if (alarmAcknowledged(alarm)) { - for (NotificationRequest notificationRequest : notificationRequests) { - if (notificationRequest.getStatus() == NotificationRequestStatus.SCHEDULED) { - // using regular service due to no need to send an update to subscription manager - notificationRequestService.deleteNotificationRequestById(tenantId, notificationRequest.getId()); - } else { - notificationManager.deleteNotificationRequest(tenantId, notificationRequest.getId()); - // todo: or should we mark already sent notifications as read? - } + if (notificationRule.getEscalationConfig() != null) { + for (NonConfirmedNotificationEscalation escalation : notificationRule.getEscalationConfig().getEscalations()) { + submitNotificationRequest(tenantId, escalation.getNotificationTargetId(), notificationRule, alarm, escalation.getDelayInMinutes()); } - } else { - NotificationInfo newNotificationInfo = constructNotificationInfo(alarm, notificationRule); - for (NotificationRequest notificationRequest : notificationRequests) { - NotificationInfo previousNotificationInfo = notificationRequest.getNotificationInfo(); - if (!previousNotificationInfo.equals(newNotificationInfo)) { - notificationRequest.setNotificationInfo(newNotificationInfo); - notificationManager.updateNotificationRequest(tenantId, notificationRequest); - } - // fixme: no need to send an update event for scheduled requests, only for sent + } + } else { + NotificationInfo newNotificationInfo = constructNotificationInfo(alarm, notificationRule); + for (NotificationRequest notificationRequest : notificationRequests) { + NotificationInfo previousNotificationInfo = notificationRequest.getNotificationInfo(); + if (!previousNotificationInfo.equals(newNotificationInfo)) { + notificationRequest.setNotificationInfo(newNotificationInfo); + notificationManager.updateNotificationRequest(tenantId, notificationRequest); } } } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationRuleProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationRuleProcessingService.java index 4f3d0f7d9e..2283e49cdd 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/NotificationRuleProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationRuleProcessingService.java @@ -17,12 +17,15 @@ package org.thingsboard.server.service.notification; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; public interface NotificationRuleProcessingService { - ListenableFuture onAlarmCreatedOrUpdated(TenantId tenantId, Alarm alarm); + ListenableFuture onAlarmCreatedOrUpdated(TenantId tenantId, Alarm alarm); - ListenableFuture onAlarmAcknowledged(TenantId tenantId, Alarm alarm); + ListenableFuture onAlarmDeleted(TenantId tenantId, Alarm alarm); + + ListenableFuture onNotificationRuleDeleted(TenantId tenantId, NotificationRuleId ruleId); } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 60a9567488..518197f690 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -347,6 +347,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService alarm, true ); + notificationRuleProcessingService.onAlarmDeleted(tenantId, alarm); callback.onSuccess(); } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java index 58fd7d6db7..d35f7eb275 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java @@ -50,6 +50,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesDeletePr import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesSubscriptionProto; import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; @@ -380,7 +381,7 @@ public class TbSubscriptionUtils { return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build(); } - public static TransportProtos.ToCoreNotificationMsg notificationsSubUpdateToProto(TbSubscription subscription, NotificationsSubscriptionUpdate update) { + public static ToCoreNotificationMsg notificationsSubUpdateToProto(TbSubscription subscription, NotificationsSubscriptionUpdate update) { TransportProtos.NotificationsSubscriptionUpdateProto.Builder updateProto = TransportProtos.NotificationsSubscriptionUpdateProto.newBuilder() .setSessionId(subscription.getSessionId()) .setSubscriptionId(subscription.getSubscriptionId()); @@ -390,7 +391,7 @@ public class TbSubscriptionUtils { if (update.getNotificationRequestUpdate() != null) { updateProto.setNotificationRequestUpdate(JacksonUtil.toString(update.getNotificationRequestUpdate())); } - return TransportProtos.ToCoreNotificationMsg.newBuilder() + return ToCoreNotificationMsg.newBuilder() .setToLocalSubscriptionServiceMsg(TransportProtos.LocalSubscriptionServiceMsgProto.newBuilder() .setNotificationsSubUpdate(updateProto) .build()) @@ -412,13 +413,13 @@ public class TbSubscriptionUtils { .build(); } - public static ToCoreMsg notificationRequestUpdateToProto(TenantId tenantId, NotificationRequestUpdate notificationRequestUpdate) { + public static ToCoreNotificationMsg notificationRequestUpdateToProto(TenantId tenantId, NotificationRequestUpdate notificationRequestUpdate) { TransportProtos.NotificationRequestUpdateProto updateProto = TransportProtos.NotificationRequestUpdateProto.newBuilder() .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) .setUpdate(JacksonUtil.toString(notificationRequestUpdate)) .build(); - return ToCoreMsg.newBuilder() + return ToCoreNotificationMsg.newBuilder() .setToSubscriptionMgrMsg(SubscriptionMgrMsgProto.newBuilder() .setNotificationRequestUpdate(updateProto) .build()) diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index 947c80a175..eb84c3b40f 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -168,7 +168,6 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService }); } }); - // todo: handle notification rule } private void onAlarmDeleted(AlarmOperationResult result) { diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java index 5e31924e45..5dd8bd7096 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java @@ -105,8 +105,10 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH log.trace("[{}, subId: {}] Fetching unread notifications from DB", subscription.getSessionId(), subscription.getSubscriptionId()); PageData notifications = notificationService.findLatestUnreadNotificationsByUserId(subscription.getTenantId(), (UserId) subscription.getEntityId(), subscription.getLimit()); - subscription.getUnreadNotifications().clear(); - subscription.getUnreadNotifications().putAll(notifications.getData().stream().collect(Collectors.toMap(IdBased::getUuidId, n -> n))); + subscription.getLatestUnreadNotifications().clear(); + notifications.getData().forEach(notification -> { + subscription.getLatestUnreadNotifications().put(notification.getUuidId(), notification); + }); subscription.getTotalUnreadCounter().set((int) notifications.getTotalElements()); } @@ -129,19 +131,30 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH private void handleNotificationUpdate(NotificationsSubscription subscription, NotificationUpdate update) { log.trace("[{}, subId: {}] Handling notification update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); Notification notification = update.getNotification(); - if (notification.getStatus() == NotificationStatus.READ) { - fetchUnreadNotifications(subscription); - sendUpdate(subscription.getSessionId(), subscription.createFullUpdate()); - } else { - subscription.getUnreadNotifications().put(notification.getUuidId(), notification); - if (update.isNew()) { - subscription.getTotalUnreadCounter().incrementAndGet(); - Set beyondLimit = subscription.getUnreadNotifications().keySet().stream() - .skip(subscription.getLimit()) - .collect(Collectors.toSet()); - beyondLimit.forEach(notificationId -> subscription.getUnreadNotifications().remove(notificationId)); + if (update.isNew()) { + subscription.getLatestUnreadNotifications().put(notification.getUuidId(), notification); + subscription.getTotalUnreadCounter().incrementAndGet(); + if (subscription.getLatestUnreadNotifications().size() > subscription.getLimit()) { + Set beyondLimit = subscription.getSortedNotifications().stream().skip(subscription.getLimit()) + .map(IdBased::getUuidId).collect(Collectors.toSet()); + beyondLimit.forEach(notificationId -> subscription.getLatestUnreadNotifications().remove(notificationId)); } sendUpdate(subscription.getSessionId(), subscription.createPartialUpdate(notification)); + } else { + if (notification.getStatus() != NotificationStatus.READ) { + if (subscription.getLatestUnreadNotifications().containsKey(notification.getUuidId())) { + subscription.getLatestUnreadNotifications().put(notification.getUuidId(), notification); + sendUpdate(subscription.getSessionId(), subscription.createPartialUpdate(notification)); + } + } else { + if (subscription.getLatestUnreadNotifications().containsKey(notification.getUuidId())) { + fetchUnreadNotifications(subscription); + sendUpdate(subscription.getSessionId(), subscription.createFullUpdate()); + } else { + subscription.getTotalUnreadCounter().decrementAndGet(); + sendUpdate(subscription.getSessionId(), subscription.createCountUpdate()); + } + } } } @@ -149,14 +162,14 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH log.trace("[{}, subId: {}] Handling notification request update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); NotificationRequestId notificationRequestId = update.getNotificationRequestId(); if (update.isDeleted()) { - if (subscription.getUnreadNotifications().values().stream() + if (subscription.getLatestUnreadNotifications().values().stream() .anyMatch(notification -> notification.getRequestId().equals(notificationRequestId))) { fetchUnreadNotifications(subscription); sendUpdate(subscription.getSessionId(), subscription.createFullUpdate()); } } else { NotificationInfo notificationInfo = update.getNotificationInfo(); - subscription.getUnreadNotifications().values().stream() + subscription.getLatestUnreadNotifications().values().stream() .filter(notification -> notification.getRequestId().equals(notificationRequestId)) .forEach(notification -> { notification.setInfo(notificationInfo); @@ -197,7 +210,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH private void sendUpdate(String sessionId, CmdUpdate update) { - log.trace("[{}] Sending WS update: {}", sessionId, update); + log.trace("[{}, cmdId: {}] Sending WS update: {}", sessionId, update.getCmdId(), update); wsService.sendWsMsg(sessionId, update); } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java index 25d6e8725e..137f49763e 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.ws.notification.sub; import lombok.Builder; import lombok.Getter; +import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.Notification; @@ -24,16 +25,19 @@ import org.thingsboard.server.service.subscription.TbSubscription; import org.thingsboard.server.service.subscription.TbSubscriptionType; import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; -import java.util.LinkedHashMap; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; +import java.util.stream.Collectors; @Getter public class NotificationsSubscription extends TbSubscription { - private final Map unreadNotifications = new LinkedHashMap<>(); + private final Map latestUnreadNotifications = new HashMap<>(); private final int limit; private final AtomicInteger totalUnreadCounter = new AtomicInteger(); @@ -48,11 +52,17 @@ public class NotificationsSubscription extends TbSubscription getSortedNotifications() { + return latestUnreadNotifications.values().stream() + .sorted(Comparator.comparing(BaseData::getCreatedTime, Comparator.reverseOrder())) + .collect(Collectors.toList()); + } + public UnreadNotificationsUpdate createPartialUpdate(Notification notification) { return UnreadNotificationsUpdate.builder() .cmdId(getSubscriptionId()) @@ -61,4 +71,11 @@ public class NotificationsSubscription extends TbSubscription notifications; + public NotificationApiWsClient(String wsUrl, String token) throws URISyntaxException { super(new URI(wsUrl + "/api/ws/plugins/notifications?token=" + token)); } @@ -50,6 +56,7 @@ public class NotificationApiWsClient extends TbTestWebSocketClient { NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper(); cmdsWrapper.setUnreadSubCmd(new NotificationsSubCmd(1, limit)); sendCmd(cmdsWrapper); + this.limit = limit; } public void subscribeForUnreadNotificationsCount() { @@ -75,8 +82,30 @@ public class NotificationApiWsClient extends TbTestWebSocketClient { CmdUpdateType updateType = CmdUpdateType.valueOf(update.get("cmdUpdateType").asText()); if (updateType == CmdUpdateType.NOTIFICATIONS) { lastDataUpdate = JacksonUtil.treeToValue(update, UnreadNotificationsUpdate.class); + unreadCount = lastDataUpdate.getTotalUnreadCount(); + if (lastDataUpdate.getNotifications() != null) { + notifications = new ArrayList<>(lastDataUpdate.getNotifications()); + } else { + Notification notificationUpdate = lastDataUpdate.getUpdate(); + boolean updated = false; + for (int i = 0; i < notifications.size(); i++) { + Notification existing = notifications.get(i); + if (existing.getId().equals(notificationUpdate.getId())) { + notifications.set(i, notificationUpdate); + updated = true; + break; + } + } + if (!updated) { + notifications.add(0, notificationUpdate); + if (notifications.size() > limit) { + notifications = notifications.subList(0, limit); + } + } + } } else if (updateType == CmdUpdateType.NOTIFICATIONS_COUNT) { lastCountUpdate = JacksonUtil.treeToValue(update, UnreadNotificationsCountUpdate.class); + unreadCount = lastCountUpdate.getTotalUnreadCount(); } super.onMessage(s); } diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index d10c337df3..05a8f41be1 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -970,6 +970,7 @@ message ToCoreNotificationMsg { VersionControlResponseMsg vcResponseMsg = 7; bytes toEdgeSyncRequestMsg = 8; bytes fromEdgeSyncResponseMsg = 9; + SubscriptionMgrMsgProto toSubscriptionMgrMsg = 10; } /* Messages that are handled by ThingsBoard RuleEngine Service */ diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.java index 291688a422..958012c8c7 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.java @@ -18,9 +18,15 @@ package org.thingsboard.server.dao.notification; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.rule.NotificationRule; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; public interface NotificationRuleService { + NotificationRule saveNotificationRule(TenantId tenantId, NotificationRule notificationRule); + NotificationRule findNotificationRuleById(TenantId tenantId, NotificationRuleId notificationRuleId); + PageData findNotificationRulesByTenantId(TenantId tenantId, PageLink pageLink); + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java index 10c7155d97..eb5c209383 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java @@ -29,7 +29,7 @@ public interface NotificationTargetService { NotificationTarget findNotificationTargetById(TenantId tenantId, NotificationTargetId id); - PageData findNotificationTargetsByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); + PageData findNotificationTargetsByTenantId(TenantId tenantId, PageLink pageLink); PageData findRecipientsForNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId, PageLink pageLink); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index bdcff739f7..37f69b575f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -21,5 +21,5 @@ package org.thingsboard.server.common.data; public enum EntityType { TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, ASSET_PROFILE, API_USAGE_STATE, TB_RESOURCE, OTA_PACKAGE, EDGE, RPC, QUEUE, - NOTIFICATION_TARGET, NOTIFICATION_REQUEST; + NOTIFICATION_TARGET, NOTIFICATION_REQUEST, NOTIFICATION_RULE; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index 012a8e269f..0462952ad0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -85,6 +85,8 @@ public class EntityIdFactory { return new NotificationTargetId(uuid); case NOTIFICATION_REQUEST: return new NotificationRequestId(uuid); + case NOTIFICATION_RULE: + return new NotificationRuleId(uuid); } throw new IllegalArgumentException("EntityType " + type + " is not supported!"); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRuleId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRuleId.java index 2b963c5b88..b9b07ca334 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRuleId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRuleId.java @@ -21,16 +21,16 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -public class NotificationRuleId extends UUIDBased { +public class NotificationRuleId extends UUIDBased implements EntityId { @JsonCreator public NotificationRuleId(@JsonProperty("id") UUID id) { super(id); } -// @Override -// public EntityType getEntityType() { -// return EntityType.NOTIFICATION_TARGET; -// } + @Override + public EntityType getEntityType() { + return EntityType.NOTIFICATION_RULE; + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java index f962db97e4..8ba27a96a8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java @@ -56,7 +56,7 @@ public class NotificationRequest extends BaseData impleme private NotificationOriginatorType originatorType; private EntityId originatorEntityId; // userId, alarmId or tenantId - private NotificationRuleId ruleId; // maybe move to child class + private NotificationRuleId ruleId; private NotificationRequestConfig additionalConfig; private NotificationRequestStatus status; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationEscalationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationEscalationConfig.java new file mode 100644 index 0000000000..8a81ad7990 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationEscalationConfig.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.notification.rule; + +import lombok.Data; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import java.util.List; + +@Data +public class NotificationEscalationConfig { + + @NotNull + @Valid + private List escalations; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java index 4d2a8ed184..9ce62f1420 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java @@ -27,10 +27,6 @@ import org.thingsboard.server.common.data.id.TenantId; import javax.validation.Valid; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @@ -44,8 +40,7 @@ public class NotificationRule extends BaseData implements Ha private String notificationTextTemplate; @NotNull private NotificationTargetId initialNotificationTargetId; - @NotNull @Valid - private List escalations; + private NotificationEscalationConfig escalationConfig; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 9d477059d6..0cb2c38c6c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -677,7 +677,9 @@ public class ModelConstants { public static final String NOTIFICATION_REQUEST_RULE_ID_PROPERTY = "rule_id"; public static final String NOTIFICATION_RULE_TABLE_NAME = "notification_rule"; - // ... + public static final String NOTIFICATION_RULE_NOTIFICATION_TEXT_TEMPLATE_PROPERTY = "notification_text_template"; + public static final String NOTIFICATION_RULE_INITIAL_NOTIFICATION_TARGET_ID_PROPERTY = "initial_notification_target_id"; + public static final String NOTIFICATION_RULE_ESCALATION_CONFIG_PROPERTY = "escalation_config"; 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}; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java index fb669a970d..f5f88d8f3a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java @@ -15,11 +15,15 @@ */ 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.NotificationRuleId; +import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.rule.NotificationEscalationConfig; import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; @@ -42,6 +46,15 @@ public class NotificationRuleEntity extends BaseSqlEntity { @Column(name = ModelConstants.NAME_PROPERTY, nullable = false) private String name; + @Column(name = ModelConstants.NOTIFICATION_RULE_NOTIFICATION_TEXT_TEMPLATE_PROPERTY, nullable = false) + private String notificationTextTemplate; + + @Column(name = ModelConstants.NOTIFICATION_RULE_INITIAL_NOTIFICATION_TARGET_ID_PROPERTY) + private UUID initialNotificationTargetId; + + @Type(type = "json") + @Column(name = ModelConstants.NOTIFICATION_RULE_ESCALATION_CONFIG_PROPERTY) + private JsonNode escalationConfig; public NotificationRuleEntity() {} @@ -50,6 +63,9 @@ public class NotificationRuleEntity extends BaseSqlEntity { setCreatedTime(notificationRule.getCreatedTime()); setTenantId(getUuid(notificationRule.getTenantId())); setName(notificationRule.getName()); + setNotificationTextTemplate(notificationRule.getNotificationTextTemplate()); + setInitialNotificationTargetId(getUuid(notificationRule.getInitialNotificationTargetId())); + setEscalationConfig(toJson(notificationRule.getEscalationConfig())); } @Override @@ -59,6 +75,9 @@ public class NotificationRuleEntity extends BaseSqlEntity { notificationRule.setCreatedTime(createdTime); notificationRule.setTenantId(createId(tenantId, TenantId::fromUUID)); notificationRule.setName(name); + notificationRule.setNotificationTextTemplate(notificationTextTemplate); + notificationRule.setInitialNotificationTargetId(createId(initialNotificationTargetId, NotificationTargetId::new)); + notificationRule.setEscalationConfig(fromJson(escalationConfig, NotificationEscalationConfig.class)); return notificationRule; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java index 7c9e404b3b..ae475a76a1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java @@ -20,6 +20,9 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.rule.NotificationRule; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.service.DataValidator; @Service @RequiredArgsConstructor @@ -27,9 +30,25 @@ public class DefaultNotificationRuleService implements NotificationRuleService { private final NotificationRuleDao notificationRuleDao; + private final NotificationRuleValidator validator = new NotificationRuleValidator(); + + @Override + public NotificationRule saveNotificationRule(TenantId tenantId, NotificationRule notificationRule) { + validator.validate(notificationRule, NotificationRule::getTenantId); + return notificationRuleDao.save(tenantId, notificationRule); + } + @Override public NotificationRule findNotificationRuleById(TenantId tenantId, NotificationRuleId notificationRuleId) { return notificationRuleDao.findById(tenantId, notificationRuleId.getId()); } + @Override + public PageData findNotificationRulesByTenantId(TenantId tenantId, PageLink pageLink) { + return notificationRuleDao.findByTenantIdAndPageLink(tenantId, pageLink); + } + + private static class NotificationRuleValidator extends DataValidator { + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java index 00892576e4..04c5403a91 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java @@ -57,7 +57,7 @@ public class DefaultNotificationTargetService implements NotificationTargetServi } @Override - public PageData findNotificationTargetsByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) { + public PageData findNotificationTargetsByTenantId(TenantId tenantId, PageLink pageLink) { return notificationTargetDao.findByTenantIdAndPageLink(tenantId, pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java index 3a7cbb777b..215766d1c8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java @@ -15,8 +15,14 @@ */ package org.thingsboard.server.dao.notification; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.rule.NotificationRule; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; public interface NotificationRuleDao extends Dao { + + PageData findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java index 365a439bd5..40c081512f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java @@ -15,10 +15,15 @@ */ package org.thingsboard.server.dao.sql.notification; +import com.google.common.base.Strings; import lombok.RequiredArgsConstructor; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.rule.NotificationRule; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.NotificationRuleEntity; import org.thingsboard.server.dao.notification.NotificationRuleDao; import org.thingsboard.server.dao.sql.JpaAbstractDao; @@ -33,6 +38,12 @@ public class JpaNotificationRuleDao extends JpaAbstractDao findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) { + return DaoUtil.toPageData(notificationRuleRepository.findByTenantIdAndNameContainingIgnoreCase(tenantId.getId(), + Strings.nullToEmpty(pageLink.getTextSearch()), DaoUtil.toPageable(pageLink))); + } + @Override protected Class getEntityClass() { return NotificationRuleEntity.class; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java index a991407ab2..fd041c52e7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.dao.sql.notification; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import org.thingsboard.server.dao.model.sql.NotificationRuleEntity; @@ -23,4 +25,7 @@ import java.util.UUID; @Repository public interface NotificationRuleRepository extends JpaRepository { + + Page findByTenantIdAndNameContainingIgnoreCase(UUID tenantId, String searchText, Pageable pageable); + } diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 885608c7f5..d25ab2f6f2 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -788,7 +788,13 @@ CREATE TABLE IF NOT EXISTS notification_target ( ); 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, + tenant_id UUID NOT NULL, + name VARCHAR(255) NOT NULL, + notification_text_template VARCHAR NOT NULL, + initial_notification_target_id UUID NULL CONSTRAINT fk_notification_rule_target_id REFERENCES notification_target(id), + escalation_config VARCHAR(500) ); CREATE TABLE IF NOT EXISTS notification_request ( From 6e8dba6e2cfc4d2a222324e797d9230b64e034aa Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Thu, 10 Nov 2022 15:42:29 +0200 Subject: [PATCH 012/111] More tests for notifications --- .../DefaultNotificationManager.java | 2 +- ...aultNotificationRuleProcessingService.java | 8 +- .../DefaultNotificationSchedulerService.java | 14 +- .../controller/TbTestWebSocketClient.java | 28 ++- .../notification/NotificationApiTest.java | 235 +++++++++++++++--- .../notification/NotificationApiWsClient.java | 7 + .../notification/NotificationsClient.java | 86 +++++++ .../NotificationRequestConfig.java | 2 +- .../NonConfirmedNotificationEscalation.java | 2 +- .../main/resources/sql/schema-entities.sql | 2 +- 10 files changed, 340 insertions(+), 46 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/service/notification/NotificationsClient.java diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java index f4232fb72d..46d9604577 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java @@ -87,7 +87,7 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl notificationRequest.setTenantId(tenantId); if (notificationRequest.getAdditionalConfig() != null) { NotificationRequestConfig config = notificationRequest.getAdditionalConfig(); - if (config.getSendingDelayInMinutes() > 0 && notificationRequest.getId() == null) { + if (config.getSendingDelayInSec() > 0 && notificationRequest.getId() == null) { notificationRequest.setStatus(NotificationRequestStatus.SCHEDULED); NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); forwardToNotificationSchedulerService(tenantId, savedNotificationRequest.getId(), false); diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java index c8e472a8cc..b103eb28f8 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java @@ -97,7 +97,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul } if (notificationRule.getEscalationConfig() != null) { for (NonConfirmedNotificationEscalation escalation : notificationRule.getEscalationConfig().getEscalations()) { - submitNotificationRequest(tenantId, escalation.getNotificationTargetId(), notificationRule, alarm, escalation.getDelayInMinutes()); + submitNotificationRequest(tenantId, escalation.getNotificationTargetId(), notificationRule, alarm, escalation.getDelayInSec()); } } } else { @@ -116,10 +116,10 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul return alarm.getStatus().isAck() && alarm.getStatus().isCleared(); } - private void submitNotificationRequest(TenantId tenantId, NotificationTargetId targetId, NotificationRule notificationRule, Alarm alarm, int delayInMinutes) { + private void submitNotificationRequest(TenantId tenantId, NotificationTargetId targetId, NotificationRule notificationRule, Alarm alarm, int delayInSec) { NotificationRequestConfig config = new NotificationRequestConfig(); - if (delayInMinutes > 0) { - config.setSendingDelayInMinutes(delayInMinutes); + if (delayInSec > 0) { + config.setSendingDelayInSec(delayInSec); } NotificationInfo notificationInfo = constructNotificationInfo(alarm, notificationRule); diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java index 36faeeee87..2ae1ca43b6 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java @@ -83,12 +83,16 @@ public class DefaultNotificationSchedulerService extends AbstractPartitionBasedS } private void scheduleNotificationRequest(TenantId tenantId, NotificationRequest request, long requestTs) { - int delayInMinutes = Optional.ofNullable(request) + int delayInSec = Optional.ofNullable(request) .map(NotificationRequest::getAdditionalConfig) - .map(NotificationRequestConfig::getSendingDelayInMinutes) + .map(NotificationRequestConfig::getSendingDelayInSec) .orElse(0); - if (delayInMinutes <= 0) return; // todo: think about: if server was down for some time and delayMs will be negative - need to send these requests as well (but when the value is within some range) - long delayMs = TimeUnit.MINUTES.toMillis(delayInMinutes) - (System.currentTimeMillis() - requestTs); + if (delayInSec <= 0) return; + long delayInMs = TimeUnit.SECONDS.toMillis(delayInSec) - (System.currentTimeMillis() - requestTs); + if (delayInMs < 0) { // in case the scheduled request processing time was during the downtime + delayInMs = 0; + // or maybe no need to process outdated notification requests ? + } ListenableScheduledFuture scheduledTask = scheduledExecutor.schedule(() -> { NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(tenantId, request.getId()); @@ -96,7 +100,7 @@ public class DefaultNotificationSchedulerService extends AbstractPartitionBasedS notificationManager.processNotificationRequest(tenantId, notificationRequest); scheduledNotificationRequests.remove(notificationRequest.getId()); - }, delayMs, TimeUnit.MILLISECONDS); + }, delayInMs, TimeUnit.MILLISECONDS); scheduledNotificationRequests.put(request.getId(), scheduledTask); } diff --git a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java index 112442afe0..aca00222a3 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java +++ b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.controller; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; @@ -43,6 +44,7 @@ import java.util.concurrent.TimeUnit; @Slf4j public class TbTestWebSocketClient extends WebSocketClient { + @Getter private volatile String lastMsg; private volatile CountDownLatch reply; private volatile CountDownLatch update; @@ -107,10 +109,18 @@ public class TbTestWebSocketClient extends WebSocketClient { } public String waitForUpdate() { - return waitForUpdate(TimeUnit.SECONDS.toMillis(3)); + return waitForUpdate(false); + } + + public String waitForUpdate(boolean throwExceptionOnTimeout) { + return waitForUpdate(TimeUnit.SECONDS.toMillis(3), throwExceptionOnTimeout); } public String waitForUpdate(long ms) { + return waitForUpdate(ms, false); + } + + public String waitForUpdate(long ms, boolean throwExceptionOnTimeout) { try { if (update.await(ms, TimeUnit.MILLISECONDS)) { return lastMsg; @@ -118,10 +128,18 @@ public class TbTestWebSocketClient extends WebSocketClient { } catch (InterruptedException e) { log.warn("Failed to await reply", e); } - return null; + if (throwExceptionOnTimeout) { + throw new AssertionError("Waited for update for " + ms + " ms but none arrived"); + } else { + return null; + } } public String waitForReply() { + return waitForReply(false); + } + + public String waitForReply(boolean throwExceptionOnTimeout) { try { if (reply.await(3, TimeUnit.SECONDS)) { return lastMsg; @@ -129,7 +147,11 @@ public class TbTestWebSocketClient extends WebSocketClient { } catch (InterruptedException e) { log.warn("Failed to await reply", e); } - return null; + if (throwExceptionOnTimeout) { + throw new AssertionError("Waited for reply for 3 seconds but none arrived"); + } else { + return null; + } } public EntityDataUpdate parseDataReply(String msg) { diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java index b524cf7c4c..3d7e55757b 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java @@ -15,14 +15,28 @@ */ package org.thingsboard.server.service.notification; +import com.fasterxml.jackson.core.type.TypeReference; +import org.assertj.core.data.Offset; +import org.java_websocket.client.WebSocketClient; import org.junit.Before; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.rule.engine.api.NotificationManager; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationRequestConfig; +import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.SingleUserNotificationTargetConfig; +import org.thingsboard.server.common.data.notification.targets.UserListNotificationTargetConfig; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.controller.TbTestWebSocketClient; import org.thingsboard.server.dao.service.DaoSqlTest; @@ -30,13 +44,22 @@ import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsCou import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; @DaoSqlTest public class NotificationApiTest extends AbstractControllerTest { + @Autowired + private NotificationManager notificationManager; + @Before public void beforeEach() throws Exception { loginTenantAdmin(); @@ -46,12 +69,12 @@ public class NotificationApiTest extends AbstractControllerTest { public void testSubscribingToUnreadNotificationsCount() { NotificationTarget notificationTarget = createNotificationTarget(tenantAdminUserId); String notificationText1 = "Notification 1"; - submitNotificationRequest(notificationTarget.getId(), "Just a test", notificationText1); + submitNotificationRequest(notificationTarget.getId(), notificationText1); String notificationText2 = "Notification 2"; - submitNotificationRequest(notificationTarget.getId(), "Just a test", notificationText2); + submitNotificationRequest(notificationTarget.getId(), notificationText2); getWsClient().subscribeForUnreadNotificationsCount(); - getWsClient().waitForReply(); + getWsClient().waitForReply(true); UnreadNotificationsCountUpdate update = getWsClient().getLastCountUpdate(); assertThat(update.getTotalUnreadCount()).isEqualTo(2); @@ -61,17 +84,17 @@ public class NotificationApiTest extends AbstractControllerTest { public void testReceivingCountUpdates_multipleSessions() { getWsClient().subscribeForUnreadNotificationsCount(); getAnotherWsClient().subscribeForUnreadNotificationsCount(); - getWsClient().waitForReply(); - getAnotherWsClient().waitForReply(); + getWsClient().waitForReply(true); + getAnotherWsClient().waitForReply(true); assertThat(getWsClient().getLastCountUpdate().getTotalUnreadCount()).isZero(); getWsClient().registerWaitForUpdate(); getAnotherWsClient().registerWaitForUpdate(); NotificationTarget notificationTarget = createNotificationTarget(tenantAdminUserId); String notificationText = "Notification"; - submitNotificationRequest(notificationTarget.getId(), "Just a test", notificationText); - getWsClient().waitForUpdate(); - getAnotherWsClient().waitForUpdate(); + submitNotificationRequest(notificationTarget.getId(), notificationText); + getWsClient().waitForUpdate(true); + getAnotherWsClient().waitForUpdate(true); assertThat(getWsClient().getLastCountUpdate().getTotalUnreadCount()).isOne(); assertThat(getAnotherWsClient().getLastCountUpdate().getTotalUnreadCount()).isOne(); @@ -81,14 +104,14 @@ public class NotificationApiTest extends AbstractControllerTest { public void testSubscribingToUnreadNotifications_multipleSessions() throws Exception { NotificationTarget notificationTarget = createNotificationTarget(tenantAdminUserId); String notificationText1 = "Notification 1"; - submitNotificationRequest(notificationTarget.getId(), "Just a test", notificationText1); + submitNotificationRequest(notificationTarget.getId(), notificationText1); String notificationText2 = "Notification 2"; - submitNotificationRequest(notificationTarget.getId(), "Just a test", notificationText2); + submitNotificationRequest(notificationTarget.getId(), notificationText2); getWsClient().subscribeForUnreadNotifications(10); getAnotherWsClient().subscribeForUnreadNotifications(10); - getWsClient().waitForReply(); - getAnotherWsClient().waitForReply(); + getWsClient().waitForReply(true); + getAnotherWsClient().waitForReply(true); checkFullNotificationsUpdate(getWsClient().getLastDataUpdate(), notificationText1, notificationText2); checkFullNotificationsUpdate(getAnotherWsClient().getLastDataUpdate(), notificationText1, notificationText2); @@ -98,8 +121,8 @@ public class NotificationApiTest extends AbstractControllerTest { public void testReceivingNotificationUpdates_multipleSessions() { getWsClient().subscribeForUnreadNotifications(10); getAnotherWsClient().subscribeForUnreadNotifications(10); - getWsClient().waitForReply(); - getAnotherWsClient().waitForReply(); + getWsClient().waitForReply(true); + getAnotherWsClient().waitForReply(true); UnreadNotificationsUpdate notificationsUpdate = getWsClient().getLastDataUpdate(); assertThat(notificationsUpdate.getTotalUnreadCount()).isZero(); @@ -107,9 +130,9 @@ public class NotificationApiTest extends AbstractControllerTest { getAnotherWsClient().registerWaitForUpdate(); NotificationTarget notificationTarget = createNotificationTarget(tenantAdminUserId); String notificationText = "Notification 1"; - submitNotificationRequest(notificationTarget.getId(), "Just a test", notificationText); - assertThat(getWsClient().waitForUpdate()).isNotNull(); - assertThat(getAnotherWsClient().waitForUpdate()).isNotNull(); + submitNotificationRequest(notificationTarget.getId(), notificationText); + getWsClient().waitForUpdate(true); + getAnotherWsClient().waitForUpdate(true); checkPartialNotificationsUpdate(getWsClient().getLastDataUpdate(), notificationText, 1); checkPartialNotificationsUpdate(getAnotherWsClient().getLastDataUpdate(), notificationText, 1); @@ -119,26 +142,26 @@ public class NotificationApiTest extends AbstractControllerTest { public void testMarkingAsRead_multipleSessions() { getWsClient().subscribeForUnreadNotifications(10); getAnotherWsClient().subscribeForUnreadNotifications(10); - getWsClient().waitForReply(); - getAnotherWsClient().waitForReply(); + getWsClient().waitForReply(true); + getAnotherWsClient().waitForReply(true); getAnotherWsClient().subscribeForUnreadNotificationsCount(); - getAnotherWsClient().waitForReply(); + getAnotherWsClient().waitForReply(true); NotificationTarget notificationTarget = createNotificationTarget(tenantAdminUserId); getWsClient().registerWaitForUpdate(); getAnotherWsClient().registerWaitForUpdate(2); String notificationText1 = "Notification 1"; - submitNotificationRequest(notificationTarget.getId(), "Just a test", notificationText1); - assertThat(getWsClient().waitForUpdate()).isNotNull(); - assertThat(getAnotherWsClient().waitForUpdate()).isNotNull(); + submitNotificationRequest(notificationTarget.getId(), notificationText1); + getWsClient().waitForUpdate(true); + getAnotherWsClient().waitForUpdate(true); Notification notification1 = getWsClient().getLastDataUpdate().getUpdate(); getWsClient().registerWaitForUpdate(); getAnotherWsClient().registerWaitForUpdate(2); String notificationText2 = "Notification 2"; - submitNotificationRequest(notificationTarget.getId(), "Just a test", notificationText2); - assertThat(getWsClient().waitForUpdate()).isNotNull(); - assertThat(getAnotherWsClient().waitForUpdate()).isNotNull(); + submitNotificationRequest(notificationTarget.getId(), notificationText2); + getWsClient().waitForUpdate(true); + getAnotherWsClient().waitForUpdate(true); assertThat(getWsClient().getLastDataUpdate().getTotalUnreadCount()).isEqualTo(2); assertThat(getAnotherWsClient().getLastDataUpdate().getTotalUnreadCount()).isEqualTo(2); assertThat(getAnotherWsClient().getLastCountUpdate().getTotalUnreadCount()).isEqualTo(2); @@ -146,14 +169,139 @@ public class NotificationApiTest extends AbstractControllerTest { getWsClient().registerWaitForUpdate(); getAnotherWsClient().registerWaitForUpdate(2); getWsClient().markNotificationAsRead(notification1.getUuidId()); - assertThat(getWsClient().waitForUpdate()).isNotNull(); - assertThat(getAnotherWsClient().waitForUpdate()).isNotNull(); + getWsClient().waitForUpdate(true); + getAnotherWsClient().waitForUpdate(true); checkFullNotificationsUpdate(getWsClient().getLastDataUpdate(), notificationText2); checkFullNotificationsUpdate(getAnotherWsClient().getLastDataUpdate(), notificationText2); assertThat(getAnotherWsClient().getLastCountUpdate().getTotalUnreadCount()).isOne(); } + @Test + public void testDelayedNotificationRequest() throws Exception { + getWsClient().subscribeForUnreadNotifications(5); + getWsClient().waitForReply(true); + + getWsClient().registerWaitForUpdate(); + NotificationTarget notificationTarget = createNotificationTarget(tenantAdminUserId); + String notificationText = "Was scheduled for 5 sec"; + NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), notificationText, 5); + assertThat(notificationRequest.getStatus()).isEqualTo(NotificationRequestStatus.SCHEDULED); + await().atLeast(4, TimeUnit.SECONDS) + .atMost(6, TimeUnit.SECONDS) + .until(() -> getWsClient().getLastMsg() != null); + + Notification delayedNotification = getWsClient().getLastDataUpdate().getUpdate(); + assertThat(delayedNotification).extracting(Notification::getText).isEqualTo(notificationText); + assertThat(delayedNotification.getCreatedTime() - notificationRequest.getCreatedTime()) + .isCloseTo(TimeUnit.SECONDS.toMillis(5), Offset.offset(500L)); + assertThat(findNotificationRequest(notificationRequest.getId()).getStatus()).isEqualTo(NotificationRequestStatus.PROCESSED); + } + + @Test + public void whenNotificationRequestIsDeleted_thenDeleteNotifications() throws Exception { + getWsClient().subscribeForUnreadNotifications(10); + getWsClient().waitForReply(true); + + getWsClient().registerWaitForUpdate(); + NotificationTarget notificationTarget = createNotificationTarget(tenantAdminUserId); + NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), "Test"); + getWsClient().waitForUpdate(true); + assertThat(getWsClient().getNotifications()).singleElement().extracting(Notification::getRequestId) + .isEqualTo(notificationRequest.getId()); + assertThat(getWsClient().getUnreadCount()).isOne(); + + getWsClient().registerWaitForUpdate(); + deleteNotificationRequest(notificationRequest.getId()); + getWsClient().waitForUpdate(true); + + assertThat(getWsClient().getNotifications()).isEmpty(); + assertThat(getWsClient().getUnreadCount()).isZero(); + assertThat(getMyNotifications(false, 10)).size().isZero(); + } + + @Test + public void whenNotificationRequestIsUpdated_thenUpdateNotifications() throws Exception { + getWsClient().subscribeForUnreadNotifications(10); + getWsClient().waitForReply(true); + + NotificationTarget notificationTarget = createNotificationTarget(tenantAdminUserId); + String notificationText = "Text"; + getWsClient().registerWaitForUpdate(); + NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), notificationText); + getWsClient().waitForUpdate(true); + Notification initialNotification = getWsClient().getLastDataUpdate().getUpdate(); + assertThat(getMyNotifications(false, 10)).singleElement().isEqualTo(initialNotification); + assertThat(initialNotification.getInfo()).isNotNull().isEqualTo(notificationRequest.getNotificationInfo()); + + getWsClient().registerWaitForUpdate(); + NotificationInfo newNotificationInfo = new NotificationInfo(); + newNotificationInfo.setDescription("New description"); + notificationRequest.setNotificationInfo(newNotificationInfo); + notificationManager.updateNotificationRequest(tenantId, notificationRequest); + getWsClient().waitForUpdate(true); + Notification updatedNotification = getWsClient().getLastDataUpdate().getUpdate(); + assertThat(updatedNotification.getInfo()).isEqualTo(newNotificationInfo); + assertThat(getMyNotifications(false, 10)).singleElement().isEqualTo(updatedNotification); + } + + @Test + public void testNotificationUpdatesForUsersInTarget() throws Exception { + Map wsSessions = createUsersAndSetUpWsSessions(150); + wsSessions.forEach((user, wsClient) -> { + wsClient.subscribeForUnreadNotifications(10); + wsClient.waitForReply(true); + wsClient.subscribeForUnreadNotificationsCount(); + wsClient.waitForReply(true); + }); + + NotificationTarget notificationTarget = new NotificationTarget(); + UserListNotificationTargetConfig config = new UserListNotificationTargetConfig(); + config.setUsersIds(wsSessions.keySet().stream().map(User::getUuidId).collect(Collectors.toList())); + notificationTarget.setName("150 users"); + notificationTarget.setTenantId(tenantId); + notificationTarget.setConfiguration(config); + notificationTarget = saveNotificationTarget(notificationTarget); + + wsSessions.forEach((user, wsClient) -> wsClient.registerWaitForUpdate(2)); + NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), "Hello, ${recipientEmail}"); + await().atMost(3, TimeUnit.SECONDS) + .pollDelay(1, TimeUnit.SECONDS).pollInterval(500, TimeUnit.MILLISECONDS) + .until(() -> wsSessions.values().stream() + .allMatch(wsClient -> wsClient.getLastDataUpdate() != null + && wsClient.getLastCountUpdate() != null)); + + wsSessions.forEach((user, wsClient) -> { + assertThat(wsClient.getLastDataUpdate().getTotalUnreadCount()).isOne(); + assertThat(wsClient.getLastCountUpdate().getTotalUnreadCount()).isOne(); + + Notification notification = wsClient.getLastDataUpdate().getUpdate(); + assertThat(notification.getRecipientId()).isEqualTo(user.getId()); + assertThat(notification.getRequestId()).isEqualTo(notificationRequest.getId()); + assertThat(notification.getText()).isEqualTo("Hello, " + user.getEmail()); + }); + wsSessions.values().forEach(WebSocketClient::close); + } + + private Map createUsersAndSetUpWsSessions(int count) throws Exception { + List users = new LinkedList<>(); + for (int i = 1; i <= count; i++) { + User user = new User(); + user.setTenantId(tenantId); + user.setAuthority(Authority.TENANT_ADMIN); + user.setEmail("test-user-" + i + "@thingsboard.org"); + users.add(createUser(user, "12345678")); + } + Map wsSessions = new HashMap<>(); + for (User user : users) { + login(user.getEmail(), "12345678"); + NotificationApiWsClient wsClient = (NotificationApiWsClient) buildAndConnectWebSocketClient(); + wsSessions.put(user, wsClient); + } + loginTenantAdmin(); + return wsSessions; + } + private void checkFullNotificationsUpdate(UnreadNotificationsUpdate notificationsUpdate, String... expectedNotifications) { assertThat(notificationsUpdate.getNotifications()).extracting(Notification::getText).containsOnly(expectedNotifications); @@ -172,19 +320,46 @@ public class NotificationApiTest extends AbstractControllerTest { SingleUserNotificationTargetConfig config = new SingleUserNotificationTargetConfig(); config.setUserId(userId.getId()); notificationTarget.setConfiguration(config); + return saveNotificationTarget(notificationTarget); + } + + private NotificationTarget saveNotificationTarget(NotificationTarget notificationTarget) { return doPost("/api/notification/target", notificationTarget, NotificationTarget.class); } - private NotificationRequest submitNotificationRequest(NotificationTargetId targetId, String notificationReason, String text) { + private NotificationRequest submitNotificationRequest(NotificationTargetId targetId, String text) { + return submitNotificationRequest(targetId, text, 0); + } + + private NotificationRequest submitNotificationRequest(NotificationTargetId targetId, String text, int delayInSec) { + NotificationRequestConfig config = new NotificationRequestConfig(); + config.setSendingDelayInSec(delayInSec); + NotificationInfo notificationInfo = new NotificationInfo(); + notificationInfo.setDescription("The text: " + text); NotificationRequest notificationRequest = NotificationRequest.builder() .tenantId(tenantId) .targetId(targetId) - .notificationReason(notificationReason) + .notificationReason("Test") .textTemplate(text) + .notificationInfo(notificationInfo) + .additionalConfig(config) .build(); return doPost("/api/notification/request", notificationRequest, NotificationRequest.class); } + private NotificationRequest findNotificationRequest(NotificationRequestId id) throws Exception { + return doGet("/api/notification/request/" + id, NotificationRequest.class); + } + + private void deleteNotificationRequest(NotificationRequestId id) throws Exception { + doDelete("/api/notification/request/" + id); + } + + private List getMyNotifications(boolean unreadOnly, int limit) throws Exception { + return doGetTypedWithPageLink("/api/notifications?unreadOnly={unreadOnly}&", new TypeReference>() {}, + new PageLink(limit, 0), unreadOnly).getData(); + } + @Override protected TbTestWebSocketClient buildAndConnectWebSocketClient() throws URISyntaxException, InterruptedException { NotificationApiWsClient wsClient = new NotificationApiWsClient(WS_URL + wsPort, token); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java index 38b907bd2c..f266e763fa 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java @@ -76,6 +76,13 @@ public class NotificationApiWsClient extends TbTestWebSocketClient { send(cmd); } + @Override + public void registerWaitForUpdate(int count) { + lastDataUpdate = null; + lastCountUpdate = null; + super.registerWaitForUpdate(count); + } + @Override public void onMessage(String s) { JsonNode update = JacksonUtil.toJsonNode(s); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationsClient.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationsClient.java new file mode 100644 index 0000000000..bcd6134478 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationsClient.java @@ -0,0 +1,86 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification; + +import com.google.common.base.Strings; +import org.apache.commons.lang3.StringUtils; +import org.thingsboard.rest.client.RestClient; +import org.thingsboard.server.common.data.notification.AlarmOriginatedNotificationInfo; +import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationInfo; +import org.thingsboard.server.common.data.notification.NotificationOriginatorType; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Scanner; + +public class NotificationsClient extends NotificationApiWsClient { + + private NotificationsClient(String wsUrl, String token) throws Exception { + super(wsUrl, token); + } + + public static NotificationsClient newInstance(String username, String password) throws Exception { + RestClient restClient = new RestClient("http://localhost:8080"); + restClient.login(username, password); + NotificationsClient client = new NotificationsClient("ws://localhost:8080", restClient.getToken()); + client.connectBlocking(); + return client; + } + + @Override + public void onMessage(String s) { + super.onMessage(s); +// printNotificationsCount(); + printNotifications(); + } + + public void printNotifications() { + System.out.println(StringUtils.repeat(System.lineSeparator(), 20)); + List notifications = getNotifications(); + System.out.printf(" %s NEW MESSAGE%s\n\n", getUnreadCount(), notifications.size() > 1 ? "S" : ""); + notifications.forEach(notification -> { + String notificationInfoStr = ""; + if (notification.getOriginatorType() == NotificationOriginatorType.ALARM) { + AlarmOriginatedNotificationInfo info = (AlarmOriginatedNotificationInfo) notification.getInfo(); + notificationInfoStr = String.format("Alarm of type %s - %s severity - status: %s", + info.getAlarmType(), info.getAlarmSeverity(), info.getAlarmStatus()); + } else if (notification.getInfo() != null) { + notificationInfoStr = Strings.nullToEmpty(notification.getInfo().getDescription()); + } + SimpleDateFormat format = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss"); + String time = format.format(new Date(notification.getCreatedTime())); + System.out.printf("[%s] %-19s | %-30s | (%s)\n", time, notification.getReason(), notification.getText(), notificationInfoStr); + }); + System.out.println(StringUtils.repeat(System.lineSeparator(), 5)); + } + + public void printNotificationsCount() { + System.out.println(); + System.out.println(); + System.out.println(); + int unreadCount = getUnreadCount(); + System.out.printf("\r\r%s NEW MESSAGE%s", unreadCount, unreadCount > 1 ? "S" : ""); + } + + public static void main(String[] args) throws Exception { + NotificationsClient client = NotificationsClient.newInstance("tenant@thingsboard.org", "tenant"); + client.subscribeForUnreadNotifications(5); +// client.subscribeForUnreadNotificationsCount(); + new Scanner(System.in).nextLine(); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java index fd01c91255..1ecff879f6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java @@ -19,5 +19,5 @@ import lombok.Data; @Data public class NotificationRequestConfig { - private int sendingDelayInMinutes; + private int sendingDelayInSec; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NonConfirmedNotificationEscalation.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NonConfirmedNotificationEscalation.java index 1174cedb06..a62af09eb5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NonConfirmedNotificationEscalation.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NonConfirmedNotificationEscalation.java @@ -25,7 +25,7 @@ import javax.validation.constraints.NotNull; public class NonConfirmedNotificationEscalation { @Min(1) - private int delayInMinutes; // delay since initial notification request // if no one from previous escalation item has read the notification, send notifications after this time to other recipients + private int delayInSec; @NotNull private NotificationTargetId notificationTargetId; diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index d25ab2f6f2..262bd9a609 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -784,7 +784,7 @@ CREATE TABLE IF NOT EXISTS notification_target ( created_time BIGINT NOT NULL, tenant_id UUID NOT NULL, name VARCHAR(255) NOT NULL, - configuration varchar(1000) NOT NULL + configuration VARCHAR NOT NULL ); CREATE TABLE IF NOT EXISTS notification_rule ( From 80ca9cc787c3836264ddbc8af7551aaa036c60e7 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Thu, 10 Nov 2022 15:49:02 +0200 Subject: [PATCH 013/111] WS session updates: remove 'synchronized' and use binary semaphore --- .../controller/plugin/TbWebSocketHandler.java | 60 +++++++------------ 1 file changed, 21 insertions(+), 39 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index 00f3be68c4..87bf110c81 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -52,13 +52,11 @@ import javax.websocket.Session; import java.io.IOException; import java.net.URI; import java.security.InvalidParameterException; -import java.util.Queue; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.Semaphore; import static org.thingsboard.server.service.ws.DefaultWebSocketService.NUMBER_OF_PING_ATTEMPTS; @@ -139,9 +137,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke if (!checkLimits(session, sessionRef)) { return; } - var tenantProfileConfiguration = tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultProfileConfiguration(); - internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef, tenantProfileConfiguration.getWsMsgQueueLimitPerSession() > 0 ? - tenantProfileConfiguration.getWsMsgQueueLimitPerSession() : 500)); + internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef)); externalSessionMap.put(externalSessionId, internalSessionId); processInWebSocketService(sessionRef, SessionEvent.onEstablished()); @@ -216,22 +212,21 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke private final RemoteEndpoint.Async asyncRemote; private final WebSocketSessionRef sessionRef; - private final AtomicBoolean isSending = new AtomicBoolean(false); - private final Queue> msgQueue; + // TODO: carefully review ( + discuss removal of the msgQueue) + private final Semaphore sendingSemaphore = new Semaphore(1); private volatile long lastActivityTime; - SessionMetaData(WebSocketSession session, WebSocketSessionRef sessionRef, int maxMsgQueuePerSession) { + SessionMetaData(WebSocketSession session, WebSocketSessionRef sessionRef) { super(); this.session = session; Session nativeSession = ((NativeWebSocketSession) session).getNativeSession(Session.class); this.asyncRemote = nativeSession.getAsyncRemote(); this.sessionRef = sessionRef; - this.msgQueue = new LinkedBlockingQueue<>(maxMsgQueuePerSession); this.lastActivityTime = System.currentTimeMillis(); } - synchronized void sendPing(long currentTime) { + void sendPing(long currentTime) { try { long timeSinceLastActivity = currentTime - lastActivityTime; if (timeSinceLastActivity >= pingTimeout) { @@ -246,40 +241,37 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } } - private void closeSession(CloseStatus reason) { + void closeSession(CloseStatus reason) { try { close(this.sessionRef, reason); } catch (IOException ioe) { log.trace("[{}] Session transport error", session.getId(), ioe); + } finally { + sendingSemaphore.release(); } } - synchronized void processPongMessage(long currentTime) { + void processPongMessage(long currentTime) { lastActivityTime = currentTime; } - synchronized void sendMsg(String msg) { + void sendMsg(String msg) { sendMsg(new TbWebSocketTextMsg(msg)); } - synchronized void sendMsg(TbWebSocketMsg msg) { - if (isSending.compareAndSet(false, true)) { + void sendMsg(TbWebSocketMsg msg) { + try { + sendingSemaphore.acquire(); sendMsgInternal(msg); - } else { - try { - msgQueue.add(msg); - } catch (RuntimeException e) { - if (log.isTraceEnabled()) { - log.trace("[{}][{}] Session closed due to queue error", sessionRef.getSecurityCtx().getTenantId(), session.getId(), e); - } else { - log.info("[{}][{}] Session closed due to queue error", sessionRef.getSecurityCtx().getTenantId(), session.getId()); - } - closeSession(CloseStatus.POLICY_VIOLATION.withReason("Max pending updates limit reached!")); - } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (Exception e) { + sendingSemaphore.release(); + throw e; } } - private void sendMsgInternal(TbWebSocketMsg msg) { + void sendMsgInternal(TbWebSocketMsg msg) { try { if (TbWebSocketMsgType.TEXT.equals(msg.getType())) { TbWebSocketTextMsg textMsg = (TbWebSocketTextMsg) msg; @@ -287,7 +279,6 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } else { TbWebSocketPingMsg pingMsg = (TbWebSocketPingMsg) msg; this.asyncRemote.sendPing(pingMsg.getMsg()); - processNextMsg(); } } catch (Exception e) { log.trace("[{}] Failed to send msg", session.getId(), e); @@ -301,16 +292,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke log.trace("[{}] Failed to send msg", session.getId(), result.getException()); closeSession(CloseStatus.SESSION_NOT_RELIABLE); } else { - processNextMsg(); - } - } - - private void processNextMsg() { - TbWebSocketMsg msg = msgQueue.poll(); - if (msg != null) { - sendMsgInternal(msg); - } else { - isSending.set(false); + sendingSemaphore.release(); } } } From 684ef4a65883cc1ea1eee91a5c7f2e0603460f24 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Fri, 11 Nov 2022 14:06:09 +0200 Subject: [PATCH 014/111] Add ability to update notification request --- .../controller/NotificationController.java | 69 ++++++++++++------- .../DefaultNotificationManager.java | 9 ++- ...aultNotificationRuleProcessingService.java | 25 +++++-- .../DefaultNotificationCommandsHandler.java | 19 +++-- .../sub/NotificationRequestUpdate.java | 1 + .../notification/NotificationApiTest.java | 6 +- .../NotificationRequestService.java | 3 + .../dao/notification/NotificationService.java | 2 +- .../notification/NotificationRequest.java | 15 +++- .../notification/NotificationRequestInfo.java | 35 ++++++++++ .../DefaultNotificationRequestService.java | 6 ++ .../DefaultNotificationService.java | 13 +--- .../dao/notification/NotificationDao.java | 2 +- .../notification/NotificationRequestDao.java | 4 ++ .../sql/notification/JpaNotificationDao.java | 4 +- .../JpaNotificationRequestDao.java | 19 +++++ .../notification/NotificationRepository.java | 17 ++++- .../rule/engine/api/NotificationManager.java | 2 +- .../rule/engine/profile/DeviceState.java | 1 - 19 files changed, 187 insertions(+), 65 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestInfo.java diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index a392e6ec74..40d3c47fff 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationOriginatorType; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationRequestInfo; import org.thingsboard.server.common.data.notification.NotificationSeverity; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -88,34 +89,43 @@ public class NotificationController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public NotificationRequest createNotificationRequest(@RequestBody NotificationRequest notificationRequest, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { - accessControlService.checkPermission(user, Resource.NOTIFICATION_REQUEST, Operation.CREATE, null, notificationRequest); // todo: check permission for notification target - if (notificationRequest.getId() != null) { - // TODO: think about notification request update - throw new IllegalArgumentException("Notification request cannot be changed. You can delete it and create a new one"); - } - notificationRequest.setOriginatorType(NotificationOriginatorType.USER); - notificationRequest.setOriginatorEntityId(user.getId()); - if (StringUtils.isBlank(notificationRequest.getNotificationReason())) { - notificationRequest.setNotificationReason(NotificationRequest.GENERAL_NOTIFICATION_REASON); - } - 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); - try { - NotificationRequest savedNotificationRequest = notificationManager.processNotificationRequest(user.getTenantId(), notificationRequest); - logEntityAction(user, EntityType.NOTIFICATION_REQUEST, savedNotificationRequest, ActionType.ADDED); - return savedNotificationRequest; - } catch (Exception e) { - logEntityAction(user, EntityType.NOTIFICATION_REQUEST, notificationRequest, null, ActionType.ADDED, e); - throw e; + if (notificationRequest.getId() == null) { + accessControlService.checkPermission(user, Resource.NOTIFICATION_REQUEST, Operation.CREATE, null, notificationRequest); + notificationRequest.setOriginatorType(NotificationOriginatorType.USER); + notificationRequest.setOriginatorEntityId(user.getId()); + if (StringUtils.isBlank(notificationRequest.getNotificationReason())) { + notificationRequest.setNotificationReason("General"); + } + if (notificationRequest.getNotificationSeverity() == null) { + notificationRequest.setNotificationSeverity(NotificationSeverity.NORMAL); + } + if (notificationRequest.getNotificationInfo() != null && notificationRequest.getNotificationInfo().getOriginatorType() != null) { + throw new IllegalArgumentException("Unsupported notification info type"); + } + notificationRequest.setRuleId(null); + notificationRequest.setStatus(null); + return notificationManager.processNotificationRequest(user.getTenantId(), notificationRequest); + } else { + NotificationRequest existingNotificationRequest = notificationRequestService.findNotificationRequestById(user.getTenantId(), notificationRequest.getId()); + checkNotNull(existingNotificationRequest); + accessControlService.checkPermission(user, Resource.NOTIFICATION_REQUEST, Operation.WRITE, notificationRequest.getId(), notificationRequest); + + existingNotificationRequest.setNotificationReason(notificationRequest.getNotificationReason()); + existingNotificationRequest.setTextTemplate(notificationRequest.getTextTemplate()); + existingNotificationRequest.setNotificationSeverity(notificationRequest.getNotificationSeverity()); + return notificationManager.updateNotificationRequest(user.getTenantId(), existingNotificationRequest); } +// +// try { +// NotificationRequest savedNotificationRequest = ; +// logEntityAction(user, EntityType.NOTIFICATION_REQUEST, savedNotificationRequest, ActionType.ADDED); +// return savedNotificationRequest; +// } catch (Exception e) { +// logEntityAction(user, EntityType.NOTIFICATION_REQUEST, notificationRequest, null, ActionType.ADDED, e); +// throw e; +// } } @GetMapping("/notification/request/{id}") @@ -126,6 +136,15 @@ public class NotificationController extends BaseController { return notificationRequestService.findNotificationRequestById(user.getTenantId(), notificationRequestId); } + @GetMapping("/notification/request/info/{id}") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationRequestInfo getNotificationRequestInfoById(@PathVariable UUID id, + @AuthenticationPrincipal SecurityUser user) { + // fixme: permission checks + NotificationRequestId notificationRequestId = new NotificationRequestId(id); + return notificationRequestService.getNotificationRequestInfoById(user.getTenantId(), notificationRequestId); + } + @GetMapping("/notification/requests") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public PageData getNotificationRequests(@RequestParam int pageSize, diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java index 46d9604577..73926c70be 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java @@ -153,15 +153,18 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl } @Override - public void updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { + public NotificationRequest updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { log.debug("Updating notification request {}", notificationRequest.getId()); - notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); - notificationService.updateNotificationsInfosByRequestId(tenantId, notificationRequest.getId(), notificationRequest.getNotificationInfo()); + notificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); + notificationService.updateNotificationsByRequestId(tenantId, notificationRequest.getId(), + notificationRequest.getNotificationReason(), notificationRequest.getNotificationInfo()); onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder() .notificationRequestId(notificationRequest.getId()) + .notificationReason(notificationRequest.getNotificationReason()) .notificationInfo(notificationRequest.getNotificationInfo()) .deleted(false) .build()); + return notificationRequest; } private Notification createNotification(User recipient, NotificationRequest notificationRequest) { diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java index b103eb28f8..a0e1dc1bb1 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java @@ -18,8 +18,10 @@ package org.thingsboard.server.service.notification; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.NotificationManager; +import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationTargetId; @@ -38,10 +40,12 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import java.util.List; +import java.util.Map; @Service @TbCoreComponent @RequiredArgsConstructor +@Slf4j public class DefaultNotificationRuleProcessingService implements NotificationRuleProcessingService { private final NotificationRuleService notificationRuleService; @@ -78,6 +82,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul // todo: think about: what if notification rule was updated? private void onAlarmUpdate(TenantId tenantId, NotificationRuleId notificationRuleId, Alarm alarm, boolean deleted) { + log.debug("Processing alarm update ({}) with notification rule {}", alarm.getId(), notificationRuleId); List notificationRequests = notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityId(tenantId, notificationRuleId, alarm.getId()); NotificationRule notificationRule = notificationRuleService.findNotificationRuleById(tenantId, notificationRuleId); if (notificationRule == null) return; @@ -101,7 +106,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul } } } else { - NotificationInfo newNotificationInfo = constructNotificationInfo(alarm, notificationRule); + NotificationInfo newNotificationInfo = constructNotificationInfo(alarm); for (NotificationRequest notificationRequest : notificationRequests) { NotificationInfo previousNotificationInfo = notificationRequest.getNotificationInfo(); if (!previousNotificationInfo.equals(newNotificationInfo)) { @@ -121,13 +126,13 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul if (delayInSec > 0) { config.setSendingDelayInSec(delayInSec); } - NotificationInfo notificationInfo = constructNotificationInfo(alarm, notificationRule); + NotificationInfo notificationInfo = constructNotificationInfo(alarm); NotificationRequest notificationRequest = NotificationRequest.builder() .tenantId(tenantId) .targetId(targetId) .notificationReason("Alarm") - .textTemplate(notificationRule.getNotificationTextTemplate()) // todo: format with alarm vars + .textTemplate(formatNotificationTextTemplate(notificationRule.getNotificationTextTemplate(), alarm)) .notificationInfo(notificationInfo) .notificationSeverity(NotificationSeverity.NORMAL) // todo: from alarm severity .originatorType(NotificationOriginatorType.ALARM) @@ -138,7 +143,19 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul notificationManager.processNotificationRequest(tenantId, notificationRequest); } - private NotificationInfo constructNotificationInfo(Alarm alarm, NotificationRule notificationRule) { + private String formatNotificationTextTemplate(String textTemplate, Alarm alarm) { + Map context = Map.of( // fixme: notification text is not updatable + "alarmType", alarm.getType(), + "alarmId", alarm.getId().toString(), + "alarmOriginatorEntityType", alarm.getOriginator().getEntityType().toString(), + "alarmOriginatorId", alarm.getOriginator().getId().toString(), + "alarmSeverity", alarm.getSeverity().toString(), + "alarmStatus", alarm.getStatus().toString() + ); + return TbNodeUtils.processTemplate(textTemplate, context); + } + + private NotificationInfo constructNotificationInfo(Alarm alarm) { return AlarmOriginatedNotificationInfo.builder() .alarmId(alarm.getId()) .alarmType(alarm.getType()) diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java index 5dd8bd7096..e86fd58161 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java @@ -20,30 +20,29 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.NotificationManager; import org.thingsboard.server.common.data.id.IdBased; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; -import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.rule.engine.api.NotificationManager; import org.thingsboard.server.service.security.model.SecurityUser; -import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd; -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.NotificationsSubscription; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; +import org.thingsboard.server.service.ws.WebSocketService; import org.thingsboard.server.service.ws.WebSocketSessionRef; import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd; +import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd; import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; -import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; +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.NotificationsCountSubscription; -import org.thingsboard.server.service.ws.WebSocketService; +import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscription; +import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; @@ -168,11 +167,11 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH sendUpdate(subscription.getSessionId(), subscription.createFullUpdate()); } } else { - NotificationInfo notificationInfo = update.getNotificationInfo(); subscription.getLatestUnreadNotifications().values().stream() .filter(notification -> notification.getRequestId().equals(notificationRequestId)) .forEach(notification -> { - notification.setInfo(notificationInfo); + notification.setReason(update.getNotificationReason()); + notification.setInfo(update.getNotificationInfo()); sendUpdate(subscription.getSessionId(), subscription.createPartialUpdate(notification)); }); } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationRequestUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationRequestUpdate.java index 36c3b2a441..05642743ee 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationRequestUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationRequestUpdate.java @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.notification.NotificationInfo; @Builder public class NotificationRequestUpdate { private NotificationRequestId notificationRequestId; + private String notificationReason; private NotificationInfo notificationInfo; private boolean deleted; } diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java index 3d7e55757b..cce7ff749e 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java @@ -247,7 +247,7 @@ public class NotificationApiTest extends AbstractControllerTest { @Test public void testNotificationUpdatesForUsersInTarget() throws Exception { - Map wsSessions = createUsersAndSetUpWsSessions(150); + Map wsSessions = createUsersAndSetUpWsSessions(100); wsSessions.forEach((user, wsClient) -> { wsClient.subscribeForUnreadNotifications(10); wsClient.waitForReply(true); @@ -258,14 +258,14 @@ public class NotificationApiTest extends AbstractControllerTest { NotificationTarget notificationTarget = new NotificationTarget(); UserListNotificationTargetConfig config = new UserListNotificationTargetConfig(); config.setUsersIds(wsSessions.keySet().stream().map(User::getUuidId).collect(Collectors.toList())); - notificationTarget.setName("150 users"); + notificationTarget.setName("100 users"); notificationTarget.setTenantId(tenantId); notificationTarget.setConfiguration(config); notificationTarget = saveNotificationTarget(notificationTarget); wsSessions.forEach((user, wsClient) -> wsClient.registerWaitForUpdate(2)); NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), "Hello, ${recipientEmail}"); - await().atMost(3, TimeUnit.SECONDS) + await().atMost(5, TimeUnit.SECONDS) .pollDelay(1, TimeUnit.SECONDS).pollInterval(500, TimeUnit.MILLISECONDS) .until(() -> wsSessions.values().stream() .allMatch(wsClient -> wsClient.getLastDataUpdate() != null diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java index 7ae96ada79..9a5687d04c 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationRequestInfo; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -39,4 +40,6 @@ public interface NotificationRequestService { PageData findScheduledNotificationRequests(PageLink pageLink); + NotificationRequestInfo getNotificationRequestInfoById(TenantId tenantId, NotificationRequestId id); + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java index 498e3dfa3f..7cc4fd9183 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java @@ -38,6 +38,6 @@ public interface NotificationService { int countUnreadNotificationsByUserId(TenantId tenantId, UserId userId); - int updateNotificationsInfosByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, NotificationInfo notificationInfo); + int updateNotificationsByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, String notificationReason, NotificationInfo notificationInfo); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java index 8ba27a96a8..142c3c6b9e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java @@ -61,7 +61,20 @@ public class NotificationRequest extends BaseData impleme private NotificationRequestConfig additionalConfig; private NotificationRequestStatus status; - public static final String GENERAL_NOTIFICATION_REASON = "General"; + public NotificationRequest(NotificationRequest other) { + super(other); + this.tenantId = other.tenantId; + this.targetId = other.targetId; + this.notificationReason = other.notificationReason; + this.textTemplate = other.textTemplate; + this.notificationInfo = other.notificationInfo; + this.notificationSeverity = other.notificationSeverity; + this.originatorType = other.originatorType; + this.originatorEntityId = other.originatorEntityId; + this.ruleId = other.ruleId; + this.additionalConfig = other.additionalConfig; + this.status = other.status; + } @JsonIgnore @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestInfo.java new file mode 100644 index 0000000000..53835200d7 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestInfo.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; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Map; + +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationRequestInfo extends NotificationRequest { + + private int sent; + private int read; + private Map statusesByRecipient; + + public NotificationRequestInfo(NotificationRequest notificationRequest) { + super(notificationRequest); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java index af67c8ae40..be49f05c09 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationRequestInfo; import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -71,6 +72,11 @@ public class DefaultNotificationRequestService implements NotificationRequestSer return notificationRequestDao.findAllByStatus(NotificationRequestStatus.SCHEDULED, pageLink); } + @Override + public NotificationRequestInfo getNotificationRequestInfoById(TenantId tenantId, NotificationRequestId id) { + return notificationRequestDao.getNotificationRequestInfoById(tenantId, id); + } + private static class NotificationRequestValidator extends DataValidator { diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java index 37a45a3a72..1e57ffaf5d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java @@ -17,28 +17,19 @@ package org.thingsboard.server.dao.notification; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; -import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationInfo; -import org.thingsboard.server.common.data.notification.NotificationRequest; -import org.thingsboard.server.common.data.notification.NotificationRequestStatus; -import org.thingsboard.server.common.data.notification.NotificationSeverity; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; -import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.sql.query.EntityKeyMapping; -import java.util.List; - @Service @Slf4j @RequiredArgsConstructor @@ -83,8 +74,8 @@ public class DefaultNotificationService implements NotificationService { } @Override - public int updateNotificationsInfosByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, NotificationInfo notificationInfo) { - return notificationDao.updateInfosByRequestId(tenantId, notificationRequestId, notificationInfo); + public int updateNotificationsByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, String notificationReason, NotificationInfo notificationInfo) { + return notificationDao.updateByRequestId(tenantId, notificationRequestId, notificationReason, notificationInfo); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java index dcfba4d704..8df17f4491 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java @@ -38,6 +38,6 @@ public interface NotificationDao extends Dao { PageData findByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, PageLink pageLink); - int updateInfosByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, NotificationInfo notificationInfo); + int updateByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, String notificationReason, NotificationInfo notificationInfo); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java index 2036105ffb..6036143f87 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java @@ -16,9 +16,11 @@ package org.thingsboard.server.dao.notification; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationRequestInfo; import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -34,4 +36,6 @@ public interface NotificationRequestDao extends Dao { PageData findAllByStatus(NotificationRequestStatus status, PageLink pageLink); + NotificationRequestInfo getNotificationRequestInfoById(TenantId tenantId, NotificationRequestId id); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java index cd98941011..5b4bf6eaa5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java @@ -91,8 +91,8 @@ public class JpaNotificationDao extends JpaAbstractDao implements NotificationRequestDao { private final NotificationRequestRepository notificationRequestRepository; + private final NotificationRepository notificationRepository; @Override public PageData findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) { @@ -58,6 +65,18 @@ public class JpaNotificationRequestDao extends JpaAbstractDao statusesByRecipient = notificationRepository.getStatusesByRecipientForRequestId(id.getId()).stream() + .collect(Collectors.toMap(r -> (String) r[0], r -> (NotificationStatus) r[1])); + notificationRequestInfo.setStatusesByRecipient(statusesByRecipient); + return notificationRequestInfo; + } + @Override protected Class getEntityClass() { return NotificationRequestEntity.class; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java index 3f46a3dfa9..09ce53e7b8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java @@ -27,6 +27,7 @@ import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.dao.model.sql.NotificationEntity; +import java.util.List; import java.util.UUID; @Repository @@ -50,7 +51,19 @@ public interface NotificationRepository extends JpaRepository getStatusesByRecipientForRequestId(@Param("requestId") UUID requestId); } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationManager.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationManager.java index ac336e9fe4..dc57a876fd 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationManager.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationManager.java @@ -29,6 +29,6 @@ public interface NotificationManager { void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId); - void updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); + NotificationRequest updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java index 1ef06547ba..5ab5adc7fc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java @@ -191,7 +191,6 @@ class DeviceState { AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm), dynamicPredicateValueCtx)); alarmState.processAckAlarm(alarmNf); - // todo: process notification rule } ctx.tellSuccess(msg); } From a4ce6252de84c311aaec782a6c2428a11bfacc47 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Sat, 19 Nov 2022 15:03:41 +0200 Subject: [PATCH 015/111] Retrieve notification info from request when selecting notifications --- .../notification/DefaultNotificationManager.java | 3 --- .../DefaultNotificationCommandsHandler.java | 1 - .../server/dao/notification/NotificationService.java | 4 ---- .../thingsboard/server/dao/model/ModelConstants.java | 1 - .../server/dao/model/sql/NotificationEntity.java | 3 ++- .../dao/notification/DefaultNotificationService.java | 5 ----- .../server/dao/notification/NotificationDao.java | 3 --- .../dao/sql/notification/JpaNotificationDao.java | 5 ----- .../dao/sql/notification/NotificationRepository.java | 10 ---------- 9 files changed, 2 insertions(+), 33 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java index 73926c70be..5c01015f55 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java @@ -156,11 +156,8 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl public NotificationRequest updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { log.debug("Updating notification request {}", notificationRequest.getId()); notificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); - notificationService.updateNotificationsByRequestId(tenantId, notificationRequest.getId(), - notificationRequest.getNotificationReason(), notificationRequest.getNotificationInfo()); onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder() .notificationRequestId(notificationRequest.getId()) - .notificationReason(notificationRequest.getNotificationReason()) .notificationInfo(notificationRequest.getNotificationInfo()) .deleted(false) .build()); diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java index e86fd58161..c9a0882f88 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java @@ -170,7 +170,6 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH subscription.getLatestUnreadNotifications().values().stream() .filter(notification -> notification.getRequestId().equals(notificationRequestId)) .forEach(notification -> { - notification.setReason(update.getNotificationReason()); notification.setInfo(update.getNotificationInfo()); sendUpdate(subscription.getSessionId(), subscription.createPartialUpdate(notification)); }); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java index 7cc4fd9183..7c724be881 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java @@ -16,11 +16,9 @@ package org.thingsboard.server.dao.notification; import org.thingsboard.server.common.data.id.NotificationId; -import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; -import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -38,6 +36,4 @@ public interface NotificationService { int countUnreadNotificationsByUserId(TenantId tenantId, UserId userId); - int updateNotificationsByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, String notificationReason, NotificationInfo notificationInfo); - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 0cb2c38c6c..65917e9b2d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -658,7 +658,6 @@ public class ModelConstants { public static final String NOTIFICATION_RECIPIENT_ID_PROPERTY = "recipient_id"; public static final String NOTIFICATION_REASON_PROPERTY = "reason"; public static final String NOTIFICATION_TEXT_PROPERTY = "text"; - public static final String NOTIFICATION_INFO_PROPERTY = "info"; public static final String NOTIFICATION_SEVERITY_PROPERTY = "severity"; public static final String NOTIFICATION_ORIGINATOR_TYPE_PROPERTY = "originator_type"; public static final String NOTIFICATION_STATUS_PROPERTY = "status"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java index c94ec80016..2e6255eecd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Formula; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.common.util.JacksonUtil; @@ -60,7 +61,7 @@ public class NotificationEntity extends BaseSqlEntity { private String text; @Type(type = "json") - @Column(name = ModelConstants.NOTIFICATION_INFO_PROPERTY) + @Formula("(SELECT r.notification_info FROM notification_request r WHERE r.id = request_id)") private JsonNode info; @Enumerated(EnumType.STRING) diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java index 1e57ffaf5d..ba4274df5b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java @@ -73,9 +73,4 @@ public class DefaultNotificationService implements NotificationService { return notificationDao.countUnreadByUserId(tenantId, userId); } - @Override - public int updateNotificationsByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, String notificationReason, NotificationInfo notificationInfo) { - return notificationDao.updateByRequestId(tenantId, notificationRequestId, notificationReason, notificationInfo); - } - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java index 8df17f4491..dc09cd09cb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java @@ -20,7 +20,6 @@ import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; -import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -38,6 +37,4 @@ public interface NotificationDao extends Dao { PageData findByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, PageLink pageLink); - int updateByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, String notificationReason, NotificationInfo notificationInfo); - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java index 5b4bf6eaa5..6ab6e63592 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java @@ -90,11 +90,6 @@ public class JpaNotificationDao extends JpaAbstractDao getEntityClass() { return NotificationEntity.class; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java index 09ce53e7b8..9b952c292e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao.sql.notification; -import com.fasterxml.jackson.databind.JsonNode; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -49,15 +48,6 @@ public interface NotificationRepository extends JpaRepository findByRequestId(UUID requestId, Pageable pageable); - @Modifying - @Transactional - @Query("UPDATE NotificationEntity n " + - "SET n.info = :info, n.reason = :reason " + - "WHERE n.requestId = :requestId") - int updateReasonAndInfoByRequestId(@Param("requestId") UUID requestId, - @Param("reason") String reason, - @Param("info") JsonNode info); - int countByRequestId(UUID requestId); int countByRequestIdAndStatus(UUID requestId, NotificationStatus status); From 17e4b322c1c75df3bfd3b088c48cbdc8e8199220 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Tue, 6 Dec 2022 10:19:08 +0200 Subject: [PATCH 016/111] Set notificationRuleId in TbCreateAlarmNode --- .../org/thingsboard/rule/engine/action/TbCreateAlarmNode.java | 2 ++ .../rule/engine/action/TbCreateAlarmNodeConfiguration.java | 3 +++ .../java/org/thingsboard/rule/engine/profile/AlarmState.java | 1 + 3 files changed, 6 insertions(+) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java index 707aa53cd2..e27e5d2e5b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java @@ -184,6 +184,7 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode relationTypes; + private NotificationRuleId notificationRuleId; + @Override public TbCreateAlarmNodeConfiguration defaultConfiguration() { TbCreateAlarmNodeConfiguration configuration = new TbCreateAlarmNodeConfiguration(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 29bd7d6177..797e72d96e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -241,6 +241,7 @@ class AlarmState { // Skip update if severity is decreased. if (severity.ordinal() <= oldSeverity.ordinal()) { currentAlarm.setDetails(createDetails(ruleState)); + currentAlarm.setNotificationRuleId(alarmDefinition.getNotificationRuleId()); if (!oldSeverity.equals(severity)) { currentAlarm.setSeverity(severity); currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm); From deb2867df1c692ead8f7b7a8d6ed9b86ed589298 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Wed, 14 Dec 2022 14:27:20 +0200 Subject: [PATCH 017/111] Multiple notification delivery methods; notification templates --- .../{3.4.2 => 3.4.3}/schema_update.sql | 37 +++-- .../controller/NotificationController.java | 40 ++---- .../DefaultNotificationManager.java | 129 ++++++++++-------- ...aultNotificationRuleProcessingService.java | 40 +++--- .../DefaultNotificationSchedulerService.java | 3 +- .../NotificationTemplateUtil.java | 56 ++++++++ .../channels/EmailNotificationChannel.java | 49 +++++++ .../channels/NotificationChannel.java | 31 +++++ .../channels/SmsNotificationChannel.java | 53 +++++++ .../state/DefaultDeviceStateService.java | 8 +- .../DefaultSubscriptionManagerService.java | 42 ++---- .../AbstractSubscriptionService.java | 24 ++-- .../DefaultAlarmSubscriptionService.java | 18 +-- .../DefaultTelemetrySubscriptionService.java | 3 - .../notification/NotificationApiTest.java | 13 +- .../notification/NotificationsClient.java | 2 +- .../NotificationTemplateService.java | 31 +++++ .../thingsboard/server/common/data/User.java | 11 ++ .../data/id/NotificationTemplateId.java | 30 ++++ .../data/notification/Notification.java | 5 +- ...s.java => NotificationDeliveryMethod.java} | 4 +- .../NotificationOriginatorType.java | 2 +- .../notification/NotificationRequest.java | 38 ++++-- .../NotificationRequestConfig.java | 4 + .../notification/rule/NotificationRule.java | 10 +- .../NotificationDeliveryMethodConfig.java | 38 ++++++ .../settings/NotificationSettings.java | 29 ++++ .../template/NotificationTemplate.java | 35 +++++ .../template/NotificationTemplateConfig.java | 29 ++++ .../template/NotificationText.java | 30 ++++ .../template/NotificationTextTemplate.java | 26 ++++ .../server/dao/model/ModelConstants.java | 17 ++- .../dao/model/sql/NotificationEntity.java | 23 +--- .../model/sql/NotificationRequestEntity.java | 57 ++++---- .../dao/model/sql/NotificationRuleEntity.java | 21 ++- .../model/sql/NotificationTargetEntity.java | 8 +- .../model/sql/NotificationTemplateEntity.java | 74 ++++++++++ .../server/dao/model/sql/UserEntity.java | 5 + .../DefaultNotificationTemplateService.java | 40 ++++++ .../notification/NotificationTemplateDao.java | 22 +++ .../JpaNotificationTemplateDao.java | 46 +++++++ .../NotificationRequestRepository.java | 3 +- .../NotificationTemplateRepository.java | 26 ++++ .../main/resources/sql/schema-entities.sql | 1 + .../rule/engine/api/NotificationManager.java | 5 +- .../notification/TbNotificationNode.java | 38 +++--- 46 files changed, 956 insertions(+), 300 deletions(-) rename application/src/main/data/upgrade/{3.4.2 => 3.4.3}/schema_update.sql (74%) create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/NotificationTemplateUtil.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTemplateId.java rename common/data/src/main/java/org/thingsboard/server/common/data/notification/{NotificationSettings.java => NotificationDeliveryMethod.java} (85%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationText.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTextTemplate.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java diff --git a/application/src/main/data/upgrade/3.4.2/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql similarity index 74% rename from application/src/main/data/upgrade/3.4.2/schema_update.sql rename to application/src/main/data/upgrade/3.4.3/schema_update.sql index 3dcc7c3a07..bee8454596 100644 --- a/application/src/main/data/upgrade/3.4.2/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.3/schema_update.sql @@ -23,32 +23,39 @@ CREATE TABLE IF NOT EXISTS notification_target ( ); CREATE INDEX IF NOT EXISTS idx_notification_target_tenant_id_created_time ON notification_target(tenant_id, created_time DESC); +CREATE TABLE IF NOT EXISTS notification_template ( + id UUID NOT NULL CONSTRAINT notification_template_pkey PRIMARY KEY, + created_time BIGINT NOT NULL, + tenant_id UUID NOT NULL, + name VARCHAR(255) NOT NULL, + configuration VARCHAR(10000) NOT NULL +); + CREATE TABLE IF NOT EXISTS notification_rule ( id UUID NOT NULL CONSTRAINT notification_rule_pkey PRIMARY KEY, created_time BIGINT NOT NULL, tenant_id UUID NOT NULL, name VARCHAR(255) NOT NULL, - notification_text_template VARCHAR NOT NULL, + template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id), + delivery_methods VARCHAR(255), initial_notification_target_id UUID NULL CONSTRAINT fk_notification_rule_target_id REFERENCES notification_target(id), escalation_config VARCHAR(500) ); -ALTER TABLE alarm ADD COLUMN IF NOT EXISTS notification_rule_id UUID; - CREATE TABLE IF NOT EXISTS notification_request ( id UUID NOT NULL CONSTRAINT notification_request_pkey PRIMARY KEY, created_time BIGINT NOT NULL, tenant_id UUID NOT NULL, target_id UUID NOT NULL CONSTRAINT fk_notification_request_target_id REFERENCES notification_target(id), - notification_reason VARCHAR NOT NULL, - text_template VARCHAR NOT NULL, - notification_info VARCHAR(1000), - notification_severity VARCHAR(32), + type VARCHAR(255) NOT NULL, + template_id UUID NOT NULL CONSTRAINT fk_notification_request_template_id REFERENCES notification_template(id), + info VARCHAR(1000), + delivery_methods VARCHAR(255), + additional_config VARCHAR(1000), originator_type VARCHAR(32) NOT NULL, originator_entity_id UUID, originator_entity_type VARCHAR(32), rule_id UUID NULL CONSTRAINT fk_notification_request_rule_id REFERENCES notification_rule(id), - additional_config VARCHAR(1000), status VARCHAR(32) ); CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id_originator_type_created_time ON notification_request(tenant_id, originator_type, created_time DESC); @@ -56,16 +63,18 @@ CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id_originator_type_cr CREATE TABLE IF NOT EXISTS notification ( id UUID NOT NULL, created_time BIGINT NOT NULL, - request_id UUID NOT NULL CONSTRAINT fk_notification_request_id - REFERENCES notification_request(id) ON DELETE CASCADE, - recipient_id UUID NOT NULL, - reason VARCHAR NOT NULL, - text VARCHAR NOT NULL, + request_id UUID NOT NULL CONSTRAINT fk_notification_request_id REFERENCES notification_request(id) ON DELETE CASCADE, + recipient_id UUID NOT NULL CONSTRAINT fk_notification_recipient_id REFERENCES tb_user(id) ON DELETE CASCADE, + type VARCHAR(255) NOT NULL, + text VARCHAR(1000) NOT NULL, info VARCHAR(1000), - severity VARCHAR(32), originator_type VARCHAR(32) NOT NULL, status VARCHAR(32) ) PARTITION BY RANGE (created_time); CREATE INDEX IF NOT EXISTS idx_notification_id ON notification(id); CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_created_time ON notification(recipient_id, created_time DESC); CREATE INDEX IF NOT EXISTS idx_notification_notification_request_id ON notification(request_id); + +ALTER TABLE alarm ADD COLUMN IF NOT EXISTS notification_rule_id UUID; + +ALTER TABLE tb_user ADD COLUMN IF NOT EXISTS phone VARCHAR(255); diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index 40d3c47fff..9064acb860 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -91,33 +91,23 @@ public class NotificationController extends BaseController { @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { // todo: check permission for notification target - if (notificationRequest.getId() == null) { - accessControlService.checkPermission(user, Resource.NOTIFICATION_REQUEST, Operation.CREATE, null, notificationRequest); - notificationRequest.setOriginatorType(NotificationOriginatorType.USER); - notificationRequest.setOriginatorEntityId(user.getId()); - if (StringUtils.isBlank(notificationRequest.getNotificationReason())) { - notificationRequest.setNotificationReason("General"); - } - if (notificationRequest.getNotificationSeverity() == null) { - notificationRequest.setNotificationSeverity(NotificationSeverity.NORMAL); - } - if (notificationRequest.getNotificationInfo() != null && notificationRequest.getNotificationInfo().getOriginatorType() != null) { - throw new IllegalArgumentException("Unsupported notification info type"); - } - notificationRequest.setRuleId(null); - notificationRequest.setStatus(null); - return notificationManager.processNotificationRequest(user.getTenantId(), notificationRequest); - } else { - NotificationRequest existingNotificationRequest = notificationRequestService.findNotificationRequestById(user.getTenantId(), notificationRequest.getId()); - checkNotNull(existingNotificationRequest); - accessControlService.checkPermission(user, Resource.NOTIFICATION_REQUEST, Operation.WRITE, notificationRequest.getId(), notificationRequest); + if (notificationRequest.getId() != null) { + throw new IllegalArgumentException("Notification request cannot be updated. You may only cancel/delete it"); + } - existingNotificationRequest.setNotificationReason(notificationRequest.getNotificationReason()); - existingNotificationRequest.setTextTemplate(notificationRequest.getTextTemplate()); - existingNotificationRequest.setNotificationSeverity(notificationRequest.getNotificationSeverity()); - return notificationManager.updateNotificationRequest(user.getTenantId(), existingNotificationRequest); + accessControlService.checkPermission(user, Resource.NOTIFICATION_REQUEST, Operation.CREATE, null, notificationRequest); + notificationRequest.setOriginatorType(NotificationOriginatorType.ADMIN); + notificationRequest.setOriginatorEntityId(user.getId()); + if (StringUtils.isBlank(notificationRequest.getType())) { + notificationRequest.setType("General"); + } + if (notificationRequest.getInfo() != null && notificationRequest.getInfo().getOriginatorType() != null) { + throw new IllegalArgumentException("Unsupported notification info type"); } -// + notificationRequest.setRuleId(null); + notificationRequest.setStatus(null); + return notificationManager.processNotificationRequest(user.getTenantId(), notificationRequest); + // // try { // NotificationRequest savedNotificationRequest = ; // logEntityAction(user, EntityType.NOTIFICATION_REQUEST, savedNotificationRequest, ActionType.ADDED); diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java index 5c01015f55..a343804921 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java @@ -16,21 +16,26 @@ package org.thingsboard.server.service.notification; import com.google.common.base.Strings; +import com.google.common.util.concurrent.Futures; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.NotificationManager; -import org.thingsboard.rule.engine.api.util.TbNodeUtils; -import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.NotificationStatus; +import org.thingsboard.server.common.data.notification.template.NotificationText; +import org.thingsboard.server.common.data.notification.template.NotificationTextTemplate; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; @@ -41,45 +46,37 @@ import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.NotificationsTopicService; -import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.service.executors.DbCallbackExecutorService; +import org.thingsboard.server.service.notification.channels.NotificationChannel; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.telemetry.AbstractSubscriptionService; import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.concurrent.Future; +import java.util.stream.Collectors; @Service @Slf4j -public class DefaultNotificationManager extends AbstractSubscriptionService implements NotificationManager { +@RequiredArgsConstructor +public class DefaultNotificationManager extends AbstractSubscriptionService implements NotificationManager, NotificationChannel { private final NotificationTargetService notificationTargetService; private final NotificationRequestService notificationRequestService; private final NotificationService notificationService; + private final NotificationTemplateUtil notificationTemplateUtil; private final DbCallbackExecutorService dbCallbackExecutorService; private final NotificationsTopicService notificationsTopicService; private final TbQueueProducerProvider producerProvider; + private Map channels; - public DefaultNotificationManager(TbClusterService clusterService, PartitionService partitionService, - NotificationTargetService notificationTargetService, - NotificationRequestService notificationRequestService, - NotificationService notificationService, - DbCallbackExecutorService dbCallbackExecutorService, - NotificationsTopicService notificationsTopicService, - TbQueueProducerProvider producerProvider) { - super(clusterService, partitionService); - this.notificationTargetService = notificationTargetService; - this.notificationRequestService = notificationRequestService; - this.notificationService = notificationService; - this.dbCallbackExecutorService = dbCallbackExecutorService; - this.notificationsTopicService = notificationsTopicService; - this.producerProvider = producerProvider; - } @Override public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { @@ -98,17 +95,18 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl notificationRequest.setStatus(NotificationRequestStatus.PROCESSED); NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); + Map textTemplates = notificationTemplateUtil.getTemplates(tenantId, notificationRequest.getTemplateId(), savedNotificationRequest.getDeliveryMethods()); DaoUtil.processBatches(pageLink -> { return notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), pageLink); }, 100, recipients -> { dbCallbackExecutorService.submit(() -> { - log.debug("Sending notifications for request {} to recipients batch", savedNotificationRequest.getId()); - for (User recipient : recipients) { - try { - Notification notification = createNotification(recipient, savedNotificationRequest); - onNotificationUpdate(recipient.getTenantId(), recipient.getId(), notification, true); - } catch (Exception e) { - log.error("Failed to create notification for recipient {}", recipient.getId(), e); + for (NotificationDeliveryMethod deliveryMethod : savedNotificationRequest.getDeliveryMethods()) { + log.debug("Sending {} notifications for request {} to recipients batch", deliveryMethod, savedNotificationRequest.getId()); + NotificationChannel notificationChannel = channels.get(deliveryMethod); + for (User recipient : recipients) { + Map templateContext = createTemplateContext(notificationRequest, recipient); + NotificationText text = notificationTemplateUtil.processTemplate(textTemplates.get(deliveryMethod), templateContext); + notificationChannel.sendNotification(recipient, savedNotificationRequest, text); } } }); @@ -117,6 +115,17 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl return savedNotificationRequest; } + private Map createTemplateContext(NotificationRequest notificationRequest, User recipient) { + Map templateContext = new HashMap<>(); + templateContext.put("email", recipient.getEmail()); + templateContext.put("firstName", Strings.nullToEmpty(recipient.getFirstName())); + templateContext.put("lastName", Strings.nullToEmpty(recipient.getLastName())); + if (notificationRequest.getTemplateContext() != null) { + templateContext.putAll(notificationRequest.getTemplateContext()); + } + return templateContext; + } + private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId, boolean deleted) { TransportProtos.NotificationSchedulerServiceMsg.Builder msg = TransportProtos.NotificationSchedulerServiceMsg.newBuilder() .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) @@ -131,6 +140,27 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl clusterService.pushMsgToCore(tenantId, notificationRequestId, toCoreMsg, null); } + @Override + public Future sendNotification(User recipient, NotificationRequest request, NotificationText text) { + log.trace("Creating notification for recipient {} (notification request id: {})", recipient.getId(), request.getId()); + Notification notification = Notification.builder() + .requestId(request.getId()) + .recipientId(recipient.getId()) + .type(request.getType()) + .text(text.getBody()) + .info(request.getInfo()) + .originatorType(request.getOriginatorType()) + .status(NotificationStatus.SENT) + .build(); + try { + notification = notificationService.saveNotification(recipient.getTenantId(), notification); + } catch (Exception e) { + log.error("Failed to create notification for recipient {}", recipient.getId(), e); + return Futures.immediateFailedFuture(e); + } + return onNotificationUpdate(recipient.getTenantId(), recipient.getId(), notification, true); + } + @Override public void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId) { boolean updated = notificationService.markNotificationAsRead(tenantId, recipientId, notificationId); @@ -158,52 +188,28 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl notificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder() .notificationRequestId(notificationRequest.getId()) - .notificationInfo(notificationRequest.getNotificationInfo()) + .notificationInfo(notificationRequest.getInfo()) .deleted(false) .build()); return notificationRequest; } - private Notification createNotification(User recipient, NotificationRequest notificationRequest) { - log.trace("Creating notification for recipient {} (notification request id: {})", recipient.getId(), notificationRequest.getId()); - Notification notification = Notification.builder() - .requestId(notificationRequest.getId()) - .recipientId(recipient.getId()) - .reason(notificationRequest.getNotificationReason()) - .text(formatNotificationText(notificationRequest.getTextTemplate(), recipient)) - .info(notificationRequest.getNotificationInfo()) - .severity(notificationRequest.getNotificationSeverity()) - .originatorType(notificationRequest.getOriginatorType()) - .status(NotificationStatus.SENT) - .build(); - return notificationService.saveNotification(recipient.getTenantId(), notification); - } - - private String formatNotificationText(String template, User recipient) { - Map context = Map.of( - "recipientEmail", recipient.getEmail(), - "recipientFirstName", Strings.nullToEmpty(recipient.getFirstName()), - "recipientLastName", Strings.nullToEmpty(recipient.getLastName()) - ); - return TbNodeUtils.processTemplate(template, context); - } - - private void onNotificationUpdate(TenantId tenantId, UserId recipientId, Notification notification, boolean isNew) { + private Future onNotificationUpdate(TenantId tenantId, UserId recipientId, Notification notification, boolean isNew) { NotificationUpdate update = NotificationUpdate.builder() .notification(notification) .isNew(isNew) .build(); log.trace("Submitting notification update for recipient {}: {}", recipientId, update); - wsCallBackExecutor.submit(() -> { + return wsCallBackExecutor.submit(() -> { forwardToSubscriptionManagerService(tenantId, recipientId, subscriptionManagerService -> { subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, update, TbCallback.EMPTY); - }, () -> { - return TbSubscriptionUtils.notificationUpdateToProto(tenantId, recipientId, update); - }); + }, () -> TbSubscriptionUtils.notificationUpdateToProto(tenantId, recipientId, update)); + return null; }); } private void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate update) { + // todo: check delivery method log.trace("Submitting notification request update: {}", update); wsCallBackExecutor.submit(() -> { TransportProtos.ToCoreNotificationMsg notificationRequestUpdateProto = TbSubscriptionUtils.notificationRequestUpdateToProto(tenantId, update); @@ -215,9 +221,20 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl }); } + @Override + public NotificationDeliveryMethod getDeliveryMethod() { + return NotificationDeliveryMethod.WEBSOCKET; + } + @Override protected String getExecutorPrefix() { return "notification"; } + @Autowired + public void setChannels(List channels, NotificationManager websocketNotificationChannel) { + this.channels = channels.stream().collect(Collectors.toMap(NotificationChannel::getDeliveryMethod, c -> c)); + this.channels.put(NotificationDeliveryMethod.WEBSOCKET, (NotificationChannel) websocketNotificationChannel); + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java index a0e1dc1bb1..39bd325a99 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java @@ -19,9 +19,10 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.NotificationManager; -import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationTargetId; @@ -50,7 +51,8 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul private final NotificationRuleService notificationRuleService; private final NotificationRequestService notificationRequestService; - private final NotificationManager notificationManager; + @Autowired @Lazy + private NotificationManager notificationManager; private final DbCallbackExecutorService dbCallbackExecutorService; @Override @@ -108,9 +110,9 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul } else { NotificationInfo newNotificationInfo = constructNotificationInfo(alarm); for (NotificationRequest notificationRequest : notificationRequests) { - NotificationInfo previousNotificationInfo = notificationRequest.getNotificationInfo(); + NotificationInfo previousNotificationInfo = notificationRequest.getInfo(); if (!previousNotificationInfo.equals(newNotificationInfo)) { - notificationRequest.setNotificationInfo(newNotificationInfo); + notificationRequest.setInfo(newNotificationInfo); notificationManager.updateNotificationRequest(tenantId, notificationRequest); } } @@ -127,34 +129,28 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul config.setSendingDelayInSec(delayInSec); } NotificationInfo notificationInfo = constructNotificationInfo(alarm); - + Map templateContext = Map.of( + "alarmType", alarm.getType(), + "alarmId", alarm.getId().toString(), + "alarmOriginatorEntityType", alarm.getOriginator().getEntityType().toString(), + "alarmOriginatorId", alarm.getOriginator().getId().toString() + ); NotificationRequest notificationRequest = NotificationRequest.builder() .tenantId(tenantId) .targetId(targetId) - .notificationReason("Alarm") - .textTemplate(formatNotificationTextTemplate(notificationRule.getNotificationTextTemplate(), alarm)) - .notificationInfo(notificationInfo) - .notificationSeverity(NotificationSeverity.NORMAL) // todo: from alarm severity + .type("Alarm") + .templateId(notificationRule.getTemplateId()) + .deliveryMethods(notificationRule.getDeliveryMethods()) + .additionalConfig(config) + .info(notificationInfo) .originatorType(NotificationOriginatorType.ALARM) .originatorEntityId(alarm.getId()) .ruleId(notificationRule.getId()) - .additionalConfig(config) + .templateContext(templateContext) .build(); notificationManager.processNotificationRequest(tenantId, notificationRequest); } - private String formatNotificationTextTemplate(String textTemplate, Alarm alarm) { - Map context = Map.of( // fixme: notification text is not updatable - "alarmType", alarm.getType(), - "alarmId", alarm.getId().toString(), - "alarmOriginatorEntityType", alarm.getOriginator().getEntityType().toString(), - "alarmOriginatorId", alarm.getOriginator().getId().toString(), - "alarmSeverity", alarm.getSeverity().toString(), - "alarmStatus", alarm.getStatus().toString() - ); - return TbNodeUtils.processTemplate(textTemplate, context); - } - private NotificationInfo constructNotificationInfo(Alarm alarm) { return AlarmOriginatedNotificationInfo.builder() .alarmId(alarm.getId()) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java index 2ae1ca43b6..a2757dc3dd 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java @@ -89,9 +89,8 @@ public class DefaultNotificationSchedulerService extends AbstractPartitionBasedS .orElse(0); if (delayInSec <= 0) return; long delayInMs = TimeUnit.SECONDS.toMillis(delayInSec) - (System.currentTimeMillis() - requestTs); - if (delayInMs < 0) { // in case the scheduled request processing time was during the downtime + if (delayInMs < 0) { delayInMs = 0; - // or maybe no need to process outdated notification requests ? } ListenableScheduledFuture scheduledTask = scheduledExecutor.schedule(() -> { diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationTemplateUtil.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationTemplateUtil.java new file mode 100644 index 0000000000..b269712177 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationTemplateUtil.java @@ -0,0 +1,56 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.id.NotificationTemplateId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; +import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; +import org.thingsboard.server.common.data.notification.template.NotificationText; +import org.thingsboard.server.common.data.notification.template.NotificationTextTemplate; +import org.thingsboard.server.dao.notification.NotificationTemplateService; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +public class NotificationTemplateUtil { + + private final NotificationTemplateService templateService; + + public Map getTemplates(TenantId tenantId, NotificationTemplateId templateId, List deliveryMethods) { + NotificationTemplate notificationTemplate = templateService.findNotificationTemplateById(tenantId, templateId); + NotificationTemplateConfig config = notificationTemplate.getConfiguration(); + return deliveryMethods.stream() + .collect(Collectors.toMap(k -> k, deliveryMethod -> { + return Optional.ofNullable(config.getTextTemplates()) + .map(templates -> templates.get(deliveryMethod)) + .orElse(config.getDefaultTextTemplate()); + })); + } + + public NotificationText processTemplate(NotificationTextTemplate template, Map templateContext) { + return new NotificationText(TbNodeUtils.processTemplate(template.getBody(), templateContext), template.getSubject()); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java new file mode 100644 index 0000000000..a3ec0fb2c3 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java @@ -0,0 +1,49 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.channels; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.thingsboard.rule.engine.api.MailService; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.template.NotificationText; +import org.thingsboard.server.service.mail.MailExecutorService; + +import java.util.concurrent.Future; + +@Component +@RequiredArgsConstructor +public class EmailNotificationChannel implements NotificationChannel { + + private final MailService mailService; + private final MailExecutorService executor; + + @Override + public Future sendNotification(User recipient, NotificationRequest request, NotificationText text) { + return executor.submit(() -> { + mailService.sendEmail(recipient.getTenantId(), recipient.getEmail(), text.getSubject(), text.getBody()); + return null; + }); + } + + @Override + public NotificationDeliveryMethod getDeliveryMethod() { + return NotificationDeliveryMethod.EMAIL; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java new file mode 100644 index 0000000000..ccf13bf2e8 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.channels; + +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.template.NotificationText; + +import java.util.concurrent.Future; + +public interface NotificationChannel { + + Future sendNotification(User recipient, NotificationRequest request, NotificationText text); + + NotificationDeliveryMethod getDeliveryMethod(); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java new file mode 100644 index 0000000000..6ab263c779 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java @@ -0,0 +1,53 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.channels; + +import com.google.common.util.concurrent.Futures; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import org.thingsboard.rule.engine.api.SmsService; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.template.NotificationText; +import org.thingsboard.server.service.sms.SmsExecutorService; + +import java.util.concurrent.Future; + +@Component +@RequiredArgsConstructor +public class SmsNotificationChannel implements NotificationChannel { + + private final SmsService smsService; + private final SmsExecutorService executor; + + @Override + public Future sendNotification(User recipient, NotificationRequest request, NotificationText text) { + String phone = recipient.getPhone(); + if (StringUtils.isBlank(phone)) return Futures.immediateFuture(null); + return executor.submit(() -> { + smsService.sendSms(recipient.getTenantId(), recipient.getCustomerId(), new String[]{phone}, text.getBody()); + return null; + }); + } + + @Override + public NotificationDeliveryMethod getDeliveryMethod() { + return NotificationDeliveryMethod.SMS; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index d52c712074..6bd9b2f278 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -28,6 +28,7 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; @@ -149,7 +150,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService implements SubscriptionManagerService { - @Autowired - private AttributesService attrService; - - @Autowired - private TimeseriesService tsService; - - @Autowired - private NotificationsTopicService notificationsTopicService; - - @Autowired - private PartitionService partitionService; - - @Autowired - private TbServiceInfoProvider serviceInfoProvider; - - @Autowired - private TbQueueProducerProvider producerProvider; - - @Autowired - private TbLocalSubscriptionService localSubscriptionService; - - @Autowired - private DeviceStateService deviceStateService; - - @Autowired - private TbClusterService clusterService; - - @Autowired - private NotificationRuleProcessingService notificationRuleProcessingService; + private final AttributesService attrService; + private final TimeseriesService tsService; + private final NotificationsTopicService notificationsTopicService; + private final PartitionService partitionService; + private final TbServiceInfoProvider serviceInfoProvider; + private final TbQueueProducerProvider producerProvider; + private final TbLocalSubscriptionService localSubscriptionService; + private final DeviceStateService deviceStateService; + private final TbClusterService clusterService; + private final NotificationRuleProcessingService notificationRuleProcessingService; private final Map> subscriptionsByEntityId = new ConcurrentHashMap<>(); private final Map> subscriptionsByWsSessionId = new ConcurrentHashMap<>(); diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java index c0b58bf807..f33f8b833d 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java @@ -20,16 +20,17 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; -import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.service.subscription.SubscriptionManagerService; import javax.annotation.Nullable; @@ -51,23 +52,15 @@ public abstract class AbstractSubscriptionService extends TbApplicationEventList protected final Set currentPartitions = ConcurrentHashMap.newKeySet(); - protected final TbClusterService clusterService; - protected final PartitionService partitionService; + @Autowired + protected TbClusterService clusterService; + @Autowired + protected PartitionService partitionService; + @Autowired protected Optional subscriptionManagerService; protected ExecutorService wsCallBackExecutor; - public AbstractSubscriptionService(TbClusterService clusterService, - PartitionService partitionService) { - this.clusterService = clusterService; - this.partitionService = partitionService; - } - - @Autowired(required = false) - public void setSubscriptionManagerService(Optional subscriptionManagerService) { - this.subscriptionManagerService = subscriptionManagerService; - } - protected abstract String getExecutorPrefix(); @PostConstruct @@ -118,4 +111,5 @@ public abstract class AbstractSubscriptionService extends TbApplicationEventList } }, wsCallBackExecutor); } + } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index 3b8a72a21a..0833b42fd1 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; @@ -55,28 +56,13 @@ import java.util.Optional; */ @Service @Slf4j +@RequiredArgsConstructor public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService implements AlarmSubscriptionService { private final AlarmService alarmService; private final TbApiUsageReportClient apiUsageClient; private final TbApiUsageStateService apiUsageStateService; - public DefaultAlarmSubscriptionService(TbClusterService clusterService, - PartitionService partitionService, - AlarmService alarmService, - TbApiUsageReportClient apiUsageClient, - TbApiUsageStateService apiUsageStateService) { - super(clusterService, partitionService); - this.alarmService = alarmService; - this.apiUsageClient = apiUsageClient; - this.apiUsageStateService = apiUsageStateService; - } - - @Autowired(required = false) - public void setSubscriptionManagerService(Optional subscriptionManagerService) { - this.subscriptionManagerService = subscriptionManagerService; - } - @Override protected String getExecutorPrefix() { return "alarm"; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index 76a9582bd2..b2dbc3c2a3 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -83,11 +83,8 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer public DefaultTelemetrySubscriptionService(AttributesService attrService, TimeseriesService tsService, @Lazy TbEntityViewService tbEntityViewService, - TbClusterService clusterService, - PartitionService partitionService, TbApiUsageReportClient apiUsageClient, TbApiUsageStateService apiUsageStateService) { - super(clusterService, partitionService); this.attrService = attrService; this.tsService = tsService; this.tbEntityViewService = tbEntityViewService; diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java index cce7ff749e..163a98224c 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java @@ -232,12 +232,12 @@ public class NotificationApiTest extends AbstractControllerTest { getWsClient().waitForUpdate(true); Notification initialNotification = getWsClient().getLastDataUpdate().getUpdate(); assertThat(getMyNotifications(false, 10)).singleElement().isEqualTo(initialNotification); - assertThat(initialNotification.getInfo()).isNotNull().isEqualTo(notificationRequest.getNotificationInfo()); + assertThat(initialNotification.getInfo()).isNotNull().isEqualTo(notificationRequest.getInfo()); getWsClient().registerWaitForUpdate(); NotificationInfo newNotificationInfo = new NotificationInfo(); newNotificationInfo.setDescription("New description"); - notificationRequest.setNotificationInfo(newNotificationInfo); + notificationRequest.setInfo(newNotificationInfo); notificationManager.updateNotificationRequest(tenantId, notificationRequest); getWsClient().waitForUpdate(true); Notification updatedNotification = getWsClient().getLastDataUpdate().getUpdate(); @@ -264,7 +264,7 @@ public class NotificationApiTest extends AbstractControllerTest { notificationTarget = saveNotificationTarget(notificationTarget); wsSessions.forEach((user, wsClient) -> wsClient.registerWaitForUpdate(2)); - NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), "Hello, ${recipientEmail}"); + NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), "Hello, ${email}"); await().atMost(5, TimeUnit.SECONDS) .pollDelay(1, TimeUnit.SECONDS).pollInterval(500, TimeUnit.MILLISECONDS) .until(() -> wsSessions.values().stream() @@ -339,9 +339,10 @@ public class NotificationApiTest extends AbstractControllerTest { NotificationRequest notificationRequest = NotificationRequest.builder() .tenantId(tenantId) .targetId(targetId) - .notificationReason("Test") - .textTemplate(text) - .notificationInfo(notificationInfo) + .type("Test") + .templateId(notificationTemplate.getId()) + .info(notificationInfo) + .deliveryMethods(List.of(NotificationDeliveryMethod.WEBSOCKET)) .additionalConfig(config) .build(); return doPost("/api/notification/request", notificationRequest, NotificationRequest.class); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationsClient.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationsClient.java index bcd6134478..9bf492a3e3 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationsClient.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationsClient.java @@ -64,7 +64,7 @@ public class NotificationsClient extends NotificationApiWsClient { } SimpleDateFormat format = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss"); String time = format.format(new Date(notification.getCreatedTime())); - System.out.printf("[%s] %-19s | %-30s | (%s)\n", time, notification.getReason(), notification.getText(), notificationInfoStr); +// System.out.printf("[%s] %-19s | %-30s | (%s)\n", time, notification.getReason(), notification.getText(), notificationInfoStr); }); System.out.println(StringUtils.repeat(System.lineSeparator(), 5)); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.java new file mode 100644 index 0000000000..c22e7a1caa --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.notification; + +import org.thingsboard.server.common.data.id.NotificationTemplateId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; + +import java.util.List; + +public interface NotificationTemplateService { + + NotificationTemplate findNotificationTemplateById(TenantId tenantId, NotificationTemplateId id); + + NotificationTemplate saveNotificationTemplate(TenantId tenantId, NotificationTemplate notificationTemplate); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/User.java b/common/data/src/main/java/org/thingsboard/server/common/data/User.java index 60d8d7eca9..859f982d9c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/User.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/User.java @@ -45,6 +45,8 @@ public class User extends SearchTextBasedWithAdditionalInfo implements H @NoXss @Length(fieldName = "last name") private String lastName; + @NoXss + private String phone; public User() { super(); @@ -62,6 +64,7 @@ public class User extends SearchTextBasedWithAdditionalInfo implements H this.authority = user.getAuthority(); this.firstName = user.getFirstName(); this.lastName = user.getLastName(); + this.phone = user.getPhone(); } @@ -141,6 +144,14 @@ public class User extends SearchTextBasedWithAdditionalInfo implements H this.lastName = lastName; } + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + @ApiModelProperty(position = 10, value = "Additional parameters of the user", dataType = "com.fasterxml.jackson.databind.JsonNode") @Override public JsonNode getAdditionalInfo() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTemplateId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTemplateId.java new file mode 100644 index 0000000000..596fe4b83c --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTemplateId.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.UUID; + +public class NotificationTemplateId extends UUIDBased { + + @JsonCreator + public NotificationTemplateId(@JsonProperty("id") UUID id) { + super(id); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java index a39d858fcc..8cb879c178 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java @@ -34,10 +34,11 @@ public class Notification extends BaseData { private NotificationRequestId requestId; private UserId recipientId; - private String reason; + + private String type; // todo: maybe to enum private String text; private NotificationInfo info; - private NotificationSeverity severity; + private NotificationOriginatorType originatorType; private NotificationStatus status; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java similarity index 85% rename from common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationSettings.java rename to common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java index 2632d1b76e..3f19364afc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java @@ -15,6 +15,6 @@ */ package org.thingsboard.server.common.data.notification; -public class NotificationSettings { - // location on the screen, shown notifications count, timings of displaying +public enum NotificationDeliveryMethod { + WEBSOCKET, SMS, EMAIL, SLACK } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationOriginatorType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationOriginatorType.java index 44aa879a00..c02c7e47b6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationOriginatorType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationOriginatorType.java @@ -16,7 +16,7 @@ package org.thingsboard.server.common.data.notification; public enum NotificationOriginatorType { - USER, + ADMIN, ALARM, RULE_NODE } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java index 142c3c6b9e..403391e385 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java @@ -28,12 +28,15 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.NoXss; import javax.validation.Valid; -import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; +import java.util.List; +import java.util.Map; @Data @EqualsAndHashCode(callSuper = true) @@ -47,39 +50,46 @@ public class NotificationRequest extends BaseData impleme private NotificationTargetId targetId; @NoXss - private String notificationReason; - @NotBlank(message = "Notification text template is missing") - private String textTemplate; // fixme: xss + private String type; + @NotNull + private NotificationTemplateId templateId; @Valid - private NotificationInfo notificationInfo; - private NotificationSeverity notificationSeverity; + private NotificationInfo info; + @NotEmpty + private List deliveryMethods; + @NotNull + @Valid + private NotificationRequestConfig additionalConfig; private NotificationOriginatorType originatorType; - private EntityId originatorEntityId; // userId, alarmId or tenantId + private EntityId originatorEntityId; private NotificationRuleId ruleId; - private NotificationRequestConfig additionalConfig; private NotificationRequestStatus status; + @JsonIgnore + private transient Map templateContext; + public NotificationRequest(NotificationRequest other) { super(other); this.tenantId = other.tenantId; this.targetId = other.targetId; - this.notificationReason = other.notificationReason; - this.textTemplate = other.textTemplate; - this.notificationInfo = other.notificationInfo; - this.notificationSeverity = other.notificationSeverity; + this.type = other.type; + this.templateId = other.templateId; + this.info = other.info; + this.deliveryMethods = other.deliveryMethods; + this.additionalConfig = other.additionalConfig; this.originatorType = other.originatorType; this.originatorEntityId = other.originatorEntityId; this.ruleId = other.ruleId; - this.additionalConfig = other.additionalConfig; this.status = other.status; + this.templateContext = other.templateContext; } @JsonIgnore @Override public String getName() { - return notificationReason; + return type; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java index 1ecff879f6..71bddf1342 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java @@ -17,7 +17,11 @@ package org.thingsboard.server.common.data.notification; import lombok.Data; +import java.util.Map; + @Data public class NotificationRequestConfig { + private int sendingDelayInSec; + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java index 9ce62f1420..ab343cce2f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java @@ -22,11 +22,15 @@ import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import javax.validation.Valid; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; +import java.util.List; @Data @EqualsAndHashCode(callSuper = true) @@ -36,8 +40,10 @@ public class NotificationRule extends BaseData implements Ha private TenantId tenantId; @NotBlank private String name; - @NotBlank - private String notificationTextTemplate; + @NotNull + private NotificationTemplateId templateId; + @NotEmpty + private List deliveryMethods; @NotNull private NotificationTargetId initialNotificationTargetId; @Valid diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java new file mode 100644 index 0000000000..ff91068f84 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.notification.settings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import lombok.Data; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; + +@JsonIgnoreProperties(ignoreUnknown = true) +//@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "method", visible = true, include = JsonTypeInfo.As.EXISTING_PROPERTY, defaultImpl = NotificationDeliveryMethodConfig.class) +//@JsonSubTypes({ +//// @JsonSubTypes.Type(name = "SLACK", value = SlackNotificationDeliveryMethodConfig.class), +//}) +@Data +public class NotificationDeliveryMethodConfig { + + private boolean enabled; + + public NotificationDeliveryMethod getMethod() { + return null; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java new file mode 100644 index 0000000000..51ec3af828 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.notification.settings; + +import lombok.Data; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; + +import java.util.Map; + +@Data +public class NotificationSettings { + + // location on the screen, shown notifications count, timings of displaying + private Map deliveryMethodsConfigs; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java new file mode 100644 index 0000000000..3cbf8ca97c --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.notification.template; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.id.NotificationTemplateId; +import org.thingsboard.server.common.data.id.TenantId; + +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationTemplate extends BaseData implements HasTenantId, HasName { + + private TenantId tenantId; + private String name; + private NotificationTemplateConfig configuration; + // add notification type (notification reason) + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java new file mode 100644 index 0000000000..bc92add8f4 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.notification.template; + +import lombok.Data; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; + +import java.util.Map; + +@Data +public class NotificationTemplateConfig { + + private NotificationTextTemplate defaultTextTemplate; + private Map textTemplates; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationText.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationText.java new file mode 100644 index 0000000000..227e4731b2 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationText.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.notification.template; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class NotificationText { + + private String body; + private String subject; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTextTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTextTemplate.java new file mode 100644 index 0000000000..3e0ecff9d1 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTextTemplate.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.notification.template; + +import lombok.Data; + +@Data +public class NotificationTextTemplate { + + private String body; + private String subject; + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 65917e9b2d..941e9459ba 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -656,18 +656,17 @@ public class ModelConstants { public static final String NOTIFICATION_TABLE_NAME = "notification"; public static final String NOTIFICATION_REQUEST_ID_PROPERTY = "request_id"; public static final String NOTIFICATION_RECIPIENT_ID_PROPERTY = "recipient_id"; - public static final String NOTIFICATION_REASON_PROPERTY = "reason"; + public static final String NOTIFICATION_TYPE_PROPERTY = "type"; public static final String NOTIFICATION_TEXT_PROPERTY = "text"; - public static final String NOTIFICATION_SEVERITY_PROPERTY = "severity"; public static final String NOTIFICATION_ORIGINATOR_TYPE_PROPERTY = "originator_type"; public static final String NOTIFICATION_STATUS_PROPERTY = "status"; public static final String NOTIFICATION_REQUEST_TABLE_NAME = "notification_request"; public static final String NOTIFICATION_REQUEST_TARGET_ID_PROPERTY = "target_id"; - public static final String NOTIFICATION_REQUEST_TEXT_TEMPLATE_PROPERTY = "text_template"; - public static final String NOTIFICATION_REQUEST_NOTIFICATION_REASON_PROPERTY = "notification_reason"; - public static final String NOTIFICATION_REQUEST_NOTIFICATION_INFO_PROPERTY = "notification_info"; - public static final String NOTIFICATION_REQUEST_NOTIFICATION_SEVERITY_PROPERTY = "notification_severity"; + public static final String NOTIFICATION_REQUEST_TEMPLATE_ID_PROPERTY = "template_id"; + public static final String NOTIFICATION_REQUEST_DELIVERY_METHODS_PROPERTY = "delivery_methods"; + public static final String NOTIFICATION_REQUEST_TYPE_PROPERTY = "type"; + public static final String NOTIFICATION_REQUEST_INFO_PROPERTY = "info"; public static final String NOTIFICATION_REQUEST_ORIGINATOR_TYPE_PROPERTY = "originator_type"; public static final String NOTIFICATION_REQUEST_ORIGINATOR_ENTITY_ID_PROPERTY = "originator_entity_id"; public static final String NOTIFICATION_REQUEST_ORIGINATOR_ENTITY_TYPE_PROPERTY = "originator_entity_type"; @@ -676,10 +675,14 @@ public class ModelConstants { public static final String NOTIFICATION_REQUEST_RULE_ID_PROPERTY = "rule_id"; public static final String NOTIFICATION_RULE_TABLE_NAME = "notification_rule"; - public static final String NOTIFICATION_RULE_NOTIFICATION_TEXT_TEMPLATE_PROPERTY = "notification_text_template"; + public static final String NOTIFICATION_RULE_TEMPLATE_ID_PROPERTY = "template_id"; + public static final String NOTIFICATION_RULE_DELIVERY_METHODS_PROPERTY = "delivery_methods"; public static final String NOTIFICATION_RULE_INITIAL_NOTIFICATION_TARGET_ID_PROPERTY = "initial_notification_target_id"; public static final String NOTIFICATION_RULE_ESCALATION_CONFIG_PROPERTY = "escalation_config"; + public static final String NOTIFICATION_TEMPLATE_TABLE_NAME = "notification_template"; + public static final String NOTIFICATION_TEMPLATE_CONFIGURATION = "configuration"; + protected static final String[] NONE_AGGREGATION_COLUMNS = new String[]{LONG_VALUE_COLUMN, DOUBLE_VALUE_COLUMN, BOOLEAN_VALUE_COLUMN, STRING_VALUE_COLUMN, JSON_VALUE_COLUMN, KEY_COLUMN, TS_COLUMN}; protected static final String[] COUNT_AGGREGATION_COLUMNS = new String[]{count(LONG_VALUE_COLUMN), count(DOUBLE_VALUE_COLUMN), count(BOOLEAN_VALUE_COLUMN), count(STRING_VALUE_COLUMN), count(JSON_VALUE_COLUMN), max(TS_COLUMN)}; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java index 2e6255eecd..7855e773a4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java @@ -28,7 +28,6 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationOriginatorType; -import org.thingsboard.server.common.data.notification.NotificationSeverity; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; @@ -54,8 +53,8 @@ public class NotificationEntity extends BaseSqlEntity { @Column(name = ModelConstants.NOTIFICATION_RECIPIENT_ID_PROPERTY, nullable = false) private UUID recipientId; - @Column(name = ModelConstants.NOTIFICATION_REASON_PROPERTY, nullable = false) - private String reason; + @Column(name = ModelConstants.NOTIFICATION_TYPE_PROPERTY, nullable = false) + private String type; @Column(name = ModelConstants.NOTIFICATION_TEXT_PROPERTY, nullable = false) private String text; @@ -64,10 +63,6 @@ public class NotificationEntity extends BaseSqlEntity { @Formula("(SELECT r.notification_info FROM notification_request r WHERE r.id = request_id)") private JsonNode info; - @Enumerated(EnumType.STRING) - @Column(name = ModelConstants.NOTIFICATION_SEVERITY_PROPERTY) - private NotificationSeverity severity; - @Enumerated(EnumType.STRING) @Column(name = ModelConstants.NOTIFICATION_ORIGINATOR_TYPE_PROPERTY) private NotificationOriginatorType originatorType; @@ -83,12 +78,9 @@ public class NotificationEntity extends BaseSqlEntity { setCreatedTime(notification.getCreatedTime()); setRequestId(getUuid(notification.getRequestId())); setRecipientId(getUuid(notification.getRecipientId())); - setReason(notification.getReason()); + setType(notification.getType()); setText(notification.getText()); - if (notification.getInfo() != null) { - setInfo(JacksonUtil.valueToTree(notification.getInfo())); - } - setSeverity(notification.getSeverity()); + setInfo(toJson(notification.getInfo())); setOriginatorType(notification.getOriginatorType()); setStatus(notification.getStatus()); } @@ -100,12 +92,9 @@ public class NotificationEntity extends BaseSqlEntity { notification.setCreatedTime(createdTime); notification.setRequestId(createId(requestId, NotificationRequestId::new)); notification.setRecipientId(createId(recipientId, UserId::new)); - notification.setReason(reason); + notification.setText(type); notification.setText(text); - if (info != null) { - notification.setInfo(JacksonUtil.treeToValue(info, NotificationInfo.class)); - } - notification.setSeverity(severity); + notification.setInfo(fromJson(info, NotificationInfo.class)); notification.setOriginatorType(originatorType); notification.setStatus(status); return notification; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java index 52b5aee2b9..1001f96be0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java @@ -18,23 +18,22 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import lombok.EqualsAndHashCode; +import org.apache.commons.lang3.StringUtils; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationOriginatorType; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; import org.thingsboard.server.common.data.notification.NotificationRequestStatus; -import org.thingsboard.server.common.data.notification.NotificationSeverity; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonStringType; @@ -44,7 +43,9 @@ import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.Table; +import java.util.Arrays; import java.util.UUID; +import java.util.stream.Collectors; @Data @EqualsAndHashCode(callSuper = true) @@ -59,19 +60,22 @@ public class NotificationRequestEntity extends BaseSqlEntity { @Column(name = ModelConstants.NAME_PROPERTY, nullable = false) private String name; - @Column(name = ModelConstants.NOTIFICATION_RULE_NOTIFICATION_TEXT_TEMPLATE_PROPERTY, nullable = false) - private String notificationTextTemplate; + @Column(name = ModelConstants.NOTIFICATION_RULE_TEMPLATE_ID_PROPERTY, nullable = false) + private UUID templateId; + + @Column(name = ModelConstants.NOTIFICATION_RULE_DELIVERY_METHODS_PROPERTY, nullable = false) + private String deliveryMethods; @Column(name = ModelConstants.NOTIFICATION_RULE_INITIAL_NOTIFICATION_TARGET_ID_PROPERTY) private UUID initialNotificationTargetId; @@ -63,7 +71,8 @@ public class NotificationRuleEntity extends BaseSqlEntity { setCreatedTime(notificationRule.getCreatedTime()); setTenantId(getUuid(notificationRule.getTenantId())); setName(notificationRule.getName()); - setNotificationTextTemplate(notificationRule.getNotificationTextTemplate()); + setTemplateId(getUuid(notificationRule.getTemplateId())); + setDeliveryMethods(StringUtils.join(notificationRule.getDeliveryMethods(), ',')); setInitialNotificationTargetId(getUuid(notificationRule.getInitialNotificationTargetId())); setEscalationConfig(toJson(notificationRule.getEscalationConfig())); } @@ -75,7 +84,11 @@ public class NotificationRuleEntity extends BaseSqlEntity { notificationRule.setCreatedTime(createdTime); notificationRule.setTenantId(createId(tenantId, TenantId::fromUUID)); notificationRule.setName(name); - notificationRule.setNotificationTextTemplate(notificationTextTemplate); + notificationRule.setTemplateId(createId(templateId, NotificationTemplateId::new)); + if (deliveryMethods != null) { + notificationRule.setDeliveryMethods(Arrays.stream(StringUtils.split(deliveryMethods, ',')) + .filter(StringUtils::isNotBlank).map(NotificationDeliveryMethod::valueOf).collect(Collectors.toList())); + } notificationRule.setInitialNotificationTargetId(createId(initialNotificationTargetId, NotificationTargetId::new)); notificationRule.setEscalationConfig(fromJson(escalationConfig, NotificationEscalationConfig.class)); return notificationRule; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java index 37a4c51fda..6f274acc70 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java @@ -20,14 +20,12 @@ import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.model.SearchTextEntity; import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; @@ -59,7 +57,7 @@ public class NotificationTargetEntity extends BaseSqlEntity setCreatedTime(notificationTarget.getCreatedTime()); setTenantId(getUuid(notificationTarget.getTenantId())); setName(notificationTarget.getName()); - setConfiguration(JacksonUtil.valueToTree(notificationTarget.getConfiguration())); + setConfiguration(toJson(notificationTarget.getConfiguration())); } @Override @@ -69,9 +67,7 @@ public class NotificationTargetEntity extends BaseSqlEntity notificationTarget.setCreatedTime(createdTime); notificationTarget.setTenantId(createId(tenantId, TenantId::fromUUID)); notificationTarget.setName(name); - if (configuration != null) { - notificationTarget.setConfiguration(JacksonUtil.treeToValue(configuration, NotificationTargetConfig.class)); - } + notificationTarget.setConfiguration(fromJson(configuration, NotificationTargetConfig.class)); return notificationTarget; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java new file mode 100644 index 0000000000..ba6f46accd --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java @@ -0,0 +1,74 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.id.NotificationTemplateId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; +import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.util.mapping.JsonStringType; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.util.UUID; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@TypeDef(name = "json", typeClass = JsonStringType.class) +@Table(name = ModelConstants.NOTIFICATION_TEMPLATE_TABLE_NAME) +public class NotificationTemplateEntity extends BaseSqlEntity { + + @Column(name = ModelConstants.TENANT_ID_PROPERTY, nullable = false) + private UUID tenantId; + + @Column(name = ModelConstants.NAME_PROPERTY, nullable = false) + private String name; + + @Type(type = "json") + @Column(name = ModelConstants.NOTIFICATION_TEMPLATE_CONFIGURATION, nullable = false) + private JsonNode configuration; + + public NotificationTemplateEntity() {} + + public NotificationTemplateEntity(NotificationTemplate notificationTemplate) { + setId(notificationTemplate.getUuidId()); + setCreatedTime(notificationTemplate.getCreatedTime()); + setTenantId(getUuid(notificationTemplate.getTenantId())); + setName(notificationTemplate.getName()); + setConfiguration(toJson(notificationTemplate.getConfiguration())); + } + + @Override + public NotificationTemplate toData() { + NotificationTemplate notificationTemplate = new NotificationTemplate(); + notificationTemplate.setId(new NotificationTemplateId(id)); + notificationTemplate.setCreatedTime(createdTime); + notificationTemplate.setTenantId(createId(tenantId, TenantId::fromUUID)); + notificationTemplate.setName(name); + notificationTemplate.setConfiguration(fromJson(configuration, NotificationTemplateConfig.class)); + return notificationTemplate; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserEntity.java index 8e6c66fcf8..2d96742ee3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserEntity.java @@ -69,6 +69,9 @@ public class UserEntity extends BaseSqlEntity implements SearchTextEntity< @Column(name = ModelConstants.USER_LAST_NAME_PROPERTY) private String lastName; + @Column(name = ModelConstants.PHONE_PROPERTY) + private String phone; + @Type(type = "json") @Column(name = ModelConstants.USER_ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; @@ -91,6 +94,7 @@ public class UserEntity extends BaseSqlEntity implements SearchTextEntity< this.email = user.getEmail(); this.firstName = user.getFirstName(); this.lastName = user.getLastName(); + this.phone = user.getPhone(); this.additionalInfo = user.getAdditionalInfo(); } @@ -118,6 +122,7 @@ public class UserEntity extends BaseSqlEntity implements SearchTextEntity< user.setEmail(email); user.setFirstName(firstName); user.setLastName(lastName); + user.setPhone(phone); user.setAdditionalInfo(additionalInfo); return user; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java new file mode 100644 index 0000000000..6e14ee1fac --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.notification; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.id.NotificationTemplateId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; + +@Service +@RequiredArgsConstructor +public class DefaultNotificationTemplateService implements NotificationTemplateService { + + private final NotificationTemplateDao notificationTemplateDao; + + @Override + public NotificationTemplate findNotificationTemplateById(TenantId tenantId, NotificationTemplateId id) { + return notificationTemplateDao.findById(tenantId, id.getId()); + } + + @Override + public NotificationTemplate saveNotificationTemplate(TenantId tenantId, NotificationTemplate notificationTemplate) { + return notificationTemplateDao.save(tenantId, notificationTemplate); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java new file mode 100644 index 0000000000..7417c7f43d --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.notification; + +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; +import org.thingsboard.server.dao.Dao; + +public interface NotificationTemplateDao extends Dao { +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java new file mode 100644 index 0000000000..b549ee80e3 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.notification; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; +import org.thingsboard.server.dao.model.sql.NotificationTemplateEntity; +import org.thingsboard.server.dao.notification.NotificationTemplateDao; +import org.thingsboard.server.dao.sql.JpaAbstractDao; +import org.thingsboard.server.dao.util.SqlDao; + +import java.util.UUID; + +@Component +@SqlDao +@RequiredArgsConstructor +public class JpaNotificationTemplateDao extends JpaAbstractDao implements NotificationTemplateDao { + + private final NotificationTemplateRepository notificationTemplateRepository; + + @Override + protected Class getEntityClass() { + return NotificationTemplateEntity.class; + } + + @Override + protected JpaRepository getRepository() { + return notificationTemplateRepository; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java index 40ae696979..b6d22a62fc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java @@ -32,8 +32,7 @@ import java.util.UUID; public interface NotificationRequestRepository extends JpaRepository { @Query("SELECT r FROM NotificationRequestEntity r WHERE r.tenantId = :tenantId AND " + - "(lower(r.notificationReason) LIKE lower(concat('%', :searchText, '%')) OR " + - "lower(r.textTemplate) LIKE lower(concat('%', :searchText, '%')))") + "(lower(r.type) LIKE lower(concat('%', :searchText, '%')))") Page findByTenantIdAndSearchText(@Param("tenantId") UUID tenantId, @Param("searchText") String searchText, Pageable pageable); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java new file mode 100644 index 0000000000..5d007fc677 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.notification; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import org.thingsboard.server.dao.model.sql.NotificationTemplateEntity; + +import java.util.UUID; + +@Repository +public interface NotificationTemplateRepository extends JpaRepository { +} diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 3332034f8e..d09fc97abd 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -431,6 +431,7 @@ CREATE TABLE IF NOT EXISTS tb_user ( email varchar(255) UNIQUE, first_name varchar(255), last_name varchar(255), + phone varchar(255), search_text varchar(255), tenant_id uuid ); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationManager.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationManager.java index dc57a876fd..88d9cc76be 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationManager.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationManager.java @@ -25,10 +25,11 @@ public interface NotificationManager { NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); - void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId); - void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId); NotificationRequest updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); + + void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId); + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java index f87debd4dc..64921ef299 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java @@ -53,25 +53,25 @@ public class TbNotificationNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { - NotificationRequest notificationRequest = NotificationRequest.builder() - .tenantId(ctx.getTenantId()) - .targetId(new NotificationTargetId(config.getTargetId())) - .notificationReason(config.getNotificationReason()) - .textTemplate(TbNodeUtils.processPattern(config.getNotificationTextTemplate(), msg)) - .notificationSeverity(config.getNotificationSeverity()) - .originatorType(NotificationOriginatorType.RULE_NODE) - .originatorEntityId(ctx.getTenantId()) - .build(); - withCallback(ctx.getDbCallbackExecutor().executeAsync(() -> { - return ctx.getNotificationManager().processNotificationRequest(ctx.getTenantId(), notificationRequest); - }), - r -> { - TbMsgMetaData msgMetaData = msg.getMetaData().copy(); - msgMetaData.putValue("notificationRequestId", r.getUuidId().toString()); - msgMetaData.putValue("notificationTextTemplate", r.getTextTemplate()); - ctx.tellSuccess(TbMsg.transformMsg(msg, msgMetaData)); - }, - e -> ctx.tellFailure(msg, e)); +// NotificationRequest notificationRequest = NotificationRequest.builder() +// .tenantId(ctx.getTenantId()) +// .targetId(new NotificationTargetId(config.getTargetId())) +// .type(config.getNotificationReason()) +// .textTemplate(TbNodeUtils.processPattern(config.getNotificationTextTemplate(), msg)) +// .notificationSeverity(config.getNotificationSeverity()) +// .originatorType(NotificationOriginatorType.RULE_NODE) +// .originatorEntityId(ctx.getTenantId()) +// .build(); +// withCallback(ctx.getDbCallbackExecutor().executeAsync(() -> { +// return ctx.getNotificationManager().processNotificationRequest(ctx.getTenantId(), notificationRequest); +// }), +// r -> { +// TbMsgMetaData msgMetaData = msg.getMetaData().copy(); +// msgMetaData.putValue("notificationRequestId", r.getUuidId().toString()); +// msgMetaData.putValue("notificationTextTemplate", r.getTextTemplate()); +// ctx.tellSuccess(TbMsg.transformMsg(msg, msgMetaData)); +// }, +// e -> ctx.tellFailure(msg, e)); } private void validateConfig(TbNotificationNodeConfiguration config) throws TbNodeException { From 377f59400d6d05862eb50f6ef960ec89db608e88 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Wed, 14 Dec 2022 18:57:57 +0200 Subject: [PATCH 018/111] BaseController refactoring: remove boilerplate permission checks --- .../server/controller/BaseController.java | 308 +++++------------- 1 file changed, 74 insertions(+), 234 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 4e179301eb..841aff37e5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -86,6 +86,7 @@ import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; +import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetsBundleId; @@ -99,6 +100,7 @@ import org.thingsboard.server.common.data.rpc.Rpc; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.common.data.util.ThrowingBiFunction; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.asset.AssetProfileService; @@ -147,6 +149,7 @@ import org.thingsboard.server.service.security.permission.AccessControlService; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.state.DeviceStateService; +import org.thingsboard.server.service.sync.ie.exporting.ExportableEntitiesService; import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; @@ -157,6 +160,8 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; import java.util.stream.Collectors; import static org.thingsboard.server.controller.ControllerConstants.INCORRECT_TENANT_ID; @@ -297,6 +302,9 @@ public abstract class BaseController { @Autowired protected EntitiesVersionControlService vcService; + @Autowired + protected ExportableEntitiesService entitiesService; + @Value("${server.log_controller_error_stack_trace}") @Getter private boolean logControllerErrorStackTrace; @@ -459,27 +467,11 @@ public abstract class BaseController { } Tenant checkTenantId(TenantId tenantId, Operation operation) throws ThingsboardException { - try { - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - Tenant tenant = tenantService.findTenantById(tenantId); - checkNotNull(tenant, "Tenant with id [" + tenantId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.TENANT, operation, tenantId, tenant); - return tenant; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(tenantId, (t, i) -> tenantService.findTenantById(tenantId), operation); } TenantInfo checkTenantInfoId(TenantId tenantId, Operation operation) throws ThingsboardException { - try { - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - TenantInfo tenant = tenantService.findTenantInfoById(tenantId); - checkNotNull(tenant, "Tenant with id [" + tenantId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.TENANT, operation, tenantId, tenant); - return tenant; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(tenantId, (t, i) -> tenantService.findTenantInfoById(tenantId), operation); } TenantProfile checkTenantProfileId(TenantProfileId tenantProfileId, Operation operation) throws ThingsboardException { @@ -499,33 +491,16 @@ public abstract class BaseController { } Customer checkCustomerId(CustomerId customerId, Operation operation) throws ThingsboardException { - try { - validateId(customerId, "Incorrect customerId " + customerId); - Customer customer = customerService.findCustomerById(getTenantId(), customerId); - checkNotNull(customer, "Customer with id [" + customerId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.CUSTOMER, operation, customerId, customer); - return customer; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(customerId, customerService::findCustomerById, operation); } User checkUserId(UserId userId, Operation operation) throws ThingsboardException { - try { - validateId(userId, "Incorrect userId " + userId); - User user = userService.findUserById(getCurrentUser().getTenantId(), userId); - checkNotNull(user, "User with id [" + userId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.USER, operation, userId, user); - return user; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(userId, userService::findUserById, operation); } protected void checkEntity(I entityId, T entity, Resource resource) throws ThingsboardException { if (entityId == null) { - accessControlService - .checkPermission(getCurrentUser(), resource, Operation.CREATE, null, entity); + accessControlService.checkPermission(getCurrentUser(), resource, Operation.CREATE, null, entity); } else { checkEntityId(entityId, Operation.WRITE); } @@ -596,203 +571,90 @@ public abstract class BaseController { checkQueueId(new QueueId(entityId.getId()), operation); return; default: - throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType()); + checkEntityId(entityId, entitiesService::findEntityByTenantIdAndId, operation); } } catch (Exception e) { throw handleException(e, false); } } - Device checkDeviceId(DeviceId deviceId, Operation operation) throws ThingsboardException { + protected & HasTenantId, I extends EntityId> E checkEntityId(I entityId, ThrowingBiFunction findingFunction, Operation operation) throws ThingsboardException { try { - validateId(deviceId, "Incorrect deviceId " + deviceId); - Device device = deviceService.findDeviceById(getCurrentUser().getTenantId(), deviceId); - checkNotNull(device, "Device with id [" + deviceId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, deviceId, device); - return device; + validateId((UUIDBased) entityId, "Invalid " + entityId.getClass().getSimpleName().toLowerCase()); + SecurityUser user = getCurrentUser(); + E entity = findingFunction.apply(user.getTenantId(), entityId); + checkNotNull(entity, entity.getClass().getSimpleName() + " with id [" + entityId + "] not found"); + accessControlService.checkPermission(user, Resource.of(entityId.getEntityType()), operation, entityId, entity); + return entity; } catch (Exception e) { throw handleException(e, false); } } + Device checkDeviceId(DeviceId deviceId, Operation operation) throws ThingsboardException { + return checkEntityId(deviceId, deviceService::findDeviceById, operation); + } + DeviceInfo checkDeviceInfoId(DeviceId deviceId, Operation operation) throws ThingsboardException { - try { - validateId(deviceId, "Incorrect deviceId " + deviceId); - DeviceInfo device = deviceService.findDeviceInfoById(getCurrentUser().getTenantId(), deviceId); - checkNotNull(device, "Device with id [" + deviceId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, deviceId, device); - return device; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(deviceId, deviceService::findDeviceInfoById, operation); } DeviceProfile checkDeviceProfileId(DeviceProfileId deviceProfileId, Operation operation) throws ThingsboardException { - try { - validateId(deviceProfileId, "Incorrect deviceProfileId " + deviceProfileId); - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(getCurrentUser().getTenantId(), deviceProfileId); - checkNotNull(deviceProfile, "Device profile with id [" + deviceProfileId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE_PROFILE, operation, deviceProfileId, deviceProfile); - return deviceProfile; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(deviceProfileId, deviceProfileService::findDeviceProfileById, operation); } protected EntityView checkEntityViewId(EntityViewId entityViewId, Operation operation) throws ThingsboardException { - try { - validateId(entityViewId, "Incorrect entityViewId " + entityViewId); - EntityView entityView = entityViewService.findEntityViewById(getCurrentUser().getTenantId(), entityViewId); - checkNotNull(entityView, "Entity view with id [" + entityViewId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation, entityViewId, entityView); - return entityView; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(entityViewId, entityViewService::findEntityViewById, operation); } EntityViewInfo checkEntityViewInfoId(EntityViewId entityViewId, Operation operation) throws ThingsboardException { - try { - validateId(entityViewId, "Incorrect entityViewId " + entityViewId); - EntityViewInfo entityView = entityViewService.findEntityViewInfoById(getCurrentUser().getTenantId(), entityViewId); - checkNotNull(entityView, "Entity view with id [" + entityViewId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation, entityViewId, entityView); - return entityView; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(entityViewId, entityViewService::findEntityViewInfoById, operation); } Asset checkAssetId(AssetId assetId, Operation operation) throws ThingsboardException { - try { - validateId(assetId, "Incorrect assetId " + assetId); - Asset asset = assetService.findAssetById(getCurrentUser().getTenantId(), assetId); - checkNotNull(asset, "Asset with id [" + assetId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation, assetId, asset); - return asset; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(assetId, assetService::findAssetById, operation); } AssetInfo checkAssetInfoId(AssetId assetId, Operation operation) throws ThingsboardException { - try { - validateId(assetId, "Incorrect assetId " + assetId); - AssetInfo asset = assetService.findAssetInfoById(getCurrentUser().getTenantId(), assetId); - checkNotNull(asset, "Asset with id [" + assetId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation, assetId, asset); - return asset; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(assetId, assetService::findAssetInfoById, operation); } AssetProfile checkAssetProfileId(AssetProfileId assetProfileId, Operation operation) throws ThingsboardException { - try { - validateId(assetProfileId, "Incorrect assetProfileId " + assetProfileId); - AssetProfile assetProfile = assetProfileService.findAssetProfileById(getCurrentUser().getTenantId(), assetProfileId); - checkNotNull(assetProfile, "Asset profile with id [" + assetProfileId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ASSET_PROFILE, operation, assetProfileId, assetProfile); - return assetProfile; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(assetProfileId, assetProfileService::findAssetProfileById, operation); } Alarm checkAlarmId(AlarmId alarmId, Operation operation) throws ThingsboardException { - try { - validateId(alarmId, "Incorrect alarmId " + alarmId); - Alarm alarm = alarmService.findAlarmByIdAsync(getCurrentUser().getTenantId(), alarmId).get(); - checkNotNull(alarm, "Alarm with id [" + alarmId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, operation, alarmId, alarm); - return alarm; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(alarmId, alarmService::findAlarmById, operation); } AlarmInfo checkAlarmInfoId(AlarmId alarmId, Operation operation) throws ThingsboardException { - try { - validateId(alarmId, "Incorrect alarmId " + alarmId); - AlarmInfo alarmInfo = alarmService.findAlarmInfoByIdAsync(getCurrentUser().getTenantId(), alarmId).get(); - checkNotNull(alarmInfo, "Alarm with id [" + alarmId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, operation, alarmId, alarmInfo); - return alarmInfo; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(alarmId, (tenantId, id) -> { + return alarmService.findAlarmInfoByIdAsync(tenantId, alarmId).get(); + }, operation); } WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, Operation operation) throws ThingsboardException { - try { - validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId); - WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleById(getCurrentUser().getTenantId(), widgetsBundleId); - checkNotNull(widgetsBundle, "Widgets bundle with id [" + widgetsBundleId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.WIDGETS_BUNDLE, operation, widgetsBundleId, widgetsBundle); - return widgetsBundle; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(widgetsBundleId, widgetsBundleService::findWidgetsBundleById, operation); } WidgetTypeDetails checkWidgetTypeId(WidgetTypeId widgetTypeId, Operation operation) throws ThingsboardException { - try { - validateId(widgetTypeId, "Incorrect widgetTypeId " + widgetTypeId); - WidgetTypeDetails widgetTypeDetails = widgetTypeService.findWidgetTypeDetailsById(getCurrentUser().getTenantId(), widgetTypeId); - checkNotNull(widgetTypeDetails, "Widget type with id [" + widgetTypeId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, operation, widgetTypeId, widgetTypeDetails); - return widgetTypeDetails; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(widgetTypeId, widgetTypeService::findWidgetTypeDetailsById, operation); } Dashboard checkDashboardId(DashboardId dashboardId, Operation operation) throws ThingsboardException { - try { - validateId(dashboardId, "Incorrect dashboardId " + dashboardId); - Dashboard dashboard = dashboardService.findDashboardById(getCurrentUser().getTenantId(), dashboardId); - checkNotNull(dashboard, "Dashboard with id [" + dashboardId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.DASHBOARD, operation, dashboardId, dashboard); - return dashboard; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(dashboardId, dashboardService::findDashboardById, operation); } Edge checkEdgeId(EdgeId edgeId, Operation operation) throws ThingsboardException { - try { - validateId(edgeId, "Incorrect edgeId " + edgeId); - Edge edge = edgeService.findEdgeById(getTenantId(), edgeId); - checkNotNull(edge, "Edge with id [" + edgeId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, edgeId, edge); - return edge; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(edgeId, edgeService::findEdgeById, operation); } EdgeInfo checkEdgeInfoId(EdgeId edgeId, Operation operation) throws ThingsboardException { - try { - validateId(edgeId, "Incorrect edgeId " + edgeId); - EdgeInfo edge = edgeService.findEdgeInfoById(getCurrentUser().getTenantId(), edgeId); - checkNotNull(edge, "Edge with id [" + edgeId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, edgeId, edge); - return edge; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(edgeId, edgeService::findEdgeInfoById, operation); } DashboardInfo checkDashboardInfoId(DashboardId dashboardId, Operation operation) throws ThingsboardException { - try { - validateId(dashboardId, "Incorrect dashboardId " + dashboardId); - DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(getCurrentUser().getTenantId(), dashboardId); - checkNotNull(dashboardInfo, "Dashboard with id [" + dashboardId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.DASHBOARD, operation, dashboardId, dashboardInfo); - return dashboardInfo; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(dashboardId, dashboardService::findDashboardInfoById, operation); } ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException { @@ -823,11 +685,7 @@ public abstract class BaseController { } protected RuleChain checkRuleChain(RuleChainId ruleChainId, Operation operation) throws ThingsboardException { - validateId(ruleChainId, "Incorrect ruleChainId " + ruleChainId); - RuleChain ruleChain = ruleChainService.findRuleChainById(getCurrentUser().getTenantId(), ruleChainId); - checkNotNull(ruleChain, "Rule chain with id [" + ruleChainId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.RULE_CHAIN, operation, ruleChainId, ruleChain); - return ruleChain; + return checkEntityId(ruleChainId, ruleChainService::findRuleChainById, operation); } protected RuleNode checkRuleNode(RuleNodeId ruleNodeId, Operation operation) throws ThingsboardException { @@ -839,70 +697,27 @@ public abstract class BaseController { } TbResource checkResourceId(TbResourceId resourceId, Operation operation) throws ThingsboardException { - try { - validateId(resourceId, "Incorrect resourceId " + resourceId); - TbResource resource = resourceService.findResourceById(getCurrentUser().getTenantId(), resourceId); - checkNotNull(resource, "Resource with id [" + resourceId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.TB_RESOURCE, operation, resourceId, resource); - return resource; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(resourceId, resourceService::findResourceById, operation); } TbResourceInfo checkResourceInfoId(TbResourceId resourceId, Operation operation) throws ThingsboardException { - try { - validateId(resourceId, "Incorrect resourceId " + resourceId); - TbResourceInfo resourceInfo = resourceService.findResourceInfoById(getCurrentUser().getTenantId(), resourceId); - checkNotNull(resourceInfo, "Resource with id [" + resourceId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.TB_RESOURCE, operation, resourceId, resourceInfo); - return resourceInfo; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(resourceId, resourceService::findResourceInfoById, operation); } OtaPackage checkOtaPackageId(OtaPackageId otaPackageId, Operation operation) throws ThingsboardException { - try { - validateId(otaPackageId, "Incorrect otaPackageId " + otaPackageId); - OtaPackage otaPackage = otaPackageService.findOtaPackageById(getCurrentUser().getTenantId(), otaPackageId); - checkNotNull(otaPackage, "OTA package with id [" + otaPackageId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.OTA_PACKAGE, operation, otaPackageId, otaPackage); - return otaPackage; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(otaPackageId, otaPackageService::findOtaPackageById, operation); } OtaPackageInfo checkOtaPackageInfoId(OtaPackageId otaPackageId, Operation operation) throws ThingsboardException { - try { - validateId(otaPackageId, "Incorrect otaPackageId " + otaPackageId); - OtaPackageInfo otaPackageIn = otaPackageService.findOtaPackageInfoById(getCurrentUser().getTenantId(), otaPackageId); - checkNotNull(otaPackageIn, "OTA package with id [" + otaPackageId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.OTA_PACKAGE, operation, otaPackageId, otaPackageIn); - return otaPackageIn; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(otaPackageId, otaPackageService::findOtaPackageInfoById, operation); } Rpc checkRpcId(RpcId rpcId, Operation operation) throws ThingsboardException { - try { - validateId(rpcId, "Incorrect rpcId " + rpcId); - Rpc rpc = rpcService.findById(getCurrentUser().getTenantId(), rpcId); - checkNotNull(rpc, "RPC with id [" + rpcId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.RPC, operation, rpcId, rpc); - return rpc; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(rpcId, rpcService::findById, operation); } protected Queue checkQueueId(QueueId queueId, Operation operation) throws ThingsboardException { - validateId(queueId, "Incorrect queueId " + queueId); - Queue queue = queueService.findQueueById(getCurrentUser().getTenantId(), queueId); - checkNotNull(queue); - accessControlService.checkPermission(getCurrentUser(), Resource.QUEUE, operation, queueId, queue); + Queue queue = checkEntityId(queueId, queueService::findQueueById, operation); TenantId tenantId = getTenantId(); if (queue.getTenantId().isNullUid() && !tenantId.isNullUid()) { TenantProfile tenantProfile = tenantProfileCache.get(tenantId); @@ -932,6 +747,30 @@ public abstract class BaseController { user.getCustomerId(), actionType, e); } + protected > E doSaveAndLog(EntityType entityType, E entity, BiFunction savingFunction) throws Exception { + ActionType actionType = entity.getId() == null ? ActionType.ADDED : ActionType.UPDATED; + SecurityUser user = getCurrentUser(); + try { + E savedEntity = savingFunction.apply(user.getTenantId(), entity); + logEntityAction(user, entityType, savedEntity, actionType); + return savedEntity; + } catch (Exception e) { + logEntityAction(user, entityType, entity, null, actionType, e); + throw e; + } + } + + protected , I extends EntityId> void doDeleteAndLog(EntityType entityType, E entity, BiConsumer deleteFunction) throws Exception { + SecurityUser user = getCurrentUser(); + try { + deleteFunction.accept(user.getTenantId(), entity.getId()); + logEntityAction(user, entityType, entity, ActionType.DELETED); + } catch (Exception e) { + logEntityAction(user, entityType, entity, entity, ActionType.DELETED, e); + throw e; + } + } + protected void sendEntityNotificationMsg(TenantId tenantId, EntityId entityId, EdgeEventActionType action) { sendNotificationMsgToEdge(tenantId, null, entityId, null, null, action); } @@ -976,4 +815,5 @@ public abstract class BaseController { }, MoreExecutors.directExecutor()); return deferredResult; } + } From a6bfb049aabd416457ff8a77dd0e66e6e25c5e11 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Wed, 14 Dec 2022 19:02:23 +0200 Subject: [PATCH 019/111] Improvements for notification templates --- .../main/data/upgrade/3.4.3/schema_update.sql | 2 +- .../controller/NotificationController.java | 46 ++++--------- .../NotificationRuleController.java | 34 ++++++---- .../NotificationTargetController.java | 37 +++------- .../NotificationTemplateController.java | 68 +++++++++++++++++++ .../service/security/permission/Resource.java | 1 + .../permission/SysAdminPermissions.java | 1 + .../permission/TenantAdminPermissions.java | 1 + .../DefaultEntitiesExportImportService.java | 2 +- .../DefaultEntitiesVersionControlService.java | 2 +- .../sync/vc/data/EntitiesImportCtx.java | 2 +- .../notification/NotificationApiTest.java | 18 +++++ .../sync/ie/BaseExportImportServiceTest.java | 2 +- .../notification/NotificationRuleService.java | 2 + .../NotificationTemplateService.java | 2 + .../server/common/data/EntityType.java | 2 +- .../data/id/NotificationTemplateId.java | 8 ++- .../data/sync/ie/EntityImportResult.java | 2 +- .../common/data/util/ThrowingBiFunction.java | 8 +++ .../data/{sync => util}/ThrowingRunnable.java | 2 +- .../dao/model/sql/NotificationEntity.java | 2 +- .../DefaultNotificationRuleService.java | 5 ++ .../DefaultNotificationTemplateService.java | 5 ++ .../JpaNotificationRequestDao.java | 6 ++ .../notification/JpaNotificationRuleDao.java | 6 ++ .../JpaNotificationTargetDao.java | 6 ++ .../JpaNotificationTemplateDao.java | 6 ++ .../main/resources/sql/schema-entities.sql | 33 +++++---- 28 files changed, 211 insertions(+), 100 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/util/ThrowingBiFunction.java rename common/data/src/main/java/org/thingsboard/server/common/data/{sync => util}/ThrowingRunnable.java (94%) diff --git a/application/src/main/data/upgrade/3.4.3/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql index bee8454596..073e54a1a9 100644 --- a/application/src/main/data/upgrade/3.4.3/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.3/schema_update.sql @@ -19,7 +19,7 @@ CREATE TABLE IF NOT EXISTS notification_target ( created_time BIGINT NOT NULL, tenant_id UUID NOT NULL, name VARCHAR(255) NOT NULL, - configuration VARCHAR(1000) NOT NULL + configuration VARCHAR(10000) NOT NULL ); CREATE INDEX IF NOT EXISTS idx_notification_target_tenant_id_created_time ON notification_target(tenant_id, created_time DESC); diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index 9064acb860..46352754f6 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -29,8 +29,8 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.rule.engine.api.NotificationManager; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; @@ -38,13 +38,11 @@ import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationOriginatorType; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestInfo; -import org.thingsboard.server.common.data.notification.NotificationSeverity; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.rule.engine.api.NotificationManager; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -88,14 +86,12 @@ public class NotificationController extends BaseController { @PostMapping("/notification/request") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public NotificationRequest createNotificationRequest(@RequestBody NotificationRequest notificationRequest, - @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { - // todo: check permission for notification target - + @AuthenticationPrincipal SecurityUser user) throws Exception { if (notificationRequest.getId() != null) { throw new IllegalArgumentException("Notification request cannot be updated. You may only cancel/delete it"); } + checkEntity(notificationRequest.getId(), notificationRequest, Resource.NOTIFICATION_REQUEST); - accessControlService.checkPermission(user, Resource.NOTIFICATION_REQUEST, Operation.CREATE, null, notificationRequest); notificationRequest.setOriginatorType(NotificationOriginatorType.ADMIN); notificationRequest.setOriginatorEntityId(user.getId()); if (StringUtils.isBlank(notificationRequest.getType())) { @@ -106,33 +102,22 @@ public class NotificationController extends BaseController { } notificationRequest.setRuleId(null); notificationRequest.setStatus(null); - return notificationManager.processNotificationRequest(user.getTenantId(), notificationRequest); - // -// try { -// NotificationRequest savedNotificationRequest = ; -// logEntityAction(user, EntityType.NOTIFICATION_REQUEST, savedNotificationRequest, ActionType.ADDED); -// return savedNotificationRequest; -// } catch (Exception e) { -// logEntityAction(user, EntityType.NOTIFICATION_REQUEST, notificationRequest, null, ActionType.ADDED, e); -// throw e; -// } + + return doSaveAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationManager::processNotificationRequest); } @GetMapping("/notification/request/{id}") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - public NotificationRequest getNotificationRequestById(@PathVariable UUID id, - @AuthenticationPrincipal SecurityUser user) { + public NotificationRequest getNotificationRequestById(@PathVariable UUID id) throws ThingsboardException { NotificationRequestId notificationRequestId = new NotificationRequestId(id); - return notificationRequestService.findNotificationRequestById(user.getTenantId(), notificationRequestId); + return checkEntityId(notificationRequestId, notificationRequestService::findNotificationRequestById, Operation.READ); } @GetMapping("/notification/request/info/{id}") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - public NotificationRequestInfo getNotificationRequestInfoById(@PathVariable UUID id, - @AuthenticationPrincipal SecurityUser user) { - // fixme: permission checks + public NotificationRequestInfo getNotificationRequestInfoById(@PathVariable UUID id) throws ThingsboardException { NotificationRequestId notificationRequestId = new NotificationRequestId(id); - return notificationRequestService.getNotificationRequestInfoById(user.getTenantId(), notificationRequestId); + return checkEntityId(notificationRequestId, notificationRequestService::getNotificationRequestInfoById, Operation.READ); } @GetMapping("/notification/requests") @@ -149,17 +134,10 @@ public class NotificationController extends BaseController { @DeleteMapping("/notification/request/{id}") public void deleteNotificationRequest(@PathVariable UUID id, - @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + @AuthenticationPrincipal SecurityUser user) throws Exception { NotificationRequestId notificationRequestId = new NotificationRequestId(id); - NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(user.getTenantId(), notificationRequestId); - accessControlService.checkPermission(user, Resource.NOTIFICATION_REQUEST, Operation.DELETE, notificationRequestId, notificationRequest); - try { - notificationManager.deleteNotificationRequest(user.getTenantId(), notificationRequestId); - logEntityAction(user, EntityType.NOTIFICATION_REQUEST, notificationRequest, ActionType.DELETED); - } catch (Exception e) { - logEntityAction(user, EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationRequest, ActionType.DELETED, e); - throw e; - } + NotificationRequest notificationRequest = checkEntityId(notificationRequestId, notificationRequestService::findNotificationRequestById, Operation.DELETE); + doDeleteAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationManager::deleteNotificationRequest); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java index 78f5827c6d..0ed5e1bfaf 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java @@ -19,6 +19,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -26,6 +27,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.notification.rule.NotificationRule; @@ -41,32 +43,28 @@ import java.util.UUID; @RestController @TbCoreComponent -@RequestMapping("/api") +@RequestMapping("/api/notification") @RequiredArgsConstructor @Slf4j public class NotificationRuleController extends BaseController { - // todo: logEntityAction + private final NotificationRuleService notificationRuleService; - @PostMapping("/notification/rule") + @PostMapping("/rule") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") - public NotificationRule saveNotificationRule(@RequestBody NotificationRule notificationRule, - @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { -// accessControlService.checkPermission(user, Resource.NOTIFICATION_RULE, notificationRule.getId() == null ? Operation.CREATE : Operation.WRITE, notificationRule.getId(), ); - return notificationRuleService.saveNotificationRule(user.getTenantId(), notificationRule); + public NotificationRule saveNotificationRule(@RequestBody NotificationRule notificationRule) throws Exception { + checkEntity(notificationRule.getId(), notificationRule, Resource.NOTIFICATION_RULE); + return doSaveAndLog(EntityType.NOTIFICATION_RULE, notificationRule, notificationRuleService::saveNotificationRule); } - @GetMapping("/notification/rule/{id}") + @GetMapping("/rule/{id}") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") - public NotificationRule getNotificationRuleById(@PathVariable UUID id, - @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + public NotificationRule getNotificationRuleById(@PathVariable UUID id) throws ThingsboardException { NotificationRuleId notificationRuleId = new NotificationRuleId(id); - NotificationRule notificationRule = notificationRuleService.findNotificationRuleById(user.getTenantId(), notificationRuleId); - accessControlService.checkPermission(user, Resource.NOTIFICATION_RULE, Operation.READ, notificationRuleId, notificationRule); - return notificationRule; + return checkEntityId(notificationRuleId, notificationRuleService::findNotificationRuleById, Operation.READ); } - @GetMapping("/notification/rules") + @GetMapping("/rules") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") public PageData getNotificationRules(@RequestParam int pageSize, @RequestParam int page, @@ -78,4 +76,12 @@ public class NotificationRuleController extends BaseController { return notificationRuleService.findNotificationRulesByTenantId(user.getTenantId(), pageLink); } + @DeleteMapping("/rule/{id}") + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + public void deleteNotificationRule(@PathVariable UUID id) throws Exception { + NotificationRuleId notificationRuleId = new NotificationRuleId(id); + NotificationRule notificationRule = checkEntityId(notificationRuleId, notificationRuleService::findNotificationRuleById, Operation.DELETE); + doDeleteAndLog(EntityType.NOTIFICATION_RULE, notificationRule, notificationRuleService::deleteNotificationRule); + } + } diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java index 22ab0475c2..ee984d9a9a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java @@ -29,7 +29,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; @@ -57,8 +56,8 @@ public class NotificationTargetController extends BaseController { @PostMapping("/target") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public NotificationTarget saveNotificationTarget(@RequestBody NotificationTarget notificationTarget, - @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { - accessControlService.checkPermission(user, Resource.NOTIFICATION_TARGET, Operation.CREATE, null, notificationTarget); + @AuthenticationPrincipal SecurityUser user) throws Exception { + checkEntity(notificationTarget.getId(), notificationTarget, Resource.NOTIFICATION_TARGET); if (!user.isSystemAdmin()) { NotificationTargetConfig targetConfig = notificationTarget.getConfiguration(); if (targetConfig.getType() == NotificationTargetConfigType.SINGLE_USER || @@ -70,26 +69,14 @@ public class NotificationTargetController extends BaseController { } } - try { - NotificationTarget savedNotificationTarget = notificationTargetService.saveNotificationTarget(user.getTenantId(), notificationTarget); - logEntityAction(user, EntityType.NOTIFICATION_TARGET, savedNotificationTarget, - notificationTarget.getId() == null ? ActionType.ADDED : ActionType.UPDATED); - return savedNotificationTarget; - } catch (Exception e) { - logEntityAction(user, EntityType.NOTIFICATION_TARGET, notificationTarget, null, - notificationTarget.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); - throw e; - } + return doSaveAndLog(EntityType.NOTIFICATION_TARGET, notificationTarget, notificationTargetService::saveNotificationTarget); } @GetMapping("/target/{id}") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - public NotificationTarget getNotificationTargetById(@PathVariable UUID id, - @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + public NotificationTarget getNotificationTargetById(@PathVariable UUID id) throws ThingsboardException { NotificationTargetId notificationTargetId = new NotificationTargetId(id); - NotificationTarget notificationTarget = notificationTargetService.findNotificationTargetById(user.getTenantId(), notificationTargetId); - accessControlService.checkPermission(user, Resource.NOTIFICATION_TARGET, Operation.READ, notificationTargetId, notificationTarget); - return notificationTarget; + return checkEntityId(notificationTargetId, notificationTargetService::findNotificationTargetById, Operation.READ); } @PostMapping("/target/recipients") @@ -122,18 +109,10 @@ public class NotificationTargetController extends BaseController { @DeleteMapping("/target/{id}") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - public void deleteNotificationTarget(@PathVariable UUID id, - @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + public void deleteNotificationTarget(@PathVariable UUID id) throws Exception { NotificationTargetId notificationTargetId = new NotificationTargetId(id); - NotificationTarget notificationTarget = checkNotNull(notificationTargetService.findNotificationTargetById(user.getTenantId(), notificationTargetId)); - accessControlService.checkPermission(user, Resource.NOTIFICATION_TARGET, Operation.DELETE, notificationTargetId, notificationTarget); - - try { - notificationTargetService.deleteNotificationTarget(user.getTenantId(), notificationTargetId); - logEntityAction(user, EntityType.NOTIFICATION_TARGET, notificationTarget, ActionType.DELETED); - } catch (Exception e) { - logEntityAction(user, EntityType.NOTIFICATION_TARGET, null, notificationTarget, ActionType.DELETED, e); - } + NotificationTarget notificationTarget = checkEntityId(notificationTargetId, notificationTargetService::findNotificationTargetById, Operation.DELETE); + doDeleteAndLog(EntityType.NOTIFICATION_TARGET, notificationTarget, notificationTargetService::deleteNotificationTarget); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java new file mode 100644 index 0000000000..3d5e0c8332 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java @@ -0,0 +1,68 @@ +/** + * 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.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.NotificationTemplateId; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; +import org.thingsboard.server.dao.notification.NotificationTemplateService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; + +import java.util.UUID; + +@RestController +@TbCoreComponent +@RequiredArgsConstructor +@RequestMapping("/api/notification") +public class NotificationTemplateController extends BaseController { + + private final NotificationTemplateService notificationTemplateService; + + @PostMapping("/template") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationTemplate saveNotificationTemplate(@RequestBody NotificationTemplate notificationTemplate) throws Exception { + checkEntity(notificationTemplate.getId(), notificationTemplate, Resource.NOTIFICATION_TEMPLATE); + return doSaveAndLog(EntityType.NOTIFICATION_TEMPLATE, notificationTemplate, notificationTemplateService::saveNotificationTemplate); + } + + @GetMapping("/template/{id}") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationTemplate getNotificationTemplateById(@PathVariable UUID id) throws ThingsboardException { + NotificationTemplateId notificationTemplateId = new NotificationTemplateId(id); + return checkEntityId(notificationTemplateId, notificationTemplateService::findNotificationTemplateById, Operation.READ); + } + + @DeleteMapping("/template/{id}") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public void deleteNotificationTemplate(@PathVariable UUID id) throws Exception { + NotificationTemplateId notificationTemplateId = new NotificationTemplateId(id); + NotificationTemplate notificationTemplate = checkEntityId(notificationTemplateId, notificationTemplateService::findNotificationTemplateById, Operation.DELETE); + doDeleteAndLog(EntityType.NOTIFICATION_TEMPLATE, notificationTemplate, notificationTemplateService::deleteNotificationTemplateById); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java index 9af52897fb..c00a791233 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java @@ -45,6 +45,7 @@ public enum Resource { QUEUE(EntityType.QUEUE), VERSION_CONTROL, NOTIFICATION_TARGET(EntityType.NOTIFICATION_TARGET), + NOTIFICATION_TEMPLATE(EntityType.NOTIFICATION_TEMPLATE), NOTIFICATION_REQUEST(EntityType.NOTIFICATION_REQUEST), NOTIFICATION_RULE(EntityType.NOTIFICATION_RULE); diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java index cf4ea18eaa..5f0f544d07 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java @@ -41,6 +41,7 @@ public class SysAdminPermissions extends AbstractPermissions { put(Resource.TB_RESOURCE, systemEntityPermissionChecker); put(Resource.QUEUE, systemEntityPermissionChecker); put(Resource.NOTIFICATION_TARGET, systemEntityPermissionChecker); + put(Resource.NOTIFICATION_TEMPLATE, systemEntityPermissionChecker); put(Resource.NOTIFICATION_REQUEST, systemEntityPermissionChecker); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java index 9f0fb9e249..a064324c31 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java @@ -50,6 +50,7 @@ public class TenantAdminPermissions extends AbstractPermissions { put(Resource.QUEUE, queuePermissionChecker); put(Resource.VERSION_CONTROL, PermissionChecker.allowAllPermissionChecker); put(Resource.NOTIFICATION_TARGET, tenantEntityPermissionChecker); + put(Resource.NOTIFICATION_TEMPLATE, tenantEntityPermissionChecker); put(Resource.NOTIFICATION_REQUEST, tenantEntityPermissionChecker); put(Resource.NOTIFICATION_RULE, tenantEntityPermissionChecker); } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java index f4e4e3dc14..bc691b97ec 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.sync.ThrowingRunnable; +import org.thingsboard.server.common.data.util.ThrowingRunnable; import org.thingsboard.server.common.data.sync.ie.EntityExportData; import org.thingsboard.server.common.data.sync.ie.EntityImportResult; import org.thingsboard.server.dao.exception.DataValidationException; diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java index 8cf17fd5b0..0046b76cce 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java @@ -44,7 +44,7 @@ import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.sync.ThrowingRunnable; +import org.thingsboard.server.common.data.util.ThrowingRunnable; import org.thingsboard.server.common.data.sync.ie.EntityExportData; import org.thingsboard.server.common.data.sync.ie.EntityExportSettings; import org.thingsboard.server.common.data.sync.ie.EntityImportResult; diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java index fddc91883e..9edc91e374 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.sync.ThrowingRunnable; +import org.thingsboard.server.common.data.util.ThrowingRunnable; import org.thingsboard.server.common.data.sync.ie.EntityImportResult; import org.thingsboard.server.common.data.sync.ie.EntityImportSettings; import org.thingsboard.server.common.data.sync.vc.EntityTypeLoadResult; diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java index 163a98224c..fcf8005c1b 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; @@ -34,6 +35,9 @@ import org.thingsboard.server.common.data.notification.NotificationRequestStatus import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.SingleUserNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.UserListNotificationTargetConfig; +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.NotificationTextTemplate; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; @@ -52,6 +56,7 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.not; import static org.awaitility.Awaitility.await; @DaoSqlTest @@ -332,6 +337,7 @@ public class NotificationApiTest extends AbstractControllerTest { } private NotificationRequest submitNotificationRequest(NotificationTargetId targetId, String text, int delayInSec) { + NotificationTemplate notificationTemplate = createNotificationTemplate(text); NotificationRequestConfig config = new NotificationRequestConfig(); config.setSendingDelayInSec(delayInSec); NotificationInfo notificationInfo = new NotificationInfo(); @@ -348,6 +354,18 @@ public class NotificationApiTest extends AbstractControllerTest { return doPost("/api/notification/request", notificationRequest, NotificationRequest.class); } + private NotificationTemplate createNotificationTemplate(String text) { + NotificationTemplate notificationTemplate = new NotificationTemplate(); + notificationTemplate.setTenantId(tenantId); + notificationTemplate.setName("Notification template for testing"); + NotificationTemplateConfig config = new NotificationTemplateConfig(); + NotificationTextTemplate textTemplate = new NotificationTextTemplate(); + textTemplate.setBody(text); + config.setDefaultTextTemplate(textTemplate); + notificationTemplate.setConfiguration(config); + return doPost("/api/notification/template", notificationTemplate, NotificationTemplate.class); + } + private NotificationRequest findNotificationRequest(NotificationRequestId id) throws Exception { return doGet("/api/notification/request/" + id, NotificationRequest.class); } diff --git a/application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java b/application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java index 48eb0c9e27..7dcb4905fe 100644 --- a/application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java @@ -60,7 +60,7 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.common.data.sync.ThrowingRunnable; +import org.thingsboard.server.common.data.util.ThrowingRunnable; import org.thingsboard.server.common.data.sync.ie.EntityExportData; import org.thingsboard.server.common.data.sync.ie.EntityExportSettings; import org.thingsboard.server.common.data.sync.ie.EntityImportResult; diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.java index 958012c8c7..aba79e0871 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.java @@ -29,4 +29,6 @@ public interface NotificationRuleService { PageData findNotificationRulesByTenantId(TenantId tenantId, PageLink pageLink); + void deleteNotificationRule(TenantId tenantId, NotificationRuleId notificationRuleId); + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.java index c22e7a1caa..02c4d98249 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.java @@ -28,4 +28,6 @@ public interface NotificationTemplateService { NotificationTemplate saveNotificationTemplate(TenantId tenantId, NotificationTemplate notificationTemplate); + void deleteNotificationTemplateById(TenantId tenantId, NotificationTemplateId id); + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index 37f69b575f..4f62519690 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -21,5 +21,5 @@ package org.thingsboard.server.common.data; public enum EntityType { TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, ASSET_PROFILE, API_USAGE_STATE, TB_RESOURCE, OTA_PACKAGE, EDGE, RPC, QUEUE, - NOTIFICATION_TARGET, NOTIFICATION_REQUEST, NOTIFICATION_RULE; + NOTIFICATION_TARGET, NOTIFICATION_TEMPLATE, NOTIFICATION_REQUEST, NOTIFICATION_RULE; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTemplateId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTemplateId.java index 596fe4b83c..0bcc39434e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTemplateId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTemplateId.java @@ -17,14 +17,20 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -public class NotificationTemplateId extends UUIDBased { +public class NotificationTemplateId extends UUIDBased implements EntityId { @JsonCreator public NotificationTemplateId(@JsonProperty("id") UUID id) { super(id); } + @Override + public EntityType getEntityType() { + return EntityType.NOTIFICATION_TEMPLATE; + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityImportResult.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityImportResult.java index 1c6f4c060a..4347c53634 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityImportResult.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityImportResult.java @@ -19,7 +19,7 @@ import lombok.Data; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.sync.ThrowingRunnable; +import org.thingsboard.server.common.data.util.ThrowingRunnable; @Data public class EntityImportResult> { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/util/ThrowingBiFunction.java b/common/data/src/main/java/org/thingsboard/server/common/data/util/ThrowingBiFunction.java new file mode 100644 index 0000000000..9dbafb1734 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/util/ThrowingBiFunction.java @@ -0,0 +1,8 @@ +package org.thingsboard.server.common.data.util; + +@FunctionalInterface +public interface ThrowingBiFunction { + + R apply(T t, U u) throws Exception; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ThrowingRunnable.java b/common/data/src/main/java/org/thingsboard/server/common/data/util/ThrowingRunnable.java similarity index 94% rename from common/data/src/main/java/org/thingsboard/server/common/data/sync/ThrowingRunnable.java rename to common/data/src/main/java/org/thingsboard/server/common/data/util/ThrowingRunnable.java index 1cb2ac8c74..ff3cf46cc3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ThrowingRunnable.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/util/ThrowingRunnable.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.sync; +package org.thingsboard.server.common.data.util; import org.thingsboard.server.common.data.exception.ThingsboardException; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java index 7855e773a4..3ee0601a62 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java @@ -60,7 +60,7 @@ public class NotificationEntity extends BaseSqlEntity { private String text; @Type(type = "json") - @Formula("(SELECT r.notification_info FROM notification_request r WHERE r.id = request_id)") + @Formula("(SELECT r.info FROM notification_request r WHERE r.id = request_id)") private JsonNode info; @Enumerated(EnumType.STRING) diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java index ae475a76a1..337ed7aa78 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java @@ -48,6 +48,11 @@ public class DefaultNotificationRuleService implements NotificationRuleService { return notificationRuleDao.findByTenantIdAndPageLink(tenantId, pageLink); } + @Override + public void deleteNotificationRule(TenantId tenantId, NotificationRuleId notificationRuleId) { + notificationRuleDao.removeById(tenantId, notificationRuleId.getId()); + } + private static class NotificationRuleValidator extends DataValidator { } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java index 6e14ee1fac..9e7c14300a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java @@ -37,4 +37,9 @@ public class DefaultNotificationTemplateService implements NotificationTemplateS return notificationTemplateDao.save(tenantId, notificationTemplate); } + @Override + public void deleteNotificationTemplateById(TenantId tenantId, NotificationTemplateId id) { + notificationTemplateDao.removeById(tenantId, id.getId()); + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java index 1a9aeff216..f03e0eda0c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java @@ -20,6 +20,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRuleId; @@ -87,4 +88,9 @@ public class JpaNotificationRequestDao extends JpaAbstractDao Date: Thu, 15 Dec 2022 15:50:01 +0200 Subject: [PATCH 020/111] Notification request stats --- .../main/data/upgrade/3.4.3/schema_update.sql | 16 +++- .../controller/NotificationController.java | 13 +--- .../controller/plugin/TbWebSocketHandler.java | 3 +- .../DefaultNotificationManager.java | 77 ++++++++++++------- .../channels/EmailNotificationChannel.java | 5 +- .../channels/NotificationChannel.java | 5 +- .../channels/SmsNotificationChannel.java | 7 +- .../ttl/NotificationsCleanUpService.java | 72 +++++++++++++++++ .../service/ws/DefaultWebSocketService.java | 2 +- .../src/main/resources/thingsboard.yml | 6 +- .../controller/BaseWebsocketApiTest.java | 2 +- .../controller/TbTestWebSocketClient.java | 2 +- .../notification/NotificationApiTest.java | 62 ++++++++++----- .../NotificationRequestService.java | 4 +- .../common/data/id/EntityIdFactory.java | 2 + .../notification/NotificationRequest.java | 20 +---- .../notification/NotificationRequestInfo.java | 35 --------- .../NotificationRequestStats.java | 54 +++++++++++++ .../common/data/util/ThrowingBiFunction.java | 15 ++++ .../org/thingsboard/server/dao/DaoUtil.java | 6 +- .../server/dao/model/ModelConstants.java | 1 + .../model/sql/NotificationRequestEntity.java | 7 ++ .../DefaultNotificationRequestService.java | 6 +- .../notification/NotificationRequestDao.java | 4 +- .../sql/notification/JpaNotificationDao.java | 3 - .../JpaNotificationRequestDao.java | 19 +---- .../notification/NotificationRepository.java | 8 -- .../NotificationRequestRepository.java | 8 ++ .../insert/sql/SqlPartitioningRepository.java | 7 ++ .../main/resources/sql/schema-entities.sql | 3 +- 30 files changed, 315 insertions(+), 159 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/ttl/NotificationsCleanUpService.java delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java diff --git a/application/src/main/data/upgrade/3.4.3/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql index 073e54a1a9..d5d94494a3 100644 --- a/application/src/main/data/upgrade/3.4.3/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.3/schema_update.sql @@ -56,7 +56,8 @@ CREATE TABLE IF NOT EXISTS notification_request ( originator_entity_id UUID, originator_entity_type VARCHAR(32), rule_id UUID NULL CONSTRAINT fk_notification_request_rule_id REFERENCES notification_rule(id), - status VARCHAR(32) + status VARCHAR(32), + stats VARCHAR(1000) ); CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id_originator_type_created_time ON notification_request(tenant_id, originator_type, created_time DESC); @@ -78,3 +79,16 @@ CREATE INDEX IF NOT EXISTS idx_notification_notification_request_id ON notificat ALTER TABLE alarm ADD COLUMN IF NOT EXISTS notification_rule_id UUID; ALTER TABLE tb_user ADD COLUMN IF NOT EXISTS phone VARCHAR(255); + +CREATE OR REPLACE FUNCTION on_notification_deleted() RETURNS TRIGGER as $notification_deleted$ +BEGIN + RAISE NOTICE 'ABAAAAAAAAAAAAAAAAAAA'; + INSERT INTO id_and_time values ('13814000-1dd2-11b2-8080-808080808080', 0); + RETURN NULL; +END; +$notification_deleted$ LANGUAGE plpgsql; + +CREATE TRIGGER notification_deleted_trigger + AFTER DELETE ON id_and_time + REFERENCING OLD TABLE AS deleted + FOR EACH STATEMENT EXECUTE FUNCTION on_notification_deleted(); diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index 46352754f6..22f09921e5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -37,7 +37,6 @@ import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationOriginatorType; import org.thingsboard.server.common.data.notification.NotificationRequest; -import org.thingsboard.server.common.data.notification.NotificationRequestInfo; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.notification.NotificationRequestService; @@ -102,6 +101,7 @@ public class NotificationController extends BaseController { } notificationRequest.setRuleId(null); notificationRequest.setStatus(null); + notificationRequest.setStats(null); return doSaveAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationManager::processNotificationRequest); } @@ -113,13 +113,6 @@ public class NotificationController extends BaseController { return checkEntityId(notificationRequestId, notificationRequestService::findNotificationRequestById, Operation.READ); } - @GetMapping("/notification/request/info/{id}") - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - public NotificationRequestInfo getNotificationRequestInfoById(@PathVariable UUID id) throws ThingsboardException { - NotificationRequestId notificationRequestId = new NotificationRequestId(id); - return checkEntityId(notificationRequestId, notificationRequestService::getNotificationRequestInfoById, Operation.READ); - } - @GetMapping("/notification/requests") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public PageData getNotificationRequests(@RequestParam int pageSize, @@ -133,8 +126,8 @@ public class NotificationController extends BaseController { } @DeleteMapping("/notification/request/{id}") - public void deleteNotificationRequest(@PathVariable UUID id, - @AuthenticationPrincipal SecurityUser user) throws Exception { + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public void deleteNotificationRequest(@PathVariable UUID id) throws Exception { NotificationRequestId notificationRequestId = new NotificationRequestId(id); NotificationRequest notificationRequest = checkEntityId(notificationRequestId, notificationRequestService::findNotificationRequestById, Operation.DELETE); doDeleteAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationManager::deleteNotificationRequest); diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index 2e2b1a1204..55381a5f94 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -55,7 +55,6 @@ import java.io.IOException; import java.net.URI; import java.security.InvalidParameterException; import java.util.Optional; -import java.util.Queue; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -472,7 +471,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } } - private DefaultTenantProfileConfiguration getTenantProfileConfiguration(TelemetryWebSocketSessionRef sessionRef) { + private DefaultTenantProfileConfiguration getTenantProfileConfiguration(WebSocketSessionRef sessionRef) { return Optional.ofNullable(tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId())) .map(TenantProfile::getDefaultProfileConfiguration).orElse(null); } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java index a343804921..0606762d0c 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java @@ -17,11 +17,12 @@ package org.thingsboard.server.service.notification; import com.google.common.base.Strings; import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.rule.engine.api.NotificationManager; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.NotificationId; @@ -32,6 +33,7 @@ import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; +import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.notification.template.NotificationText; @@ -54,18 +56,19 @@ import org.thingsboard.server.service.telemetry.AbstractSubscriptionService; import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.concurrent.Future; import java.util.stream.Collectors; @Service @Slf4j @RequiredArgsConstructor +@SuppressWarnings("UnstableApiUsage") public class DefaultNotificationManager extends AbstractSubscriptionService implements NotificationManager, NotificationChannel { private final NotificationTargetService notificationTargetService; @@ -95,35 +98,58 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl notificationRequest.setStatus(NotificationRequestStatus.PROCESSED); NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); + NotificationRequestStats stats = new NotificationRequestStats(); Map textTemplates = notificationTemplateUtil.getTemplates(tenantId, notificationRequest.getTemplateId(), savedNotificationRequest.getDeliveryMethods()); + savedNotificationRequest.setTemplateContext(notificationRequest.getTemplateContext()); + DaoUtil.processBatches(pageLink -> { return notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), pageLink); - }, 100, recipients -> { - dbCallbackExecutorService.submit(() -> { - for (NotificationDeliveryMethod deliveryMethod : savedNotificationRequest.getDeliveryMethods()) { - log.debug("Sending {} notifications for request {} to recipients batch", deliveryMethod, savedNotificationRequest.getId()); - NotificationChannel notificationChannel = channels.get(deliveryMethod); - for (User recipient : recipients) { - Map templateContext = createTemplateContext(notificationRequest, recipient); - NotificationText text = notificationTemplateUtil.processTemplate(textTemplates.get(deliveryMethod), templateContext); - notificationChannel.sendNotification(recipient, savedNotificationRequest, text); - } + }, 200, recipientsBatch -> { + List> results = new ArrayList<>(); + for (NotificationDeliveryMethod deliveryMethod : savedNotificationRequest.getDeliveryMethods()) { + NotificationChannel notificationChannel = channels.get(deliveryMethod); + log.debug("Sending {} notifications for request {} to recipients batch", deliveryMethod, savedNotificationRequest.getId()); + + List recipients = recipientsBatch.getData(); + NotificationTextTemplate textTemplate = textTemplates.get(deliveryMethod); + for (User recipient : recipients) { + ListenableFuture resultFuture = processForRecipient(recipient, savedNotificationRequest, notificationChannel, textTemplate, stats); + DonAsynchron.withCallback(resultFuture, result -> { + stats.reportSent(deliveryMethod); + }, error -> { + stats.reportError(deliveryMethod, recipient, error); + }, dbCallbackExecutorService); + results.add(resultFuture); } - }); + } + Futures.whenAllComplete(results).run(() -> { + try { + notificationRequestService.updateNotificationRequestStats(tenantId, savedNotificationRequest.getId(), stats); + } catch (Exception e) { + log.error("Failed to update stats for notification request {}", savedNotificationRequest.getId(), e); + } + }, dbCallbackExecutorService); }); return savedNotificationRequest; } - private Map createTemplateContext(NotificationRequest notificationRequest, User recipient) { - Map templateContext = new HashMap<>(); - templateContext.put("email", recipient.getEmail()); - templateContext.put("firstName", Strings.nullToEmpty(recipient.getFirstName())); - templateContext.put("lastName", Strings.nullToEmpty(recipient.getLastName())); - if (notificationRequest.getTemplateContext() != null) { - templateContext.putAll(notificationRequest.getTemplateContext()); + private ListenableFuture processForRecipient(User recipient, NotificationRequest notificationRequest, NotificationChannel notificationChannel, + NotificationTextTemplate textTemplate, NotificationRequestStats stats) { + NotificationText text; + try { + Map templateContext = new HashMap<>(); + templateContext.put("email", recipient.getEmail()); + templateContext.put("firstName", Strings.nullToEmpty(recipient.getFirstName())); + templateContext.put("lastName", Strings.nullToEmpty(recipient.getLastName())); + if (notificationRequest.getTemplateContext() != null) { + templateContext.putAll(notificationRequest.getTemplateContext()); + } + text = notificationTemplateUtil.processTemplate(textTemplate, templateContext); + } catch (Exception e) { + return Futures.immediateFailedFuture(e); } - return templateContext; + return notificationChannel.sendNotification(recipient, notificationRequest, text); } private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId, boolean deleted) { @@ -141,7 +167,7 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl } @Override - public Future sendNotification(User recipient, NotificationRequest request, NotificationText text) { + public ListenableFuture 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()) @@ -194,18 +220,17 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl return notificationRequest; } - private Future onNotificationUpdate(TenantId tenantId, UserId recipientId, Notification notification, boolean isNew) { + private ListenableFuture onNotificationUpdate(TenantId tenantId, UserId recipientId, Notification notification, boolean isNew) { NotificationUpdate update = NotificationUpdate.builder() .notification(notification) .isNew(isNew) .build(); log.trace("Submitting notification update for recipient {}: {}", recipientId, update); - return wsCallBackExecutor.submit(() -> { + return Futures.submit(() -> { forwardToSubscriptionManagerService(tenantId, recipientId, subscriptionManagerService -> { subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, update, TbCallback.EMPTY); }, () -> TbSubscriptionUtils.notificationUpdateToProto(tenantId, recipientId, update)); - return null; - }); + }, wsCallBackExecutor); } private void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate update) { diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java index a3ec0fb2c3..72347795f3 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.service.notification.channels; +import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.MailService; @@ -24,8 +25,6 @@ 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 { @@ -34,7 +33,7 @@ public class EmailNotificationChannel implements NotificationChannel { private final MailExecutorService executor; @Override - public Future sendNotification(User recipient, NotificationRequest request, NotificationText text) { + public ListenableFuture sendNotification(User recipient, NotificationRequest request, NotificationText text) { return executor.submit(() -> { mailService.sendEmail(recipient.getTenantId(), recipient.getEmail(), text.getSubject(), text.getBody()); return null; diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java index ccf13bf2e8..2cb8889ba0 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java @@ -15,16 +15,15 @@ */ package org.thingsboard.server.service.notification.channels; +import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.template.NotificationText; -import java.util.concurrent.Future; - public interface NotificationChannel { - Future sendNotification(User recipient, NotificationRequest request, NotificationText text); + ListenableFuture sendNotification(User recipient, NotificationRequest request, NotificationText text); NotificationDeliveryMethod getDeliveryMethod(); diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java index 6ab263c779..0a9d61cd54 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.notification.channels; import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; @@ -26,8 +27,6 @@ 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 { @@ -36,9 +35,9 @@ public class SmsNotificationChannel implements NotificationChannel { private final SmsExecutorService executor; @Override - public Future sendNotification(User recipient, NotificationRequest request, NotificationText text) { + public ListenableFuture sendNotification(User recipient, NotificationRequest request, NotificationText text) { String phone = recipient.getPhone(); - if (StringUtils.isBlank(phone)) return Futures.immediateFuture(null); + if (StringUtils.isBlank(phone)) return Futures.immediateFailedFuture(new RuntimeException("User does not have phone number")); return executor.submit(() -> { smsService.sendSms(recipient.getTenantId(), recipient.getCustomerId(), new String[]{phone}, text.getBody()); return null; diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/NotificationsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/NotificationsCleanUpService.java new file mode 100644 index 0000000000..aca14c4a54 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ttl/NotificationsCleanUpService.java @@ -0,0 +1,72 @@ +/** + * 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.ttl; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.thingsboard.server.dao.notification.NotificationDao; +import org.thingsboard.server.dao.notification.NotificationRequestDao; +import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository; +import org.thingsboard.server.queue.discovery.PartitionService; + +import java.util.concurrent.TimeUnit; + +import static org.thingsboard.server.dao.model.ModelConstants.NOTIFICATION_TABLE_NAME; + +@Service +@ConditionalOnExpression("${sql.ttl.notifications.enabled:true} && ${sql.ttl.notifications.ttl:0} > 0") +@Slf4j +public class NotificationsCleanUpService extends AbstractCleanUpService { + + private final NotificationDao notificationDao; + private final NotificationRequestDao notificationRequestDao; + private final SqlPartitioningRepository partitioningRepository; + + @Value("${sql.ttl.notifications.ttl:2592000}") + private long ttlInSec; + @Value("${sql.notifications.partition_size:168}") + private int partitionSizeInHours; + + public NotificationsCleanUpService(PartitionService partitionService, + NotificationDao notificationDao, + NotificationRequestDao notificationRequestDao, + SqlPartitioningRepository partitioningRepository) { + super(partitionService); + this.notificationDao = notificationDao; + this.notificationRequestDao = notificationRequestDao; + this.partitioningRepository = partitioningRepository; + } + + @Scheduled(initialDelayString = "#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${sql.ttl.notifications.checking_interval_ms:86400000})}", + fixedDelayString = "${sql.ttl.notifications.checking_interval_ms:86400000}") + public void cleanUp() { + long expTime = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(ttlInSec); + long partitionDurationMs = TimeUnit.HOURS.toMillis(partitionSizeInHours); + if (isSystemTenantPartitionMine()) { + long actualExpTime = partitioningRepository.getLastPartitionEnd(NOTIFICATION_TABLE_NAME, expTime, partitionDurationMs); + + partitioningRepository.dropPartitionsBefore(NOTIFICATION_TABLE_NAME, expTime, partitionDurationMs); + // select distinct request_id for period and manually delete them ? + // sql trigger ? ---- + } else { + partitioningRepository.cleanupPartitionsCache(NOTIFICATION_TABLE_NAME, expTime, partitionDurationMs); + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java index 625b16d6d2..0072028353 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java @@ -952,7 +952,7 @@ public class DefaultWebSocketService implements WebSocketService { return limit == 0 ? DEFAULT_LIMIT : limit; } - private DefaultTenantProfileConfiguration getTenantProfileConfiguration(TelemetryWebSocketSessionRef sessionRef) { + private DefaultTenantProfileConfiguration getTenantProfileConfiguration(WebSocketSessionRef sessionRef) { return Optional.ofNullable(tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId())) .map(TenantProfile::getDefaultProfileConfiguration).orElse(null); } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 31cf413a48..8b9d22a3a5 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -278,7 +278,7 @@ sql: audit_logs: partition_size: "${SQL_AUDIT_LOGS_PARTITION_SIZE_HOURS:168}" # Default value - 1 week notifications: - partition_size: "${SQL_NOTIFICATIONS_PARTITION_SIZE_HOURS:168}" + partition_size: "${SQL_NOTIFICATIONS_PARTITION_SIZE_HOURS:168}" # Default value - 1 week # Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks batch_sort: "${SQL_BATCH_SORT:true}" # Specify whether to remove null characters from strValue of attributes and timeseries before insert @@ -321,6 +321,10 @@ sql: enabled: "${SQL_TTL_AUDIT_LOGS_ENABLED:true}" ttl: "${SQL_TTL_AUDIT_LOGS_SECS:0}" # Disabled by default. Accuracy of the cleanup depends on the sql.audit_logs.partition_size checking_interval_ms: "${SQL_TTL_AUDIT_LOGS_CHECKING_INTERVAL_MS:86400000}" # Default value - 1 day + notifications: + enabled: "${SQL_TTL_NOTIFICATIONS_ENABLED:true}" + ttl: "${SQL_TTL_NOTIFICATIONS_SECS:2592000}" # Default value - 30 days + checking_interval_ms: "${SQL_TTL_NOTIFICATIONS_CHECKING_INTERVAL_MS:86400000}" # Default value - 1 day relations: max_level: "${SQL_RELATIONS_MAX_LEVEL:50}" # This value has to be reasonable small to prevent infinite recursion as early as possible pool_size: "${SQL_RELATIONS_POOL_SIZE:4}" # This value has to be reasonable small to prevent relation query blocking all other DB calls diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java index d12d956a2f..060f3351b8 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java @@ -48,12 +48,12 @@ import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.query.SingleEntityFilter; import org.thingsboard.server.common.data.query.TsValue; +import org.thingsboard.server.service.subscription.SubscriptionErrorCode; import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; -import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; import java.util.Arrays; import java.util.Collections; diff --git a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java index 3de71789e9..9a19873777 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java +++ b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java @@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.common.data.query.EntityFilter; import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryPluginCmdsWrapper; -import org.thingsboard.server.service.telemetry.cmd.v1.AttributesSubscriptionCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java index fcf8005c1b..066493fc8b 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.notification; import com.fasterxml.jackson.core.type.TypeReference; +import lombok.extern.slf4j.Slf4j; import org.assertj.core.data.Offset; import org.java_websocket.client.WebSocketClient; import org.junit.Before; @@ -31,6 +32,7 @@ import org.thingsboard.server.common.data.notification.NotificationDeliveryMetho import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; +import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.SingleUserNotificationTargetConfig; @@ -49,17 +51,16 @@ import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpd import java.net.URISyntaxException; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.not; import static org.awaitility.Awaitility.await; @DaoSqlTest +@Slf4j public class NotificationApiTest extends AbstractControllerTest { @Autowired @@ -251,9 +252,10 @@ public class NotificationApiTest extends AbstractControllerTest { } @Test - public void testNotificationUpdatesForUsersInTarget() throws Exception { - Map wsSessions = createUsersAndSetUpWsSessions(100); - wsSessions.forEach((user, wsClient) -> { + public void testNotificationUpdatesForALotOfUsers() throws Exception { + int usersCount = 100; + Map wsSessions = createUsersAndSetUpWsSessions(usersCount); + wsSessions.values().forEach(wsClient -> { wsClient.subscribeForUnreadNotifications(10); wsClient.waitForReply(true); wsClient.subscribeForUnreadNotificationsCount(); @@ -263,14 +265,15 @@ public class NotificationApiTest extends AbstractControllerTest { NotificationTarget notificationTarget = new NotificationTarget(); UserListNotificationTargetConfig config = new UserListNotificationTargetConfig(); config.setUsersIds(wsSessions.keySet().stream().map(User::getUuidId).collect(Collectors.toList())); - notificationTarget.setName("100 users"); + notificationTarget.setName("Test users"); notificationTarget.setTenantId(tenantId); notificationTarget.setConfiguration(config); notificationTarget = saveNotificationTarget(notificationTarget); wsSessions.forEach((user, wsClient) -> wsClient.registerWaitForUpdate(2)); - NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), "Hello, ${email}"); - await().atMost(5, TimeUnit.SECONDS) + NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), "Hello, ${email}", + NotificationDeliveryMethod.WEBSOCKET); + await().atMost(20, TimeUnit.SECONDS) .pollDelay(1, TimeUnit.SECONDS).pollInterval(500, TimeUnit.MILLISECONDS) .until(() -> wsSessions.values().stream() .allMatch(wsClient -> wsClient.getLastDataUpdate() != null @@ -285,21 +288,23 @@ public class NotificationApiTest extends AbstractControllerTest { assertThat(notification.getRequestId()).isEqualTo(notificationRequest.getId()); assertThat(notification.getText()).isEqualTo("Hello, " + user.getEmail()); }); + + await().atMost(2, TimeUnit.SECONDS) + .until(() -> findNotificationRequest(notificationRequest.getId()).getStats() != null); + NotificationRequestStats stats = findNotificationRequest(notificationRequest.getId()).getStats(); + assertThat(stats.getSent().get(NotificationDeliveryMethod.WEBSOCKET)).hasValue(usersCount); + wsSessions.values().forEach(WebSocketClient::close); } private Map createUsersAndSetUpWsSessions(int count) throws Exception { - List users = new LinkedList<>(); + Map wsSessions = new HashMap<>(); for (int i = 1; i <= count; i++) { User user = new User(); user.setTenantId(tenantId); user.setAuthority(Authority.TENANT_ADMIN); user.setEmail("test-user-" + i + "@thingsboard.org"); - users.add(createUser(user, "12345678")); - } - Map wsSessions = new HashMap<>(); - for (User user : users) { - login(user.getEmail(), "12345678"); + user = createUserAndLogin(user, "12345678"); NotificationApiWsClient wsClient = (NotificationApiWsClient) buildAndConnectWebSocketClient(); wsSessions.put(user, wsClient); } @@ -307,6 +312,27 @@ public class NotificationApiTest extends AbstractControllerTest { return wsSessions; } + @Test + public void testNotificationRequestStats() throws Exception { + getWsClient().subscribeForUnreadNotifications(10); + getWsClient().waitForReply(true); + + getWsClient().registerWaitForUpdate(); + NotificationTarget notificationTarget = createNotificationTarget(tenantAdminUserId); + NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), "Test :)", + NotificationDeliveryMethod.WEBSOCKET, NotificationDeliveryMethod.EMAIL, NotificationDeliveryMethod.SMS); + getWsClient().waitForUpdate(); + + await().atMost(2, TimeUnit.SECONDS) + .until(() -> findNotificationRequest(notificationRequest.getId()).getStats() != null); + NotificationRequestStats stats = findNotificationRequest(notificationRequest.getId()).getStats(); + + assertThat(stats.getSent().get(NotificationDeliveryMethod.WEBSOCKET)).hasValue(1); + assertThat(stats.getSent().get(NotificationDeliveryMethod.EMAIL)).hasValue(1); + assertThat(stats.getErrors().get(NotificationDeliveryMethod.SMS)).size().isOne(); + System.err.println(stats); + } + private void checkFullNotificationsUpdate(UnreadNotificationsUpdate notificationsUpdate, String... expectedNotifications) { assertThat(notificationsUpdate.getNotifications()).extracting(Notification::getText).containsOnly(expectedNotifications); @@ -332,11 +358,11 @@ public class NotificationApiTest extends AbstractControllerTest { return doPost("/api/notification/target", notificationTarget, NotificationTarget.class); } - private NotificationRequest submitNotificationRequest(NotificationTargetId targetId, String text) { - return submitNotificationRequest(targetId, text, 0); + private NotificationRequest submitNotificationRequest(NotificationTargetId targetId, String text, NotificationDeliveryMethod... deliveryMethods) { + return submitNotificationRequest(targetId, text, 0, deliveryMethods); } - private NotificationRequest submitNotificationRequest(NotificationTargetId targetId, String text, int delayInSec) { + private NotificationRequest submitNotificationRequest(NotificationTargetId targetId, String text, int delayInSec, NotificationDeliveryMethod... deliveryMethods) { NotificationTemplate notificationTemplate = createNotificationTemplate(text); NotificationRequestConfig config = new NotificationRequestConfig(); config.setSendingDelayInSec(delayInSec); @@ -348,7 +374,7 @@ public class NotificationApiTest extends AbstractControllerTest { .type("Test") .templateId(notificationTemplate.getId()) .info(notificationInfo) - .deliveryMethods(List.of(NotificationDeliveryMethod.WEBSOCKET)) + .deliveryMethods(deliveryMethods.length > 0 ? List.of(deliveryMethods) : List.of(NotificationDeliveryMethod.WEBSOCKET)) .additionalConfig(config) .build(); return doPost("/api/notification/request", notificationRequest, NotificationRequest.class); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java index 9a5687d04c..aa194c3641 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java @@ -20,7 +20,7 @@ import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationRequest; -import org.thingsboard.server.common.data.notification.NotificationRequestInfo; +import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -40,6 +40,6 @@ public interface NotificationRequestService { PageData findScheduledNotificationRequests(PageLink pageLink); - NotificationRequestInfo getNotificationRequestInfoById(TenantId tenantId, NotificationRequestId id); + void updateNotificationRequestStats(TenantId tenantId, NotificationRequestId notificationRequestId, NotificationRequestStats stats); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index 0462952ad0..741343704b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -87,6 +87,8 @@ public class EntityIdFactory { return new NotificationRequestId(uuid); case NOTIFICATION_RULE: return new NotificationRuleId(uuid); + case NOTIFICATION_TEMPLATE: + return new NotificationTemplateId(uuid); } throw new IllegalArgumentException("EntityType " + type + " is not supported!"); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java index 403391e385..b59b24d6dc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.data.notification; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -38,6 +39,8 @@ import javax.validation.constraints.NotNull; import java.util.List; import java.util.Map; +import static com.fasterxml.jackson.annotation.JsonProperty.Access.READ_ONLY; + @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor @@ -67,25 +70,10 @@ public class NotificationRequest extends BaseData impleme private NotificationRequestStatus status; + private NotificationRequestStats stats; @JsonIgnore private transient Map templateContext; - public NotificationRequest(NotificationRequest other) { - super(other); - this.tenantId = other.tenantId; - this.targetId = other.targetId; - this.type = other.type; - this.templateId = other.templateId; - this.info = other.info; - this.deliveryMethods = other.deliveryMethods; - this.additionalConfig = other.additionalConfig; - this.originatorType = other.originatorType; - this.originatorEntityId = other.originatorEntityId; - this.ruleId = other.ruleId; - this.status = other.status; - this.templateContext = other.templateContext; - } - @JsonIgnore @Override public String getName() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestInfo.java deleted file mode 100644 index 53835200d7..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestInfo.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright © 2016-2022 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.common.data.notification; - -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.util.Map; - -@Data -@EqualsAndHashCode(callSuper = true) -public class NotificationRequestInfo extends NotificationRequest { - - private int sent; - private int read; - private Map statusesByRecipient; - - public NotificationRequestInfo(NotificationRequest notificationRequest) { - super(notificationRequest); - } - -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java new file mode 100644 index 0000000000..0db4656f8f --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java @@ -0,0 +1,54 @@ +/** + * 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; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import org.thingsboard.server.common.data.User; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +@Data +public class NotificationRequestStats { + + private final Map sent; + private final Map> errors; + + public NotificationRequestStats() { + this.sent = new ConcurrentHashMap<>(); + this.errors = new ConcurrentHashMap<>(); + } + + @JsonCreator + public NotificationRequestStats(@JsonProperty("sent") Map sent, + @JsonProperty("errors") Map> errors) { + this.sent = sent; + this.errors = errors; + } + + public void reportSent(NotificationDeliveryMethod deliveryMethod) { + sent.computeIfAbsent(deliveryMethod, k -> new AtomicInteger()).incrementAndGet(); + } + + public void reportError(NotificationDeliveryMethod deliveryMethod, User recipient, Throwable error) { + String errorMessage = error.getMessage(); + errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(recipient.getEmail(), errorMessage); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/util/ThrowingBiFunction.java b/common/data/src/main/java/org/thingsboard/server/common/data/util/ThrowingBiFunction.java index 9dbafb1734..cdf339d964 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/util/ThrowingBiFunction.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/util/ThrowingBiFunction.java @@ -1,3 +1,18 @@ +/** + * 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.util; @FunctionalInterface diff --git a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java index 4e82583ba2..816069ef9a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java +++ b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java @@ -110,17 +110,17 @@ public abstract class DaoUtil { } public static void processInBatches(Function> finder, int batchSize, Consumer processor) { - processBatches(finder, batchSize, batch -> batch.forEach(processor)); + processBatches(finder, batchSize, batch -> batch.getData().forEach(processor)); } - public static void processBatches(Function> finder, int batchSize, Consumer> processor) { + public static void processBatches(Function> finder, int batchSize, Consumer> processor) { PageLink pageLink = new PageLink(batchSize); PageData batch; boolean hasNextBatch; do { batch = finder.apply(pageLink); - processor.accept(batch.getData()); + processor.accept(batch); hasNextBatch = batch.hasNext(); pageLink = pageLink.nextPageLink(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 941e9459ba..89855b8003 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -673,6 +673,7 @@ public class ModelConstants { public static final String NOTIFICATION_REQUEST_ADDITIONAL_CONFIG_PROPERTY = "additional_config"; public static final String NOTIFICATION_REQUEST_STATUS_PROPERTY = "status"; public static final String NOTIFICATION_REQUEST_RULE_ID_PROPERTY = "rule_id"; + public static final String NOTIFICATION_REQUEST_STATS_PROPERTY = "stats"; public static final String NOTIFICATION_RULE_TABLE_NAME = "notification_rule"; public static final String NOTIFICATION_RULE_TEMPLATE_ID_PROPERTY = "template_id"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java index 1001f96be0..56e6bc30a2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationOriginatorType; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; +import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; @@ -95,6 +96,10 @@ public class NotificationRequestEntity extends BaseSqlEntity { PageData findAllByStatus(NotificationRequestStatus status, PageLink pageLink); - NotificationRequestInfo getNotificationRequestInfoById(TenantId tenantId, NotificationRequestId id); + void updateStatsById(TenantId tenantId, NotificationRequestId notificationRequestId, NotificationRequestStats stats); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java index 6ab6e63592..f3a0698b1e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java @@ -20,13 +20,11 @@ import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; -import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -58,7 +56,6 @@ public class JpaNotificationDao extends JpaAbstractDao implements NotificationRequestDao { private final NotificationRequestRepository notificationRequestRepository; - private final NotificationRepository notificationRepository; @Override public PageData findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) { @@ -66,16 +62,9 @@ public class JpaNotificationRequestDao extends JpaAbstractDao statusesByRecipient = notificationRepository.getStatusesByRecipientForRequestId(id.getId()).stream() - .collect(Collectors.toMap(r -> (String) r[0], r -> (NotificationStatus) r[1])); - notificationRequestInfo.setStatusesByRecipient(statusesByRecipient); - return notificationRequestInfo; + public void updateStatsById(TenantId tenantId, NotificationRequestId notificationRequestId, NotificationRequestStats stats) { + notificationRequestRepository.updateStatsById(notificationRequestId.getId(), JacksonUtil.valueToTree(stats)); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java index 9b952c292e..0746dfd611 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java @@ -48,12 +48,4 @@ public interface NotificationRepository extends JpaRepository findByRequestId(UUID requestId, Pageable pageable); - int countByRequestId(UUID requestId); - - int countByRequestIdAndStatus(UUID requestId, NotificationStatus status); - - @Query("SELECT u.email, n.status FROM NotificationEntity n INNER JOIN UserEntity u ON n.recipientId = u.id " + - "WHERE n.requestId = :requestId") - List getStatusesByRecipientForRequestId(@Param("requestId") UUID requestId); - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java index b6d22a62fc..ec81174c84 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java @@ -15,12 +15,15 @@ */ package org.thingsboard.server.dao.sql.notification; +import com.fasterxml.jackson.databind.JsonNode; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.dao.model.sql.NotificationRequestEntity; @@ -40,4 +43,9 @@ public interface NotificationRequestRepository extends JpaRepository findAllByStatus(NotificationRequestStatus status, Pageable pageable); + @Modifying + @Transactional + @Query("UPDATE NotificationRequestEntity r SET r.stats = :stats WHERE r.id = :id") + void updateStatsById(@Param("id") UUID id, @Param("stats") JsonNode stats); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java index 12295b71cb..a69f207805 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java @@ -95,6 +95,13 @@ public class SqlPartitioningRepository { } } + public long getLastPartitionEnd(String table, long before, long partitionDurationMs) { + return fetchPartitions(table).stream() + .mapToLong(partitionStartTime -> partitionStartTime + partitionDurationMs) + .filter(partitionEndTime -> partitionEndTime < before) + .max().orElse(0); + } + public void cleanupPartitionsCache(String table, long expTime, long partitionDurationMs) { Map partitions = tablesPartitions.get(table); if (partitions == null) return; diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 146754bbf6..c3dace68fe 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -821,7 +821,8 @@ CREATE TABLE IF NOT EXISTS notification_request ( originator_entity_id UUID, originator_entity_type VARCHAR(32), rule_id UUID NULL CONSTRAINT fk_notification_request_rule_id REFERENCES notification_rule(id), - status VARCHAR(32) + status VARCHAR(32), + stats VARCHAR(1000) ); CREATE TABLE IF NOT EXISTS notification ( From 738b9dc494270f0cc2ae916e83e645a84a1579dc Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Mon, 19 Dec 2022 19:56:20 +0200 Subject: [PATCH 021/111] Slack notification channel; refactoring --- application/pom.xml | 4 + .../main/data/upgrade/3.4.3/schema_update.sql | 13 --- .../controller/NotificationController.java | 38 +++++++ .../DefaultNotificationManager.java | 57 +++++----- .../NotificationManagerHelper.java | 90 ++++++++++++++++ .../NotificationProcessingContext.java | 79 ++++++++++++++ .../NotificationTemplateUtil.java | 56 ---------- .../channels/EmailNotificationChannel.java | 9 +- .../channels/NotificationChannel.java | 5 +- .../channels/SlackNotificationChannel.java | 81 ++++++++++++++ .../channels/SmsNotificationChannel.java | 7 +- .../service/slack/DefaultSlackService.java | 101 ++++++++++++++++++ .../service/slack/SlackConversation.java | 32 ++++++ .../server/service/slack/SlackService.java | 28 +++++ .../notification/NotificationApiTest.java | 33 ++++-- .../NotificationRequestStats.java | 4 + .../NotificationDeliveryMethodConfig.java | 15 ++- ...SlackNotificationDeliveryMethodConfig.java | 27 +++++ .../DeliveryMethodNotificationTemplate.java | 36 +++++++ ...ilDeliveryMethodNotificationTemplate.java} | 5 +- .../template/NotificationTemplateConfig.java | 4 +- ...ackDeliveryMethodNotificationTemplate.java | 27 +++++ pom.xml | 6 ++ 23 files changed, 631 insertions(+), 126 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/NotificationManagerHelper.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/notification/NotificationTemplateUtil.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java create mode 100644 application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/slack/SlackConversation.java create mode 100644 application/src/main/java/org/thingsboard/server/service/slack/SlackService.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/SlackNotificationDeliveryMethodConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/template/DeliveryMethodNotificationTemplate.java rename common/data/src/main/java/org/thingsboard/server/common/data/notification/template/{NotificationTextTemplate.java => EmailDeliveryMethodNotificationTemplate.java} (81%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SlackDeliveryMethodNotificationTemplate.java diff --git a/application/pom.xml b/application/pom.xml index 61fa75cf49..c673eafeb2 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -356,6 +356,10 @@ org.jboss.aerogear aerogear-otp-java + + com.slack.api + slack-api-client + diff --git a/application/src/main/data/upgrade/3.4.3/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql index d5d94494a3..09398588b3 100644 --- a/application/src/main/data/upgrade/3.4.3/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.3/schema_update.sql @@ -79,16 +79,3 @@ CREATE INDEX IF NOT EXISTS idx_notification_notification_request_id ON notificat ALTER TABLE alarm ADD COLUMN IF NOT EXISTS notification_rule_id UUID; ALTER TABLE tb_user ADD COLUMN IF NOT EXISTS phone VARCHAR(255); - -CREATE OR REPLACE FUNCTION on_notification_deleted() RETURNS TRIGGER as $notification_deleted$ -BEGIN - RAISE NOTICE 'ABAAAAAAAAAAAAAAAAAAA'; - INSERT INTO id_and_time values ('13814000-1dd2-11b2-8080-808080808080', 0); - RETURN NULL; -END; -$notification_deleted$ LANGUAGE plpgsql; - -CREATE TRIGGER notification_deleted_trigger - AFTER DELETE ON id_and_time - REFERENCING OLD TABLE AS deleted - FOR EACH STATEMENT EXECUTE FUNCTION on_notification_deleted(); diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index 22f09921e5..c461ccfdb1 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -35,17 +35,24 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationOriginatorType; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.settings.NotificationSettings; +import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.notification.NotificationManagerHelper; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; +import org.thingsboard.server.service.slack.SlackConversation; +import org.thingsboard.server.service.slack.SlackService; +import java.util.List; import java.util.UUID; @RestController @@ -58,6 +65,8 @@ public class NotificationController extends BaseController { private final NotificationService notificationService; private final NotificationRequestService notificationRequestService; private final NotificationManager notificationManager; + private final NotificationManagerHelper notificationManagerHelper; + private final SlackService slackService; @GetMapping("/notifications") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @@ -133,4 +142,33 @@ public class NotificationController extends BaseController { doDeleteAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationManager::deleteNotificationRequest); } + + @PostMapping("/notification/settings") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationSettings saveNotificationSettings(@RequestBody NotificationSettings notificationSettings, + @AuthenticationPrincipal SecurityUser user) { + notificationManagerHelper.saveNotificationSettings(user.getTenantId(), notificationSettings); + return notificationSettings; + } + + @GetMapping("/notification/settings") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationSettings getNotificationSettings(@AuthenticationPrincipal SecurityUser user) { + return notificationManagerHelper.getNotificationSettings(user.getTenantId()); + } + + @GetMapping("/notification/slack/conversations") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public List listSlackConversations(@RequestParam SlackConversation.Type type, + @AuthenticationPrincipal SecurityUser user) throws Exception { + NotificationSettings settings = getNotificationSettings(user); + SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig) + settings.getDeliveryMethodsConfigs().get(NotificationDeliveryMethod.SLACK); + if (slackConfig == null) { + throw new IllegalArgumentException("Slack is not configured"); + } + + return slackService.listConversations(user.getTenantId(), slackConfig.getBotToken(), type); + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java index 0606762d0c..b7a6e804e3 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.notification; -import com.google.common.base.Strings; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; @@ -33,11 +32,10 @@ import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; -import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.NotificationStatus; -import org.thingsboard.server.common.data.notification.template.NotificationText; -import org.thingsboard.server.common.data.notification.template.NotificationTextTemplate; +import org.thingsboard.server.common.data.notification.settings.NotificationSettings; +import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; @@ -57,7 +55,6 @@ import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpd import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -74,7 +71,7 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl private final NotificationTargetService notificationTargetService; private final NotificationRequestService notificationRequestService; private final NotificationService notificationService; - private final NotificationTemplateUtil notificationTemplateUtil; + private final NotificationManagerHelper notificationManagerHelper; private final DbCallbackExecutorService dbCallbackExecutorService; private final NotificationsTopicService notificationsTopicService; private final TbQueueProducerProvider producerProvider; @@ -85,6 +82,13 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { log.debug("Processing notification request (tenant id: {}, notification target id: {})", tenantId, notificationRequest.getTargetId()); notificationRequest.setTenantId(tenantId); + NotificationSettings settings = notificationManagerHelper.getNotificationSettings(tenantId); + notificationRequest.getDeliveryMethods().forEach(deliveryMethod -> { + if (!settings.getDeliveryMethodsConfigs().containsKey(deliveryMethod) || !settings.getDeliveryMethodsConfigs().get(deliveryMethod).isEnabled()) { + throw new IllegalArgumentException("Delivery method " + deliveryMethod + " is not enabled or configured"); + } + }); + if (notificationRequest.getAdditionalConfig() != null) { NotificationRequestConfig config = notificationRequest.getAdditionalConfig(); if (config.getSendingDelayInSec() > 0 && notificationRequest.getId() == null) { @@ -98,9 +102,13 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl notificationRequest.setStatus(NotificationRequestStatus.PROCESSED); NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); - NotificationRequestStats stats = new NotificationRequestStats(); - Map textTemplates = notificationTemplateUtil.getTemplates(tenantId, notificationRequest.getTemplateId(), savedNotificationRequest.getDeliveryMethods()); - savedNotificationRequest.setTemplateContext(notificationRequest.getTemplateContext()); + NotificationProcessingContext ctx = NotificationProcessingContext.builder() + .tenantId(tenantId) + .settings(settings) + .request(savedNotificationRequest) + .additionalTemplateContext(notificationRequest.getTemplateContext()) + .build(); + ctx.init(notificationManagerHelper); DaoUtil.processBatches(pageLink -> { return notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), pageLink); @@ -111,20 +119,19 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl log.debug("Sending {} notifications for request {} to recipients batch", deliveryMethod, savedNotificationRequest.getId()); List recipients = recipientsBatch.getData(); - NotificationTextTemplate textTemplate = textTemplates.get(deliveryMethod); for (User recipient : recipients) { - ListenableFuture resultFuture = processForRecipient(recipient, savedNotificationRequest, notificationChannel, textTemplate, stats); + ListenableFuture resultFuture = processForRecipient(notificationChannel, recipient, ctx); DonAsynchron.withCallback(resultFuture, result -> { - stats.reportSent(deliveryMethod); + ctx.getStats().reportSent(deliveryMethod); }, error -> { - stats.reportError(deliveryMethod, recipient, error); + ctx.getStats().reportError(deliveryMethod, recipient, error); }, dbCallbackExecutorService); results.add(resultFuture); } } Futures.whenAllComplete(results).run(() -> { try { - notificationRequestService.updateNotificationRequestStats(tenantId, savedNotificationRequest.getId(), stats); + notificationRequestService.updateNotificationRequestStats(tenantId, savedNotificationRequest.getId(), ctx.getStats()); } catch (Exception e) { log.error("Failed to update stats for notification request {}", savedNotificationRequest.getId(), e); } @@ -134,22 +141,15 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl return savedNotificationRequest; } - private ListenableFuture processForRecipient(User recipient, NotificationRequest notificationRequest, NotificationChannel notificationChannel, - NotificationTextTemplate textTemplate, NotificationRequestStats stats) { - NotificationText text; + private ListenableFuture processForRecipient(NotificationChannel notificationChannel, User recipient, NotificationProcessingContext ctx) { + String text; try { - Map templateContext = new HashMap<>(); - templateContext.put("email", recipient.getEmail()); - templateContext.put("firstName", Strings.nullToEmpty(recipient.getFirstName())); - templateContext.put("lastName", Strings.nullToEmpty(recipient.getLastName())); - if (notificationRequest.getTemplateContext() != null) { - templateContext.putAll(notificationRequest.getTemplateContext()); - } - text = notificationTemplateUtil.processTemplate(textTemplate, templateContext); + DeliveryMethodNotificationTemplate template = ctx.getTemplate(notificationChannel.getDeliveryMethod()); + text = notificationManagerHelper.processTemplate(template.getBody(), ctx.createTemplateContext(recipient)); } catch (Exception e) { return Futures.immediateFailedFuture(e); } - return notificationChannel.sendNotification(recipient, notificationRequest, text); + return notificationChannel.sendNotification(recipient, text, ctx); } private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId, boolean deleted) { @@ -167,13 +167,14 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl } @Override - public ListenableFuture sendNotification(User recipient, NotificationRequest request, NotificationText text) { + public ListenableFuture sendNotification(User recipient, String text, NotificationProcessingContext ctx) { + NotificationRequest request = ctx.getRequest(); 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()) + .text(text) .info(request.getInfo()) .originatorType(request.getOriginatorType()) .status(NotificationStatus.SENT) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationManagerHelper.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationManagerHelper.java new file mode 100644 index 0000000000..3ecb0bab0c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationManagerHelper.java @@ -0,0 +1,90 @@ +/** + * 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.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.AdminSettings; +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.settings.NotificationDeliveryMethodConfig; +import org.thingsboard.server.common.data.notification.settings.NotificationSettings; +import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; +import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; +import org.thingsboard.server.dao.notification.NotificationTemplateService; +import org.thingsboard.server.dao.settings.AdminSettingsService; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +public class NotificationManagerHelper { + + public static final String SETTINGS_KEY = "notifications"; + private final NotificationTemplateService templateService; + private final AdminSettingsService adminSettingsService; + + public Map getTemplates(TenantId tenantId, NotificationTemplateId templateId, List deliveryMethods) { + NotificationTemplate notificationTemplate = templateService.findNotificationTemplateById(tenantId, templateId); + NotificationTemplateConfig config = notificationTemplate.getConfiguration(); + return deliveryMethods.stream() + .collect(Collectors.toMap(k -> k, deliveryMethod -> { + return Optional.ofNullable(config.getTemplates()) + .map(templates -> templates.get(deliveryMethod)) + .orElse(config.getDefaultTemplate()); + })); + } + + public String processTemplate(String template, Map templateContext) { + return TbNodeUtils.processTemplate(template, templateContext); + } + + public NotificationSettings getNotificationSettings(TenantId tenantId) { + return Optional.ofNullable(adminSettingsService.findAdminSettingsByTenantIdAndKey(tenantId, SETTINGS_KEY)) + .map(adminSettings -> JacksonUtil.treeToValue(adminSettings.getJsonValue(), NotificationSettings.class)) + .orElseGet(() -> { + NotificationSettings settings = new NotificationSettings(); + NotificationDeliveryMethodConfig defaultConfig = new NotificationDeliveryMethodConfig(); + defaultConfig.setEnabled(true); + settings.setDeliveryMethodsConfigs(Map.of( + NotificationDeliveryMethod.WEBSOCKET, defaultConfig, + NotificationDeliveryMethod.EMAIL, defaultConfig, + NotificationDeliveryMethod.SMS, defaultConfig + )); + return settings; + }); + } + + public void saveNotificationSettings(TenantId tenantId, NotificationSettings notificationSettings) { + AdminSettings adminSettings = Optional.ofNullable(adminSettingsService.findAdminSettingsByTenantIdAndKey(tenantId, SETTINGS_KEY)) + .orElseGet(() -> { + AdminSettings newAdminSettings = new AdminSettings(); + newAdminSettings.setKey(SETTINGS_KEY); + newAdminSettings.setTenantId(tenantId); + return newAdminSettings; + }); + adminSettings.setJsonValue(JacksonUtil.valueToTree(notificationSettings)); + adminSettingsService.saveAdminSettings(tenantId, adminSettings); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java new file mode 100644 index 0000000000..ff4f5d76a8 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java @@ -0,0 +1,79 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification; + +import com.google.common.base.Strings; +import lombok.Builder; +import lombok.Getter; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationRequestStats; +import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig; +import org.thingsboard.server.common.data.notification.settings.NotificationSettings; +import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; + +import java.util.HashMap; +import java.util.Map; + +@SuppressWarnings("unchecked") +public class NotificationProcessingContext { + + @Getter + private final TenantId tenantId; + private final NotificationSettings settings; + @Getter + private final NotificationRequest request; + private final Map additionalTemplateContext; + + private Map templates; + @Getter + private NotificationRequestStats stats; + + @Builder + public NotificationProcessingContext(TenantId tenantId, NotificationSettings settings, NotificationRequest request, Map additionalTemplateContext) { + this.tenantId = tenantId; + this.settings = settings; + this.request = request; + this.additionalTemplateContext = additionalTemplateContext; + } + + public void init(NotificationManagerHelper notificationManagerHelper) { + templates = notificationManagerHelper.getTemplates(tenantId, request.getTemplateId(), request.getDeliveryMethods()); + stats = new NotificationRequestStats(); + } + + public T getTemplate(NotificationDeliveryMethod deliveryMethod) { + return (T) templates.get(deliveryMethod); + } + + public C getDeliveryMethodConfig(NotificationDeliveryMethod deliveryMethod) { + return (C) settings.getDeliveryMethodsConfigs().get(deliveryMethod); + } + + public Map createTemplateContext(User recipient) { + Map templateContext = new HashMap<>(); + templateContext.put("email", recipient.getEmail()); + templateContext.put("firstName", Strings.nullToEmpty(recipient.getFirstName())); + templateContext.put("lastName", Strings.nullToEmpty(recipient.getLastName())); + if (additionalTemplateContext != null) { + templateContext.putAll(additionalTemplateContext); + } + return templateContext; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationTemplateUtil.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationTemplateUtil.java deleted file mode 100644 index b269712177..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/notification/NotificationTemplateUtil.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright © 2016-2022 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.notification; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; -import org.thingsboard.rule.engine.api.util.TbNodeUtils; -import org.thingsboard.server.common.data.id.NotificationTemplateId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; -import org.thingsboard.server.common.data.notification.template.NotificationTemplate; -import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; -import org.thingsboard.server.common.data.notification.template.NotificationText; -import org.thingsboard.server.common.data.notification.template.NotificationTextTemplate; -import org.thingsboard.server.dao.notification.NotificationTemplateService; - -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; - -@Component -@RequiredArgsConstructor -public class NotificationTemplateUtil { - - private final NotificationTemplateService templateService; - - public Map getTemplates(TenantId tenantId, NotificationTemplateId templateId, List deliveryMethods) { - NotificationTemplate notificationTemplate = templateService.findNotificationTemplateById(tenantId, templateId); - NotificationTemplateConfig config = notificationTemplate.getConfiguration(); - return deliveryMethods.stream() - .collect(Collectors.toMap(k -> k, deliveryMethod -> { - return Optional.ofNullable(config.getTextTemplates()) - .map(templates -> templates.get(deliveryMethod)) - .orElse(config.getDefaultTextTemplate()); - })); - } - - public NotificationText processTemplate(NotificationTextTemplate template, Map templateContext) { - return new NotificationText(TbNodeUtils.processTemplate(template.getBody(), templateContext), template.getSubject()); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java index 72347795f3..af2d811bbb 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java @@ -21,9 +21,9 @@ 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.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; import org.thingsboard.server.service.mail.MailExecutorService; +import org.thingsboard.server.service.notification.NotificationProcessingContext; @Component @RequiredArgsConstructor @@ -33,9 +33,10 @@ public class EmailNotificationChannel implements NotificationChannel { private final MailExecutorService executor; @Override - public ListenableFuture sendNotification(User recipient, NotificationRequest request, NotificationText text) { + public ListenableFuture sendNotification(User recipient, String text, NotificationProcessingContext ctx) { + EmailDeliveryMethodNotificationTemplate template = ctx.getTemplate(NotificationDeliveryMethod.EMAIL); return executor.submit(() -> { - mailService.sendEmail(recipient.getTenantId(), recipient.getEmail(), text.getSubject(), text.getBody()); + mailService.sendEmail(recipient.getTenantId(), recipient.getEmail(), text, template.getSubject()); return null; }); } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java index 2cb8889ba0..85113d3469 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java @@ -18,12 +18,11 @@ package org.thingsboard.server.service.notification.channels; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; -import org.thingsboard.server.common.data.notification.NotificationRequest; -import org.thingsboard.server.common.data.notification.template.NotificationText; +import org.thingsboard.server.service.notification.NotificationProcessingContext; public interface NotificationChannel { - ListenableFuture sendNotification(User recipient, NotificationRequest request, NotificationText text); + ListenableFuture sendNotification(User recipient, String text, NotificationProcessingContext ctx); NotificationDeliveryMethod getDeliveryMethod(); diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java new file mode 100644 index 0000000000..a091d1517e --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java @@ -0,0 +1,81 @@ +/** + * 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 com.google.common.util.concurrent.ListenableFuture; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import org.thingsboard.server.service.slack.SlackService; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; +import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate; +import org.thingsboard.server.service.notification.NotificationProcessingContext; + +import javax.annotation.PostConstruct; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@Component +@RequiredArgsConstructor +public class SlackNotificationChannel implements NotificationChannel { + + private final SlackService slackService; + private ExecutorService executor; + + @PostConstruct + private void init() { + executor = Executors.newSingleThreadExecutor(); + } + + @Override + public ListenableFuture sendNotification(User recipient, String text, NotificationProcessingContext ctx) { + SlackDeliveryMethodNotificationTemplate template = ctx.getTemplate(NotificationDeliveryMethod.SLACK); + SlackNotificationDeliveryMethodConfig config = ctx.getDeliveryMethodConfig(NotificationDeliveryMethod.SLACK); + + String conversationId = template.getConversationId(); + if (StringUtils.isNotEmpty(conversationId)) { + if (ctx.getStats().contains(NotificationDeliveryMethod.SLACK)) { + // FIXME stats.sent will be reported anyway + return Futures.immediateFuture(null); // if conversationId is set, we only need to send message once + } + } else { + String username = StringUtils.join(new String[]{recipient.getFirstName(), recipient.getLastName()}, ' '); + if (StringUtils.isNotEmpty(username)) { + conversationId = username; + } else { + return Futures.immediateFailedFuture(new IllegalArgumentException("Couldn't determine Slack username for the user")); + } + } + return send(ctx.getTenantId(), config.getBotToken(), conversationId, text); + } + + private ListenableFuture send(TenantId tenantId, String botToken, String conversationId, String text) { + return Futures.submit(() -> { + slackService.sendMessage(tenantId, botToken, conversationId, text); + return null; + }, executor); + } + + @Override + public NotificationDeliveryMethod getDeliveryMethod() { + return NotificationDeliveryMethod.SLACK; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java index 0a9d61cd54..8d155e25f2 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java @@ -23,8 +23,7 @@ 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.notification.NotificationProcessingContext; import org.thingsboard.server.service.sms.SmsExecutorService; @Component @@ -35,11 +34,11 @@ public class SmsNotificationChannel implements NotificationChannel { private final SmsExecutorService executor; @Override - public ListenableFuture sendNotification(User recipient, NotificationRequest request, NotificationText text) { + public ListenableFuture sendNotification(User recipient, String text, NotificationProcessingContext ctx) { String phone = recipient.getPhone(); if (StringUtils.isBlank(phone)) return Futures.immediateFailedFuture(new RuntimeException("User does not have phone number")); return executor.submit(() -> { - smsService.sendSms(recipient.getTenantId(), recipient.getCustomerId(), new String[]{phone}, text.getBody()); + smsService.sendSms(recipient.getTenantId(), recipient.getCustomerId(), new String[]{phone}, text); return null; }); } diff --git a/application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java b/application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java new file mode 100644 index 0000000000..e6f3d57386 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java @@ -0,0 +1,101 @@ +/** + * 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.slack; + +import com.slack.api.Slack; +import com.slack.api.methods.MethodsClient; +import com.slack.api.methods.SlackApiTextResponse; +import com.slack.api.methods.request.chat.ChatPostMessageRequest; +import com.slack.api.methods.request.conversations.ConversationsListRequest; +import com.slack.api.methods.request.users.UsersListRequest; +import com.slack.api.methods.response.chat.ChatPostMessageResponse; +import com.slack.api.methods.response.conversations.ConversationsListResponse; +import com.slack.api.methods.response.users.UsersListResponse; +import com.slack.api.model.ConversationType; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.id.TenantId; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class DefaultSlackService implements SlackService { + + private final Slack slack = Slack.getInstance(); + + @Override + public void sendMessage(TenantId tenantId, String token, String conversationId, String message) throws Exception { + ChatPostMessageRequest request = ChatPostMessageRequest.builder() + .channel(conversationId) + .text(message) + .build(); + ChatPostMessageResponse response = slack.methods(token).chatPostMessage(request); + check(response); + } + + @Override + public List listConversations(TenantId tenantId, String token, SlackConversation.Type conversationType) throws Exception { + MethodsClient methods = slack.methods(token); + if (conversationType == SlackConversation.Type.USER) { + UsersListResponse usersListResponse = methods.usersList(UsersListRequest.builder() + .limit(1000) + .build()); + check(usersListResponse); + return usersListResponse.getMembers().stream() + .filter(user -> !user.isDeleted() && !user.isStranger() && !user.isBot()) + .map(user -> { + SlackConversation conversation = new SlackConversation(); + conversation.setId(user.getId()); + conversation.setName(String.format("@%s (%s)", user.getName(), user.getRealName())); + return conversation; + }) + .collect(Collectors.toList()); + } else { + ConversationsListResponse conversationsListResponse = methods.conversationsList(ConversationsListRequest.builder() + .types(List.of(conversationType == SlackConversation.Type.PUBLIC_CHANNEL ? + ConversationType.PUBLIC_CHANNEL : + ConversationType.PRIVATE_CHANNEL)) + .limit(1000) + .excludeArchived(true) + .build()); + check(conversationsListResponse); + return conversationsListResponse.getChannels().stream() + .filter(channel -> !channel.isArchived()) + .map(channel -> { + SlackConversation conversation = new SlackConversation(); + conversation.setId(channel.getId()); + conversation.setName("#" + channel.getName()); + return conversation; + }) + .collect(Collectors.toList()); + } + } + + private void check(SlackApiTextResponse slackResponse) { + if (!slackResponse.isOk()) { + String error = slackResponse.getError(); + if (error == null) { + error = "unknown error"; + } + if (error.contains("missing_scope")) { + String neededScope = slackResponse.getNeeded(); + throw new RuntimeException("Bot token scope '" + neededScope + "' is needed"); + } + throw new RuntimeException("Failed to send message via Slack: " + error); + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/slack/SlackConversation.java b/application/src/main/java/org/thingsboard/server/service/slack/SlackConversation.java new file mode 100644 index 0000000000..a5ca1b1099 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/slack/SlackConversation.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.slack; + +import lombok.Data; + +@Data +public class SlackConversation { + + private String id; + private String name; + + public enum Type { + USER, + PUBLIC_CHANNEL, + PRIVATE_CHANNEL + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/slack/SlackService.java b/application/src/main/java/org/thingsboard/server/service/slack/SlackService.java new file mode 100644 index 0000000000..0e68fe82d1 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/slack/SlackService.java @@ -0,0 +1,28 @@ +/** + * 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.slack; + +import org.thingsboard.server.common.data.id.TenantId; + +import java.util.List; + +public interface SlackService { + + void sendMessage(TenantId tenantId, String token, String conversationId, String message) throws Exception; + + List listConversations(TenantId tenantId, String token, SlackConversation.Type conversationType) throws Exception; + +} diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java index 066493fc8b..8e53a6c063 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java @@ -34,12 +34,15 @@ import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationRequestStatus; +import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig; +import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.SingleUserNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.UserListNotificationTargetConfig; +import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; 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.NotificationTextTemplate; +import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; @@ -363,7 +366,7 @@ public class NotificationApiTest extends AbstractControllerTest { } private NotificationRequest submitNotificationRequest(NotificationTargetId targetId, String text, int delayInSec, NotificationDeliveryMethod... deliveryMethods) { - NotificationTemplate notificationTemplate = createNotificationTemplate(text); + NotificationTemplate notificationTemplate = createNotificationTemplate(text, deliveryMethods); NotificationRequestConfig config = new NotificationRequestConfig(); config.setSendingDelayInSec(delayInSec); NotificationInfo notificationInfo = new NotificationInfo(); @@ -380,18 +383,36 @@ public class NotificationApiTest extends AbstractControllerTest { return doPost("/api/notification/request", notificationRequest, NotificationRequest.class); } - private NotificationTemplate createNotificationTemplate(String text) { + private NotificationTemplate createNotificationTemplate(String text, NotificationDeliveryMethod... deliveryMethods) { NotificationTemplate notificationTemplate = new NotificationTemplate(); notificationTemplate.setTenantId(tenantId); notificationTemplate.setName("Notification template for testing"); NotificationTemplateConfig config = new NotificationTemplateConfig(); - NotificationTextTemplate textTemplate = new NotificationTextTemplate(); - textTemplate.setBody(text); - config.setDefaultTextTemplate(textTemplate); + DeliveryMethodNotificationTemplate defaultTemplate = new DeliveryMethodNotificationTemplate(); + defaultTemplate.setBody(text); + config.setDefaultTemplate(defaultTemplate); + for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) { + if (deliveryMethod == NotificationDeliveryMethod.EMAIL) { + EmailDeliveryMethodNotificationTemplate emailNotificationTemplate = new EmailDeliveryMethodNotificationTemplate(); + emailNotificationTemplate.setSubject("Hello from test"); + emailNotificationTemplate.setBody(text); + config.setTemplates(Map.of( + deliveryMethod, emailNotificationTemplate + )); + } + } notificationTemplate.setConfiguration(config); return doPost("/api/notification/template", notificationTemplate, NotificationTemplate.class); } + private void configureNotificationDeliveryMethods(NotificationDeliveryMethod... deliveryMethods) { + NotificationSettings notificationSettings = new NotificationSettings(); + notificationSettings.setDeliveryMethodsConfigs(new HashMap<>()); + for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) { + notificationSettings.getDeliveryMethodsConfigs().put(deliveryMethod, new NotificationDeliveryMethodConfig()); + } + } + private NotificationRequest findNotificationRequest(NotificationRequestId id) throws Exception { return doGet("/api/notification/request/" + id, NotificationRequest.class); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java index 0db4656f8f..cbfeb7a613 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java @@ -51,4 +51,8 @@ public class NotificationRequestStats { errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(recipient.getEmail(), errorMessage); } + public boolean contains(NotificationDeliveryMethod deliveryMethod) { + return sent.containsKey(deliveryMethod) || errors.containsKey(deliveryMethod); + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java index ff91068f84..05665132f8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java @@ -17,22 +17,21 @@ 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.JsonSubTypes.Type; 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), -//}) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "method", include = JsonTypeInfo.As.EXISTING_PROPERTY, + visible = true, defaultImpl = NotificationDeliveryMethodConfig.class) +@JsonSubTypes({ + @Type(name = "SLACK", value = SlackNotificationDeliveryMethodConfig.class), +}) @Data public class NotificationDeliveryMethodConfig { private boolean enabled; - - public NotificationDeliveryMethod getMethod() { - return null; - } + private NotificationDeliveryMethod method; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/SlackNotificationDeliveryMethodConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/SlackNotificationDeliveryMethodConfig.java new file mode 100644 index 0000000000..9c0e5a7c78 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/SlackNotificationDeliveryMethodConfig.java @@ -0,0 +1,27 @@ +/** + * 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 lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class SlackNotificationDeliveryMethodConfig extends NotificationDeliveryMethodConfig { + + private String botToken; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/DeliveryMethodNotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/DeliveryMethodNotificationTemplate.java new file mode 100644 index 0000000000..1a2301c361 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/DeliveryMethodNotificationTemplate.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.notification.template; + +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 = DeliveryMethodNotificationTemplate.class) +@JsonSubTypes({ + @JsonSubTypes.Type(name = "EMAIL", value = EmailDeliveryMethodNotificationTemplate.class), + @JsonSubTypes.Type(name = "SLACK", value = SlackDeliveryMethodNotificationTemplate.class) +}) +@Data +public class DeliveryMethodNotificationTemplate { + + private String body; + private NotificationDeliveryMethod method; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTextTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/EmailDeliveryMethodNotificationTemplate.java similarity index 81% rename from common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTextTemplate.java rename to common/data/src/main/java/org/thingsboard/server/common/data/notification/template/EmailDeliveryMethodNotificationTemplate.java index 3e0ecff9d1..b7f4a6063f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTextTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/EmailDeliveryMethodNotificationTemplate.java @@ -16,11 +16,12 @@ package org.thingsboard.server.common.data.notification.template; import lombok.Data; +import lombok.EqualsAndHashCode; @Data -public class NotificationTextTemplate { +@EqualsAndHashCode(callSuper = true) +public class EmailDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate { - private String body; private String subject; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java index bc92add8f4..8f94e3c441 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java @@ -23,7 +23,7 @@ import java.util.Map; @Data public class NotificationTemplateConfig { - private NotificationTextTemplate defaultTextTemplate; - private Map textTemplates; + private DeliveryMethodNotificationTemplate defaultTemplate; + private Map templates; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SlackDeliveryMethodNotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SlackDeliveryMethodNotificationTemplate.java new file mode 100644 index 0000000000..6cb6bdcab4 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SlackDeliveryMethodNotificationTemplate.java @@ -0,0 +1,27 @@ +/** + * 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; + +@Data +@EqualsAndHashCode(callSuper = true) +public class SlackDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate { + + private String conversationId; // not required, set from user's name if not set + +} diff --git a/pom.xml b/pom.xml index 3d51b748ce..0c2da6ed8a 100755 --- a/pom.xml +++ b/pom.xml @@ -144,6 +144,7 @@ 6.1.0.202203080745-r 0.4.8 1.0.0 + 1.12.1 @@ -1945,6 +1946,11 @@ aerogear-otp-java ${aerogear-otp.version} + + com.slack.api + slack-api-client + ${slack-api.version} + org.eclipse.jgit org.eclipse.jgit From 58b9b1b6d3e8a1bd5301722b1e7cb5e54be09a9b Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Mon, 19 Dec 2022 20:07:52 +0200 Subject: [PATCH 022/111] Clear notification system entities on cascade on tenant's deletion --- application/src/main/data/upgrade/3.4.3/schema_update.sql | 8 ++++---- dao/src/main/resources/sql/schema-entities.sql | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/application/src/main/data/upgrade/3.4.3/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql index 09398588b3..44ef280553 100644 --- a/application/src/main/data/upgrade/3.4.3/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.3/schema_update.sql @@ -17,7 +17,7 @@ CREATE TABLE IF NOT EXISTS notification_target ( id UUID NOT NULL CONSTRAINT notification_target_pkey PRIMARY KEY, created_time BIGINT NOT NULL, - tenant_id UUID NOT NULL, + tenant_id UUID NOT NULL CONSTRAINT fk_notification_target_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, configuration VARCHAR(10000) NOT NULL ); @@ -26,7 +26,7 @@ CREATE INDEX IF NOT EXISTS idx_notification_target_tenant_id_created_time ON not 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, + tenant_id UUID NOT NULL CONSTRAINT fk_notification_template_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, configuration VARCHAR(10000) NOT NULL ); @@ -34,7 +34,7 @@ CREATE TABLE IF NOT EXISTS notification_template ( CREATE TABLE IF NOT EXISTS notification_rule ( id UUID NOT NULL CONSTRAINT notification_rule_pkey PRIMARY KEY, created_time BIGINT NOT NULL, - tenant_id UUID NOT NULL, + tenant_id UUID NOT NULL CONSTRAINT fk_notification_rule_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id), delivery_methods VARCHAR(255), @@ -45,7 +45,7 @@ CREATE TABLE IF NOT EXISTS notification_rule ( CREATE TABLE IF NOT EXISTS notification_request ( id UUID NOT NULL CONSTRAINT notification_request_pkey PRIMARY KEY, created_time BIGINT NOT NULL, - tenant_id UUID NOT NULL, + tenant_id UUID NOT NULL CONSTRAINT fk_notification_request_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, target_id UUID NOT NULL CONSTRAINT fk_notification_request_target_id REFERENCES notification_target(id), type VARCHAR(255) NOT NULL, template_id UUID NOT NULL CONSTRAINT fk_notification_request_template_id REFERENCES notification_template(id), diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index c3dace68fe..c5d6ada139 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -783,7 +783,7 @@ CREATE TABLE IF NOT EXISTS user_auth_settings ( CREATE TABLE IF NOT EXISTS notification_target ( id UUID NOT NULL CONSTRAINT notification_target_pkey PRIMARY KEY, created_time BIGINT NOT NULL, - tenant_id UUID NOT NULL, + tenant_id UUID NOT NULL CONSTRAINT fk_notification_target_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, configuration VARCHAR(10000) NOT NULL ); @@ -791,7 +791,7 @@ CREATE TABLE IF NOT EXISTS notification_target ( 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, + tenant_id UUID NOT NULL CONSTRAINT fk_notification_template_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, configuration VARCHAR(10000) NOT NULL ); @@ -799,7 +799,7 @@ CREATE TABLE IF NOT EXISTS notification_template ( CREATE TABLE IF NOT EXISTS notification_rule ( id UUID NOT NULL CONSTRAINT notification_rule_pkey PRIMARY KEY, created_time BIGINT NOT NULL, - tenant_id UUID NOT NULL, + tenant_id UUID NOT NULL CONSTRAINT fk_notification_rule_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id), delivery_methods VARCHAR(255), @@ -810,7 +810,7 @@ CREATE TABLE IF NOT EXISTS notification_rule ( CREATE TABLE IF NOT EXISTS notification_request ( id UUID NOT NULL CONSTRAINT notification_request_pkey PRIMARY KEY, created_time BIGINT NOT NULL, - tenant_id UUID NOT NULL, + tenant_id UUID NOT NULL CONSTRAINT fk_notification_request_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, target_id UUID NOT NULL CONSTRAINT fk_notification_request_target_id REFERENCES notification_target(id), type VARCHAR(255) NOT NULL, template_id UUID NOT NULL CONSTRAINT fk_notification_request_template_id REFERENCES notification_template(id), From 0a17f99360ff01e2b91966936e96c4b32ed5ff1d Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Wed, 21 Dec 2022 15:04:06 +0200 Subject: [PATCH 023/111] Refactoring; TbSlackNode --- .../main/data/upgrade/3.4.3/schema_update.sql | 7 +- .../server/actors/ActorSystemContext.java | 5 + .../actors/ruleChain/DefaultTbContext.java | 6 + .../controller/NotificationController.java | 21 ++- .../DefaultNotificationManager.java | 22 ++- ...aultNotificationRuleProcessingService.java | 13 +- .../NotificationProcessingContext.java | 18 ++- .../channels/SlackNotificationChannel.java | 50 +++---- .../service/slack/DefaultSlackService.java | 134 ++++++++++++------ .../ttl/NotificationsCleanUpService.java | 15 +- .../notification/NotificationApiTest.java | 11 +- .../NotificationSettingsService.java | 27 ++++ ...everity.java => AlreadySentException.java} | 5 +- .../notification/NotificationRequest.java | 11 +- .../NotificationRequestConfig.java | 3 +- .../NotificationRequestStats.java | 3 + .../notification/rule/NotificationRule.java | 3 +- .../rule/NotificationRuleConfig.java | 32 +++++ .../template/NotificationTemplate.java | 11 +- .../server/dao/model/ModelConstants.java | 7 +- .../model/sql/NotificationRequestEntity.java | 5 - .../dao/model/sql/NotificationRuleEntity.java | 16 +-- .../model/sql/NotificationTemplateEntity.java | 7 +- .../DefaultNotificationSettingsService.java | 57 +++----- .../DefaultNotificationTargetService.java | 3 +- .../JpaNotificationRequestDao.java | 4 +- .../NotificationRequestRepository.java | 5 +- .../insert/sql/SqlPartitioningRepository.java | 17 +-- .../main/resources/sql/schema-entities.sql | 7 +- .../rule/engine/api/TbContext.java | 3 + .../engine/api}/slack/SlackConversation.java | 2 +- .../rule/engine/api}/slack/SlackService.java | 10 +- .../notification/TbNotificationNode.java | 55 +++---- .../TbNotificationNodeConfiguration.java | 29 ++-- .../rule/engine/notification/TbSlackNode.java | 86 +++++++++++ .../TbSlackNodeConfiguration.java | 47 ++++++ 36 files changed, 493 insertions(+), 264 deletions(-) create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationSettingsService.java rename common/data/src/main/java/org/thingsboard/server/common/data/notification/{NotificationSeverity.java => AlreadySentException.java} (87%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleConfig.java rename application/src/main/java/org/thingsboard/server/service/notification/NotificationManagerHelper.java => dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java (58%) rename {application/src/main/java/org/thingsboard/server/service => rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api}/slack/SlackConversation.java (94%) rename {application/src/main/java/org/thingsboard/server/service => rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api}/slack/SlackService.java (73%) create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNodeConfiguration.java diff --git a/application/src/main/data/upgrade/3.4.3/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql index 44ef280553..b9aaaa5c7b 100644 --- a/application/src/main/data/upgrade/3.4.3/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.3/schema_update.sql @@ -28,6 +28,7 @@ CREATE TABLE IF NOT EXISTS notification_template ( created_time BIGINT NOT NULL, tenant_id UUID NOT NULL CONSTRAINT fk_notification_template_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, + notification_type VARCHAR(255) NOT NULL, configuration VARCHAR(10000) NOT NULL ); @@ -37,9 +38,8 @@ CREATE TABLE IF NOT EXISTS notification_rule ( tenant_id UUID NOT NULL CONSTRAINT fk_notification_rule_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id), - delivery_methods VARCHAR(255), - initial_notification_target_id UUID NULL CONSTRAINT fk_notification_rule_target_id REFERENCES notification_target(id), - escalation_config VARCHAR(500) + delivery_methods VARCHAR(255) NOT NULL, + configuration VARCHAR(2000) NOT NULL ); CREATE TABLE IF NOT EXISTS notification_request ( @@ -47,7 +47,6 @@ CREATE TABLE IF NOT EXISTS notification_request ( created_time BIGINT NOT NULL, tenant_id UUID NOT NULL CONSTRAINT fk_notification_request_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, target_id UUID NOT NULL CONSTRAINT fk_notification_request_target_id REFERENCES notification_target(id), - type VARCHAR(255) NOT NULL, template_id UUID NOT NULL CONSTRAINT fk_notification_request_template_id REFERENCES notification_template(id), info VARCHAR(1000), delivery_methods VARCHAR(255), diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 90b5921c71..9035a77ea3 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -32,6 +32,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.NotificationManager; import org.thingsboard.rule.engine.api.SmsService; +import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.script.api.js.JsInvokeService; import org.thingsboard.script.api.tbel.TbelInvokeService; @@ -323,6 +324,10 @@ public class ActorSystemContext { @Getter private NotificationManager notificationManager; + @Autowired + @Getter + private SlackService slackService; + @Lazy @Autowired(required = false) @Getter diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index de50179e44..eeb5a8fe1b 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -36,6 +36,7 @@ import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.rule.engine.util.TenantIdLoader; import org.thingsboard.server.actors.ActorSystemContext; @@ -685,6 +686,11 @@ class DefaultTbContext implements TbContext { return mainCtx.getNotificationManager(); } + @Override + public SlackService getSlackService() { + return mainCtx.getSlackService(); + } + @Override public RuleEngineRpcService getRpcService() { return mainCtx.getTbRuleEngineDeviceRpcService(); diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index c461ccfdb1..3c7e8865b9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -17,7 +17,6 @@ package org.thingsboard.server.controller; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; @@ -30,10 +29,13 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.rule.engine.api.NotificationManager; +import org.thingsboard.rule.engine.api.slack.SlackConversation; +import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationOriginatorType; @@ -44,13 +46,11 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationService; +import org.thingsboard.server.dao.notification.NotificationSettingsService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.notification.NotificationManagerHelper; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; -import org.thingsboard.server.service.slack.SlackConversation; -import org.thingsboard.server.service.slack.SlackService; import java.util.List; import java.util.UUID; @@ -65,7 +65,7 @@ public class NotificationController extends BaseController { private final NotificationService notificationService; private final NotificationRequestService notificationRequestService; private final NotificationManager notificationManager; - private final NotificationManagerHelper notificationManagerHelper; + private final NotificationSettingsService notificationSettingsService; private final SlackService slackService; @GetMapping("/notifications") @@ -102,9 +102,6 @@ public class NotificationController extends BaseController { notificationRequest.setOriginatorType(NotificationOriginatorType.ADMIN); notificationRequest.setOriginatorEntityId(user.getId()); - if (StringUtils.isBlank(notificationRequest.getType())) { - notificationRequest.setType("General"); - } if (notificationRequest.getInfo() != null && notificationRequest.getInfo().getOriginatorType() != null) { throw new IllegalArgumentException("Unsupported notification info type"); } @@ -147,20 +144,22 @@ public class NotificationController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public NotificationSettings saveNotificationSettings(@RequestBody NotificationSettings notificationSettings, @AuthenticationPrincipal SecurityUser user) { - notificationManagerHelper.saveNotificationSettings(user.getTenantId(), notificationSettings); + TenantId tenantId = user.isSystemAdmin() ? TenantId.SYS_TENANT_ID : user.getTenantId(); + notificationSettingsService.saveNotificationSettings(tenantId, notificationSettings); return notificationSettings; } @GetMapping("/notification/settings") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public NotificationSettings getNotificationSettings(@AuthenticationPrincipal SecurityUser user) { - return notificationManagerHelper.getNotificationSettings(user.getTenantId()); + TenantId tenantId = user.isSystemAdmin() ? TenantId.SYS_TENANT_ID : user.getTenantId(); + return notificationSettingsService.findNotificationSettings(tenantId); } @GetMapping("/notification/slack/conversations") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public List listSlackConversations(@RequestParam SlackConversation.Type type, - @AuthenticationPrincipal SecurityUser user) throws Exception { + @AuthenticationPrincipal SecurityUser user) { NotificationSettings settings = getNotificationSettings(user); SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig) settings.getDeliveryMethodsConfigs().get(NotificationDeliveryMethod.SLACK); diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java index b7a6e804e3..f04cced9f6 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java @@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.rule.engine.api.NotificationManager; +import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; @@ -36,13 +37,16 @@ import org.thingsboard.server.common.data.notification.NotificationRequestStatus import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationService; +import org.thingsboard.server.dao.notification.NotificationSettingsService; import org.thingsboard.server.dao.notification.NotificationTargetService; +import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.NotificationsTopicService; @@ -71,7 +75,8 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl private final NotificationTargetService notificationTargetService; private final NotificationRequestService notificationRequestService; private final NotificationService notificationService; - private final NotificationManagerHelper notificationManagerHelper; + private final NotificationTemplateService notificationTemplateService; + private final NotificationSettingsService notificationSettingsService; private final DbCallbackExecutorService dbCallbackExecutorService; private final NotificationsTopicService notificationsTopicService; private final TbQueueProducerProvider producerProvider; @@ -82,7 +87,7 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { log.debug("Processing notification request (tenant id: {}, notification target id: {})", tenantId, notificationRequest.getTargetId()); notificationRequest.setTenantId(tenantId); - NotificationSettings settings = notificationManagerHelper.getNotificationSettings(tenantId); + NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId); notificationRequest.getDeliveryMethods().forEach(deliveryMethod -> { if (!settings.getDeliveryMethodsConfigs().containsKey(deliveryMethod) || !settings.getDeliveryMethodsConfigs().get(deliveryMethod).isEnabled()) { throw new IllegalArgumentException("Delivery method " + deliveryMethod + " is not enabled or configured"); @@ -98,6 +103,10 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl return savedNotificationRequest; } } + if (notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), new PageLink(1)) + .getTotalElements() == 0) { + throw new IllegalArgumentException("No target recipients"); + } notificationRequest.setStatus(NotificationRequestStatus.PROCESSED); NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); @@ -108,7 +117,7 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl .request(savedNotificationRequest) .additionalTemplateContext(notificationRequest.getTemplateContext()) .build(); - ctx.init(notificationManagerHelper); + ctx.init(notificationTemplateService); DaoUtil.processBatches(pageLink -> { return notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), pageLink); @@ -129,7 +138,8 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl results.add(resultFuture); } } - Futures.whenAllComplete(results).run(() -> { + + Futures.allAsList(results).addListener(() -> { try { notificationRequestService.updateNotificationRequestStats(tenantId, savedNotificationRequest.getId(), ctx.getStats()); } catch (Exception e) { @@ -145,7 +155,7 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl String text; try { DeliveryMethodNotificationTemplate template = ctx.getTemplate(notificationChannel.getDeliveryMethod()); - text = notificationManagerHelper.processTemplate(template.getBody(), ctx.createTemplateContext(recipient)); + text = TbNodeUtils.processTemplate(template.getBody(), ctx.createTemplateContext(recipient)); } catch (Exception e) { return Futures.immediateFailedFuture(e); } @@ -173,7 +183,7 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl Notification notification = Notification.builder() .requestId(request.getId()) .recipientId(recipient.getId()) - .type(request.getType()) + .type(ctx.getNotificationTemplate().getNotificationType()) .text(text) .info(request.getInfo()) .originatorType(request.getOriginatorType()) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java index 39bd325a99..5f97d47093 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java @@ -32,9 +32,9 @@ import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationOriginatorType; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; -import org.thingsboard.server.common.data.notification.NotificationSeverity; import org.thingsboard.server.common.data.notification.rule.NonConfirmedNotificationEscalation; import org.thingsboard.server.common.data.notification.rule.NotificationRule; +import org.thingsboard.server.common.data.notification.rule.NotificationRuleConfig; import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationRuleService; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -76,6 +76,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul @Override public ListenableFuture onNotificationRuleDeleted(TenantId tenantId, NotificationRuleId ruleId) { return dbCallbackExecutorService.submit(() -> { + // FIXME [viacheslav] // need to remove fk constraint in notificationRequest to rule // todo: do we need to remove all notifications when notification request is deleted? return null; @@ -98,12 +99,13 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul } if (notificationRequests.isEmpty()) { - NotificationTargetId initialNotificationTargetId = notificationRule.getInitialNotificationTargetId(); + NotificationRuleConfig config = notificationRule.getConfiguration(); + NotificationTargetId initialNotificationTargetId = config.getInitialNotificationTargetId(); if (initialNotificationTargetId != null) { submitNotificationRequest(tenantId, initialNotificationTargetId, notificationRule, alarm, 0); } - if (notificationRule.getEscalationConfig() != null) { - for (NonConfirmedNotificationEscalation escalation : notificationRule.getEscalationConfig().getEscalations()) { + if (config.getEscalationConfig() != null) { + for (NonConfirmedNotificationEscalation escalation : config.getEscalationConfig().getEscalations()) { submitNotificationRequest(tenantId, escalation.getNotificationTargetId(), notificationRule, alarm, escalation.getDelayInSec()); } } @@ -119,7 +121,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul } } - private boolean alarmAcknowledged(Alarm alarm) { // todo: decide when to consider the alarm processed by notification target (not to escalate then) + private boolean alarmAcknowledged(Alarm alarm) { return alarm.getStatus().isAck() && alarm.getStatus().isCleared(); } @@ -138,7 +140,6 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul NotificationRequest notificationRequest = NotificationRequest.builder() .tenantId(tenantId) .targetId(targetId) - .type("Alarm") .templateId(notificationRule.getTemplateId()) .deliveryMethods(notificationRule.getDeliveryMethods()) .additionalConfig(config) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java index ff4f5d76a8..9abcba6b43 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java @@ -26,9 +26,14 @@ import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; +import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; +import org.thingsboard.server.dao.notification.NotificationTemplateService; import java.util.HashMap; import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; @SuppressWarnings("unchecked") public class NotificationProcessingContext { @@ -40,6 +45,8 @@ public class NotificationProcessingContext { private final NotificationRequest request; private final Map additionalTemplateContext; + @Getter + private NotificationTemplate notificationTemplate; private Map templates; @Getter private NotificationRequestStats stats; @@ -52,8 +59,15 @@ public class NotificationProcessingContext { this.additionalTemplateContext = additionalTemplateContext; } - public void init(NotificationManagerHelper notificationManagerHelper) { - templates = notificationManagerHelper.getTemplates(tenantId, request.getTemplateId(), request.getDeliveryMethods()); + public void init(NotificationTemplateService templateService) { + notificationTemplate = templateService.findNotificationTemplateById(tenantId, request.getTemplateId()); + NotificationTemplateConfig config = notificationTemplate.getConfiguration(); + templates = request.getDeliveryMethods().stream() + .collect(Collectors.toMap(k -> k, deliveryMethod -> { + return Optional.ofNullable(config.getTemplates()) + .map(templates -> templates.get(deliveryMethod)) + .orElse(config.getDefaultTemplate()); + })); stats = new NotificationRequestStats(); } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java index a091d1517e..a12efbffa0 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java @@ -20,57 +20,53 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; -import org.thingsboard.server.service.slack.SlackService; +import org.thingsboard.rule.engine.api.slack.SlackConversation; +import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.AlreadySentException; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate; +import org.thingsboard.server.service.executors.ExternalCallExecutorService; import org.thingsboard.server.service.notification.NotificationProcessingContext; -import javax.annotation.PostConstruct; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - @Component @RequiredArgsConstructor +@SuppressWarnings("UnstableApiUsage") public class SlackNotificationChannel implements NotificationChannel { private final SlackService slackService; - private ExecutorService executor; - - @PostConstruct - private void init() { - executor = Executors.newSingleThreadExecutor(); - } + private final ExternalCallExecutorService executor; @Override public ListenableFuture sendNotification(User recipient, String text, NotificationProcessingContext ctx) { SlackDeliveryMethodNotificationTemplate template = ctx.getTemplate(NotificationDeliveryMethod.SLACK); SlackNotificationDeliveryMethodConfig config = ctx.getDeliveryMethodConfig(NotificationDeliveryMethod.SLACK); - String conversationId = template.getConversationId(); - if (StringUtils.isNotEmpty(conversationId)) { + if (StringUtils.isNotEmpty(template.getConversationId())) { // if conversationId is set, we only need to send message once if (ctx.getStats().contains(NotificationDeliveryMethod.SLACK)) { - // FIXME stats.sent will be reported anyway - return Futures.immediateFuture(null); // if conversationId is set, we only need to send message once + return Futures.immediateFailedFuture(new AlreadySentException()); + } else { + return executor.submit(() -> { + slackService.sendMessage(ctx.getTenantId(), config.getBotToken(), template.getConversationId(), text); + return null; + }); } } else { - String username = StringUtils.join(new String[]{recipient.getFirstName(), recipient.getLastName()}, ' '); - if (StringUtils.isNotEmpty(username)) { - conversationId = username; + if (StringUtils.isNoneEmpty(recipient.getFirstName(), recipient.getLastName())) { + String username = StringUtils.join(new String[]{recipient.getFirstName(), recipient.getLastName()}, ' '); + return executor.submit(() -> { + SlackConversation conversation = slackService.findConversation(recipient.getTenantId(), config.getBotToken(), SlackConversation.Type.USER, username); + if (conversation == null) { + throw new IllegalArgumentException("Slack user not found for given name '" + username + "'"); + } + slackService.sendMessage(ctx.getTenantId(), config.getBotToken(), conversation.getId(), text); + return null; + }); } else { return Futures.immediateFailedFuture(new IllegalArgumentException("Couldn't determine Slack username for the user")); } } - return send(ctx.getTenantId(), config.getBotToken(), conversationId, text); - } - - private ListenableFuture send(TenantId tenantId, String botToken, String conversationId, String text) { - return Futures.submit(() -> { - slackService.sendMessage(tenantId, botToken, conversationId, text); - return null; - }, executor); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java b/application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java index e6f3d57386..7b304e3213 100644 --- a/application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java +++ b/application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java @@ -15,87 +15,139 @@ */ package org.thingsboard.server.service.slack; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import com.slack.api.Slack; import com.slack.api.methods.MethodsClient; +import com.slack.api.methods.SlackApiRequest; import com.slack.api.methods.SlackApiTextResponse; import com.slack.api.methods.request.chat.ChatPostMessageRequest; import com.slack.api.methods.request.conversations.ConversationsListRequest; import com.slack.api.methods.request.users.UsersListRequest; -import com.slack.api.methods.response.chat.ChatPostMessageResponse; import com.slack.api.methods.response.conversations.ConversationsListResponse; import com.slack.api.methods.response.users.UsersListResponse; import com.slack.api.model.ConversationType; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.slack.SlackConversation; +import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.settings.NotificationSettings; +import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; +import org.thingsboard.server.common.data.util.ThrowingBiFunction; +import org.thingsboard.server.dao.notification.NotificationSettingsService; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Service +@RequiredArgsConstructor public class DefaultSlackService implements SlackService { + private final NotificationSettingsService notificationSettingsService; + private final Slack slack = Slack.getInstance(); + private final Cache> cache = Caffeine.newBuilder() + .expireAfterWrite(20, TimeUnit.SECONDS) + .maximumSize(100) + .build(); + private static final int CONVERSATIONS_LIMIT = 1000; @Override - public void sendMessage(TenantId tenantId, String token, String conversationId, String message) throws Exception { + public void sendMessage(TenantId tenantId, String token, String conversationId, String message) { ChatPostMessageRequest request = ChatPostMessageRequest.builder() .channel(conversationId) .text(message) .build(); - ChatPostMessageResponse response = slack.methods(token).chatPostMessage(request); - check(response); + sendRequest(token, request, MethodsClient::chatPostMessage); + } + + @Override + public List listConversations(TenantId tenantId, String token, SlackConversation.Type conversationType) { + return cache.get(conversationType + ":" + token, k -> { + if (conversationType == SlackConversation.Type.USER) { + UsersListRequest request = UsersListRequest.builder() + .limit(CONVERSATIONS_LIMIT) + .build(); + + UsersListResponse response = sendRequest(token, request, MethodsClient::usersList); + return response.getMembers().stream() + .filter(user -> !user.isDeleted() && !user.isStranger() && !user.isBot()) + .map(user -> { + SlackConversation conversation = new SlackConversation(); + conversation.setId(user.getId()); + conversation.setName(String.format("@%s (%s)", user.getName(), user.getRealName())); + return conversation; + }) + .collect(Collectors.toList()); + } else { + ConversationsListRequest request = ConversationsListRequest.builder() + .types(List.of(conversationType == SlackConversation.Type.PUBLIC_CHANNEL ? + ConversationType.PUBLIC_CHANNEL : + ConversationType.PRIVATE_CHANNEL)) + .limit(CONVERSATIONS_LIMIT) + .excludeArchived(true) + .build(); + + ConversationsListResponse response = sendRequest(token, request, MethodsClient::conversationsList); + return response.getChannels().stream() + .filter(channel -> !channel.isArchived()) + .map(channel -> { + SlackConversation conversation = new SlackConversation(); + conversation.setId(channel.getId()); + conversation.setName("#" + channel.getName()); + return conversation; + }) + .collect(Collectors.toList()); + } + }); } @Override - public List listConversations(TenantId tenantId, String token, SlackConversation.Type conversationType) throws Exception { - MethodsClient methods = slack.methods(token); - if (conversationType == SlackConversation.Type.USER) { - UsersListResponse usersListResponse = methods.usersList(UsersListRequest.builder() - .limit(1000) - .build()); - check(usersListResponse); - return usersListResponse.getMembers().stream() - .filter(user -> !user.isDeleted() && !user.isStranger() && !user.isBot()) - .map(user -> { - SlackConversation conversation = new SlackConversation(); - conversation.setId(user.getId()); - conversation.setName(String.format("@%s (%s)", user.getName(), user.getRealName())); - return conversation; - }) - .collect(Collectors.toList()); + public SlackConversation findConversation(TenantId tenantId, String token, SlackConversation.Type conversationType, String namePattern) { + List conversations = listConversations(tenantId, token, conversationType); + return conversations.stream() + .filter(conversation -> StringUtils.containsIgnoreCase(conversation.getName(), namePattern)) + .findFirst().orElse(null); + } + + @Override + public String getToken(TenantId tenantId) { + NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId); + SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig) + settings.getDeliveryMethodsConfigs().get(NotificationDeliveryMethod.SLACK); + if (slackConfig != null) { + return slackConfig.getBotToken(); } else { - ConversationsListResponse conversationsListResponse = methods.conversationsList(ConversationsListRequest.builder() - .types(List.of(conversationType == SlackConversation.Type.PUBLIC_CHANNEL ? - ConversationType.PUBLIC_CHANNEL : - ConversationType.PRIVATE_CHANNEL)) - .limit(1000) - .excludeArchived(true) - .build()); - check(conversationsListResponse); - return conversationsListResponse.getChannels().stream() - .filter(channel -> !channel.isArchived()) - .map(channel -> { - SlackConversation conversation = new SlackConversation(); - conversation.setId(channel.getId()); - conversation.setName("#" + channel.getName()); - return conversation; - }) - .collect(Collectors.toList()); + return null; } } - private void check(SlackApiTextResponse slackResponse) { - if (!slackResponse.isOk()) { - String error = slackResponse.getError(); + private R sendRequest(String token, T request, ThrowingBiFunction method) { + MethodsClient client = slack.methods(token); + R response; + try { + response = method.apply(client, request); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + + if (!response.isOk()) { + String error = response.getError(); if (error == null) { error = "unknown error"; } if (error.contains("missing_scope")) { - String neededScope = slackResponse.getNeeded(); + String neededScope = response.getNeeded(); throw new RuntimeException("Bot token scope '" + neededScope + "' is needed"); } throw new RuntimeException("Failed to send message via Slack: " + error); } + + return response; } } diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/NotificationsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/NotificationsCleanUpService.java index aca14c4a54..e84e1f7e8a 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/NotificationsCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/NotificationsCleanUpService.java @@ -20,8 +20,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; -import org.thingsboard.server.dao.notification.NotificationDao; -import org.thingsboard.server.dao.notification.NotificationRequestDao; import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository; import org.thingsboard.server.queue.discovery.PartitionService; @@ -34,8 +32,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.NOTIFICATION_TABLE @Slf4j public class NotificationsCleanUpService extends AbstractCleanUpService { - private final NotificationDao notificationDao; - private final NotificationRequestDao notificationRequestDao; private final SqlPartitioningRepository partitioningRepository; @Value("${sql.ttl.notifications.ttl:2592000}") @@ -43,13 +39,8 @@ public class NotificationsCleanUpService extends AbstractCleanUpService { @Value("${sql.notifications.partition_size:168}") private int partitionSizeInHours; - public NotificationsCleanUpService(PartitionService partitionService, - NotificationDao notificationDao, - NotificationRequestDao notificationRequestDao, - SqlPartitioningRepository partitioningRepository) { + public NotificationsCleanUpService(PartitionService partitionService, SqlPartitioningRepository partitioningRepository) { super(partitionService); - this.notificationDao = notificationDao; - this.notificationRequestDao = notificationRequestDao; this.partitioningRepository = partitioningRepository; } @@ -59,11 +50,7 @@ public class NotificationsCleanUpService extends AbstractCleanUpService { long expTime = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(ttlInSec); long partitionDurationMs = TimeUnit.HOURS.toMillis(partitionSizeInHours); if (isSystemTenantPartitionMine()) { - long actualExpTime = partitioningRepository.getLastPartitionEnd(NOTIFICATION_TABLE_NAME, expTime, partitionDurationMs); - partitioningRepository.dropPartitionsBefore(NOTIFICATION_TABLE_NAME, expTime, partitionDurationMs); - // select distinct request_id for period and manually delete them ? - // sql trigger ? ---- } else { partitioningRepository.cleanupPartitionsCache(NOTIFICATION_TABLE_NAME, expTime, partitionDurationMs); } diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java index 8e53a6c063..e7acd85fab 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java @@ -374,7 +374,6 @@ public class NotificationApiTest extends AbstractControllerTest { NotificationRequest notificationRequest = NotificationRequest.builder() .tenantId(tenantId) .targetId(targetId) - .type("Test") .templateId(notificationTemplate.getId()) .info(notificationInfo) .deliveryMethods(deliveryMethods.length > 0 ? List.of(deliveryMethods) : List.of(NotificationDeliveryMethod.WEBSOCKET)) @@ -387,6 +386,7 @@ public class NotificationApiTest extends AbstractControllerTest { NotificationTemplate notificationTemplate = new NotificationTemplate(); notificationTemplate.setTenantId(tenantId); notificationTemplate.setName("Notification template for testing"); + notificationTemplate.setNotificationType("Just a test"); NotificationTemplateConfig config = new NotificationTemplateConfig(); DeliveryMethodNotificationTemplate defaultTemplate = new DeliveryMethodNotificationTemplate(); defaultTemplate.setBody(text); @@ -396,6 +396,7 @@ public class NotificationApiTest extends AbstractControllerTest { EmailDeliveryMethodNotificationTemplate emailNotificationTemplate = new EmailDeliveryMethodNotificationTemplate(); emailNotificationTemplate.setSubject("Hello from test"); emailNotificationTemplate.setBody(text); + emailNotificationTemplate.setMethod(deliveryMethod); config.setTemplates(Map.of( deliveryMethod, emailNotificationTemplate )); @@ -405,14 +406,6 @@ public class NotificationApiTest extends AbstractControllerTest { return doPost("/api/notification/template", notificationTemplate, NotificationTemplate.class); } - private void configureNotificationDeliveryMethods(NotificationDeliveryMethod... deliveryMethods) { - NotificationSettings notificationSettings = new NotificationSettings(); - notificationSettings.setDeliveryMethodsConfigs(new HashMap<>()); - for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) { - notificationSettings.getDeliveryMethodsConfigs().put(deliveryMethod, new NotificationDeliveryMethodConfig()); - } - } - private NotificationRequest findNotificationRequest(NotificationRequestId id) throws Exception { return doGet("/api/notification/request/" + id, NotificationRequest.class); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationSettingsService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationSettingsService.java new file mode 100644 index 0000000000..65a315f2d5 --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationSettingsService.java @@ -0,0 +1,27 @@ +/** + * 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.TenantId; +import org.thingsboard.server.common.data.notification.settings.NotificationSettings; + +public interface NotificationSettingsService { + + void saveNotificationSettings(TenantId tenantId, NotificationSettings settings); + + NotificationSettings findNotificationSettings(TenantId tenantId); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationSeverity.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/AlreadySentException.java similarity index 87% rename from common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationSeverity.java rename to common/data/src/main/java/org/thingsboard/server/common/data/notification/AlreadySentException.java index 17f28bee4b..c0e860c089 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationSeverity.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/AlreadySentException.java @@ -15,8 +15,5 @@ */ package org.thingsboard.server.common.data.notification; -public enum NotificationSeverity { - NORMAL, - CRITICAL, - URGENT // send pop-up error +public class AlreadySentException extends RuntimeException { } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java index b59b24d6dc..0a602dbea1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java @@ -16,12 +16,12 @@ package org.thingsboard.server.common.data.notification; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; @@ -31,7 +31,6 @@ import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.validation.NoXss; import javax.validation.Valid; import javax.validation.constraints.NotEmpty; @@ -39,8 +38,6 @@ import javax.validation.constraints.NotNull; import java.util.List; import java.util.Map; -import static com.fasterxml.jackson.annotation.JsonProperty.Access.READ_ONLY; - @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor @@ -49,11 +46,9 @@ import static com.fasterxml.jackson.annotation.JsonProperty.Access.READ_ONLY; public class NotificationRequest extends BaseData implements HasTenantId, HasName { private TenantId tenantId; - @NotNull(message = "Target is not specified") + @NotNull private NotificationTargetId targetId; - @NoXss - private String type; @NotNull private NotificationTemplateId templateId; @Valid @@ -77,7 +72,7 @@ public class NotificationRequest extends BaseData impleme @JsonIgnore @Override public String getName() { - return type; + return "To target " + targetId + " via " + StringUtils.join(deliveryMethods, ", "); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java index 71bddf1342..431a7ed965 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java @@ -17,11 +17,12 @@ package org.thingsboard.server.common.data.notification; import lombok.Data; -import java.util.Map; +import javax.validation.constraints.Max; @Data public class NotificationRequestConfig { + @Max(value = 604800, message = "cannot be longer than 1 week") private int sendingDelayInSec; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java index cbfeb7a613..0784a1bf4b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java @@ -47,6 +47,9 @@ public class NotificationRequestStats { } public void reportError(NotificationDeliveryMethod deliveryMethod, User recipient, Throwable error) { + if (error instanceof AlreadySentException) { + return; + } String errorMessage = error.getMessage(); errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(recipient.getEmail(), errorMessage); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java index ab343cce2f..7265d93017 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java @@ -45,8 +45,7 @@ public class NotificationRule extends BaseData implements Ha @NotEmpty private List deliveryMethods; @NotNull - private NotificationTargetId initialNotificationTargetId; @Valid - private NotificationEscalationConfig escalationConfig; + private NotificationRuleConfig configuration; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleConfig.java new file mode 100644 index 0000000000..3aa01e9bb7 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleConfig.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.notification.rule; + +import lombok.Data; +import org.thingsboard.server.common.data.id.NotificationTargetId; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; + +@Data +public class NotificationRuleConfig { + + @NotNull + private NotificationTargetId initialNotificationTargetId; + @Valid + private NotificationEscalationConfig escalationConfig; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java index 3cbf8ca97c..875e986ed4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java @@ -22,14 +22,23 @@ 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; +import org.thingsboard.server.common.data.validation.NoXss; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; @Data @EqualsAndHashCode(callSuper = true) public class NotificationTemplate extends BaseData implements HasTenantId, HasName { private TenantId tenantId; + @NoXss + @NotNull private String name; + @NoXss + @NotNull + private String notificationType; + @Valid private NotificationTemplateConfig configuration; - // add notification type (notification reason) } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 89855b8003..de43da900b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -665,7 +665,6 @@ public class ModelConstants { public static final String NOTIFICATION_REQUEST_TARGET_ID_PROPERTY = "target_id"; public static final String NOTIFICATION_REQUEST_TEMPLATE_ID_PROPERTY = "template_id"; public static final String NOTIFICATION_REQUEST_DELIVERY_METHODS_PROPERTY = "delivery_methods"; - public static final String NOTIFICATION_REQUEST_TYPE_PROPERTY = "type"; public static final String NOTIFICATION_REQUEST_INFO_PROPERTY = "info"; public static final String NOTIFICATION_REQUEST_ORIGINATOR_TYPE_PROPERTY = "originator_type"; public static final String NOTIFICATION_REQUEST_ORIGINATOR_ENTITY_ID_PROPERTY = "originator_entity_id"; @@ -678,11 +677,11 @@ public class ModelConstants { public static final String NOTIFICATION_RULE_TABLE_NAME = "notification_rule"; public static final String NOTIFICATION_RULE_TEMPLATE_ID_PROPERTY = "template_id"; public static final String NOTIFICATION_RULE_DELIVERY_METHODS_PROPERTY = "delivery_methods"; - public static final String NOTIFICATION_RULE_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_CONFIGURATION_PROPERTY = "configuration"; public static final String NOTIFICATION_TEMPLATE_TABLE_NAME = "notification_template"; - public static final String NOTIFICATION_TEMPLATE_CONFIGURATION = "configuration"; + public static final String NOTIFICATION_TEMPLATE_NOTIFICATION_TYPE_PROPERTY = "notification_type"; + public static final String NOTIFICATION_TEMPLATE_CONFIGURATION_PROPERTY = "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}; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java index 56e6bc30a2..fc5bb8cf4f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java @@ -61,9 +61,6 @@ public class NotificationRequestEntity extends BaseSqlEntity { @Column(name = ModelConstants.NOTIFICATION_RULE_DELIVERY_METHODS_PROPERTY, nullable = false) private String deliveryMethods; - @Column(name = ModelConstants.NOTIFICATION_RULE_INITIAL_NOTIFICATION_TARGET_ID_PROPERTY) - private UUID initialNotificationTargetId; - @Type(type = "json") - @Column(name = ModelConstants.NOTIFICATION_RULE_ESCALATION_CONFIG_PROPERTY) - private JsonNode escalationConfig; + @Column(name = ModelConstants.NOTIFICATION_RULE_CONFIGURATION_PROPERTY, nullable = false) + private JsonNode configuration; public NotificationRuleEntity() {} @@ -73,8 +69,7 @@ public class NotificationRuleEntity extends BaseSqlEntity { setName(notificationRule.getName()); setTemplateId(getUuid(notificationRule.getTemplateId())); setDeliveryMethods(StringUtils.join(notificationRule.getDeliveryMethods(), ',')); - setInitialNotificationTargetId(getUuid(notificationRule.getInitialNotificationTargetId())); - setEscalationConfig(toJson(notificationRule.getEscalationConfig())); + setConfiguration(toJson(notificationRule.getConfiguration())); } @Override @@ -89,8 +84,7 @@ public class NotificationRuleEntity extends BaseSqlEntity { notificationRule.setDeliveryMethods(Arrays.stream(StringUtils.split(deliveryMethods, ',')) .filter(StringUtils::isNotBlank).map(NotificationDeliveryMethod::valueOf).collect(Collectors.toList())); } - notificationRule.setInitialNotificationTargetId(createId(initialNotificationTargetId, NotificationTargetId::new)); - notificationRule.setEscalationConfig(fromJson(escalationConfig, NotificationEscalationConfig.class)); + notificationRule.setConfiguration(fromJson(configuration, NotificationRuleConfig.class)); return notificationRule; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java index ba6f46accd..6c6688e8dc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java @@ -46,8 +46,11 @@ public class NotificationTemplateEntity extends BaseSqlEntity getTemplates(TenantId tenantId, NotificationTemplateId templateId, List deliveryMethods) { - NotificationTemplate notificationTemplate = templateService.findNotificationTemplateById(tenantId, templateId); - NotificationTemplateConfig config = notificationTemplate.getConfiguration(); - return deliveryMethods.stream() - .collect(Collectors.toMap(k -> k, deliveryMethod -> { - return Optional.ofNullable(config.getTemplates()) - .map(templates -> templates.get(deliveryMethod)) - .orElse(config.getDefaultTemplate()); - })); - } + private static final String SETTINGS_KEY = "notifications"; - public String processTemplate(String template, Map templateContext) { - return TbNodeUtils.processTemplate(template, templateContext); + @Override + public void saveNotificationSettings(TenantId tenantId, NotificationSettings settings) { + AdminSettings adminSettings = Optional.ofNullable(adminSettingsService.findAdminSettingsByTenantIdAndKey(tenantId, SETTINGS_KEY)) + .orElseGet(() -> { + AdminSettings newAdminSettings = new AdminSettings(); + newAdminSettings.setTenantId(tenantId); + newAdminSettings.setKey(SETTINGS_KEY); + return newAdminSettings; + }); + adminSettings.setJsonValue(JacksonUtil.valueToTree(settings)); + adminSettingsService.saveAdminSettings(tenantId, adminSettings); } - public NotificationSettings getNotificationSettings(TenantId tenantId) { + @Override + public NotificationSettings findNotificationSettings(TenantId tenantId) { return Optional.ofNullable(adminSettingsService.findAdminSettingsByTenantIdAndKey(tenantId, SETTINGS_KEY)) .map(adminSettings -> JacksonUtil.treeToValue(adminSettings.getJsonValue(), NotificationSettings.class)) .orElseGet(() -> { @@ -75,16 +66,4 @@ public class NotificationManagerHelper { }); } - public void saveNotificationSettings(TenantId tenantId, NotificationSettings notificationSettings) { - AdminSettings adminSettings = Optional.ofNullable(adminSettingsService.findAdminSettingsByTenantIdAndKey(tenantId, SETTINGS_KEY)) - .orElseGet(() -> { - AdminSettings newAdminSettings = new AdminSettings(); - newAdminSettings.setKey(SETTINGS_KEY); - newAdminSettings.setTenantId(tenantId); - return newAdminSettings; - }); - adminSettings.setJsonValue(JacksonUtil.valueToTree(notificationSettings)); - adminSettingsService.saveAdminSettings(tenantId, adminSettings); - } - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java index 04c5403a91..f7d958dd47 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java @@ -34,6 +34,7 @@ import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.user.UserService; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; @Service @@ -64,6 +65,7 @@ public class DefaultNotificationTargetService implements NotificationTargetServi @Override public PageData findRecipientsForNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId, PageLink pageLink) { NotificationTarget notificationTarget = findNotificationTargetById(tenantId, notificationTargetId); + Objects.requireNonNull(notificationTarget, "Notification target [" + notificationTargetId + "] not found"); NotificationTargetConfig configuration = notificationTarget.getConfiguration(); return findRecipientsForNotificationTargetConfig(tenantId, configuration, pageLink); } @@ -103,7 +105,6 @@ public class DefaultNotificationTargetService implements NotificationTargetServi @Override public void deleteNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId) { notificationTargetDao.removeById(tenantId, notificationTargetId.getId()); - // todo: delete related notification requests (?) } private static class NotificationTargetValidator extends DataValidator { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java index cf0c15e1c7..b34a1fc987 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao.sql.notification; -import com.google.common.base.Strings; import lombok.RequiredArgsConstructor; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; @@ -48,8 +47,7 @@ public class JpaNotificationRequestDao extends JpaAbstractDao findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) { - return DaoUtil.toPageData(notificationRequestRepository.findByTenantIdAndSearchText(tenantId.getId(), - Strings.nullToEmpty(pageLink.getTextSearch()), DaoUtil.toPageable(pageLink))); + return DaoUtil.toPageData(notificationRequestRepository.findByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink))); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java index ec81174c84..112282e31d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java @@ -34,10 +34,7 @@ import java.util.UUID; @Repository public interface NotificationRequestRepository extends JpaRepository { - @Query("SELECT r FROM NotificationRequestEntity r WHERE r.tenantId = :tenantId AND " + - "(lower(r.type) LIKE lower(concat('%', :searchText, '%')))") - Page findByTenantIdAndSearchText(@Param("tenantId") UUID tenantId, - @Param("searchText") String searchText, Pageable pageable); + Page findByTenantId(UUID tenantId, Pageable pageable); List findAllByRuleIdAndOriginatorEntityTypeAndOriginatorEntityId(UUID ruleId, EntityType originatorEntityType, UUID originatorEntityId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java index a69f207805..18f5f66282 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java @@ -57,7 +57,7 @@ public class SqlPartitioningRepository { long partitionStartTs = calculatePartitionStartTime(entityTs, partitionDurationMs); Map partitions = tablesPartitions.computeIfAbsent(table, t -> new ConcurrentHashMap<>()); if (!partitions.containsKey(partitionStartTs)) { - SqlPartition partition = new SqlPartition(table, partitionStartTs, partitionStartTs + partitionDurationMs, Long.toString(partitionStartTs)); + SqlPartition partition = new SqlPartition(table, partitionStartTs, getPartitionEndTime(partitionStartTs, partitionDurationMs), Long.toString(partitionStartTs)); partitionCreationLock.lock(); try { if (partitions.containsKey(partitionStartTs)) return; @@ -82,7 +82,7 @@ public class SqlPartitioningRepository { public void dropPartitionsBefore(String table, long ts, long partitionDurationMs) { List partitions = fetchPartitions(table); for (Long partitionStartTime : partitions) { - long partitionEndTime = partitionStartTime + partitionDurationMs; + long partitionEndTime = getPartitionEndTime(partitionStartTime, partitionDurationMs); if (partitionEndTime < ts) { log.info("[{}] Detaching expired partition: [{}-{}]", table, partitionStartTime, partitionEndTime); boolean success = detachAndDropPartition(table, partitionStartTime); @@ -95,17 +95,10 @@ public class SqlPartitioningRepository { } } - public long getLastPartitionEnd(String table, long before, long partitionDurationMs) { - return fetchPartitions(table).stream() - .mapToLong(partitionStartTime -> partitionStartTime + partitionDurationMs) - .filter(partitionEndTime -> partitionEndTime < before) - .max().orElse(0); - } - public void cleanupPartitionsCache(String table, long expTime, long partitionDurationMs) { Map partitions = tablesPartitions.get(table); if (partitions == null) return; - partitions.keySet().removeIf(startTime -> (startTime + partitionDurationMs) < expTime); + partitions.keySet().removeIf(startTime -> getPartitionEndTime(startTime, partitionDurationMs) < expTime); } private boolean detachAndDropPartition(String table, long partitionTs) { @@ -129,6 +122,10 @@ public class SqlPartitioningRepository { return false; } + private static long getPartitionEndTime(long startTime, long partitionDurationMs) { + return startTime + partitionDurationMs; + } + public List fetchPartitions(String table) { List partitions = new ArrayList<>(); List partitionsTables = jdbcTemplate.queryForList(SELECT_PARTITIONS_STMT, String.class, table); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index c5d6ada139..c73a8acc9f 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -793,6 +793,7 @@ CREATE TABLE IF NOT EXISTS notification_template ( created_time BIGINT NOT NULL, tenant_id UUID NOT NULL CONSTRAINT fk_notification_template_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, + notification_type VARCHAR(255) NOT NULL, configuration VARCHAR(10000) NOT NULL ); @@ -802,9 +803,8 @@ CREATE TABLE IF NOT EXISTS notification_rule ( tenant_id UUID NOT NULL CONSTRAINT fk_notification_rule_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id), - delivery_methods VARCHAR(255), - initial_notification_target_id UUID NULL CONSTRAINT fk_notification_rule_target_id REFERENCES notification_target(id), - escalation_config VARCHAR(500) + delivery_methods VARCHAR(255) NOT NULL, + configuration VARCHAR(2000) NOT NULL ); CREATE TABLE IF NOT EXISTS notification_request ( @@ -812,7 +812,6 @@ CREATE TABLE IF NOT EXISTS notification_request ( created_time BIGINT NOT NULL, tenant_id UUID NOT NULL CONSTRAINT fk_notification_request_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, target_id UUID NOT NULL CONSTRAINT fk_notification_request_target_id REFERENCES notification_target(id), - type VARCHAR(255) NOT NULL, template_id UUID NOT NULL CONSTRAINT fk_notification_request_template_id REFERENCES notification_template(id), info VARCHAR(1000), delivery_methods VARCHAR(255), diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 205692c9a9..942f527dee 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.api; import io.netty.channel.EventLoopGroup; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Customer; @@ -279,6 +280,8 @@ public interface TbContext { NotificationManager getNotificationManager(); + SlackService getSlackService(); + /** * Creates JS Script Engine * @deprecated diff --git a/application/src/main/java/org/thingsboard/server/service/slack/SlackConversation.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackConversation.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/slack/SlackConversation.java rename to rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackConversation.java index a5ca1b1099..4d57c29aaf 100644 --- a/application/src/main/java/org/thingsboard/server/service/slack/SlackConversation.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackConversation.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.slack; +package org.thingsboard.rule.engine.api.slack; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/service/slack/SlackService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackService.java similarity index 73% rename from application/src/main/java/org/thingsboard/server/service/slack/SlackService.java rename to rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackService.java index 0e68fe82d1..88eb5a9a16 100644 --- a/application/src/main/java/org/thingsboard/server/service/slack/SlackService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.slack; +package org.thingsboard.rule.engine.api.slack; import org.thingsboard.server.common.data.id.TenantId; @@ -21,8 +21,12 @@ import java.util.List; public interface SlackService { - void sendMessage(TenantId tenantId, String token, String conversationId, String message) throws Exception; + void sendMessage(TenantId tenantId, String token, String conversationId, String message); - List listConversations(TenantId tenantId, String token, SlackConversation.Type conversationType) throws Exception; + List listConversations(TenantId tenantId, String token, SlackConversation.Type conversationType); + + SlackConversation findConversation(TenantId tenantId, String token, SlackConversation.Type conversationType, String namePattern); + + String getToken(TenantId tenantId); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java index 64921ef299..564a48f0a5 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java @@ -15,14 +15,13 @@ */ package org.thingsboard.rule.engine.notification; -import org.apache.commons.lang3.StringUtils; +import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; -import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.notification.NotificationOriginatorType; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -31,10 +30,8 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import java.util.concurrent.ExecutionException; -import static org.thingsboard.common.util.DonAsynchron.withCallback; - @RuleNode( - type = ComponentType.ACTION, + type = ComponentType.EXTERNAL, name = "send notification", configClazz = TbNotificationNodeConfiguration.class, nodeDescription = "Sends notification to a target", @@ -48,39 +45,31 @@ public class TbNotificationNode implements TbNode { @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbNotificationNodeConfiguration.class); - validateConfig(config); } @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { -// NotificationRequest notificationRequest = NotificationRequest.builder() -// .tenantId(ctx.getTenantId()) -// .targetId(new NotificationTargetId(config.getTargetId())) -// .type(config.getNotificationReason()) -// .textTemplate(TbNodeUtils.processPattern(config.getNotificationTextTemplate(), msg)) -// .notificationSeverity(config.getNotificationSeverity()) -// .originatorType(NotificationOriginatorType.RULE_NODE) -// .originatorEntityId(ctx.getTenantId()) -// .build(); -// withCallback(ctx.getDbCallbackExecutor().executeAsync(() -> { -// return ctx.getNotificationManager().processNotificationRequest(ctx.getTenantId(), notificationRequest); -// }), -// r -> { -// TbMsgMetaData msgMetaData = msg.getMetaData().copy(); -// msgMetaData.putValue("notificationRequestId", r.getUuidId().toString()); -// msgMetaData.putValue("notificationTextTemplate", r.getTextTemplate()); -// ctx.tellSuccess(TbMsg.transformMsg(msg, msgMetaData)); -// }, -// e -> ctx.tellFailure(msg, e)); - } + NotificationRequest notificationRequest = NotificationRequest.builder() + .tenantId(ctx.getTenantId()) + .targetId(config.getTargetId()) + .templateId(config.getTemplateId()) + .deliveryMethods(config.getDeliveryMethods()) + .originatorType(NotificationOriginatorType.RULE_NODE) + .originatorEntityId(ctx.getSelfId()) + .build(); + notificationRequest.setTemplateContext(msg.getMetaData().getData()); - private void validateConfig(TbNotificationNodeConfiguration config) throws TbNodeException { - if (config.getTargetId() == null) { - throw new TbNodeException("Notification target is not specified"); - } - if (StringUtils.isBlank(config.getNotificationTextTemplate())) { - throw new TbNodeException("Notification text template is missing"); - } + DonAsynchron.withCallback(ctx.getDbCallbackExecutor().executeAsync(() -> { + return ctx.getNotificationManager().processNotificationRequest(ctx.getTenantId(), notificationRequest); + }), + r -> { + TbMsgMetaData msgMetaData = msg.getMetaData().copy(); + msgMetaData.putValue("notificationRequestId", r.getUuidId().toString()); + ctx.tellSuccess(TbMsg.transformMsg(msg, msgMetaData)); + }, + e -> { + ctx.tellFailure(msg, e); + }); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNodeConfiguration.java index 557c5e19b0..a802774a26 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNodeConfiguration.java @@ -17,24 +17,35 @@ package org.thingsboard.rule.engine.notification; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; -import org.thingsboard.server.common.data.notification.NotificationSeverity; +import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.NotificationTemplateId; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.NotificationRequestConfig; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; import java.util.UUID; @Data public class TbNotificationNodeConfiguration implements NodeConfiguration { - private UUID targetId; - private String notificationReason; - private String notificationTextTemplate; - private NotificationSeverity notificationSeverity; + @NotNull + private NotificationTargetId targetId; + @NotNull + private NotificationTemplateId templateId; + @NotEmpty + private List deliveryMethods; + private NotificationRequestConfig additionalConfig; @Override public TbNotificationNodeConfiguration defaultConfiguration() { - TbNotificationNodeConfiguration configuration = new TbNotificationNodeConfiguration(); - configuration.setNotificationReason("General"); - configuration.setNotificationSeverity(NotificationSeverity.NORMAL); - return configuration; + TbNotificationNodeConfiguration config = new TbNotificationNodeConfiguration(); + config.setTargetId(new NotificationTargetId(UUID.randomUUID())); + config.setTemplateId(new NotificationTemplateId(UUID.randomUUID())); + config.setDeliveryMethods(List.of(NotificationDeliveryMethod.WEBSOCKET)); + config.setAdditionalConfig(new NotificationRequestConfig()); + return config; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNode.java new file mode 100644 index 0000000000..8c551fd0c3 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNode.java @@ -0,0 +1,86 @@ +/** + * 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.rule.engine.notification; + +import com.google.common.util.concurrent.ListenableFuture; +import org.apache.commons.lang3.StringUtils; +import org.thingsboard.common.util.DonAsynchron; +import org.thingsboard.rule.engine.api.RuleNode; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNode; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.slack.SlackConversation; +import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.msg.TbMsg; + +import java.util.concurrent.ExecutionException; + +@RuleNode( + type = ComponentType.EXTERNAL, + name = "send to Slack", + configClazz = TbSlackNodeConfiguration.class, + nodeDescription = "Send message to a Slack channel or user", + nodeDetails = "", + uiResources = {"static/rulenode/rulenode-core-config.js"} +) +public class TbSlackNode implements TbNode { + + private TbSlackNodeConfiguration config; + + @Override + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, TbSlackNodeConfiguration.class); + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { + String token; + if (config.isUseDefaultNotificationSettings()) { + token = ctx.getSlackService().getToken(ctx.getTenantId()); + } else { + token = config.getBotToken(); + } + if (token == null) { + throw new IllegalArgumentException("Slack token is missing"); + } + String message = TbNodeUtils.processPattern(config.getMessageTemplate(), msg); + + ListenableFuture result; + if (StringUtils.isNotEmpty(config.getConversationId())) { + result = ctx.getExternalCallExecutor().executeAsync(() -> { + ctx.getSlackService().sendMessage(ctx.getTenantId(), token, config.getConversationId(), message); + }); + } else { + result = ctx.getExternalCallExecutor().executeAsync(() -> { + SlackConversation conversation = ctx.getSlackService().findConversation(ctx.getTenantId(), token, config.getConversationType(), config.getConversationNamePattern()); + if (conversation == null) { + throw new IllegalArgumentException("Couldn't find conversation by name pattern"); + } + ctx.getSlackService().sendMessage(ctx.getTenantId(), token, conversation.getId(), message); + }); + } + + DonAsynchron.withCallback(result, r -> { + ctx.tellSuccess(msg); + }, + e -> { + ctx.tellFailure(msg, e); + }); + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNodeConfiguration.java new file mode 100644 index 0000000000..4f49373a74 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNodeConfiguration.java @@ -0,0 +1,47 @@ +/** + * 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.rule.engine.notification; + +import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.api.slack.SlackConversation; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +@Data +public class TbSlackNodeConfiguration implements NodeConfiguration { + + private String botToken; + private boolean useDefaultNotificationSettings; + @NotEmpty + private String messageTemplate; + + @NotNull + private SlackConversation.Type conversationType; + private String conversationId; // if not set, need to specify conversationNamePattern + private String conversationNamePattern; + + @Override + public TbSlackNodeConfiguration defaultConfiguration() { + TbSlackNodeConfiguration config = new TbSlackNodeConfiguration(); + config.setBotToken("xoxb-"); + config.setMessageTemplate("Device ${deviceId}: temperature is $[temperature]"); + config.setConversationType(SlackConversation.Type.PUBLIC_CHANNEL); + return config; + } + +} From 409976c6a316755d02af611e41599b05180b8026 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Fri, 23 Dec 2022 18:47:40 +0200 Subject: [PATCH 024/111] Multiple notification targets for request; improvements and refactoring --- .../main/data/upgrade/3.4.3/schema_update.sql | 14 +- .../server/actors/ActorSystemContext.java | 5 + .../actors/ruleChain/DefaultTbContext.java | 5 + .../server/controller/BaseController.java | 12 +- .../controller/NotificationController.java | 3 +- .../NotificationRuleController.java | 7 +- .../NotificationTargetController.java | 20 +-- .../NotificationTemplateController.java | 3 +- .../NotificationExecutorService.java | 29 +++ .../DefaultSystemDataLoaderService.java | 5 + .../install/SystemDataLoaderService.java | 3 + .../DefaultNotificationManager.java | 80 +++++---- ...aultNotificationRuleProcessingService.java | 53 ++++-- .../DefaultNotificationSchedulerService.java | 59 ++++-- .../NotificationProcessingContext.java | 34 ++-- .../NotificationRuleProcessingService.java | 3 - .../NotificationSchedulerService.java | 2 - .../channels/SlackNotificationChannel.java | 2 +- .../queue/DefaultTbCoreConsumerService.java | 15 +- .../DefaultTbRuleEngineConsumerService.java | 6 +- .../processing/AbstractConsumerService.java | 7 +- .../service/slack/DefaultSlackService.java | 2 +- .../ttl/NotificationsCleanUpService.java | 20 ++- .../server/controller/AbstractWebTest.java | 27 +-- .../notification/NotificationApiTest.java | 23 +-- .../NotificationTargetApiTest.java | 168 ++++++++++++++++++ .../NotificationTemplateApiTest.java | 90 ++++++++++ .../notification/NotificationsClient.java | 86 --------- common/cluster-api/src/main/proto/queue.proto | 1 - .../NotificationRequestService.java | 3 + .../notification/NotificationRuleService.java | 4 +- .../NotificationTargetService.java | 7 +- .../notification/NotificationRequest.java | 14 +- .../NotificationRequestConfig.java | 4 +- .../NotificationRequestStats.java | 15 +- .../NonConfirmedNotificationEscalation.java | 5 +- ...CustomerUsersNotificationTargetConfig.java | 16 +- .../targets/NotificationTarget.java | 3 +- .../DeliveryMethodNotificationTemplate.java | 3 + ...ailDeliveryMethodNotificationTemplate.java | 3 + .../template/NotificationTemplate.java | 1 + .../template/NotificationTemplateConfig.java | 19 +- .../template}/SlackConversation.java | 2 +- ...ackDeliveryMethodNotificationTemplate.java | 2 + .../server/dao/model/BaseSqlEntity.java | 44 ++++- .../server/dao/model/ModelConstants.java | 2 +- .../dao/model/sql/AbstractAlarmEntity.java | 2 +- .../dao/model/sql/NotificationEntity.java | 5 +- .../model/sql/NotificationRequestEntity.java | 27 ++- .../dao/model/sql/NotificationRuleEntity.java | 17 +- .../model/sql/NotificationTargetEntity.java | 4 +- .../model/sql/NotificationTemplateEntity.java | 4 +- .../DefaultNotificationRequestService.java | 9 +- .../DefaultNotificationRuleService.java | 8 +- .../DefaultNotificationTargetService.java | 38 ++-- .../DefaultNotificationTemplateService.java | 19 +- .../notification/NotificationRequestDao.java | 10 ++ .../dao/notification/NotificationRuleDao.java | 3 + .../dao/service/ConstraintValidator.java | 27 ++- .../server/dao/service/NoXssValidator.java | 1 + .../JpaNotificationRequestDao.java | 24 +++ .../notification/JpaNotificationRuleDao.java | 6 + .../NotificationRequestRepository.java | 10 ++ .../NotificationRuleRepository.java | 2 + .../insert/sql/SqlPartitioningRepository.java | 5 +- .../main/resources/sql/schema-entities.sql | 14 +- .../rule/engine/api/TbContext.java | 2 + .../rule/engine/api/slack/SlackService.java | 1 + .../notification/TbNotificationNode.java | 6 +- .../TbNotificationNodeConfiguration.java | 7 +- .../rule/engine/notification/TbSlackNode.java | 2 +- .../TbSlackNodeConfiguration.java | 2 +- 72 files changed, 844 insertions(+), 342 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/executors/NotificationExecutorService.java create mode 100644 application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java create mode 100644 application/src/test/java/org/thingsboard/server/service/notification/NotificationTemplateApiTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/service/notification/NotificationsClient.java rename {rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack => common/data/src/main/java/org/thingsboard/server/common/data/notification/template}/SlackConversation.java (92%) diff --git a/application/src/main/data/upgrade/3.4.3/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql index b9aaaa5c7b..fa113d6c5d 100644 --- a/application/src/main/data/upgrade/3.4.3/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.3/schema_update.sql @@ -17,7 +17,7 @@ CREATE TABLE IF NOT EXISTS notification_target ( id UUID NOT NULL CONSTRAINT notification_target_pkey PRIMARY KEY, created_time BIGINT NOT NULL, - tenant_id UUID NOT NULL CONSTRAINT fk_notification_target_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, + tenant_id UUID NULL CONSTRAINT fk_notification_target_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, configuration VARCHAR(10000) NOT NULL ); @@ -26,7 +26,7 @@ CREATE INDEX IF NOT EXISTS idx_notification_target_tenant_id_created_time ON not 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 CONSTRAINT fk_notification_template_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, + tenant_id UUID NULL CONSTRAINT fk_notification_template_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, notification_type VARCHAR(255) NOT NULL, configuration VARCHAR(10000) NOT NULL @@ -35,7 +35,7 @@ CREATE TABLE IF NOT EXISTS notification_template ( CREATE TABLE IF NOT EXISTS notification_rule ( id UUID NOT NULL CONSTRAINT notification_rule_pkey PRIMARY KEY, created_time BIGINT NOT NULL, - tenant_id UUID NOT NULL CONSTRAINT fk_notification_rule_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, + tenant_id UUID NULL CONSTRAINT fk_notification_rule_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id), delivery_methods VARCHAR(255) NOT NULL, @@ -45,16 +45,16 @@ CREATE TABLE IF NOT EXISTS notification_rule ( CREATE TABLE IF NOT EXISTS notification_request ( id UUID NOT NULL CONSTRAINT notification_request_pkey PRIMARY KEY, created_time BIGINT NOT NULL, - tenant_id UUID NOT NULL CONSTRAINT fk_notification_request_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, - target_id UUID NOT NULL CONSTRAINT fk_notification_request_target_id REFERENCES notification_target(id), - template_id UUID NOT NULL CONSTRAINT fk_notification_request_template_id REFERENCES notification_template(id), + tenant_id UUID NULL CONSTRAINT fk_notification_request_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, + targets VARCHAR(255) NOT NULL, + template_id UUID NOT NULL, info VARCHAR(1000), delivery_methods VARCHAR(255), additional_config VARCHAR(1000), originator_type VARCHAR(32) NOT NULL, originator_entity_id UUID, originator_entity_type VARCHAR(32), - rule_id UUID NULL CONSTRAINT fk_notification_request_rule_id REFERENCES notification_rule(id), + rule_id UUID NULL, status VARCHAR(32), stats VARCHAR(1000) ); diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 9035a77ea3..8e754bf1b1 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -91,6 +91,7 @@ import org.thingsboard.server.service.edge.rpc.EdgeRpcService; import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.ExternalCallExecutorService; +import org.thingsboard.server.service.executors.NotificationExecutorService; import org.thingsboard.server.service.executors.SharedEventLoopGroupService; import org.thingsboard.server.service.mail.MailExecutorService; import org.thingsboard.server.service.profile.TbAssetProfileCache; @@ -304,6 +305,10 @@ public class ActorSystemContext { @Getter private ExternalCallExecutorService externalCallExecutorService; + @Autowired + @Getter + private NotificationExecutorService notificationExecutor; + @Autowired @Getter private SharedEventLoopGroupService sharedEventLoopGroupService; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index eeb5a8fe1b..6c70eb12b9 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -472,6 +472,11 @@ class DefaultTbContext implements TbContext { return mainCtx.getExternalCallExecutorService(); } + @Override + public ListeningExecutor getNotificationExecutor() { + return mainCtx.getNotificationExecutor(); + } + @Override @Deprecated public ScriptEngine createJsScriptEngine(String script, String... argNames) { diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 841aff37e5..e31c690a60 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -377,11 +377,17 @@ public abstract class BaseController { * */ @ExceptionHandler(MethodArgumentNotValidException.class) public void handleValidationError(MethodArgumentNotValidException e, HttpServletResponse response) { - String errorMessage = "Validation error: " + e.getBindingResult().getAllErrors().stream() - .map(DefaultMessageSourceResolvable::getDefaultMessage) + String errorMessage = "Validation error: " + e.getFieldErrors().stream() + .map(fieldError -> { + String property = fieldError.getField(); + if (property.equals("valid") || StringUtils.endsWith(property, ".valid")) { // when custom @AssertTrue is used + property = ""; + } + return (!property.isEmpty() ? (property + " ") : "") + fieldError.getDefaultMessage(); + }) .collect(Collectors.joining(", ")); ThingsboardException thingsboardException = new ThingsboardException(errorMessage, ThingsboardErrorCode.BAD_REQUEST_PARAMS); - handleThingsboardException(thingsboardException, response); + handleControllerException(thingsboardException, response); } T checkNotNull(T reference) throws ThingsboardException { diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index 3c7e8865b9..3d6153afa0 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -29,7 +29,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.rule.engine.api.NotificationManager; -import org.thingsboard.rule.engine.api.slack.SlackConversation; +import org.thingsboard.server.common.data.notification.template.SlackConversation; import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -102,6 +102,7 @@ public class NotificationController extends BaseController { notificationRequest.setOriginatorType(NotificationOriginatorType.ADMIN); notificationRequest.setOriginatorEntityId(user.getId()); + notificationRequest.setOriginatorEntity(user); if (notificationRequest.getInfo() != null && notificationRequest.getInfo().getOriginatorType() != null) { throw new IllegalArgumentException("Unsupported notification info type"); } diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java index 0ed5e1bfaf..8d5a4bde34 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.dao.notification.NotificationRuleService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; @@ -78,10 +79,12 @@ public class NotificationRuleController extends BaseController { @DeleteMapping("/rule/{id}") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") - public void deleteNotificationRule(@PathVariable UUID id) throws Exception { + public void deleteNotificationRule(@PathVariable UUID id, + @AuthenticationPrincipal SecurityUser user) throws Exception { NotificationRuleId notificationRuleId = new NotificationRuleId(id); NotificationRule notificationRule = checkEntityId(notificationRuleId, notificationRuleService::findNotificationRuleById, Operation.DELETE); - doDeleteAndLog(EntityType.NOTIFICATION_RULE, notificationRule, notificationRuleService::deleteNotificationRule); + doDeleteAndLog(EntityType.NOTIFICATION_RULE, notificationRule, notificationRuleService::deleteNotificationRuleById); + tbClusterService.broadcastEntityStateChangeEvent(user.getTenantId(), notificationRuleId, ComponentLifecycleEvent.DELETED); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java index ee984d9a9a..00ac6f4a93 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java @@ -33,8 +33,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.NotificationTargetId; 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.NotificationTargetConfigType; import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -42,6 +42,7 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; +import javax.validation.Valid; import java.util.UUID; @RestController @@ -55,17 +56,16 @@ public class NotificationTargetController extends BaseController { @PostMapping("/target") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - public NotificationTarget saveNotificationTarget(@RequestBody NotificationTarget notificationTarget, + public NotificationTarget saveNotificationTarget(@RequestBody @Valid NotificationTarget notificationTarget, @AuthenticationPrincipal SecurityUser user) throws Exception { checkEntity(notificationTarget.getId(), notificationTarget, Resource.NOTIFICATION_TARGET); if (!user.isSystemAdmin()) { NotificationTargetConfig targetConfig = notificationTarget.getConfiguration(); - if (targetConfig.getType() == NotificationTargetConfigType.SINGLE_USER || - targetConfig.getType() == NotificationTargetConfigType.USER_LIST) { - PageData recipients = notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), notificationTarget.getConfiguration(), null); - for (User recipient : recipients.getData()) { - accessControlService.checkPermission(user, Resource.USER, Operation.READ, recipient.getId(), recipient); - } + PageDataIterable recipients = new PageDataIterable<>(pageLink -> { + return notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), null, targetConfig, pageLink); + }, 200); + for (User recipient : recipients) { + accessControlService.checkPermission(user, Resource.USER, Operation.READ, recipient.getId(), recipient); } } @@ -86,7 +86,7 @@ public class NotificationTargetController extends BaseController { @RequestParam int page, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, null, null, null); - PageData recipients = notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), notificationTarget.getConfiguration(), pageLink); + PageData recipients = notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), null, notificationTarget.getConfiguration(), pageLink); if (!user.isSystemAdmin()) { for (User recipient : recipients.getData()) { accessControlService.checkPermission(user, Resource.USER, Operation.READ, recipient.getId(), recipient); @@ -112,7 +112,7 @@ public class NotificationTargetController extends BaseController { public void deleteNotificationTarget(@PathVariable UUID id) throws Exception { NotificationTargetId notificationTargetId = new NotificationTargetId(id); NotificationTarget notificationTarget = checkEntityId(notificationTargetId, notificationTargetService::findNotificationTargetById, Operation.DELETE); - doDeleteAndLog(EntityType.NOTIFICATION_TARGET, notificationTarget, notificationTargetService::deleteNotificationTarget); + doDeleteAndLog(EntityType.NOTIFICATION_TARGET, notificationTarget, notificationTargetService::deleteNotificationTargetById); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java index 3d5e0c8332..5194d57f84 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java @@ -33,6 +33,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; +import javax.validation.Valid; import java.util.UUID; @RestController @@ -45,7 +46,7 @@ public class NotificationTemplateController extends BaseController { @PostMapping("/template") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - public NotificationTemplate saveNotificationTemplate(@RequestBody NotificationTemplate notificationTemplate) throws Exception { + public NotificationTemplate saveNotificationTemplate(@RequestBody @Valid NotificationTemplate notificationTemplate) throws Exception { checkEntity(notificationTemplate.getId(), notificationTemplate, Resource.NOTIFICATION_TEMPLATE); return doSaveAndLog(EntityType.NOTIFICATION_TEMPLATE, notificationTemplate, notificationTemplateService::saveNotificationTemplate); } diff --git a/application/src/main/java/org/thingsboard/server/service/executors/NotificationExecutorService.java b/application/src/main/java/org/thingsboard/server/service/executors/NotificationExecutorService.java new file mode 100644 index 0000000000..15f1a27184 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/executors/NotificationExecutorService.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.service.executors; + +import org.springframework.stereotype.Component; +import org.thingsboard.common.util.AbstractListeningExecutor; + +@Component +public class NotificationExecutorService extends AbstractListeningExecutor { + + @Override + protected int getThreadPollSize() { + return 10; // FIXME [viacheslav] + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 9507912c39..dd1f153f51 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -671,4 +671,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { } } + @Override + public void createNotificationConfigs() { + // create default notification targets: Alarm's customer + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java index 041351f5e4..b5a54abed0 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java @@ -38,4 +38,7 @@ public interface SystemDataLoaderService { void deleteSystemWidgetBundle(String bundleAlias) throws Exception; void createQueues(); + + void createNotificationConfigs(); + } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java index f04cced9f6..28421bc429 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java @@ -27,8 +27,10 @@ import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.AlreadySentException; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationRequest; @@ -37,7 +39,7 @@ import org.thingsboard.server.common.data.notification.NotificationRequestStatus import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; -import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; @@ -85,7 +87,7 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl @Override public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { - log.debug("Processing notification request (tenant id: {}, notification target id: {})", tenantId, notificationRequest.getTargetId()); + log.debug("Processing notification request (tenant id: {}, notification targets: {})", tenantId, notificationRequest.getTargets()); notificationRequest.setTenantId(tenantId); NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId); notificationRequest.getDeliveryMethods().forEach(deliveryMethod -> { @@ -99,62 +101,63 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl if (config.getSendingDelayInSec() > 0 && notificationRequest.getId() == null) { notificationRequest.setStatus(NotificationRequestStatus.SCHEDULED); NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); - forwardToNotificationSchedulerService(tenantId, savedNotificationRequest.getId(), false); + forwardToNotificationSchedulerService(tenantId, savedNotificationRequest.getId()); return savedNotificationRequest; } } - if (notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), new PageLink(1)) - .getTotalElements() == 0) { - throw new IllegalArgumentException("No target recipients"); - } notificationRequest.setStatus(NotificationRequestStatus.PROCESSED); NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); NotificationProcessingContext ctx = NotificationProcessingContext.builder() .tenantId(tenantId) - .settings(settings) .request(savedNotificationRequest) - .additionalTemplateContext(notificationRequest.getTemplateContext()) + .settings(settings) .build(); ctx.init(notificationTemplateService); - DaoUtil.processBatches(pageLink -> { - return notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), pageLink); - }, 200, recipientsBatch -> { - List> results = new ArrayList<>(); - for (NotificationDeliveryMethod deliveryMethod : savedNotificationRequest.getDeliveryMethods()) { - NotificationChannel notificationChannel = channels.get(deliveryMethod); - log.debug("Sending {} notifications for request {} to recipients batch", deliveryMethod, savedNotificationRequest.getId()); + for (NotificationTargetId targetId : notificationRequest.getTargets()) { + DaoUtil.processBatches(pageLink -> { + return notificationTargetService.findRecipientsForNotificationTarget(tenantId, ctx.getOriginatorCustomerId(), targetId, pageLink); + }, 200, recipientsBatch -> { + List> results = new ArrayList<>(); + for (NotificationDeliveryMethod deliveryMethod : savedNotificationRequest.getDeliveryMethods()) { + NotificationChannel notificationChannel = channels.get(deliveryMethod); + log.debug("Sending {} notifications for request {} to recipients batch", deliveryMethod, savedNotificationRequest.getId()); - List recipients = recipientsBatch.getData(); - for (User recipient : recipients) { - ListenableFuture resultFuture = processForRecipient(notificationChannel, recipient, ctx); - DonAsynchron.withCallback(resultFuture, result -> { - ctx.getStats().reportSent(deliveryMethod); - }, error -> { - ctx.getStats().reportError(deliveryMethod, recipient, error); - }, dbCallbackExecutorService); - results.add(resultFuture); + List recipients = recipientsBatch.getData(); + for (User recipient : recipients) { + ListenableFuture resultFuture = processForRecipient(notificationChannel, recipient, ctx); + DonAsynchron.withCallback(resultFuture, result -> { + ctx.getStats().reportSent(deliveryMethod, recipient); + }, error -> { + ctx.getStats().reportError(deliveryMethod, recipient, error); + }); + results.add(resultFuture); + } } - } - Futures.allAsList(results).addListener(() -> { - try { - notificationRequestService.updateNotificationRequestStats(tenantId, savedNotificationRequest.getId(), ctx.getStats()); - } catch (Exception e) { - log.error("Failed to update stats for notification request {}", savedNotificationRequest.getId(), e); - } - }, dbCallbackExecutorService); - }); + Futures.allAsList(results).addListener(() -> { + try { + notificationRequestService.updateNotificationRequestStats(tenantId, savedNotificationRequest.getId(), ctx.getStats()); + } catch (Exception e) { + log.error("Failed to update stats for notification request {}", savedNotificationRequest.getId(), e); + } + }, dbCallbackExecutorService); + }); + } return savedNotificationRequest; } private ListenableFuture processForRecipient(NotificationChannel notificationChannel, User recipient, NotificationProcessingContext ctx) { + NotificationDeliveryMethod deliveryMethod = notificationChannel.getDeliveryMethod(); + if (ctx.getStats().contains(deliveryMethod, recipient.getId())) { + return Futures.immediateFailedFuture(new AlreadySentException()); + } String text; try { - DeliveryMethodNotificationTemplate template = ctx.getTemplate(notificationChannel.getDeliveryMethod()); + DeliveryMethodNotificationTemplate template = ctx.getTemplate(deliveryMethod); text = TbNodeUtils.processTemplate(template.getBody(), ctx.createTemplateContext(recipient)); } catch (Exception e) { return Futures.immediateFailedFuture(e); @@ -162,14 +165,13 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl return notificationChannel.sendNotification(recipient, text, ctx); } - private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId, boolean deleted) { + private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId) { TransportProtos.NotificationSchedulerServiceMsg.Builder msg = TransportProtos.NotificationSchedulerServiceMsg.newBuilder() .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) .setRequestIdMSB(notificationRequestId.getId().getMostSignificantBits()) .setRequestIdLSB(notificationRequestId.getId().getLeastSignificantBits()) - .setTs(System.currentTimeMillis()) - .setDeleted(deleted); + .setTs(System.currentTimeMillis()); TransportProtos.ToCoreMsg toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() .setNotificationSchedulerServiceMsg(msg) .build(); @@ -216,7 +218,7 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl .notificationRequestId(notificationRequestId) .deleted(true) .build()); - forwardToNotificationSchedulerService(tenantId, notificationRequestId, true); + clusterService.broadcastEntityStateChangeEvent(tenantId, notificationRequestId, ComponentLifecycleEvent.DELETED); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java index 5f97d47093..e6ef9bd8ba 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java @@ -21,9 +21,12 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.NotificationManager; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; @@ -32,13 +35,16 @@ import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationOriginatorType; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; +import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.rule.NonConfirmedNotificationEscalation; import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.notification.rule.NotificationRuleConfig; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationRuleService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.executors.DbCallbackExecutorService; +import org.thingsboard.server.service.executors.NotificationExecutorService; import java.util.List; import java.util.Map; @@ -53,7 +59,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul private final NotificationRequestService notificationRequestService; @Autowired @Lazy private NotificationManager notificationManager; - private final DbCallbackExecutorService dbCallbackExecutorService; + private final NotificationExecutorService notificationExecutor; @Override public ListenableFuture onAlarmCreatedOrUpdated(TenantId tenantId, Alarm alarm) { @@ -67,23 +73,12 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul private ListenableFuture processAlarmUpdate(TenantId tenantId, Alarm alarm, boolean deleted) { if (alarm.getNotificationRuleId() == null) return Futures.immediateFuture(null); - return dbCallbackExecutorService.submit(() -> { + return notificationExecutor.submit(() -> { onAlarmUpdate(tenantId, alarm.getNotificationRuleId(), alarm, deleted); return null; }); } - @Override - public ListenableFuture onNotificationRuleDeleted(TenantId tenantId, NotificationRuleId ruleId) { - return dbCallbackExecutorService.submit(() -> { - // FIXME [viacheslav] - // need to remove fk constraint in notificationRequest to rule - // todo: do we need to remove all notifications when notification request is deleted? - return null; - }); - } - - // todo: think about: what if notification rule was updated? private void onAlarmUpdate(TenantId tenantId, NotificationRuleId notificationRuleId, Alarm alarm, boolean deleted) { log.debug("Processing alarm update ({}) with notification rule {}", alarm.getId(), notificationRuleId); List notificationRequests = notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityId(tenantId, notificationRuleId, alarm.getId()); @@ -91,11 +86,14 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul if (notificationRule == null) return; if (alarmAcknowledged(alarm) || deleted) { + if (notificationRequests.isEmpty()) { + return; + } for (NotificationRequest notificationRequest : notificationRequests) { - notificationManager.deleteNotificationRequest(tenantId, notificationRequest.getId()); - // todo: or should we mark already sent notifications as read and delete only scheduled? + if (notificationRequest.getStatus() == NotificationRequestStatus.SCHEDULED) { + notificationManager.deleteNotificationRequest(tenantId, notificationRequest.getId()); + } } - return; } if (notificationRequests.isEmpty()) { @@ -139,20 +137,22 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul ); NotificationRequest notificationRequest = NotificationRequest.builder() .tenantId(tenantId) - .targetId(targetId) + .targets(List.of(targetId)) .templateId(notificationRule.getTemplateId()) .deliveryMethods(notificationRule.getDeliveryMethods()) .additionalConfig(config) .info(notificationInfo) + .ruleId(notificationRule.getId()) .originatorType(NotificationOriginatorType.ALARM) .originatorEntityId(alarm.getId()) - .ruleId(notificationRule.getId()) + .originatorEntity(alarm) .templateContext(templateContext) .build(); notificationManager.processNotificationRequest(tenantId, notificationRequest); } private NotificationInfo constructNotificationInfo(Alarm alarm) { + // TODO: add info about assignee return AlarmOriginatedNotificationInfo.builder() .alarmId(alarm.getId()) .alarmType(alarm.getType()) @@ -162,4 +162,19 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul .build(); } + @EventListener(ComponentLifecycleMsg.class) + public void onNotificationRuleDeleted(ComponentLifecycleMsg componentLifecycleMsg) { + if (componentLifecycleMsg.getEvent() != ComponentLifecycleEvent.DELETED || + componentLifecycleMsg.getEntityId().getEntityType() != EntityType.NOTIFICATION_RULE) { + return; + } + + TenantId tenantId = componentLifecycleMsg.getTenantId(); + NotificationRuleId notificationRuleId = (NotificationRuleId) componentLifecycleMsg.getEntityId(); + List scheduledForRule = notificationRequestService.findNotificationRequestsIdsByStatusAndRuleId(tenantId, NotificationRequestStatus.SCHEDULED, notificationRuleId); + for (NotificationRequestId notificationRequestId : scheduledForRule) { + notificationManager.deleteNotificationRequest(tenantId, notificationRequestId); + } + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java index a2757dc3dd..67ffa8e167 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java @@ -16,24 +16,31 @@ package org.thingsboard.server.service.notification; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListenableScheduledFuture; +import lombok.Data; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.NotificationManager; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; import org.thingsboard.server.common.data.page.PageDataIterable; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.notification.NotificationRequestService; +import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.executors.NotificationExecutorService; import org.thingsboard.server.service.partition.AbstractPartitionBasedService; import javax.annotation.PostConstruct; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -51,8 +58,10 @@ public class DefaultNotificationSchedulerService extends AbstractPartitionBasedS private final NotificationManager notificationManager; private final NotificationRequestService notificationRequestService; + private final SchedulerComponent scheduler; + private final NotificationExecutorService notificationExecutor; - private final Map> scheduledNotificationRequests = new ConcurrentHashMap<>(); + private final Map scheduledNotificationRequests = new ConcurrentHashMap<>(); @PostConstruct public void init() { @@ -93,30 +102,48 @@ public class DefaultNotificationSchedulerService extends AbstractPartitionBasedS delayInMs = 0; } - ListenableScheduledFuture scheduledTask = scheduledExecutor.schedule(() -> { + ScheduledFuture scheduledTask = scheduler.schedule(() -> { NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(tenantId, request.getId()); if (notificationRequest == null) return; - notificationManager.processNotificationRequest(tenantId, notificationRequest); + notificationExecutor.executeAsync(() -> { + notificationManager.processNotificationRequest(tenantId, notificationRequest); + }); scheduledNotificationRequests.remove(notificationRequest.getId()); }, delayInMs, TimeUnit.MILLISECONDS); - scheduledNotificationRequests.put(request.getId(), scheduledTask); + scheduledNotificationRequests.put(request.getId(), new ScheduledRequestMetadata(tenantId, scheduledTask)); } - @Override - public void onNotificationRequestDeleted(TenantId tenantId, NotificationRequestId notificationRequestId) { - removeAndCancel(notificationRequestId); + @EventListener(ComponentLifecycleMsg.class) + public void handleComponentLifecycleEvent(ComponentLifecycleMsg event) { + if (event.getEvent() == ComponentLifecycleEvent.DELETED) { + EntityId entityId = event.getEntityId(); + switch (entityId.getEntityType()) { + case NOTIFICATION_REQUEST: + cancelAndRemove((NotificationRequestId) entityId); + break; + case TENANT: + Set toCancel = new HashSet<>(); + scheduledNotificationRequests.forEach((notificationRequestId, scheduledRequestMetadata) -> { + if (scheduledRequestMetadata.getTenantId().equals(entityId)) { + toCancel.add(notificationRequestId); + } + }); + toCancel.forEach(this::cancelAndRemove); + break; + } + } } @Override protected void cleanupEntityOnPartitionRemoval(NotificationRequestId notificationRequestId) { - removeAndCancel(notificationRequestId); + cancelAndRemove(notificationRequestId); } - private void removeAndCancel(NotificationRequestId notificationRequestId) { - ScheduledFuture scheduledTask = scheduledNotificationRequests.remove(notificationRequestId); - if (scheduledTask != null) { - scheduledTask.cancel(false); + private void cancelAndRemove(NotificationRequestId notificationRequestId) { + ScheduledRequestMetadata md = scheduledNotificationRequests.remove(notificationRequestId); + if (md != null) { + md.getFuture().cancel(false); } } @@ -130,4 +157,10 @@ public class DefaultNotificationSchedulerService extends AbstractPartitionBasedS return "notifications-scheduler"; } + @Data + private static class ScheduledRequestMetadata { + private final TenantId tenantId; + private final ScheduledFuture future; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java index 9abcba6b43..46cc211403 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java @@ -18,7 +18,10 @@ package org.thingsboard.server.service.notification; import com.google.common.base.Strings; import lombok.Builder; import lombok.Getter; +import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.HasCustomerId; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationRequest; @@ -32,14 +35,13 @@ import org.thingsboard.server.dao.notification.NotificationTemplateService; import java.util.HashMap; import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; @SuppressWarnings("unchecked") public class NotificationProcessingContext { @Getter private final TenantId tenantId; + private final HasCustomerId originatorEntity; private final NotificationSettings settings; @Getter private final NotificationRequest request; @@ -49,26 +51,28 @@ public class NotificationProcessingContext { private NotificationTemplate notificationTemplate; private Map templates; @Getter - private NotificationRequestStats stats; + private final NotificationRequestStats stats; @Builder - public NotificationProcessingContext(TenantId tenantId, NotificationSettings settings, NotificationRequest request, Map additionalTemplateContext) { + public NotificationProcessingContext(TenantId tenantId, NotificationRequest request, NotificationSettings settings) { this.tenantId = tenantId; - this.settings = settings; + this.originatorEntity = request.getOriginatorEntity(); this.request = request; - this.additionalTemplateContext = additionalTemplateContext; + this.settings = settings; + this.additionalTemplateContext = request.getTemplateContext(); + this.stats = new NotificationRequestStats(); } public void init(NotificationTemplateService templateService) { notificationTemplate = templateService.findNotificationTemplateById(tenantId, request.getTemplateId()); NotificationTemplateConfig config = notificationTemplate.getConfiguration(); - templates = request.getDeliveryMethods().stream() - .collect(Collectors.toMap(k -> k, deliveryMethod -> { - return Optional.ofNullable(config.getTemplates()) - .map(templates -> templates.get(deliveryMethod)) - .orElse(config.getDefaultTemplate()); - })); - stats = new NotificationRequestStats(); + for (NotificationDeliveryMethod deliveryMethod : request.getDeliveryMethods()) { + DeliveryMethodNotificationTemplate template = config.getTemplates().get(deliveryMethod); + if (StringUtils.isEmpty(template.getBody())) { + template.setBody(config.getDefaultTextTemplate()); + } + } + templates = config.getTemplates(); } public T getTemplate(NotificationDeliveryMethod deliveryMethod) { @@ -90,4 +94,8 @@ public class NotificationProcessingContext { return templateContext; } + public CustomerId getOriginatorCustomerId() { + return originatorEntity != null ? originatorEntity.getCustomerId() : null; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationRuleProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationRuleProcessingService.java index 2283e49cdd..8eb550e12a 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/NotificationRuleProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationRuleProcessingService.java @@ -17,7 +17,6 @@ package org.thingsboard.server.service.notification; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; public interface NotificationRuleProcessingService { @@ -26,6 +25,4 @@ public interface NotificationRuleProcessingService { ListenableFuture onAlarmDeleted(TenantId tenantId, Alarm alarm); - ListenableFuture onNotificationRuleDeleted(TenantId tenantId, NotificationRuleId ruleId); - } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationSchedulerService.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationSchedulerService.java index ce018f171c..01d3aea1d3 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/NotificationSchedulerService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationSchedulerService.java @@ -22,6 +22,4 @@ public interface NotificationSchedulerService { void scheduleNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId, long requestTs); - void onNotificationRequestDeleted(TenantId tenantId, NotificationRequestId notificationRequestId); - } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java index a12efbffa0..525feb1a40 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java @@ -20,7 +20,7 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; -import org.thingsboard.rule.engine.api.slack.SlackConversation; +import org.thingsboard.server.common.data.notification.template.SlackConversation; import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.notification.AlreadySentException; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index d71613cab4..09441f2563 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; @@ -38,8 +39,6 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.stats.StatsFactory; -import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto; @@ -65,6 +64,7 @@ import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.provider.TbCoreQueueFactory; import org.thingsboard.server.queue.util.AfterStartUp; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.edge.EdgeNotificationService; @@ -76,6 +76,7 @@ import org.thingsboard.server.service.queue.processing.AbstractConsumerService; import org.thingsboard.server.service.queue.processing.IdMsgPair; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; +import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; @@ -153,9 +154,10 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService jwtSettingsService, NotificationSchedulerService notificationSchedulerService) { - super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService); + super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService); this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer(); this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer(); this.firmwareStatesConsumer = tbCoreQueueFactory.createToOtaPackageStateServiceMsgConsumer(); @@ -584,13 +586,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> nfConsumer; protected final Optional jwtSettingsService; @@ -83,7 +85,8 @@ public abstract class AbstractConsumerService> nfConsumer, Optional jwtSettingsService) { + PartitionService partitionService, ApplicationEventPublisher eventPublisher, + TbQueueConsumer> nfConsumer, Optional jwtSettingsService) { this.actorContext = actorContext; this.encodingService = encodingService; this.tenantProfileCache = tenantProfileCache; @@ -91,6 +94,7 @@ public abstract class AbstractConsumerService 0) { + long gap = TimeUnit.MINUTES.toMillis(10); + long requestExpTime = lastRemovedNotificationTs - TimeUnit.SECONDS.toMillis(NotificationRequestConfig.MAX_SENDING_DELAY) - gap; + // TODO: double-check this + notificationRequestDao.removeAllByCreatedTimeBefore(requestExpTime); } } diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index 9ea269918e..6d4a5bcb1e 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -296,21 +296,24 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { if (savedDifferentTenant != null) { login(DIFFERENT_TENANT_ADMIN_EMAIL, DIFFERENT_TENANT_ADMIN_PASSWORD); } else { - loginSysAdmin(); - - Tenant tenant = new Tenant(); - tenant.setTitle(TEST_DIFFERENT_TENANT_NAME); - savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class); - differentTenantId = savedDifferentTenant.getId(); - Assert.assertNotNull(savedDifferentTenant); - User differentTenantAdmin = new User(); - differentTenantAdmin.setAuthority(Authority.TENANT_ADMIN); - differentTenantAdmin.setTenantId(savedDifferentTenant.getId()); - differentTenantAdmin.setEmail(DIFFERENT_TENANT_ADMIN_EMAIL); - savedDifferentTenantUser = createUserAndLogin(differentTenantAdmin, DIFFERENT_TENANT_ADMIN_PASSWORD); + createDifferentTenant(); } } + protected void createDifferentTenant() throws Exception { + loginSysAdmin(); + Tenant tenant = new Tenant(); + tenant.setTitle(TEST_DIFFERENT_TENANT_NAME); + savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class); + differentTenantId = savedDifferentTenant.getId(); + Assert.assertNotNull(savedDifferentTenant); + User differentTenantAdmin = new User(); + differentTenantAdmin.setAuthority(Authority.TENANT_ADMIN); + differentTenantAdmin.setTenantId(savedDifferentTenant.getId()); + differentTenantAdmin.setEmail(DIFFERENT_TENANT_ADMIN_EMAIL); + savedDifferentTenantUser = createUserAndLogin(differentTenantAdmin, DIFFERENT_TENANT_ADMIN_PASSWORD); + } + protected void loginDifferentCustomer() throws Exception { if (savedDifferentCustomer != null) { login(savedDifferentCustomer.getEmail(), CUSTOMER_USER_PASSWORD); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java index e7acd85fab..1793ccb4b4 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java @@ -34,8 +34,6 @@ import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationRequestStatus; -import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig; -import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.SingleUserNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.UserListNotificationTargetConfig; @@ -366,6 +364,9 @@ public class NotificationApiTest extends AbstractControllerTest { } private NotificationRequest submitNotificationRequest(NotificationTargetId targetId, String text, int delayInSec, NotificationDeliveryMethod... deliveryMethods) { + if (deliveryMethods.length == 0) { + deliveryMethods = new NotificationDeliveryMethod[]{NotificationDeliveryMethod.WEBSOCKET}; + } NotificationTemplate notificationTemplate = createNotificationTemplate(text, deliveryMethods); NotificationRequestConfig config = new NotificationRequestConfig(); config.setSendingDelayInSec(delayInSec); @@ -373,10 +374,10 @@ public class NotificationApiTest extends AbstractControllerTest { notificationInfo.setDescription("The text: " + text); NotificationRequest notificationRequest = NotificationRequest.builder() .tenantId(tenantId) - .targetId(targetId) + .targets(List.of(targetId)) .templateId(notificationTemplate.getId()) .info(notificationInfo) - .deliveryMethods(deliveryMethods.length > 0 ? List.of(deliveryMethods) : List.of(NotificationDeliveryMethod.WEBSOCKET)) + .deliveryMethods(List.of(deliveryMethods)) .additionalConfig(config) .build(); return doPost("/api/notification/request", notificationRequest, NotificationRequest.class); @@ -388,18 +389,18 @@ public class NotificationApiTest extends AbstractControllerTest { notificationTemplate.setName("Notification template for testing"); notificationTemplate.setNotificationType("Just a test"); NotificationTemplateConfig config = new NotificationTemplateConfig(); - DeliveryMethodNotificationTemplate defaultTemplate = new DeliveryMethodNotificationTemplate(); - defaultTemplate.setBody(text); - config.setDefaultTemplate(defaultTemplate); + config.setDefaultTextTemplate(text); + config.setTemplates(new HashMap<>()); for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) { if (deliveryMethod == NotificationDeliveryMethod.EMAIL) { EmailDeliveryMethodNotificationTemplate emailNotificationTemplate = new EmailDeliveryMethodNotificationTemplate(); emailNotificationTemplate.setSubject("Hello from test"); - emailNotificationTemplate.setBody(text); emailNotificationTemplate.setMethod(deliveryMethod); - config.setTemplates(Map.of( - deliveryMethod, emailNotificationTemplate - )); + config.getTemplates().put(deliveryMethod, emailNotificationTemplate); + } else { + DeliveryMethodNotificationTemplate defaultTemplate = new DeliveryMethodNotificationTemplate(); + defaultTemplate.setMethod(deliveryMethod); + config.getTemplates().put(deliveryMethod, defaultTemplate); } } notificationTemplate.setConfiguration(config); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java new file mode 100644 index 0000000000..d9bcd0f6e2 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java @@ -0,0 +1,168 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.ResultMatcher; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.targets.AllUsersNotificationTargetConfig; +import org.thingsboard.server.common.data.notification.targets.CustomerUsersNotificationTargetConfig; +import org.thingsboard.server.common.data.notification.targets.NotificationTarget; +import org.thingsboard.server.common.data.notification.targets.SingleUserNotificationTargetConfig; +import org.thingsboard.server.common.data.notification.targets.UserListNotificationTargetConfig; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.controller.AbstractControllerTest; +import org.thingsboard.server.dao.notification.NotificationTargetDao; +import org.thingsboard.server.dao.service.DaoSqlTest; + +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@DaoSqlTest +public class NotificationTargetApiTest extends AbstractControllerTest { + + @Autowired + private NotificationTargetDao notificationTargetDao; + + @Before + public void beforeEach() throws Exception { + loginTenantAdmin(); + } + + @Test + public void givenInvalidNotificationTarget_whenSaving_returnValidationError() throws Exception { + NotificationTarget notificationTarget = new NotificationTarget(); + notificationTarget.setTenantId(null); + notificationTarget.setName(null); + notificationTarget.setConfiguration(null); + + String validationError = saveAndGetError(notificationTarget, status().isBadRequest()); + assertThat(validationError) + .contains("name must not be") + .contains("configuration must not be"); + + SingleUserNotificationTargetConfig singleUserConfig = new SingleUserNotificationTargetConfig(); + singleUserConfig.setUserId(null); + notificationTarget.setConfiguration(singleUserConfig); + + validationError = saveAndGetError(notificationTarget, status().isBadRequest()); + assertThat(validationError) + .contains("userId must not be"); + + UserListNotificationTargetConfig userListConfig = new UserListNotificationTargetConfig(); + userListConfig.setUsersIds(Collections.emptyList()); + notificationTarget.setConfiguration(userListConfig); + + validationError = saveAndGetError(notificationTarget, status().isBadRequest()); + assertThat(validationError) + .contains("usersIds must not be"); + } + + @Test + public void givenNotificationTargetWithUsersFromDifferentTenant_whenSaving_returnAccessDeniedError() throws Exception { + loginDifferentTenant(); + NotificationTarget notificationTarget = new NotificationTarget(); + notificationTarget.setTenantId(differentTenantId); + notificationTarget.setName("Target 1"); + UserListNotificationTargetConfig userListConfig = new UserListNotificationTargetConfig(); + userListConfig.setUsersIds(List.of(customerUserId.getId(), tenantAdminUserId.getId())); + notificationTarget.setConfiguration(userListConfig); + + saveAndGetError(notificationTarget, status().isForbidden()); + + SingleUserNotificationTargetConfig singleUserConfig = new SingleUserNotificationTargetConfig(); + singleUserConfig.setUserId(customerUserId.getId()); + notificationTarget.setConfiguration(singleUserConfig); + + saveAndGetError(notificationTarget, status().isForbidden()); + + loginSysAdmin(); + notificationTarget.setTenantId(TenantId.SYS_TENANT_ID); + notificationTarget.setConfiguration(userListConfig); + save(notificationTarget, status().isOk()); + } + + @Test + public void givenNotificationTargetConfig_testGetRecipients() throws Exception { + NotificationTarget notificationTarget = new NotificationTarget(); + notificationTarget.setTenantId(tenantId); + notificationTarget.setName("Test target"); + CustomerUsersNotificationTargetConfig customerUsersConfig = new CustomerUsersNotificationTargetConfig(); + customerUsersConfig.setCustomerId(customerId.getId()); + notificationTarget.setConfiguration(customerUsersConfig); + + List recipients = getRecipients(notificationTarget); + assertThat(recipients).size().isNotZero(); + assertThat(recipients).allSatisfy(recipient -> { + assertThat(recipient.getCustomerId()).isEqualTo(customerId); + }); + + AllUsersNotificationTargetConfig allUsersConfig = new AllUsersNotificationTargetConfig(); + notificationTarget.setConfiguration(allUsersConfig); + recipients = getRecipients(notificationTarget); + assertThat(recipients).size().isGreaterThanOrEqualTo(2); + assertThat(recipients).allSatisfy(recipient -> { + assertThat(recipient.getTenantId()).isEqualTo(tenantId); + }); + + createDifferentTenant(); + loginSysAdmin(); + recipients = getRecipients(notificationTarget); + assertThat(recipients).size().isGreaterThanOrEqualTo(3); + assertThat(recipients).anySatisfy(recipient -> { + assertThat(recipient.getTenantId()).isEqualTo(tenantId); + }); + assertThat(recipients).anySatisfy(recipient -> { + assertThat(recipient.getTenantId()).isEqualTo(differentTenantId); + }); + } + + @Test + public void whenDeletingTenant_thenDeleteNotificationTarget() throws Exception { + createDifferentTenant(); + NotificationTarget notificationTarget = new NotificationTarget(); + notificationTarget.setName("Test 1"); + notificationTarget.setTenantId(differentTenantId); + notificationTarget.setConfiguration(new AllUsersNotificationTargetConfig()); + save(notificationTarget, status().isOk()); + assertThat(notificationTargetDao.find(TenantId.SYS_TENANT_ID)).isNotEmpty(); + + deleteDifferentTenant(); + assertThat(notificationTargetDao.find(TenantId.SYS_TENANT_ID)).isEmpty(); + } + + private String saveAndGetError(NotificationTarget notificationTarget, ResultMatcher statusMatcher) throws Exception { + return getErrorMessage(save(notificationTarget, statusMatcher)); + } + + private ResultActions save(NotificationTarget notificationTarget, ResultMatcher statusMatcher) throws Exception { + return doPost("/api/notification/target", notificationTarget) + .andExpect(statusMatcher); + } + + private List getRecipients(NotificationTarget notificationTarget) throws Exception { + return doPostWithTypedResponse("/api/notification/target/recipients?page=0&pageSize=100", notificationTarget, new TypeReference>() {}).getData(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationTemplateApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationTemplateApiTest.java new file mode 100644 index 0000000000..58135472ab --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationTemplateApiTest.java @@ -0,0 +1,90 @@ +/** + * 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 org.junit.Before; +import org.junit.Test; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.ResultMatcher; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; +import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; +import org.thingsboard.server.controller.AbstractControllerTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@DaoSqlTest +public class NotificationTemplateApiTest extends AbstractControllerTest { + + @Before + public void beforeEach() throws Exception { + loginTenantAdmin(); + } + + @Test + public void givenInvalidNotificationTemplate_whenSaving_returnValidationError() throws Exception { + NotificationTemplate notificationTemplate = new NotificationTemplate(); + notificationTemplate.setTenantId(tenantId); + notificationTemplate.setName(null); + notificationTemplate.setNotificationType(null); + notificationTemplate.setConfiguration(null); + + String validationError = saveAndGetError(notificationTemplate, status().isBadRequest()); + assertThat(validationError) + .contains("name must not be") + .contains("notificationType must not be") + .contains("configuration must not be"); + + NotificationTemplateConfig config = new NotificationTemplateConfig(); + notificationTemplate.setConfiguration(config); + config.setDefaultTextTemplate("Default text"); + EmailDeliveryMethodNotificationTemplate emailTemplate = new EmailDeliveryMethodNotificationTemplate(); + emailTemplate.setMethod(NotificationDeliveryMethod.EMAIL); + emailTemplate.setBody(null); + emailTemplate.setSubject(null); + config.setTemplates(Map.of( + NotificationDeliveryMethod.EMAIL, emailTemplate + )); + notificationTemplate.setName("