From fa934f647e71e2ac84f3c53ba9ffc2326b238dcc Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Thu, 20 Oct 2022 12:54:20 +0300 Subject: [PATCH 001/496] 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/496] 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/496] 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/496] 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/496] 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/496] 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/496] 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/496] 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/496] 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/496] 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/496] 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/496] 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/496] 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/496] 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 d01201acf354bc36ea04ce9564cc690fedce0621 Mon Sep 17 00:00:00 2001 From: devaskim Date: Tue, 15 Nov 2022 21:56:59 +0500 Subject: [PATCH 015/496] Add filter duplication option. --- .../filter/filters-dialog.component.html | 9 +++++ .../filter/filters-dialog.component.ts | 34 ++++++++++++++++++- .../assets/locale/locale.constant-en_US.json | 1 + 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html b/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html index 25f6e57e2d..55079955a8 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html @@ -64,6 +64,15 @@ matTooltipPosition="above"> edit + -
+
device-profile.snmp.please-add-communication-config
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.ts index 6fe20f5e2f..d99d41724c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.ts @@ -27,7 +27,7 @@ import { Validators } from '@angular/forms'; import { SnmpCommunicationConfig, SnmpSpecType, SnmpSpecTypeTranslationMap } from '@shared/models/device.models'; -import { Subject, Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; import { isUndefinedOrNull } from '@core/utils'; import { takeUntil } from 'rxjs/operators'; @@ -58,7 +58,6 @@ export class SnmpDeviceProfileCommunicationConfigComponent implements OnInit, On disabled: boolean; private usedSpecType: SnmpSpecType[] = []; - private valueChange$: Subscription = null; private destroy$ = new Subject(); private propagateChange = (v: any) => { }; @@ -68,17 +67,17 @@ export class SnmpDeviceProfileCommunicationConfigComponent implements OnInit, On this.deviceProfileCommunicationConfig = this.fb.group({ communicationConfig: this.fb.array([]) }); + this.deviceProfileCommunicationConfig.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => this.updateModel()); } ngOnDestroy() { - if (this.valueChange$) { - this.valueChange$.unsubscribe(); - } this.destroy$.next(); this.destroy$.complete(); } - communicationConfigFormArray(): FormArray { + get communicationConfigFormArray(): FormArray { return this.deviceProfileCommunicationConfig.get('communicationConfig') as FormArray; } @@ -99,27 +98,27 @@ export class SnmpDeviceProfileCommunicationConfigComponent implements OnInit, On } writeValue(communicationConfig: SnmpCommunicationConfig[]) { - if (this.valueChange$) { - this.valueChange$.unsubscribe(); - } - const communicationConfigControl: Array = []; - if (communicationConfig) { - communicationConfig.forEach((config) => { - communicationConfigControl.push(this.createdFormGroup(config)); - }); - } - this.deviceProfileCommunicationConfig.setControl('communicationConfig', this.fb.array(communicationConfigControl)); - if (!communicationConfig || !communicationConfig.length) { - this.addCommunicationConfig(); - } - if (this.disabled) { - this.deviceProfileCommunicationConfig.disable({emitEvent: false}); + if (communicationConfig?.length === this.communicationConfigFormArray.length) { + this.communicationConfigFormArray.patchValue(communicationConfig, {emitEvent: false}); } else { - this.deviceProfileCommunicationConfig.enable({emitEvent: false}); + const communicationConfigControl: Array = []; + if (communicationConfig) { + communicationConfig.forEach((config) => { + communicationConfigControl.push(this.createdFormGroup(config)); + }); + } + this.deviceProfileCommunicationConfig.setControl( + 'communicationConfig', this.fb.array(communicationConfigControl), {emitEvent: false} + ); + if (!communicationConfig || !communicationConfig.length) { + this.addCommunicationConfig(); + } + if (this.disabled) { + this.deviceProfileCommunicationConfig.disable({emitEvent: false}); + } else { + this.deviceProfileCommunicationConfig.enable({emitEvent: false}); + } } - this.valueChange$ = this.deviceProfileCommunicationConfig.valueChanges.subscribe(() => { - this.updateModel(); - }); this.updateUsedSpecType(); if (!this.disabled && !this.deviceProfileCommunicationConfig.valid) { this.updateModel(); @@ -133,16 +132,16 @@ export class SnmpDeviceProfileCommunicationConfigComponent implements OnInit, On } public removeCommunicationConfig(index: number) { - this.communicationConfigFormArray().removeAt(index); + this.communicationConfigFormArray.removeAt(index); } get isAddEnabled(): boolean { - return this.communicationConfigFormArray().length !== Object.keys(SnmpSpecType).length; + return this.communicationConfigFormArray.length !== Object.keys(SnmpSpecType).length; } public addCommunicationConfig() { - this.communicationConfigFormArray().push(this.createdFormGroup()); + this.communicationConfigFormArray.push(this.createdFormGroup()); this.deviceProfileCommunicationConfig.updateValueAndValidity(); if (!this.deviceProfileCommunicationConfig.valid) { this.updateModel(); diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html index e202370d21..0d576b91de 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html @@ -25,7 +25,7 @@
-
@@ -67,7 +67,7 @@
-
+
device-profile.snmp.please-add-mapping-config
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.ts index 2b8192908b..8f74a82f55 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.ts @@ -69,6 +69,7 @@ export class SnmpDeviceProfileMappingComponent implements OnInit, OnDestroy, Con this.mappingsConfigForm = this.fb.group({ mappings: this.fb.array([]) }); + this.valueChange$ = this.mappingsConfigForm.valueChanges.subscribe(() => this.updateModel()); } ngOnDestroy() { @@ -100,38 +101,36 @@ export class SnmpDeviceProfileMappingComponent implements OnInit, OnDestroy, Con } writeValue(mappings: SnmpMapping[]) { - if (this.valueChange$) { - this.valueChange$.unsubscribe(); - } - const mappingsControl: Array = []; - if (mappings) { - mappings.forEach((config) => { - mappingsControl.push(this.createdFormGroup(config)); - }); - } - this.mappingsConfigForm.setControl('mappings', this.fb.array(mappingsControl)); - if (!mappings || !mappings.length) { - this.addMappingConfig(); - } - if (this.disabled) { - this.mappingsConfigForm.disable({emitEvent: false}); + if (mappings?.length === this.mappingsConfigFormArray.length) { + this.mappingsConfigFormArray.patchValue(mappings, {emitEvent: false}); } else { - this.mappingsConfigForm.enable({emitEvent: false}); + const mappingsControl: Array = []; + if (mappings) { + mappings.forEach((config) => { + mappingsControl.push(this.createdFormGroup(config)); + }); + } + this.mappingsConfigForm.setControl('mappings', this.fb.array(mappingsControl), {emitEvent: false}); + if (!mappings || !mappings.length) { + this.addMappingConfig(); + } + if (this.disabled) { + this.mappingsConfigForm.disable({emitEvent: false}); + } else { + this.mappingsConfigForm.enable({emitEvent: false}); + } } - this.valueChange$ = this.mappingsConfigForm.valueChanges.subscribe(() => { - this.updateModel(); - }); if (!this.disabled && !this.mappingsConfigForm.valid) { this.updateModel(); } } - mappingsConfigFormArray(): FormArray { + get mappingsConfigFormArray(): FormArray { return this.mappingsConfigForm.get('mappings') as FormArray; } public addMappingConfig() { - this.mappingsConfigFormArray().push(this.createdFormGroup()); + this.mappingsConfigFormArray.push(this.createdFormGroup()); this.mappingsConfigForm.updateValueAndValidity(); if (!this.mappingsConfigForm.valid) { this.updateModel(); @@ -139,7 +138,7 @@ export class SnmpDeviceProfileMappingComponent implements OnInit, OnDestroy, Con } public removeMappingConfig(index: number) { - this.mappingsConfigFormArray().removeAt(index); + this.mappingsConfigFormArray.removeAt(index); } private createdFormGroup(value?: SnmpMapping): FormGroup { diff --git a/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queues.component.ts b/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queues.component.ts index aa5325e974..1c5b110059 100644 --- a/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queues.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queues.component.ts @@ -30,10 +30,11 @@ import { import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; -import { Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; import { QueueInfo } from '@shared/models/queue.models'; import { UtilsService } from '@core/services/utils.service'; import { guid } from '@core/utils'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'tb-tenant-profile-queues', @@ -70,8 +71,7 @@ export class TenantProfileQueuesComponent implements ControlValueAccessor, Valid @Input() disabled: boolean; - private valueChangeSubscription$: Subscription = null; - + private destroy$ = new Subject(); private propagateChange = (v: any) => { }; constructor(private store: Store, @@ -83,12 +83,6 @@ export class TenantProfileQueuesComponent implements ControlValueAccessor, Valid this.propagateChange = fn; } - ngOnDestroy() { - if (this.valueChangeSubscription$) { - this.valueChangeSubscription$.unsubscribe(); - } - } - registerOnTouched(fn: any): void { } @@ -96,6 +90,15 @@ export class TenantProfileQueuesComponent implements ControlValueAccessor, Valid this.tenantProfileQueuesFormGroup = this.fb.group({ queues: this.fb.array([]) }); + + this.tenantProfileQueuesFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => this.updateModel()); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); } get queuesFormArray(): FormArray { @@ -112,30 +115,28 @@ export class TenantProfileQueuesComponent implements ControlValueAccessor, Valid } writeValue(queues: Array | null): void { - if (this.valueChangeSubscription$) { - this.valueChangeSubscription$.unsubscribe(); - } - const queuesControls: Array = []; - if (queues) { - queues.forEach((queue, index) => { - if (!queue.id) { - if (!this.idMap[index]) { - this.idMap.push(guid()); - } - queue.id = this.idMap[index]; - } - queuesControls.push(this.fb.control(queue, [Validators.required])); - }); - } - this.tenantProfileQueuesFormGroup.setControl('queues', this.fb.array(queuesControls)); - if (this.disabled) { - this.tenantProfileQueuesFormGroup.disable({emitEvent: false}); + if (queues.length === this.queuesFormArray.length) { + this.queuesFormArray.patchValue(queues, {emitEvent: false}); } else { - this.tenantProfileQueuesFormGroup.enable({emitEvent: false}); + const queuesControls: Array = []; + if (queues) { + queues.forEach((queue, index) => { + if (!queue.id) { + if (!this.idMap[index]) { + this.idMap.push(guid()); + } + queue.id = this.idMap[index]; + } + queuesControls.push(this.fb.control(queue, [Validators.required])); + }); + } + this.tenantProfileQueuesFormGroup.setControl('queues', this.fb.array(queuesControls), {emitEvent: false}); + if (this.disabled) { + this.tenantProfileQueuesFormGroup.disable({emitEvent: false}); + } else { + this.tenantProfileQueuesFormGroup.enable({emitEvent: false}); + } } - this.valueChangeSubscription$ = this.tenantProfileQueuesFormGroup.valueChanges.subscribe(() => - this.updateModel() - ); } public trackByQueue(index: number, queueControl: AbstractControl) { diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html index 1f5d6273ec..af1f663c1a 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html +++ b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html @@ -17,17 +17,17 @@ -->
-
+
relation.type entity.entity-types  
-
+
+ *ngFor="let relationFilterControl of relationFiltersFormArray.controls; let $index = index">
-
+
relation.any-relation
diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.ts b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.ts index 911aa4fe30..a19d16c689 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.ts +++ b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; import { AbstractControl, ControlValueAccessor, @@ -28,7 +28,8 @@ import { RelationEntityTypeFilter } from '@shared/models/relation.models'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { Subscription } from 'rxjs'; +import { Subject, Subscription } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'tb-relation-filters', @@ -42,7 +43,7 @@ import { Subscription } from 'rxjs'; } ] }) -export class RelationFiltersComponent extends PageComponent implements ControlValueAccessor, OnInit { +export class RelationFiltersComponent extends PageComponent implements ControlValueAccessor, OnInit, OnDestroy { @Input() disabled: boolean; @@ -50,22 +51,32 @@ export class RelationFiltersComponent extends PageComponent implements ControlVa relationFiltersFormGroup: FormGroup; + private destroy$ = new Subject(); private propagateChange = null; - private valueChangeSubscription: Subscription = null; - constructor(protected store: Store, private fb: FormBuilder) { super(store); } ngOnInit(): void { - this.relationFiltersFormGroup = this.fb.group({}); - this.relationFiltersFormGroup.addControl('relationFilters', - this.fb.array([])); + this.relationFiltersFormGroup = this.fb.group({ + relationFilters: this.fb.array([]) + }); + + this.relationFiltersFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => { + this.updateModel(); + }); } - relationFiltersFormArray(): FormArray { + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + get relationFiltersFormArray(): FormArray { return this.relationFiltersFormGroup.get('relationFilters') as FormArray; } @@ -81,19 +92,17 @@ export class RelationFiltersComponent extends PageComponent implements ControlVa } writeValue(filters: Array): void { - if (this.valueChangeSubscription) { - this.valueChangeSubscription.unsubscribe(); - } - const relationFiltersControls: Array = []; - if (filters && filters.length) { - filters.forEach((filter) => { - relationFiltersControls.push(this.createRelationFilterFormGroup(filter)); - }); + if (filters?.length === this.relationFiltersFormArray.length) { + this.relationFiltersFormArray.patchValue(filters, {emitEvent: false}); + } else { + const relationFiltersControls: Array = []; + if (filters && filters.length) { + filters.forEach((filter) => { + relationFiltersControls.push(this.createRelationFilterFormGroup(filter)); + }); + } + this.relationFiltersFormGroup.setControl('relationFilters', this.fb.array(relationFiltersControls), {emitEvent: false}); } - this.relationFiltersFormGroup.setControl('relationFilters', this.fb.array(relationFiltersControls)); - this.valueChangeSubscription = this.relationFiltersFormGroup.valueChanges.subscribe(() => { - this.updateModel(); - }); } public removeFilter(index: number) { @@ -101,12 +110,11 @@ export class RelationFiltersComponent extends PageComponent implements ControlVa } public addFilter() { - const relationFiltersFormArray = this.relationFiltersFormGroup.get('relationFilters') as FormArray; const filter: RelationEntityTypeFilter = { relationType: null, entityTypes: [] }; - relationFiltersFormArray.push(this.createRelationFilterFormGroup(filter)); + this.relationFiltersFormArray.push(this.createRelationFilterFormGroup(filter)); } private createRelationFilterFormGroup(filter: RelationEntityTypeFilter): AbstractControl { diff --git a/ui-ngx/src/app/shared/components/kv-map.component.ts b/ui-ngx/src/app/shared/components/kv-map.component.ts index b32c8c2f4a..21d17a840d 100644 --- a/ui-ngx/src/app/shared/components/kv-map.component.ts +++ b/ui-ngx/src/app/shared/components/kv-map.component.ts @@ -30,7 +30,8 @@ import { import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { Subscription } from 'rxjs'; +import { Subject, Subscription } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'tb-key-val-map', @@ -63,19 +64,27 @@ export class KeyValMapComponent extends PageComponent implements ControlValueAcc kvListFormGroup: FormGroup; + private destroy$ = new Subject(); private propagateChange = null; - private valueChangeSubscription: Subscription = null; - constructor(protected store: Store, private fb: FormBuilder) { super(store); } ngOnInit(): void { - this.kvListFormGroup = this.fb.group({}); - this.kvListFormGroup.addControl('keyVals', - this.fb.array([])); + this.kvListFormGroup = this.fb.group({ + keyVals: this.fb.array([]) + }); + + this.kvListFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => this.updateModel()); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); } keyValsFormArray(): FormArray { @@ -99,9 +108,6 @@ export class KeyValMapComponent extends PageComponent implements ControlValueAcc } writeValue(keyValMap: {[key: string]: string}): void { - if (this.valueChangeSubscription) { - this.valueChangeSubscription.unsubscribe(); - } const keyValsControls: Array = []; if (keyValMap) { for (const property of Object.keys(keyValMap)) { @@ -113,10 +119,7 @@ export class KeyValMapComponent extends PageComponent implements ControlValueAcc } } } - this.kvListFormGroup.setControl('keyVals', this.fb.array(keyValsControls)); - this.valueChangeSubscription = this.kvListFormGroup.valueChanges.subscribe(() => { - this.updateModel(); - }); + this.kvListFormGroup.setControl('keyVals', this.fb.array(keyValsControls), {emitEvent: false}); if (this.disabled) { this.kvListFormGroup.disable({emitEvent: false}); } else { From 36e040d0a7735dad31b196e70e06a314e763a7bb Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Mon, 12 Dec 2022 10:31:58 +0200 Subject: [PATCH 029/496] UI: optimaze imports --- .../home/components/relation/relation-filters.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.ts b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.ts index a19d16c689..009c2fc5f8 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.ts +++ b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.ts @@ -28,7 +28,7 @@ import { RelationEntityTypeFilter } from '@shared/models/relation.models'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { Subject, Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @Component({ From 08e356a4152aef41c895c3fb0206ab3707a48d0c Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 12 Dec 2022 13:00:22 +0200 Subject: [PATCH 030/496] Updated alarm assignment to send websocket update on assign, added originator label to alarm info --- .../queue/DefaultTbCoreConsumerService.java | 4 +- .../DefaultSubscriptionManagerService.java | 15 ++- .../SubscriptionManagerService.java | 3 +- .../subscription/TbAlarmDataSubCtx.java | 3 +- .../subscription/TbSubscriptionUtils.java | 4 +- .../DefaultAlarmSubscriptionService.java | 8 +- .../sub/AlarmSubscriptionUpdate.java | 12 +- common/cluster-api/src/main/proto/queue.proto | 2 + .../server/dao/alarm/AlarmAdditionalInfo.java | 32 +++++ .../dao/alarm/AlarmOperationResult.java | 14 +- .../server/common/data/alarm/AlarmInfo.java | 50 +++++-- .../server/common/data/query/AlarmData.java | 10 +- .../server/dao/alarm/BaseAlarmService.java | 123 +++++++++++++++++- .../server/dao/model/ModelConstants.java | 4 + .../server/dao/model/sql/AlarmInfoEntity.java | 14 +- .../dao/sql/query/AlarmDataAdapter.java | 24 +++- .../query/DefaultAlarmQueryRepository.java | 16 ++- .../alarm/alarm-details-dialog.component.ts | 25 ++++ ui-ngx/src/app/shared/models/alarm.models.ts | 26 ++++ .../assets/locale/locale.constant-en_US.json | 4 + 20 files changed, 351 insertions(+), 42 deletions(-) create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmAdditionalInfo.java 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 931acf908c..c2acd77953 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 @@ -26,6 +26,7 @@ import org.thingsboard.common.util.JacksonUtil; 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.alarm.AlarmInfo; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.rpc.RpcError; @@ -502,7 +503,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService { if (TbSubscriptionType.ALARMS.equals(s.getType())) { @@ -303,6 +304,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene }, s -> alarm.getCreatedTime() >= s.getTs(), s -> alarm, + alarmInfo, false ); callback.onSuccess(); @@ -320,6 +322,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene }, s -> alarm.getCreatedTime() >= s.getTs(), s -> alarm, + null, true ); callback.onSuccess(); @@ -414,18 +417,19 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene private void onLocalAlarmSubUpdate(EntityId entityId, Function castFunction, Predicate filterFunction, - Function processFunction, boolean deleted) { + Function processFunction, AlarmInfo alarmInfo, + boolean deleted) { Set entitySubscriptions = subscriptionsByEntityId.get(entityId); if (entitySubscriptions != null) { entitySubscriptions.stream().map(castFunction).filter(Objects::nonNull).filter(filterFunction).forEach(s -> { Alarm alarm = processFunction.apply(s); if (alarm != null) { if (serviceId.equals(s.getServiceId())) { - AlarmSubscriptionUpdate update = new AlarmSubscriptionUpdate(s.getSubscriptionId(), alarm, deleted); + AlarmSubscriptionUpdate update = new AlarmSubscriptionUpdate(s.getSubscriptionId(), alarm, alarmInfo, deleted); localSubscriptionService.onSubscriptionUpdate(s.getSessionId(), update, TbCallback.EMPTY); } else { TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, s.getServiceId()); - toCoreNotificationsProducer.send(tpi, toProto(s, alarm, deleted), null); + toCoreNotificationsProducer.send(tpi, toProto(s, alarm, alarmInfo, deleted), null); } } }); @@ -562,12 +566,13 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene return new TbProtoQueueMsg<>(subscription.getEntityId().getId(), toCoreMsg); } - private TbProtoQueueMsg toProto(TbSubscription subscription, Alarm alarm, boolean deleted) { + private TbProtoQueueMsg toProto(TbSubscription subscription, Alarm alarm, AlarmInfo alarmInfo, boolean deleted) { TbAlarmSubscriptionUpdateProto.Builder builder = TbAlarmSubscriptionUpdateProto.newBuilder(); builder.setSessionId(subscription.getSessionId()); builder.setSubscriptionId(subscription.getSubscriptionId()); builder.setAlarm(JacksonUtil.toString(alarm)); + builder.setAlarmInfo(JacksonUtil.toString(alarmInfo)); builder.setDeleted(deleted); ToCoreNotificationMsg toCoreMsg = ToCoreNotificationMsg.newBuilder().setToLocalSubscriptionServiceMsg( 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 15aab86327..747a73ebe1 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 @@ -17,6 +17,7 @@ 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.alarm.AlarmInfo; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; @@ -42,7 +43,7 @@ public interface SubscriptionManagerService extends ApplicationListener keys, TbCallback callback); - void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback); + void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, AlarmInfo alarmInfo, TbCallback callback); void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback); 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..7afc82c03b 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 @@ -20,6 +20,7 @@ import lombok.Setter; import lombok.ToString; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.EntityId; @@ -219,7 +220,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx { boolean matchesFilter = filter(alarm); if (onCurrentPage) { if (matchesFilter) { - AlarmData updated = new AlarmData(alarm, current.getOriginatorName(), current.getEntityId()); + AlarmData updated = new AlarmData(alarm, subscriptionUpdate.getAlarmInfo(), current.getEntityId()); updated.getLatest().putAll(current.getLatest()); alarmsMap.put(alarmId, updated); sendWsMsg(new AlarmDataUpdate(cmdId, null, Collections.singletonList(updated), maxEntitiesPerAlarmSubscription, data.getTotalElements())); 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 f8359a21ee..91583e1194 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 @@ -17,6 +17,7 @@ package org.thingsboard.server.service.subscription; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; @@ -190,7 +191,8 @@ public class TbSubscriptionUtils { return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), SubscriptionErrorCode.forCode(proto.getErrorCode()), proto.getErrorMsg()); } else { Alarm alarm = JacksonUtil.fromString(proto.getAlarm(), Alarm.class); - return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), alarm); + AlarmInfo alarmInfo = JacksonUtil.fromString(proto.getAlarmInfo(), AlarmInfo.class); + return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), alarm, alarmInfo); } } 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 f7d121d9ff..634c90880a 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 @@ -187,7 +187,13 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); if (currentPartitions.contains(tpi)) { if (subscriptionManagerService.isPresent()) { - subscriptionManagerService.get().onAlarmUpdate(tenantId, entityId, alarm, TbCallback.EMPTY); + AlarmInfo alarmInfo = new AlarmInfo(alarm); + alarmInfo.setOriginatorName(result.getAlarmAdditionalInfo().getOriginatorName()); + alarmInfo.setOriginatorLabel(result.getAlarmAdditionalInfo().getOriginatorName()); + alarmInfo.setAssigneeFirstName(result.getAlarmAdditionalInfo().getFirstName()); + alarmInfo.setAssigneeLastName(result.getAlarmAdditionalInfo().getLastName()); + alarmInfo.setAssigneeEmail(result.getAlarmAdditionalInfo().getEmail()); + subscriptionManagerService.get().onAlarmUpdate(tenantId, entityId, alarm, alarmInfo, TbCallback.EMPTY); } else { log.warn("Possible misconfiguration because subscriptionManagerService is null!"); } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java b/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java index 3f4bc9ce1e..10b750c5c7 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.telemetry.sub; import lombok.Getter; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.query.AlarmData; @@ -38,16 +39,19 @@ public class AlarmSubscriptionUpdate { @Getter private Alarm alarm; @Getter + private AlarmInfo alarmInfo; + @Getter private boolean alarmDeleted; - public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm) { - this(subscriptionId, alarm, false); + public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm, AlarmInfo alarmInfo) { + this(subscriptionId, alarm, alarmInfo, false); } - public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm, boolean alarmDeleted) { + public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm, AlarmInfo alarmInfo, boolean alarmDeleted) { super(); this.subscriptionId = subscriptionId; this.alarm = alarm; + this.alarmInfo = alarmInfo; this.alarmDeleted = alarmDeleted; } @@ -65,6 +69,6 @@ public class AlarmSubscriptionUpdate { @Override public String toString() { return "AlarmUpdate [subscriptionId=" + subscriptionId + ", errorCode=" + errorCode + ", errorMsg=" + errorMsg + ", alarm=" - + alarm + "]"; + + alarm + ", alarmInfo=" + alarmInfo + "]"; } } diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 5a28d42546..4ef017715d 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -562,6 +562,7 @@ message TbAlarmSubscriptionUpdateProto { string errorMsg = 4; string alarm = 5; bool deleted = 6; + string alarmInfo = 7; } message TbAttributeUpdateProto { @@ -581,6 +582,7 @@ message TbAlarmUpdateProto { int64 tenantIdMSB = 4; int64 tenantIdLSB = 5; string alarm = 6; + string alarmInfo = 7; } message TbAlarmDeleteProto { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmAdditionalInfo.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmAdditionalInfo.java new file mode 100644 index 0000000000..a726ca599a --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmAdditionalInfo.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.alarm; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +@AllArgsConstructor +public class AlarmAdditionalInfo { + private final String originatorName; + private final String originatorLabel; + + private final String firstName; + private final String lastName; + private final String email; +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java index 4940c74191..c8d2c0aad9 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java @@ -28,19 +28,25 @@ public class AlarmOperationResult { private final boolean successful; private final boolean created; private final List propagatedEntitiesList; + private final AlarmAdditionalInfo alarmAdditionalInfo; public AlarmOperationResult(Alarm alarm, boolean successful) { - this(alarm, successful, Collections.emptyList()); + this(alarm, successful, Collections.emptyList(), new AlarmAdditionalInfo(null, null, null, null, null)); } - public AlarmOperationResult(Alarm alarm, boolean successful, List propagatedEntitiesList) { - this(alarm, successful, false, propagatedEntitiesList); + public AlarmOperationResult(Alarm alarm, boolean successful, AlarmAdditionalInfo alarmAdditionalInfo) { + this(alarm, successful, Collections.emptyList(), alarmAdditionalInfo); } - public AlarmOperationResult(Alarm alarm, boolean successful, boolean created, List propagatedEntitiesList) { + public AlarmOperationResult(Alarm alarm, boolean successful, List propagatedEntitiesList, AlarmAdditionalInfo alarmAdditionalInfo) { + this(alarm, successful, false, propagatedEntitiesList, alarmAdditionalInfo); + } + + public AlarmOperationResult(Alarm alarm, boolean successful, boolean created, List propagatedEntitiesList, AlarmAdditionalInfo alarmAdditionalInfo) { this.alarm = alarm; this.successful = successful; this.created = created; this.propagatedEntitiesList = propagatedEntitiesList; + this.alarmAdditionalInfo = alarmAdditionalInfo; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java index 54537276bd..9b55324800 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java @@ -17,15 +17,42 @@ package org.thingsboard.server.common.data.alarm; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import org.thingsboard.server.common.data.User; + +import java.util.Objects; @ApiModel public class AlarmInfo extends Alarm { private static final long serialVersionUID = 2807343093519543363L; + @Getter + @Setter @ApiModelProperty(position = 19, value = "Alarm originator name", example = "Thermostat") private String originatorName; + @Getter + @Setter + @ApiModelProperty(position = 20, value = "Alarm originator label", example = "Thermostat label") + private String originatorLabel; + + @Getter + @Setter + @ApiModelProperty(position = 21, value = "Alarm assignee first name") + private String assigneeFirstName; + + @Getter + @Setter + @ApiModelProperty(position = 22, value = "Alarm assignee last name") + private String assigneeLastName; + + @Getter + @Setter + @ApiModelProperty(position = 23, value = "Alarm assignee email") + private String assigneeEmail; + public AlarmInfo() { super(); } @@ -34,17 +61,13 @@ public class AlarmInfo extends Alarm { super(alarm); } - public AlarmInfo(Alarm alarm, String originatorName) { + public AlarmInfo(Alarm alarm, AlarmInfo alarmInfo) { super(alarm); - this.originatorName = originatorName; - } - - public String getOriginatorName() { - return originatorName; - } - - public void setOriginatorName(String originatorName) { - this.originatorName = originatorName; + originatorName = alarmInfo.originatorName; + originatorLabel = alarmInfo.originatorLabel; + assigneeFirstName = alarmInfo.assigneeFirstName; + assigneeLastName = alarmInfo.assigneeLastName; + assigneeEmail = alarmInfo.assigneeEmail; } @Override @@ -55,8 +78,11 @@ public class AlarmInfo extends Alarm { AlarmInfo alarmInfo = (AlarmInfo) o; - return originatorName != null ? originatorName.equals(alarmInfo.originatorName) : alarmInfo.originatorName == null; - + return (Objects.equals(originatorName, alarmInfo.originatorName)) && + (Objects.equals(originatorLabel, alarmInfo.originatorLabel)) && + (Objects.equals(assigneeFirstName, alarmInfo.assigneeFirstName)) && + (Objects.equals(assigneeLastName, alarmInfo.assigneeLastName)) && + (Objects.equals(assigneeEmail, alarmInfo.assigneeEmail)); } @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java index c288fec13e..cae512af18 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java @@ -31,8 +31,14 @@ public class AlarmData extends AlarmInfo { @Getter private final Map> latest; - public AlarmData(Alarm alarm, String originatorName, EntityId entityId) { - super(alarm, originatorName); + public AlarmData(Alarm alarm, AlarmInfo alarmInfo, EntityId entityId) { + super(alarm, alarmInfo); + this.entityId = entityId; + this.latest = new HashMap<>(); + } + + public AlarmData(Alarm alarm, EntityId entityId) { + super(alarm); this.entityId = entityId; this.latest = new HashMap<>(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 3abefb780b..1ccac7a19b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -27,6 +27,12 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; @@ -34,10 +40,15 @@ import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.EntityAlarm; +import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.exception.ApiUsageLimitsExceededException; import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; @@ -47,9 +58,16 @@ import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationsSearchParameters; +import org.thingsboard.server.dao.asset.AssetService; +import org.thingsboard.server.dao.customer.CustomerService; +import org.thingsboard.server.dao.dashboard.DashboardService; +import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.EntityService; +import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.dao.user.UserService; import javax.annotation.Nullable; import javax.annotation.PostConstruct; @@ -81,6 +99,27 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @Autowired private EntityService entityService; + @Autowired + private UserService userService; + + @Autowired + private TenantService tenantService; + + @Autowired + private CustomerService customerService; + + @Autowired + private DashboardService dashboardService; + + @Autowired + private AssetService assetService; + + @Autowired + private DeviceService deviceService; + + @Autowired + private EntityViewService entityViewService; + @Autowired private DataValidator alarmDataValidator; @@ -151,7 +190,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ if (alarm == null) { return new AlarmOperationResult(alarm, false); } - AlarmOperationResult result = new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm))); + AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(tenantId, alarm); + AlarmOperationResult result = new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)), alarmAdditionalInfo); deleteEntityRelations(tenantId, alarm.getId()); alarmDao.removeById(tenantId, alarm.getUuidId()); return result; @@ -161,7 +201,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ log.debug("New Alarm : {}", alarm); Alarm saved = alarmDao.save(alarm.getTenantId(), alarm); List propagatedEntitiesList = createEntityAlarmRecords(saved); - return new AlarmOperationResult(saved, true, true, propagatedEntitiesList); + AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(alarm.getTenantId(), alarm); + return new AlarmOperationResult(saved, true, true, propagatedEntitiesList, alarmAdditionalInfo); } private List createEntityAlarmRecords(Alarm alarm) throws InterruptedException, ExecutionException { @@ -216,7 +257,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ } else { propagatedEntitiesList = new ArrayList<>(getPropagationEntityIds(result)); } - return new AlarmOperationResult(result, true, propagatedEntitiesList); + AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(newAlarm.getTenantId(), newAlarm); + return new AlarmOperationResult(result, true, propagatedEntitiesList, alarmAdditionalInfo); } @Override @@ -233,7 +275,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ alarm.setStatus(newStatus); alarm.setAckTs(ackTime); alarm = alarmDao.save(alarm.getTenantId(), alarm); - return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm))); + AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(tenantId, alarm); + return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)), alarmAdditionalInfo); } } }); @@ -256,7 +299,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ alarm.setDetails(details); } alarm = alarmDao.save(alarm.getTenantId(), alarm); - return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm))); + AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(tenantId, alarm); + return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)), alarmAdditionalInfo); } } }); @@ -274,7 +318,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ alarm.setAssigneeId(assigneeId); alarm.setAssignTs(assignTime); alarm = alarmDao.save(alarm.getTenantId(), alarm); - return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm))); + AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(tenantId, alarm); + return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)), alarmAdditionalInfo); } } }); @@ -292,7 +337,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ alarm.setAssigneeId(null); alarm.setAssignTs(assignTime); alarm = alarmDao.save(alarm.getTenantId(), alarm); - return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm))); + AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(tenantId, alarm); + return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)), alarmAdditionalInfo); } } }); @@ -396,10 +442,14 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ if (alarm.getAckTs() > existing.getAckTs()) { existing.setAckTs(alarm.getAckTs()); } + if (alarm.getAssignTs() > existing.getAssignTs()) { + existing.setAssignTs(alarm.getAssignTs()); + } existing.setStatus(alarm.getStatus()); existing.setSeverity(alarm.getSeverity()); existing.setDetails(alarm.getDetails()); existing.setCustomerId(alarm.getCustomerId()); + existing.setAssigneeId(alarm.getAssigneeId()); existing.setPropagate(existing.isPropagate() || alarm.isPropagate()); existing.setPropagateToOwner(existing.isPropagateToOwner() || alarm.isPropagateToOwner()); existing.setPropagateToTenant(existing.isPropagateToTenant() || alarm.isPropagateToTenant()); @@ -447,4 +497,63 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ Alarm entity = alarmDao.findAlarmById(tenantId, alarmId.getId()); return function.apply(entity); } + + private AlarmAdditionalInfo getAlarmAdditionalInfo(TenantId tenantId, Alarm alarm) { + AlarmAdditionalInfo.AlarmAdditionalInfoBuilder builder = AlarmAdditionalInfo.builder(); + + addAlarmOriginatorNameAndLabel(tenantId, alarm.getOriginator(), builder); + + if (alarm.getAssigneeId() != null) { + User assignedUser = userService.findUserById(tenantId, alarm.getAssigneeId()); + builder.firstName(assignedUser.getFirstName()); + builder.lastName(assignedUser.getLastName()); + builder.email(assignedUser.getEmail()); + } + return builder.build(); + } + + private void addAlarmOriginatorNameAndLabel(TenantId tenantId, EntityId originatorId, AlarmAdditionalInfo.AlarmAdditionalInfoBuilder builder) { + String originatorName = "Unknown"; + String originatorLabel = "Unknown"; + switch(originatorId.getEntityType()) { + case TENANT: + Tenant tenantOriginator = tenantService.findTenantById((TenantId) originatorId); + originatorName = tenantOriginator.getTitle(); + originatorLabel = tenantOriginator.getEmail(); + break; + case CUSTOMER: + Customer customerOriginator = customerService.findCustomerById(tenantId, (CustomerId) originatorId); + originatorName = customerOriginator.getTitle(); + originatorLabel = customerOriginator.getEmail(); + break; + case USER: + User userOriginator = userService.findUserById(tenantId, (UserId) originatorId); + originatorName = userOriginator.getEmail(); + originatorLabel = userOriginator.getName(); + break; + case DASHBOARD: + Dashboard dashboardOriginator = dashboardService.findDashboardById(tenantId, (DashboardId) originatorId); + originatorName = dashboardOriginator.getTitle(); + originatorLabel = dashboardOriginator.getName(); + break; + case ASSET: + Asset assetOriginator = assetService.findAssetById(tenantId, (AssetId) originatorId); + originatorName = assetOriginator.getName(); + originatorLabel = assetOriginator.getLabel(); + break; + case DEVICE: + Device deviceOriginator = deviceService.findDeviceById(tenantId, (DeviceId) originatorId); + originatorName = deviceOriginator.getName(); + originatorLabel = deviceOriginator.getLabel(); + break; + case ENTITY_VIEW: + EntityView entityViewOriginator = entityViewService.findEntityViewById(tenantId, (EntityViewId) originatorId); + originatorName = entityViewOriginator.getName(); + originatorLabel = entityViewOriginator.getType(); // TODO Should we use something else? + break; + } + + builder.originatorName(originatorName); + builder.originatorLabel(originatorLabel); + } } 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 c64dea0c40..da2ffc7c89 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 @@ -284,10 +284,14 @@ public class ModelConstants { public static final String ALARM_DETAILS_PROPERTY = "details"; public static final String ALARM_ORIGINATOR_ID_PROPERTY = "originator_id"; public static final String ALARM_ORIGINATOR_NAME_PROPERTY = "originator_name"; + public static final String ALARM_ORIGINATOR_LABEL_PROPERTY = "originator_label"; public static final String ALARM_ORIGINATOR_TYPE_PROPERTY = "originator_type"; public static final String ALARM_SEVERITY_PROPERTY = "severity"; public static final String ALARM_STATUS_PROPERTY = "status"; public static final String ALARM_ASSIGNEE_ID_PROPERTY = "assignee_id"; + public static final String ALARM_ASSIGNEE_FIRST_NAME_PROPERTY = "assignee_first_name"; + public static final String ALARM_ASSIGNEE_LAST_NAME_PROPERTY = "assignee_last_name"; + public static final String ALARM_ASSIGNEE_EMAIL_PROPERTY = "assignee_email"; public static final String ALARM_START_TS_PROPERTY = "start_ts"; public static final String ALARM_END_TS_PROPERTY = "end_ts"; public static final String ALARM_ACK_TS_PROPERTY = "ack_ts"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java index cef7c47a73..aada0c7c17 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java @@ -24,6 +24,11 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo; public class AlarmInfoEntity extends AbstractAlarmEntity { private String originatorName; + private String originatorLabel; + + private String assigneeFirstName; + private String assigneeLastName; + private String assigneeEmail; public AlarmInfoEntity() { super(); @@ -35,6 +40,13 @@ public class AlarmInfoEntity extends AbstractAlarmEntity { @Override public AlarmInfo toData() { - return new AlarmInfo(super.toAlarm(), this.originatorName); + AlarmInfo alarmInfo = new AlarmInfo(super.toAlarm()); + alarmInfo.setOriginatorName(originatorName); + alarmInfo.setOriginatorLabel(originatorLabel); + + alarmInfo.setAssigneeFirstName(assigneeFirstName); + alarmInfo.setAssigneeLastName(assigneeLastName); + alarmInfo.setAssigneeEmail(assigneeEmail); + return alarmInfo; } } 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..e7ea6a1b66 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 @@ -28,6 +28,7 @@ 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.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.EntityDataPageLink; @@ -68,6 +69,7 @@ public class AlarmDataAdapter { alarm.setCreatedTime((long) row.get(ModelConstants.CREATED_TIME_PROPERTY)); alarm.setAckTs((long) row.get(ModelConstants.ALARM_ACK_TS_PROPERTY)); alarm.setClearTs((long) row.get(ModelConstants.ALARM_CLEAR_TS_PROPERTY)); + alarm.setAssignTs((long) row.get(ModelConstants.ALARM_ASSIGN_TS_PROPERTY)); alarm.setStartTs((long) row.get(ModelConstants.ALARM_START_TS_PROPERTY)); alarm.setEndTs((long) row.get(ModelConstants.ALARM_END_TS_PROPERTY)); Object additionalInfo = row.get(ModelConstants.ADDITIONAL_INFO_PROPERTY); @@ -81,6 +83,16 @@ public class AlarmDataAdapter { EntityType originatorType = EntityType.values()[(int) row.get(ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY)]; UUID originatorId = (UUID) row.get(ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY); alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(originatorType, originatorId)); + Object assigneeIdObj = row.get(ModelConstants.ASSIGNEE_ID_PROPERTY); + String assigneeFirstName = null; + String assigneeLastName = null; + String assigneeEmail = null; + if (assigneeIdObj != null) { + alarm.setAssigneeId(new UserId((UUID) row.get(ModelConstants.ALARM_ASSIGNEE_ID_PROPERTY))); + assigneeFirstName = row.get(ModelConstants.ALARM_ASSIGNEE_FIRST_NAME_PROPERTY).toString(); + assigneeLastName = row.get(ModelConstants.ALARM_ASSIGNEE_LAST_NAME_PROPERTY).toString(); + assigneeEmail = row.get(ModelConstants.ALARM_ASSIGNEE_EMAIL_PROPERTY).toString(); + } alarm.setPropagate((boolean) row.get(ModelConstants.ALARM_PROPAGATE_PROPERTY)); alarm.setPropagateToOwner((boolean) row.get(ModelConstants.ALARM_PROPAGATE_TO_OWNER_PROPERTY)); alarm.setPropagateToTenant((boolean) row.get(ModelConstants.ALARM_PROPAGATE_TO_TENANT_PROPERTY)); @@ -105,7 +117,17 @@ public class AlarmDataAdapter { EntityId entityId = entityIdMap.get(entityUuid); Object originatorNameObj = row.get(ModelConstants.ALARM_ORIGINATOR_NAME_PROPERTY); String originatorName = originatorNameObj != null ? originatorNameObj.toString() : null; - return new AlarmData(alarm, originatorName, entityId); + Object originatorLabelObj = row.get(ModelConstants.ALARM_ORIGINATOR_LABEL_PROPERTY); + String originatorLabel = originatorLabelObj != null ? originatorLabelObj.toString() : null; + + AlarmData alarmData = new AlarmData(alarm, entityId); + alarmData.setOriginatorName(originatorName); + alarmData.setOriginatorLabel(originatorLabel); + alarmData.setAssigneeFirstName(assigneeFirstName); + alarmData.setAssigneeLastName(assigneeLastName); + alarmData.setAssigneeEmail(assigneeEmail); + + return alarmData; } } 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 d91a8c5fc4..d3b8137f8b 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 @@ -57,6 +57,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { alarmFieldColumnMap.put("ackTime", ModelConstants.ALARM_ACK_TS_PROPERTY); alarmFieldColumnMap.put("clearTs", ModelConstants.ALARM_CLEAR_TS_PROPERTY); alarmFieldColumnMap.put("clearTime", ModelConstants.ALARM_CLEAR_TS_PROPERTY); + alarmFieldColumnMap.put("assignTime", ModelConstants.ALARM_ASSIGN_TS_PROPERTY); alarmFieldColumnMap.put("details", ModelConstants.ADDITIONAL_INFO_PROPERTY); alarmFieldColumnMap.put("endTs", ModelConstants.ALARM_END_TS_PROPERTY); alarmFieldColumnMap.put("endTime", ModelConstants.ALARM_END_TS_PROPERTY); @@ -67,7 +68,12 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { alarmFieldColumnMap.put("severity", ModelConstants.ALARM_SEVERITY_PROPERTY); alarmFieldColumnMap.put("originatorId", ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY); alarmFieldColumnMap.put("originatorType", ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY); + alarmFieldColumnMap.put("assigneeId", ModelConstants.ALARM_ASSIGNEE_ID_PROPERTY); alarmFieldColumnMap.put("originator", "originator_name"); + alarmFieldColumnMap.put("originatorLabel", ModelConstants.ALARM_ORIGINATOR_LABEL_PROPERTY); + alarmFieldColumnMap.put("assigneeFirstName", ModelConstants.ALARM_ASSIGNEE_FIRST_NAME_PROPERTY); + alarmFieldColumnMap.put("assigneeLastName", ModelConstants.ALARM_ASSIGNEE_LAST_NAME_PROPERTY); + alarmFieldColumnMap.put("assigneeEmail", ModelConstants.ALARM_ASSIGNEE_EMAIL_PROPERTY); } private static final String SELECT_ORIGINATOR_NAME = " COALESCE(CASE" + @@ -87,10 +93,15 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { " THEN (select name from entity_view where id = a.originator_id)" + " END, 'Deleted') as originator_name"; + private static final String SELECT_ASSIGNEE_INFO = " tbu.first_name as assignee_first_name," + + " tbu.last_name as assignee_last_name," + + " tbu.email as assignee_email"; + private static final String FIELDS_SELECTION = "select a.id as id," + " a.created_time as created_time," + " a.ack_ts as ack_ts," + " a.clear_ts as clear_ts," + + " a.assign_ts as assign_ts," + " a.additional_info as additional_info," + " a.end_ts as end_ts," + " a.originator_id as originator_id," + @@ -104,9 +115,11 @@ 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.type as type," + SELECT_ORIGINATOR_NAME + ", "; + " a.type as type," + SELECT_ORIGINATOR_NAME + ", " + + SELECT_ASSIGNEE_INFO + ", "; private static final String JOIN_ENTITY_ALARMS = "inner join entity_alarm ea on a.id = ea.alarm_id"; + private static final String LEFT_JOIN_TB_USERS = "left join tb_user tbu on a.assignee_id = tbu.id"; protected final NamedParameterJdbcTemplate jdbcTemplate; private final TransactionTemplate transactionTemplate; @@ -139,6 +152,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { } else { selectPart.append(" a.originator_id as entity_id "); } + fromPart.append(LEFT_JOIN_TB_USERS); EntityDataSortOrder sortOrder = pageLink.getSortOrder(); String textSearchQuery = buildTextSearchQuery(ctx, query.getAlarmFields(), pageLink.getTextSearch()); if (sortOrder != null && sortOrder.getKey().getType().equals(EntityKeyType.ALARM_FIELD)) { diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts index be3aff8c50..f8c257e641 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts @@ -84,10 +84,15 @@ export class AlarmDetailsDialogComponent extends DialogComponent { export interface AlarmInfo extends Alarm { originatorName: string; + assigneeFirstName: string; + assigneeLastName: string; + assigneeEmail: string; } export interface AlarmDataInfo extends AlarmInfo { @@ -126,6 +129,9 @@ export const simulatedAlarm: AlarmInfo = { clearTs: 0, assignTs: 0, originatorName: 'Simulated', + assigneeFirstName: "", + assigneeLastName: "", + assigneeEmail: "test@example.com", originator: { entityType: EntityType.DEVICE, id: '1' @@ -207,6 +213,26 @@ export const alarmFields: {[fieldName: string]: AlarmField} = { keyName: 'status', value: 'status', name: 'alarm.status' + }, + assigneeId: { + keyName: 'assigneeId', + value: 'assigneeId.id', + name: 'alarm.assignee-id' + }, + assigneeFirstName: { + keyName: 'assigneeFirstName', + value: 'assigneeFirstName', + name: 'alarm.assignee-first-name' + }, + assigneeLastName: { + keyName: 'assigneeLastName', + value: 'assigneeLastName', + name: 'alarm.assignee-last-name' + }, + assigneeEmail: { + keyName: 'assigneeEmail', + value: 'assigneeEmail', + name: 'alarm.assignee-email' } }; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 269e2e0f67..d191442427 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -433,6 +433,10 @@ "severity": "Severity", "originator": "Originator", "originator-type": "Originator type", + "assignee-id": "Assignee id", + "assignee-first-name": "Assignee first name", + "assignee-last-name": "Assignee last name", + "assignee-email": "Assignee email", "details": "Details", "status": "Status", "alarm-details": "Alarm details", From f29d1b0effe02a8ce9639b03f845f9bea63f7b88 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 13 Dec 2022 13:39:09 +0200 Subject: [PATCH 031/496] Added HasLabel, HasEmail, HasTitle interfaces to get correctly label in entityService, refactoring for alarms processing. The main point is swithing from Alarm entity to AlarmInfo --- .../queue/DefaultTbCoreConsumerService.java | 3 +- .../DefaultSubscriptionManagerService.java | 27 ++-- .../SubscriptionManagerService.java | 5 +- .../subscription/TbAlarmDataSubCtx.java | 10 +- .../subscription/TbSubscriptionUtils.java | 11 +- .../DefaultAlarmSubscriptionService.java | 24 ++- .../sub/AlarmSubscriptionUpdate.java | 13 +- .../service/ttl/AlarmsCleanUpService.java | 5 +- common/cluster-api/src/main/proto/queue.proto | 8 +- .../dao/alarm/AlarmOperationResult.java | 19 ++- .../server/dao/entity/EntityService.java | 4 + .../server/common/data/ContactBased.java | 2 +- .../server/common/data/Customer.java | 2 +- .../server/common/data/DashboardInfo.java | 2 +- .../server/common/data/Device.java | 2 +- .../server/common/data/EntityType.java | 22 ++- .../server/common/data/HasEmail.java} | 16 +- .../server/common/data/HasLabel.java | 22 +++ .../server/common/data/HasTitle.java | 22 +++ .../server/common/data/OtaPackageInfo.java | 2 +- .../server/common/data/Tenant.java | 2 +- .../server/common/data/alarm/AlarmInfo.java | 15 +- .../server/common/data/asset/Asset.java | 3 +- .../server/common/data/edge/Edge.java | 3 +- .../server/common/data/query/AlarmData.java | 4 +- .../common/data/widget/BaseWidgetType.java | 3 +- .../common/data/widget/WidgetsBundle.java | 3 +- .../server/dao/alarm/BaseAlarmService.java | 139 ++++-------------- .../server/dao/entity/BaseEntityService.java | 26 ++++ .../query/DefaultAlarmQueryRepository.java | 37 ++++- .../dao/service/BaseAlarmServiceTest.java | 30 ++-- .../rule/engine/profile/AlarmState.java | 2 +- 32 files changed, 260 insertions(+), 228 deletions(-) rename common/{dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmAdditionalInfo.java => data/src/main/java/org/thingsboard/server/common/data/HasEmail.java} (62%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/HasLabel.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/HasTitle.java 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 c2acd77953..c106090508 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 @@ -503,14 +503,13 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService { if (TbSubscriptionType.ALARMS.equals(s.getType())) { @@ -302,16 +302,15 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene return null; } }, - s -> alarm.getCreatedTime() >= s.getTs(), - s -> alarm, - alarmInfo, + s -> alarmInfo.getCreatedTime() >= s.getTs(), + s -> alarmInfo, false ); callback.onSuccess(); } @Override - public void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback) { + public void onAlarmDeleted(TenantId tenantId, EntityId entityId, AlarmInfo alarmInfo, TbCallback callback) { onLocalAlarmSubUpdate(entityId, s -> { if (TbSubscriptionType.ALARMS.equals(s.getType())) { @@ -320,9 +319,8 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene return null; } }, - s -> alarm.getCreatedTime() >= s.getTs(), - s -> alarm, - null, + s -> alarmInfo.getCreatedTime() >= s.getTs(), + s -> alarmInfo, true ); callback.onSuccess(); @@ -417,19 +415,19 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene private void onLocalAlarmSubUpdate(EntityId entityId, Function castFunction, Predicate filterFunction, - Function processFunction, AlarmInfo alarmInfo, + Function processFunction, boolean deleted) { Set entitySubscriptions = subscriptionsByEntityId.get(entityId); if (entitySubscriptions != null) { entitySubscriptions.stream().map(castFunction).filter(Objects::nonNull).filter(filterFunction).forEach(s -> { - Alarm alarm = processFunction.apply(s); - if (alarm != null) { + AlarmInfo alarmInfo = processFunction.apply(s); + if (alarmInfo != null) { if (serviceId.equals(s.getServiceId())) { - AlarmSubscriptionUpdate update = new AlarmSubscriptionUpdate(s.getSubscriptionId(), alarm, alarmInfo, deleted); + AlarmSubscriptionUpdate update = new AlarmSubscriptionUpdate(s.getSubscriptionId(), alarmInfo, deleted); localSubscriptionService.onSubscriptionUpdate(s.getSessionId(), update, TbCallback.EMPTY); } else { TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, s.getServiceId()); - toCoreNotificationsProducer.send(tpi, toProto(s, alarm, alarmInfo, deleted), null); + toCoreNotificationsProducer.send(tpi, toProto(s, alarmInfo, deleted), null); } } }); @@ -566,12 +564,11 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene return new TbProtoQueueMsg<>(subscription.getEntityId().getId(), toCoreMsg); } - private TbProtoQueueMsg toProto(TbSubscription subscription, Alarm alarm, AlarmInfo alarmInfo, boolean deleted) { + private TbProtoQueueMsg toProto(TbSubscription subscription, AlarmInfo alarmInfo, boolean deleted) { TbAlarmSubscriptionUpdateProto.Builder builder = TbAlarmSubscriptionUpdateProto.newBuilder(); builder.setSessionId(subscription.getSessionId()); builder.setSubscriptionId(subscription.getSubscriptionId()); - builder.setAlarm(JacksonUtil.toString(alarm)); builder.setAlarmInfo(JacksonUtil.toString(alarmInfo)); builder.setDeleted(deleted); 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 747a73ebe1..63c8c9aa6e 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 @@ -16,7 +16,6 @@ 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.alarm.AlarmInfo; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -43,9 +42,9 @@ public interface SubscriptionManagerService extends ApplicationListener keys, TbCallback callback); - void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, AlarmInfo alarmInfo, TbCallback callback); + void onAlarmUpdate(TenantId tenantId, EntityId entityId, AlarmInfo alarmInfo, TbCallback callback); - void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback); + void onAlarmDeleted(TenantId tenantId, EntityId entityId, AlarmInfo alarmInfo, TbCallback callback); } 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 7afc82c03b..706cbcb8ad 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 @@ -63,10 +63,8 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx { private final AlarmService alarmService; @Getter - @Setter private final LinkedHashMap entitiesMap; @Getter - @Setter private final HashMap alarmsMap; private final int maxEntitiesPerAlarmSubscription; @@ -207,8 +205,8 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx { } private void sendWsMsg(String sessionId, AlarmSubscriptionUpdate subscriptionUpdate) { - Alarm alarm = subscriptionUpdate.getAlarm(); - AlarmId alarmId = alarm.getId(); + AlarmInfo alarmInfo = subscriptionUpdate.getAlarmInfo(); + AlarmId alarmId = alarmInfo.getId(); if (subscriptionUpdate.isAlarmDeleted()) { Alarm deleted = alarmsMap.remove(alarmId); if (deleted != null) { @@ -217,10 +215,10 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx { } else { AlarmData current = alarmsMap.get(alarmId); boolean onCurrentPage = current != null; - boolean matchesFilter = filter(alarm); + boolean matchesFilter = filter(alarmInfo); if (onCurrentPage) { if (matchesFilter) { - AlarmData updated = new AlarmData(alarm, subscriptionUpdate.getAlarmInfo(), current.getEntityId()); + AlarmData updated = new AlarmData(alarmInfo, current.getEntityId()); updated.getLatest().putAll(current.getLatest()); alarmsMap.put(alarmId, updated); sendWsMsg(new AlarmDataUpdate(cmdId, null, Collections.singletonList(updated), maxEntitiesPerAlarmSubscription, data.getTotalElements())); 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 91583e1194..7972f6f833 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 @@ -190,9 +190,8 @@ public class TbSubscriptionUtils { if (proto.getErrorCode() > 0) { return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), SubscriptionErrorCode.forCode(proto.getErrorCode()), proto.getErrorMsg()); } else { - Alarm alarm = JacksonUtil.fromString(proto.getAlarm(), Alarm.class); AlarmInfo alarmInfo = JacksonUtil.fromString(proto.getAlarmInfo(), AlarmInfo.class); - return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), alarm, alarmInfo); + return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), alarmInfo); } } @@ -318,27 +317,27 @@ public class TbSubscriptionUtils { return entry; } - public static ToCoreMsg toAlarmUpdateProto(TenantId tenantId, EntityId entityId, Alarm alarm) { + public static ToCoreMsg toAlarmUpdateProto(TenantId tenantId, EntityId entityId, AlarmInfo alarmInfo) { TbAlarmUpdateProto.Builder builder = TbAlarmUpdateProto.newBuilder(); builder.setEntityType(entityId.getEntityType().name()); builder.setEntityIdMSB(entityId.getId().getMostSignificantBits()); builder.setEntityIdLSB(entityId.getId().getLeastSignificantBits()); builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); - builder.setAlarm(JacksonUtil.toString(alarm)); + builder.setAlarmInfo(JacksonUtil.toString(alarmInfo)); SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder(); msgBuilder.setAlarmUpdate(builder); return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build(); } - public static ToCoreMsg toAlarmDeletedProto(TenantId tenantId, EntityId entityId, Alarm alarm) { + public static ToCoreMsg toAlarmDeletedProto(TenantId tenantId, EntityId entityId, AlarmInfo alarmInfo) { TbAlarmDeleteProto.Builder builder = TbAlarmDeleteProto.newBuilder(); builder.setEntityType(entityId.getEntityType().name()); builder.setEntityIdMSB(entityId.getId().getMostSignificantBits()); builder.setEntityIdLSB(entityId.getId().getLeastSignificantBits()); builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); - builder.setAlarm(JacksonUtil.toString(alarm)); + builder.setAlarmInfo(JacksonUtil.toString(alarmInfo)); SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder(); msgBuilder.setAlarmDelete(builder); return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).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 634c90880a..9ab3858730 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 @@ -95,7 +95,7 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService if (result.isCreated()) { apiUsageClient.report(alarm.getTenantId(), null, ApiUsageRecordKey.CREATED_ALARMS_COUNT); } - return result.getAlarm(); + return result.getAlarmInfo(); } @Override @@ -181,24 +181,18 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService private void onAlarmUpdated(AlarmOperationResult result) { wsCallBackExecutor.submit(() -> { - Alarm alarm = result.getAlarm(); - TenantId tenantId = result.getAlarm().getTenantId(); + AlarmInfo alarmInfo = result.getAlarmInfo(); + TenantId tenantId = alarmInfo.getTenantId(); for (EntityId entityId : result.getPropagatedEntitiesList()) { TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); if (currentPartitions.contains(tpi)) { if (subscriptionManagerService.isPresent()) { - AlarmInfo alarmInfo = new AlarmInfo(alarm); - alarmInfo.setOriginatorName(result.getAlarmAdditionalInfo().getOriginatorName()); - alarmInfo.setOriginatorLabel(result.getAlarmAdditionalInfo().getOriginatorName()); - alarmInfo.setAssigneeFirstName(result.getAlarmAdditionalInfo().getFirstName()); - alarmInfo.setAssigneeLastName(result.getAlarmAdditionalInfo().getLastName()); - alarmInfo.setAssigneeEmail(result.getAlarmAdditionalInfo().getEmail()); - subscriptionManagerService.get().onAlarmUpdate(tenantId, entityId, alarm, alarmInfo, TbCallback.EMPTY); + subscriptionManagerService.get().onAlarmUpdate(tenantId, entityId, alarmInfo, TbCallback.EMPTY); } else { log.warn("Possible misconfiguration because subscriptionManagerService is null!"); } } else { - TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm); + TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarmInfo); clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null); } } @@ -207,18 +201,18 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService private void onAlarmDeleted(AlarmOperationResult result) { wsCallBackExecutor.submit(() -> { - Alarm alarm = result.getAlarm(); - TenantId tenantId = result.getAlarm().getTenantId(); + AlarmInfo alarmInfo = result.getAlarmInfo(); + TenantId tenantId = alarmInfo.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); + subscriptionManagerService.get().onAlarmDeleted(tenantId, entityId, alarmInfo, TbCallback.EMPTY); } else { log.warn("Possible misconfiguration because subscriptionManagerService is null!"); } } else { - TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAlarmDeletedProto(tenantId, entityId, alarm); + TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAlarmDeletedProto(tenantId, entityId, alarmInfo); clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null); } } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java b/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java index 10b750c5c7..47eb7c69bf 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java @@ -37,20 +37,17 @@ public class AlarmSubscriptionUpdate { @Getter private String errorMsg; @Getter - private Alarm alarm; - @Getter private AlarmInfo alarmInfo; @Getter private boolean alarmDeleted; - public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm, AlarmInfo alarmInfo) { - this(subscriptionId, alarm, alarmInfo, false); + public AlarmSubscriptionUpdate(int subscriptionId, AlarmInfo alarmInfo) { + this(subscriptionId, alarmInfo, false); } - public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm, AlarmInfo alarmInfo, boolean alarmDeleted) { + public AlarmSubscriptionUpdate(int subscriptionId, AlarmInfo alarmInfo, boolean alarmDeleted) { super(); this.subscriptionId = subscriptionId; - this.alarm = alarm; this.alarmInfo = alarmInfo; this.alarmDeleted = alarmDeleted; } @@ -68,7 +65,7 @@ public class AlarmSubscriptionUpdate { @Override public String toString() { - return "AlarmUpdate [subscriptionId=" + subscriptionId + ", errorCode=" + errorCode + ", errorMsg=" + errorMsg + ", alarm=" - + alarm + ", alarmInfo=" + alarmInfo + "]"; + return "AlarmUpdate [subscriptionId=" + subscriptionId + ", errorCode=" + errorCode + ", errorMsg=" + errorMsg + + ", alarmInfo=" + alarmInfo + "]"; } } diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java index e178055706..cd6fbb7eb5 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java @@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.TenantId; @@ -83,8 +84,8 @@ public class AlarmsCleanUpService { PageData toRemove = alarmDao.findAlarmsIdsByEndTsBeforeAndTenantId(expirationTime, tenantId, removalBatchRequest); toRemove.getData().forEach(alarmId -> { relationService.deleteEntityRelations(tenantId, alarmId); - Alarm alarm = alarmService.deleteAlarm(tenantId, alarmId).getAlarm(); - entityActionService.pushEntityActionToRuleEngine(alarm.getOriginator(), alarm, tenantId, null, ActionType.ALARM_DELETE, null); + AlarmInfo alarmInfo = alarmService.deleteAlarm(tenantId, alarmId).getAlarmInfo(); + entityActionService.pushEntityActionToRuleEngine(alarmInfo.getOriginator(), alarmInfo, tenantId, null, ActionType.ALARM_DELETE, null); }); totalRemoved += toRemove.getTotalElements(); diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 4ef017715d..301c395a18 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -560,9 +560,8 @@ message TbAlarmSubscriptionUpdateProto { int32 subscriptionId = 2; int32 errorCode = 3; string errorMsg = 4; - string alarm = 5; + string alarmInfo = 5; bool deleted = 6; - string alarmInfo = 7; } message TbAttributeUpdateProto { @@ -581,8 +580,7 @@ message TbAlarmUpdateProto { int64 entityIdLSB = 3; int64 tenantIdMSB = 4; int64 tenantIdLSB = 5; - string alarm = 6; - string alarmInfo = 7; + string alarmInfo = 6; } message TbAlarmDeleteProto { @@ -591,7 +589,7 @@ message TbAlarmDeleteProto { int64 entityIdLSB = 3; int64 tenantIdMSB = 4; int64 tenantIdLSB = 5; - string alarm = 6; + string alarmInfo = 6; } message TbAttributeDeleteProto { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java index c8d2c0aad9..10fac5de08 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.alarm; import lombok.Data; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.id.EntityId; import java.util.Collections; @@ -24,29 +25,27 @@ import java.util.List; @Data public class AlarmOperationResult { - private final Alarm alarm; private final boolean successful; private final boolean created; private final List propagatedEntitiesList; - private final AlarmAdditionalInfo alarmAdditionalInfo; + private final AlarmInfo alarmInfo; public AlarmOperationResult(Alarm alarm, boolean successful) { - this(alarm, successful, Collections.emptyList(), new AlarmAdditionalInfo(null, null, null, null, null)); + this(new AlarmInfo(alarm, null, null, null, null, null), successful, Collections.emptyList()); } - public AlarmOperationResult(Alarm alarm, boolean successful, AlarmAdditionalInfo alarmAdditionalInfo) { - this(alarm, successful, Collections.emptyList(), alarmAdditionalInfo); + public AlarmOperationResult(AlarmInfo alarmInfo, boolean successful) { + this(alarmInfo, successful, Collections.emptyList()); } - public AlarmOperationResult(Alarm alarm, boolean successful, List propagatedEntitiesList, AlarmAdditionalInfo alarmAdditionalInfo) { - this(alarm, successful, false, propagatedEntitiesList, alarmAdditionalInfo); + public AlarmOperationResult(AlarmInfo alarmInfo, boolean successful, List propagatedEntitiesList) { + this(alarmInfo, successful, false, propagatedEntitiesList); } - public AlarmOperationResult(Alarm alarm, boolean successful, boolean created, List propagatedEntitiesList, AlarmAdditionalInfo alarmAdditionalInfo) { - this.alarm = alarm; + public AlarmOperationResult(AlarmInfo alarmInfo, boolean successful, boolean created, List propagatedEntitiesList) { + this.alarmInfo = alarmInfo; this.successful = successful; this.created = created; this.propagatedEntitiesList = propagatedEntitiesList; - this.alarmAdditionalInfo = alarmAdditionalInfo; } } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java index 986da96d46..e819420e9d 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java @@ -24,10 +24,14 @@ import org.thingsboard.server.common.data.query.EntityCountQuery; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityDataQuery; +import java.util.Optional; + public interface EntityService { ListenableFuture fetchEntityNameAsync(TenantId tenantId, EntityId entityId); + Optional fetchEntityLabel(TenantId tenantId, EntityId entityId); + CustomerId fetchEntityCustomerId(TenantId tenantId, EntityId entityId); long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java b/common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java index 5ff7b874b3..9e17b72a5b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java @@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; @EqualsAndHashCode(callSuper = true) -public abstract class ContactBased extends SearchTextBasedWithAdditionalInfo implements HasName { +public abstract class ContactBased extends SearchTextBasedWithAdditionalInfo implements HasEmail { private static final long serialVersionUID = 5047448057830660988L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java b/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java index bdd06606a6..9a2a409bcd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java @@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; @EqualsAndHashCode(callSuper = true) -public class Customer extends ContactBased implements HasTenantId, ExportableEntity { +public class Customer extends ContactBased implements HasTenantId, ExportableEntity, HasTitle { private static final long serialVersionUID = -1599722990298929275L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java index 1895b41639..9f25272433 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java @@ -31,7 +31,7 @@ import java.util.Objects; import java.util.Set; @ApiModel -public class DashboardInfo extends SearchTextBased implements HasName, HasTenantId { +public class DashboardInfo extends SearchTextBased implements HasName, HasTenantId, HasTitle { private TenantId tenantId; @NoXss diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java index 4a6589d72c..02f56a4e90 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java @@ -40,7 +40,7 @@ import java.util.Optional; @ApiModel @EqualsAndHashCode(callSuper = true) @Slf4j -public class Device extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, HasCustomerId, HasOtaPackage, ExportableEntity { +public class Device extends SearchTextBasedWithAdditionalInfo implements HasLabel, HasTenantId, HasCustomerId, HasOtaPackage, ExportableEntity { private static final long serialVersionUID = 2807343040519543363L; 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..74e03a50d6 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,25 @@ 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 } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmAdditionalInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/HasEmail.java similarity index 62% rename from common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmAdditionalInfo.java rename to common/data/src/main/java/org/thingsboard/server/common/data/HasEmail.java index a726ca599a..7f413075f8 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmAdditionalInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/HasEmail.java @@ -13,20 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.alarm; +package org.thingsboard.server.common.data; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; +public interface HasEmail extends HasName { -@Builder -@Getter -@AllArgsConstructor -public class AlarmAdditionalInfo { - private final String originatorName; - private final String originatorLabel; + String getEmail(); - private final String firstName; - private final String lastName; - private final String email; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HasLabel.java b/common/data/src/main/java/org/thingsboard/server/common/data/HasLabel.java new file mode 100644 index 0000000000..d9c3c9b98c --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/HasLabel.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; + +public interface HasLabel extends HasName { + + String getLabel(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HasTitle.java b/common/data/src/main/java/org/thingsboard/server/common/data/HasTitle.java new file mode 100644 index 0000000000..886cef5fb1 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/HasTitle.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; + +public interface HasTitle { + + String getTitle(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java index b31d0a6bc0..b1e8c9eb65 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java @@ -36,7 +36,7 @@ import org.thingsboard.server.common.data.ota.OtaPackageType; @Slf4j @Data @EqualsAndHashCode(callSuper = true) -public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId { +public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, HasTitle { private static final long serialVersionUID = 3168391583570815419L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java b/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java index 8d5a9fe3fc..68ad28eece 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java @@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.validation.NoXss; @ApiModel @EqualsAndHashCode(callSuper = true) -public class Tenant extends ContactBased implements HasTenantId { +public class Tenant extends ContactBased implements HasTenantId, HasTitle { private static final long serialVersionUID = 8057243243859922101L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java index 9b55324800..878edb856e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java @@ -61,6 +61,15 @@ public class AlarmInfo extends Alarm { super(alarm); } + public AlarmInfo(Alarm alarm, String originatorName, String originatorLabel, String assigneeFirstName, String assigneeLastName, String assigneeEmail) { + super(alarm); + this.originatorName = originatorName; + this.originatorLabel = originatorLabel; + this.assigneeFirstName = assigneeFirstName; + this.assigneeLastName = assigneeLastName; + this.assigneeEmail = assigneeEmail; + } + public AlarmInfo(Alarm alarm, AlarmInfo alarmInfo) { super(alarm); originatorName = alarmInfo.originatorName; @@ -88,7 +97,11 @@ public class AlarmInfo extends Alarm { @Override public int hashCode() { int result = super.hashCode(); - result = 31 * result + (originatorName != null ? originatorName.hashCode() : 0); + result = 31 * result + (originatorName != null ? originatorName.hashCode() : 0) + + (originatorLabel != null ? originatorLabel.hashCode() : 0) + + (assigneeFirstName != null ? assigneeFirstName.hashCode() : 0) + + (assigneeLastName != null ? assigneeLastName.hashCode() : 0) + + (assigneeEmail != null ? assigneeEmail.hashCode() : 0); return result; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java index e8eeaf75e9..46de963b66 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java @@ -23,6 +23,7 @@ import lombok.Getter; import lombok.Setter; import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.HasCustomerId; +import org.thingsboard.server.common.data.HasLabel; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; @@ -37,7 +38,7 @@ import java.util.Optional; @ApiModel @EqualsAndHashCode(callSuper = true) -public class Asset extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, HasCustomerId, ExportableEntity { +public class Asset extends SearchTextBasedWithAdditionalInfo implements HasLabel, HasTenantId, HasCustomerId, ExportableEntity { private static final long serialVersionUID = 2807343040519543363L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java index 58c23d0877..7ad50a90cf 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java @@ -21,6 +21,7 @@ import lombok.EqualsAndHashCode; import lombok.Setter; import lombok.ToString; import org.thingsboard.server.common.data.HasCustomerId; +import org.thingsboard.server.common.data.HasLabel; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; @@ -35,7 +36,7 @@ import org.thingsboard.server.common.data.validation.NoXss; @EqualsAndHashCode(callSuper = true) @ToString @Setter -public class Edge extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, HasCustomerId { +public class Edge extends SearchTextBasedWithAdditionalInfo implements HasLabel, HasTenantId, HasCustomerId { private static final long serialVersionUID = 4934987555236873728L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java index cae512af18..e7f22f2044 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java @@ -31,8 +31,8 @@ public class AlarmData extends AlarmInfo { @Getter private final Map> latest; - public AlarmData(Alarm alarm, AlarmInfo alarmInfo, EntityId entityId) { - super(alarm, alarmInfo); + public AlarmData(AlarmInfo alarmInfo, EntityId entityId) { + super(alarmInfo); this.entityId = entityId; this.latest = new HashMap<>(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java index be5014a4cd..c165e3443a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.widget; import io.swagger.annotations.ApiModelProperty; import lombok.Data; 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.TenantId; import org.thingsboard.server.common.data.id.WidgetTypeId; @@ -25,7 +26,7 @@ import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; @Data -public class BaseWidgetType extends BaseData implements HasTenantId { +public class BaseWidgetType extends BaseData implements HasName, HasTenantId { private static final long serialVersionUID = 8388684344603660756L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java index eca19f0db5..d862e67da1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java @@ -25,6 +25,7 @@ import lombok.Setter; import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.HasTitle; import org.thingsboard.server.common.data.SearchTextBased; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetsBundleId; @@ -33,7 +34,7 @@ import org.thingsboard.server.common.data.validation.NoXss; @ApiModel @EqualsAndHashCode(callSuper = true) -public class WidgetsBundle extends SearchTextBased implements HasName, HasTenantId, ExportableEntity { +public class WidgetsBundle extends SearchTextBased implements HasName, HasTenantId, ExportableEntity, HasTitle { private static final long serialVersionUID = -7627368878362410489L; diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 1ccac7a19b..4fc5075fb3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -27,11 +27,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.Dashboard; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; @@ -40,15 +35,10 @@ import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.EntityAlarm; -import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.exception.ApiUsageLimitsExceededException; import org.thingsboard.server.common.data.id.AlarmId; -import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DashboardId; -import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; @@ -58,15 +48,9 @@ import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationsSearchParameters; -import org.thingsboard.server.dao.asset.AssetService; -import org.thingsboard.server.dao.customer.CustomerService; -import org.thingsboard.server.dao.dashboard.DashboardService; -import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.EntityService; -import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.service.DataValidator; -import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; import javax.annotation.Nullable; @@ -102,24 +86,6 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @Autowired private UserService userService; - @Autowired - private TenantService tenantService; - - @Autowired - private CustomerService customerService; - - @Autowired - private DashboardService dashboardService; - - @Autowired - private AssetService assetService; - - @Autowired - private DeviceService deviceService; - - @Autowired - private EntityViewService entityViewService; - @Autowired private DataValidator alarmDataValidator; @@ -190,8 +156,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ if (alarm == null) { return new AlarmOperationResult(alarm, false); } - AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(tenantId, alarm); - AlarmOperationResult result = new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)), alarmAdditionalInfo); + AlarmInfo alarmInfo = getAlarmInfo(tenantId, alarm); + AlarmOperationResult result = new AlarmOperationResult(alarmInfo, true, new ArrayList<>(getPropagationEntityIds(alarm))); deleteEntityRelations(tenantId, alarm.getId()); alarmDao.removeById(tenantId, alarm.getUuidId()); return result; @@ -201,8 +167,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ log.debug("New Alarm : {}", alarm); Alarm saved = alarmDao.save(alarm.getTenantId(), alarm); List propagatedEntitiesList = createEntityAlarmRecords(saved); - AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(alarm.getTenantId(), alarm); - return new AlarmOperationResult(saved, true, true, propagatedEntitiesList, alarmAdditionalInfo); + AlarmInfo alarmInfo = getAlarmInfo(alarm.getTenantId(), alarm); + return new AlarmOperationResult(alarmInfo, true, true, propagatedEntitiesList); } private List createEntityAlarmRecords(Alarm alarm) throws InterruptedException, ExecutionException { @@ -257,8 +223,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ } else { propagatedEntitiesList = new ArrayList<>(getPropagationEntityIds(result)); } - AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(newAlarm.getTenantId(), newAlarm); - return new AlarmOperationResult(result, true, propagatedEntitiesList, alarmAdditionalInfo); + AlarmInfo alarmInfo = getAlarmInfo(newAlarm.getTenantId(), newAlarm); + return new AlarmOperationResult(alarmInfo, true, propagatedEntitiesList); } @Override @@ -275,8 +241,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ alarm.setStatus(newStatus); alarm.setAckTs(ackTime); alarm = alarmDao.save(alarm.getTenantId(), alarm); - AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(tenantId, alarm); - return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)), alarmAdditionalInfo); + AlarmInfo alarmInfo = getAlarmInfo(tenantId, alarm); + return new AlarmOperationResult(alarmInfo, true, new ArrayList<>(getPropagationEntityIds(alarm))); } } }); @@ -299,8 +265,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ alarm.setDetails(details); } alarm = alarmDao.save(alarm.getTenantId(), alarm); - AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(tenantId, alarm); - return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)), alarmAdditionalInfo); + AlarmInfo alarmInfo = getAlarmInfo(tenantId, alarm); + return new AlarmOperationResult(alarmInfo, true, new ArrayList<>(getPropagationEntityIds(alarm))); } } }); @@ -318,8 +284,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ alarm.setAssigneeId(assigneeId); alarm.setAssignTs(assignTime); alarm = alarmDao.save(alarm.getTenantId(), alarm); - AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(tenantId, alarm); - return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)), alarmAdditionalInfo); + AlarmInfo alarmInfo = getAlarmInfo(tenantId, alarm); + return new AlarmOperationResult(alarmInfo, true, new ArrayList<>(getPropagationEntityIds(alarm))); } } }); @@ -337,8 +303,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ alarm.setAssigneeId(null); alarm.setAssignTs(assignTime); alarm = alarmDao.save(alarm.getTenantId(), alarm); - AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(tenantId, alarm); - return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)), alarmAdditionalInfo); + AlarmInfo alarmInfo = getAlarmInfo(tenantId, alarm); + return new AlarmOperationResult(alarmInfo, true, new ArrayList<>(getPropagationEntityIds(alarm))); } } }); @@ -362,15 +328,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ public ListenableFuture findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId) { log.trace("Executing findAlarmInfoByIdAsync [{}]", alarmId); validateId(alarmId, "Incorrect alarmId " + alarmId); - return Futures.transformAsync(alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId()), - a -> { - AlarmInfo alarmInfo = new AlarmInfo(a); - return Futures.transform( - entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> { - alarmInfo.setOriginatorName(originatorName); - return alarmInfo; - }, MoreExecutors.directExecutor()); - }, MoreExecutors.directExecutor()); + return Futures.transform(alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId()), + a -> getAlarmInfo(tenantId, a), MoreExecutors.directExecutor()); } @Override @@ -478,7 +437,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ private void createEntityAlarmRecord(TenantId tenantId, EntityId entityId, Alarm alarm) { // TODO Add ability to automatically assign created alarm to some user - EntityAlarm entityAlarm = new EntityAlarm(tenantId, entityId, alarm.getCreatedTime(), alarm.getType(), alarm.getCustomerId(), null,alarm.getId()); + EntityAlarm entityAlarm = new EntityAlarm(tenantId, entityId, alarm.getCreatedTime(), alarm.getType(), alarm.getCustomerId(), null, alarm.getId()); try { alarmDao.createEntityAlarmRecord(entityAlarm); } catch (Exception e) { @@ -498,62 +457,22 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ return function.apply(entity); } - private AlarmAdditionalInfo getAlarmAdditionalInfo(TenantId tenantId, Alarm alarm) { - AlarmAdditionalInfo.AlarmAdditionalInfoBuilder builder = AlarmAdditionalInfo.builder(); + private AlarmInfo getAlarmInfo(TenantId tenantId, Alarm alarm) { + String originatorName = null; + String originatorLabel = null; + String assigneeFirstName = null; + String assigneeLastName = null; + String assigneeEmail = null; - addAlarmOriginatorNameAndLabel(tenantId, alarm.getOriginator(), builder); + originatorName = entityService.fetchEntityName(tenantId, alarm.getOriginator()); + originatorLabel = entityService.fetchEntityLabel(tenantId, alarm.getOriginator()); if (alarm.getAssigneeId() != null) { User assignedUser = userService.findUserById(tenantId, alarm.getAssigneeId()); - builder.firstName(assignedUser.getFirstName()); - builder.lastName(assignedUser.getLastName()); - builder.email(assignedUser.getEmail()); + assigneeFirstName = assignedUser.getFirstName(); + assigneeLastName = assignedUser.getLastName(); + assigneeEmail = assignedUser.getEmail(); } - return builder.build(); - } - - private void addAlarmOriginatorNameAndLabel(TenantId tenantId, EntityId originatorId, AlarmAdditionalInfo.AlarmAdditionalInfoBuilder builder) { - String originatorName = "Unknown"; - String originatorLabel = "Unknown"; - switch(originatorId.getEntityType()) { - case TENANT: - Tenant tenantOriginator = tenantService.findTenantById((TenantId) originatorId); - originatorName = tenantOriginator.getTitle(); - originatorLabel = tenantOriginator.getEmail(); - break; - case CUSTOMER: - Customer customerOriginator = customerService.findCustomerById(tenantId, (CustomerId) originatorId); - originatorName = customerOriginator.getTitle(); - originatorLabel = customerOriginator.getEmail(); - break; - case USER: - User userOriginator = userService.findUserById(tenantId, (UserId) originatorId); - originatorName = userOriginator.getEmail(); - originatorLabel = userOriginator.getName(); - break; - case DASHBOARD: - Dashboard dashboardOriginator = dashboardService.findDashboardById(tenantId, (DashboardId) originatorId); - originatorName = dashboardOriginator.getTitle(); - originatorLabel = dashboardOriginator.getName(); - break; - case ASSET: - Asset assetOriginator = assetService.findAssetById(tenantId, (AssetId) originatorId); - originatorName = assetOriginator.getName(); - originatorLabel = assetOriginator.getLabel(); - break; - case DEVICE: - Device deviceOriginator = deviceService.findDeviceById(tenantId, (DeviceId) originatorId); - originatorName = deviceOriginator.getName(); - originatorLabel = deviceOriginator.getLabel(); - break; - case ENTITY_VIEW: - EntityView entityViewOriginator = entityViewService.findEntityViewById(tenantId, (EntityViewId) originatorId); - originatorName = entityViewOriginator.getName(); - originatorLabel = entityViewOriginator.getType(); // TODO Should we use something else? - break; - } - - builder.originatorName(originatorName); - builder.originatorLabel(originatorLabel); + return new AlarmInfo(alarm, originatorName, originatorLabel, assigneeFirstName, assigneeLastName, assigneeEmail); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java index 49cea353fb..0f74f13fd9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java @@ -24,7 +24,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.thingsboard.server.common.data.HasCustomerId; +import org.thingsboard.server.common.data.HasEmail; +import org.thingsboard.server.common.data.HasLabel; import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.HasTitle; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; @@ -33,6 +36,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; 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.RuleChainId; import org.thingsboard.server.common.data.id.TbResourceId; @@ -58,6 +62,8 @@ import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; +import java.util.Optional; + import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -175,6 +181,26 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe return entityName; } + @Override + public Optional fetchEntityLabel(TenantId tenantId, EntityId entityId) { + return Optional.empty(); + HasId entity = fetchEntity(tenantId, entityId); + String entityLabel = null; + if (entity instanceof HasTitle) { + entityLabel = ((HasTitle) entity).getTitle(); + } + if (entity instanceof HasLabel && entityLabel == null) { + entityLabel = ((HasLabel) entity).getLabel(); + } + if (entity instanceof HasEmail && entityLabel == null) { + entityLabel = ((HasEmail) entity).getEmail(); + } + if (entity instanceof HasName && entityLabel == null) { + entityLabel = ((HasName) entity).getName(); + } + return Optional.ofNullable(entityLabel); + } + @Override public CustomerId fetchEntityCustomerId(TenantId tenantId, EntityId entityId) { log.trace("Executing fetchEntityCustomerId [{}]", entityId); 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 d3b8137f8b..0e0bf23f45 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 @@ -69,7 +69,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { alarmFieldColumnMap.put("originatorId", ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY); alarmFieldColumnMap.put("originatorType", ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY); alarmFieldColumnMap.put("assigneeId", ModelConstants.ALARM_ASSIGNEE_ID_PROPERTY); - alarmFieldColumnMap.put("originator", "originator_name"); + alarmFieldColumnMap.put("originator", ModelConstants.ALARM_ORIGINATOR_NAME_PROPERTY); alarmFieldColumnMap.put("originatorLabel", ModelConstants.ALARM_ORIGINATOR_LABEL_PROPERTY); alarmFieldColumnMap.put("assigneeFirstName", ModelConstants.ALARM_ASSIGNEE_FIRST_NAME_PROPERTY); alarmFieldColumnMap.put("assigneeLastName", ModelConstants.ALARM_ASSIGNEE_LAST_NAME_PROPERTY); @@ -91,7 +91,36 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { " THEN (select name from device where id = a.originator_id)" + " WHEN a.originator_type = " + EntityType.ENTITY_VIEW.ordinal() + " THEN (select name from entity_view where id = a.originator_id)" + - " END, 'Deleted') as originator_name"; + " WHEN a.originator_type = " + EntityType.DEVICE_PROFILE.ordinal() + + " THEN (select name from device_profile where id = a.originator_id)" + + " WHEN a.originator_type = " + EntityType.ASSET_PROFILE.ordinal() + + " THEN (select name from asset_profile where id = a.originator_id)" + + " WHEN a.originator_type = " + EntityType.EDGE.ordinal() + + " THEN (select name from edge where id = a.originator_id)" + + " END, 'Deleted') as " + ModelConstants.ALARM_ORIGINATOR_NAME_PROPERTY; + + private static final String SELECT_ORIGINATOR_LABEL = " COALESCE(CASE" + + " WHEN a.originator_type = " + EntityType.TENANT.ordinal() + + " THEN (select title from tenant where id = a.originator_id)" + + " WHEN a.originator_type = " + EntityType.CUSTOMER.ordinal() + + " THEN (select COALESCE(title, email) from customer where id = a.originator_id)" + + " WHEN a.originator_type = " + EntityType.USER.ordinal() + + " THEN (select email from tb_user where id = a.originator_id)" + + " WHEN a.originator_type = " + EntityType.DASHBOARD.ordinal() + + " THEN (select title from dashboard where id = a.originator_id)" + + " WHEN a.originator_type = " + EntityType.ASSET.ordinal() + + " THEN (select COALESCE(label, name) from asset where id = a.originator_id)" + + " WHEN a.originator_type = " + EntityType.DEVICE.ordinal() + + " THEN (select COALESCE(label, name) from device where id = a.originator_id)" + + " WHEN a.originator_type = " + EntityType.ENTITY_VIEW.ordinal() + + " THEN (select name from entity_view where id = a.originator_id)" + + " WHEN a.originator_type = " + EntityType.DEVICE_PROFILE.ordinal() + + " THEN (select name from device_profile where id = a.originator_id)" + + " WHEN a.originator_type = " + EntityType.ASSET_PROFILE.ordinal() + + " THEN (select name from asset_profile where id = a.originator_id)" + + " WHEN a.originator_type = " + EntityType.EDGE.ordinal() + + " THEN (select COALESCE(label, name) from edge where id = a.originator_id)" + + " END, 'Deleted') as " + ModelConstants.ALARM_ORIGINATOR_LABEL_PROPERTY; private static final String SELECT_ASSIGNEE_INFO = " tbu.first_name as assignee_first_name," + " tbu.last_name as assignee_last_name," + @@ -115,7 +144,9 @@ 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.type as type," + SELECT_ORIGINATOR_NAME + ", " + + " a.type as type," + + SELECT_ORIGINATOR_NAME + ", " + + SELECT_ORIGINATOR_LABEL + ", " + SELECT_ASSIGNEE_INFO + ", "; private static final String JOIN_ENTITY_ALARMS = "inner join entity_alarm ea on a.id = ea.alarm_id"; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java index ba22a57110..0d0503275d 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java @@ -89,7 +89,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .startTs(ts).build(); AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm); - Alarm created = result.getAlarm(); + Alarm created = result.getAlarmInfo(); Assert.assertNotNull(created); Assert.assertNotNull(created.getId()); @@ -128,7 +128,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .startTs(ts).build(); AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm); - Alarm created = result.getAlarm(); + Alarm created = result.getAlarmInfo(); // Check child relation PageData alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() @@ -153,7 +153,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { created.setPropagate(true); result = alarmService.createOrUpdateAlarm(created); - created = result.getAlarm(); + created = result.getAlarmInfo(); // Check child relation alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() @@ -242,7 +242,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) .startTs(ts).build(); AlarmOperationResult result = alarmService.createOrUpdateAlarm(tenantAlarm); - tenantAlarm = result.getAlarm(); + tenantAlarm = result.getAlarmInfo(); Alarm deviceAlarm = Alarm.builder().tenantId(tenantId) .originator(customerDevice.getId()) @@ -251,7 +251,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) .startTs(ts).build(); result = alarmService.createOrUpdateAlarm(deviceAlarm); - deviceAlarm = result.getAlarm(); + deviceAlarm = result.getAlarmInfo(); AlarmDataPageLink pageLink = new AlarmDataPageLink(); pageLink.setPage(0); @@ -318,7 +318,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) .startTs(ts).build(); AlarmOperationResult result = alarmService.createOrUpdateAlarm(tenantAlarm); - tenantAlarm = result.getAlarm(); + tenantAlarm = result.getAlarmInfo(); Alarm customerAlarm = Alarm.builder().tenantId(tenantId) .originator(tenantDevice.getId()) @@ -327,7 +327,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) .startTs(ts).build(); result = alarmService.createOrUpdateAlarm(customerAlarm); - customerAlarm = result.getAlarm(); + customerAlarm = result.getAlarmInfo(); AlarmDataPageLink pageLink = new AlarmDataPageLink(); pageLink.setPage(0); @@ -368,7 +368,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) .startTs(ts).build(); AlarmOperationResult result = alarmService.createOrUpdateAlarm(tenantAlarm); - tenantAlarm = result.getAlarm(); + tenantAlarm = result.getAlarmInfo(); Alarm customerAlarm = Alarm.builder().tenantId(tenantId) .originator(device.getId()) @@ -378,7 +378,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) .startTs(ts).build(); result = alarmService.createOrUpdateAlarm(customerAlarm); - customerAlarm = result.getAlarm(); + customerAlarm = result.getAlarmInfo(); AlarmDataPageLink pageLink = new AlarmDataPageLink(); pageLink.setPage(0); @@ -435,7 +435,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .status(AlarmStatus.ACTIVE_UNACK) .startTs(System.currentTimeMillis()) .build(); - alarm1 = alarmService.createOrUpdateAlarm(alarm1).getAlarm(); + alarm1 = alarmService.createOrUpdateAlarm(alarm1).getAlarmInfo(); alarmService.clearAlarm(tenantId, alarm1.getId(), null, System.currentTimeMillis()).get(); Alarm alarm2 = Alarm.builder() @@ -446,7 +446,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .status(AlarmStatus.ACTIVE_ACK) .startTs(System.currentTimeMillis()) .build(); - alarm2 = alarmService.createOrUpdateAlarm(alarm2).getAlarm(); + alarm2 = alarmService.createOrUpdateAlarm(alarm2).getAlarmInfo(); alarmService.clearAlarm(tenantId, alarm2.getId(), null, System.currentTimeMillis()).get(); Alarm alarm3 = Alarm.builder() @@ -457,7 +457,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .status(AlarmStatus.ACTIVE_ACK) .startTs(System.currentTimeMillis()) .build(); - alarm3 = alarmService.createOrUpdateAlarm(alarm3).getAlarm(); + alarm3 = alarmService.createOrUpdateAlarm(alarm3).getAlarmInfo(); Assert.assertEquals(AlarmSeverity.MAJOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), AlarmSearchStatus.UNACK, null, null)); Assert.assertEquals(AlarmSeverity.CRITICAL, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, null, null)); @@ -487,7 +487,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .startTs(ts).build(); AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm); - Alarm created = result.getAlarm(); + Alarm created = result.getAlarmInfo(); AlarmDataPageLink pageLink = new AlarmDataPageLink(); pageLink.setPage(0); @@ -530,7 +530,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { // Check child relation created.setPropagate(true); result = alarmService.createOrUpdateAlarm(created); - created = result.getAlarm(); + created = result.getAlarmInfo(); // Check child relation pageLink.setPage(0); @@ -645,7 +645,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .startTs(ts).build(); AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm); - Alarm created = result.getAlarm(); + Alarm created = result.getAlarmInfo(); PageData alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(childId) 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..bbda497b46 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 @@ -132,7 +132,7 @@ class AlarmState { ); DonAsynchron.withCallback(alarmClearOperationResult, result -> { - pushMsg(ctx, msg, new TbAlarmResult(false, false, true, result.getAlarm()), clearState); + pushMsg(ctx, msg, new TbAlarmResult(false, false, true, result.getAlarmInfo()), clearState); }, throwable -> { throw new RuntimeException(throwable); From deb2867df1c692ead8f7b7a8d6ed9b86ed589298 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Wed, 14 Dec 2022 14:27:20 +0200 Subject: [PATCH 032/496] 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 033/496] 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 034/496] 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 11:40:04 +0200 Subject: [PATCH 035/496] sparkplug: connection --- .../transport/DefaultTransportApiService.java | 76 +- common/cluster-api/src/main/proto/queue.proto | 14 + .../server/common/data/DataConstants.java | 1 + .../transport/mqtt/MqttTransportHandler.java | 72 +- .../session/SparkplugNodeSessionHandler.java | 288 + .../util/sparkplug/SparkplugBPayload.java | 175 + .../sparkplug/SparkplugBPayloadDecoder.java | 371 + .../sparkplug/SparkplugBPayloadEncoder.java | 545 + .../mqtt/util/sparkplug/SparkplugBProto.java | 17414 ++++++++++++++++ .../util/sparkplug/SparkplugMessageType.java | 107 + .../sparkplug/SparkplugPayloadDecoder.java | 37 + .../sparkplug/SparkplugPayloadEncoder.java | 39 + .../util/sparkplug/SparkplugTopicUtil.java | 113 + .../sparkplug/json/DataSetDeserializer.java | 129 + .../sparkplug/json/DeserializerModifier.java | 38 + .../sparkplug/json/DeserializerModule.java | 39 + .../util/sparkplug/json/FileSerializer.java | 52 + .../util/sparkplug/json/JsonValidator.java | 70 + .../sparkplug/json/MetricDeserializer.java | 70 + .../mqtt/util/sparkplug/message/DataSet.java | 234 + .../sparkplug/message/DataSetDataType.java | 117 + .../mqtt/util/sparkplug/message/File.java | 64 + .../mqtt/util/sparkplug/message/MetaData.java | 267 + .../mqtt/util/sparkplug/message/Metric.java | 320 + .../sparkplug/message/MetricDataType.java | 138 + .../util/sparkplug/message/Parameter.java | 107 + .../sparkplug/message/ParameterDataType.java | 125 + .../sparkplug/message/PropertyDataType.java | 134 + .../util/sparkplug/message/PropertySet.java | 169 + .../util/sparkplug/message/PropertyValue.java | 86 + .../mqtt/util/sparkplug/message/Row.java | 93 + .../sparkplug/message/SparkplugTopic.java | 161 + .../sparkplug/message/SparkplugValue.java | 53 + .../mqtt/util/sparkplug/message/Template.java | 202 + .../common/transport/TransportService.java | 5 + ...etOrCreateDeviceFromSparkplugResponse.java | 29 + .../service/DefaultTransportService.java | 48 +- 37 files changed, 21987 insertions(+), 15 deletions(-) create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/SparkplugNodeSessionHandler.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugBPayload.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugBPayloadDecoder.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugBPayloadEncoder.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugBProto.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugMessageType.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugPayloadDecoder.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugPayloadEncoder.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugTopicUtil.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/json/DataSetDeserializer.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/json/DeserializerModifier.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/json/DeserializerModule.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/json/FileSerializer.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/json/JsonValidator.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/json/MetricDeserializer.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/message/DataSet.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/message/DataSetDataType.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/message/File.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/message/MetaData.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/message/Metric.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/message/MetricDataType.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/message/Parameter.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/message/ParameterDataType.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/message/PropertyDataType.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/message/PropertySet.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/message/PropertyValue.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/message/Row.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/message/SparkplugTopic.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/message/SparkplugValue.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/message/Template.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/GetOrCreateDeviceFromSparkplugResponse.java diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index f8b42ecc1e..9c383a4cdb 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -65,7 +65,6 @@ import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceService; @@ -95,6 +94,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MC import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; +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.executors.DbCallbackExecutorService; @@ -161,6 +161,8 @@ public class DefaultTransportApiService implements TransportApiService { result = validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE); } else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) { result = handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()); + } else if (transportApiRequestMsg.hasGetOrCreateDeviceSparlplugRequestMsg()) { + result = handle(transportApiRequestMsg.getGetOrCreateDeviceSparlplugRequestMsg()); } else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) { result = handle(transportApiRequestMsg.getEntityProfileRequestMsg()); } else if (transportApiRequestMsg.hasLwM2MRequestMsg()) { @@ -345,6 +347,78 @@ public class DefaultTransportApiService implements TransportApiService { }, dbCallbackExecutorService); } + private ListenableFuture handle(TransportProtos.GetOrCreateDeviceFromSparkplugRequestMsg requestMsg) { + DeviceId sparkplugNodeId = new DeviceId(new UUID(requestMsg.getSparkplugIdMSB(), requestMsg.getSparkplugIdLSB())); + ListenableFuture sparkplugNodeFuture = deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, sparkplugNodeId); + return Futures.transform(sparkplugNodeFuture, sparkplugNode -> { + Lock deviceCreationLock = deviceCreationLocks.computeIfAbsent(requestMsg.getDeviceName(), id -> new ReentrantLock()); + deviceCreationLock.lock(); + try { + Device device = deviceService.findDeviceByTenantIdAndName(sparkplugNode.getTenantId(), requestMsg.getDeviceName()); + if (device == null) { + TenantId tenantId = sparkplugNode.getTenantId(); + device = new Device(); + device.setTenantId(tenantId); + device.setName(requestMsg.getDeviceName()); + device.setType(requestMsg.getDeviceType()); + device.setCustomerId(sparkplugNode.getCustomerId()); + DeviceProfile deviceProfile = deviceProfileCache.findOrCreateDeviceProfile(sparkplugNode.getTenantId(), requestMsg.getDeviceType()); + device.setDeviceProfileId(deviceProfile.getId()); + ObjectNode additionalInfo = JacksonUtil.newObjectNode(); + additionalInfo.put(DataConstants.LAST_CONNECTED_GATEWAY, sparkplugNodeId.toString()); + device.setAdditionalInfo(additionalInfo); + Device savedDevice = deviceService.saveDevice(device); + tbClusterService.onDeviceUpdated(savedDevice, null); + device = savedDevice; + + relationService.saveRelation(TenantId.SYS_TENANT_ID, new EntityRelation(sparkplugNode.getId(), device.getId(), "Created")); + + TbMsgMetaData metaData = new TbMsgMetaData(); + CustomerId customerId = sparkplugNode.getCustomerId(); + if (customerId != null && !customerId.isNullUid()) { + metaData.putValue("customerId", customerId.toString()); + } + metaData.putValue("sparkplugNodeId", sparkplugNodeId.toString()); + + DeviceId deviceId = device.getId(); + ObjectNode entityNode = mapper.valueToTree(device); + TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, customerId, metaData, TbMsgDataType.JSON, mapper.writeValueAsString(entityNode)); + tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, null); + } else { + JsonNode deviceAdditionalInfo = device.getAdditionalInfo(); + if (deviceAdditionalInfo == null) { + deviceAdditionalInfo = JacksonUtil.newObjectNode(); + } + if (deviceAdditionalInfo.isObject() && + (!deviceAdditionalInfo.has(DataConstants.LAST_CONNECTED_SPARKPLUG) + || !sparkplugNodeId.toString().equals(deviceAdditionalInfo.get(DataConstants.LAST_CONNECTED_SPARKPLUG).asText()))) { + ObjectNode newDeviceAdditionalInfo = (ObjectNode) deviceAdditionalInfo; + newDeviceAdditionalInfo.put(DataConstants.LAST_CONNECTED_SPARKPLUG, sparkplugNodeId.toString()); + Device savedDevice = deviceService.saveDevice(device); + tbClusterService.onDeviceUpdated(savedDevice, device); + } + } + TransportProtos.GetOrCreateDeviceFromSparkplugResponseMsg.Builder builder = + TransportProtos.GetOrCreateDeviceFromSparkplugResponseMsg.newBuilder() + .setDeviceInfo(getDeviceInfoProto(device)); + DeviceProfile deviceProfile = deviceProfileCache.get(device.getTenantId(), device.getDeviceProfileId()); + if (deviceProfile != null) { + builder.setProfileBody(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile))); + } else { + log.warn("[{}] Failed to find device profile [{}] for device. ", device.getId(), device.getDeviceProfileId()); + } + return TransportApiResponseMsg.newBuilder() + .setGetOrCreateDeviceSparkResponseMsg(builder.build()) + .build(); + } catch (JsonProcessingException e) { + log.warn("[{}] Failed to lookup device by sparkplug Node id and name: [{}]", sparkplugNodeId, requestMsg.getDeviceName(), e); + throw new RuntimeException(e); + } finally { + deviceCreationLock.unlock(); + } + }, dbCallbackExecutorService); + } + private ListenableFuture handle(ProvisionDeviceRequestMsg requestMsg) { ListenableFuture provisionResponseFuture = null; try { diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 5a28d42546..da425c7b2c 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -193,6 +193,18 @@ message GetOrCreateDeviceFromGatewayResponseMsg { bytes profileBody = 2; } +message GetOrCreateDeviceFromSparkplugRequestMsg { + string deviceName = 1; + string deviceType = 2; + int64 sparkplugIdMSB = 3; + int64 sparkplugIdLSB = 4; +} + +message GetOrCreateDeviceFromSparkplugResponseMsg { + DeviceInfoProto deviceInfo = 1; + bytes profileBody = 2; +} + message GetEntityProfileRequestMsg { string entityType = 1; int64 entityIdMSB = 2; @@ -897,6 +909,7 @@ message TransportApiRequestMsg { GetDeviceRequestMsg deviceRequestMsg = 12; GetDeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 13; GetAllQueueRoutingInfoRequestMsg getAllQueueRoutingInfoRequestMsg = 14; + GetOrCreateDeviceFromSparkplugRequestMsg getOrCreateDeviceSparlplugRequestMsg = 15; } /* Response from ThingsBoard Core Service to Transport Service */ @@ -912,6 +925,7 @@ message TransportApiResponseMsg { GetDeviceResponseMsg deviceResponseMsg = 9; GetDeviceCredentialsResponseMsg deviceCredentialsResponseMsg = 10; repeated GetQueueRoutingInfoResponseMsg getQueueRoutingInfoResponseMsgs = 11; + GetOrCreateDeviceFromSparkplugResponseMsg getOrCreateDeviceSparkResponseMsg = 12; } /* Messages that are handled by ThingsBoard Core Service */ diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index 970fd6177e..3ff3c42c47 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -118,4 +118,5 @@ public class DataConstants { public static final String MSG_SOURCE_KEY = "source"; public static final String LAST_CONNECTED_GATEWAY = "lastConnectedGateway"; + public static final String LAST_CONNECTED_SPARKPLUG = "lastConnectedSparkplug"; } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 2a320cc2ac..791f28c8cf 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -41,12 +41,13 @@ import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.OtaPackageId; @@ -72,6 +73,8 @@ import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; import org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler; import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher; +import org.thingsboard.server.transport.mqtt.session.SparkplugNodeSessionHandler; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.SparkplugTopic; import javax.net.ssl.SSLPeerUnverifiedException; import java.io.IOException; @@ -103,6 +106,7 @@ import static io.netty.handler.codec.mqtt.MqttQoS.AT_MOST_ONCE; import static io.netty.handler.codec.mqtt.MqttQoS.FAILURE; import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_CLOSED; import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_OPEN; +import static org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugTopicUtil.parseTopic; /** * @author Andrew Shvayka @@ -128,6 +132,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement final DeviceSessionCtx deviceSessionCtx; volatile InetSocketAddress address; volatile GatewaySessionHandler gatewaySessionHandler; + volatile SparkplugNodeSessionHandler sparkPlugSessionHandler; private final ConcurrentHashMap otaPackSessions; private final ConcurrentHashMap chunkSizes; @@ -325,6 +330,15 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement handleGatewayPublishMsg(ctx, topicName, msgId, mqttMsg); transportService.reportActivity(deviceSessionCtx.getSessionInfo()); } + } else if (sparkPlugSessionHandler != null) { + try { + SparkplugTopic sparkplugTopic = parseTopic(topicName); + log.error("Publish [{}] [{}]", sparkplugTopic.isNode() ? "node" : "device", sparkplugTopic.getType()); + handleSparkplugPublishMsg(ctx, sparkplugTopic, msgId, mqttMsg); + transportService.reportActivity(deviceSessionCtx.getSessionInfo()); + } catch (Exception e) { + e.printStackTrace(); + } } else { processDevicePublish(ctx, mqttMsg, topicName, msgId); } @@ -366,6 +380,30 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } + private void handleSparkplugPublishMsg(ChannelHandlerContext ctx, SparkplugTopic sparkplugTopic, int msgId, MqttPublishMessage mqttMsg) { + String topicName = sparkplugTopic.toString(); + try { + switch(sparkplugTopic.getType()) { + case NBIRTH: + // TODO regular publish +// sparkPlugSessionHandler.onNodeConnectProto(mqttMsg); + break; + case DBIRTH: + sparkPlugSessionHandler.onDeviceConnectProto(mqttMsg); + break; + default: + + } + + } catch (RuntimeException e) { + log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); + ctx.close(); + } catch (AdaptorException e) { + log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); + sendAckOrCloseSession(ctx, topicName, msgId); + } + } + private void processDevicePublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, String topicName, int msgId) { try { Matcher fwMatcher; @@ -617,6 +655,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement return; } log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId()); + try { + SparkplugTopic sparkplugTopic = parseTopic(mqttMsg.payload().topicSubscriptions().get(0).topicName()); + log.error("Subscribe [{}] [{}]", sparkplugTopic.isNode() ? "node" : "device", sparkplugTopic.getType()); + } catch (Exception e) { + e.printStackTrace(); + } + List grantedQoSList = new ArrayList<>(); boolean activityReported = false; for (MqttTopicSubscription subscription : mqttMsg.payload().topicSubscriptions()) { @@ -953,6 +998,30 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } + private void checkSparkPlugConnected(SessionMetaData sessionMetaData, MqttConnectMessage connectMessage) { + if (((MqttDeviceProfileTransportConfiguration) deviceSessionCtx.getDeviceProfile().getProfileData().getTransportConfiguration()) + .isSparkPlug()) { + TransportDeviceInfo device = deviceSessionCtx.getDeviceInfo(); + try { + JsonNode infoNode = context.getMapper().readTree(device.getAdditionalInfo()); + if (infoNode != null) { + SparkplugTopic sparkplugTopic = parseTopic(connectMessage.payload().willTopic()); + if (sparkPlugSessionHandler == null) { + log.error("Connected [{}] [{}]", sparkplugTopic.isNode() ? "node" : "device", sparkplugTopic.getType()); + sparkPlugSessionHandler = new SparkplugNodeSessionHandler(deviceSessionCtx, sessionId, sparkplugTopic.toString()); + } else { + log.error("ReConnected [{}] [{}]", sparkplugTopic.isNode() ? "node" : "device", sparkplugTopic.getType()); + } + if (infoNode.has(DefaultTransportService.OVERWRITE_ACTIVITY_TIME) && infoNode.get(DefaultTransportService.OVERWRITE_ACTIVITY_TIME).isBoolean()) { + sessionMetaData.setOverwriteActivityTime(infoNode.get(DefaultTransportService.OVERWRITE_ACTIVITY_TIME).asBoolean()); + } + } + } catch (Exception e) { + log.trace("[{}][{}] Failed to fetch sparkplugDevice additional info or sparkplugTopicName", sessionId, device.getDeviceName(), e); + } + } + } + @Override public void operationComplete(Future future) throws Exception { log.trace("[{}] Channel closed!", sessionId); @@ -988,6 +1057,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement public void onSuccess(Void msg) { SessionMetaData sessionMetaData = transportService.registerAsyncSession(deviceSessionCtx.getSessionInfo(), MqttTransportHandler.this); checkGatewaySession(sessionMetaData); + checkSparkPlugConnected(sessionMetaData, connectMessage); ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED, connectMessage)); deviceSessionCtx.setConnected(true); log.debug("[{}] Client connected!", sessionId); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/SparkplugNodeSessionHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/SparkplugNodeSessionHandler.java new file mode 100644 index 0000000000..46c1b64f54 --- /dev/null +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/SparkplugNodeSessionHandler.java @@ -0,0 +1,288 @@ +/** + * 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.transport.mqtt.session; + +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.SettableFuture; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.protobuf.InvalidProtocolBufferException; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.mqtt.MqttMessage; +import io.netty.handler.codec.mqtt.MqttPublishMessage; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.ConcurrentReferenceHashMap; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromSparkplugResponse; +import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; +import org.thingsboard.server.gen.transport.TransportApiProtos; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; +import org.thingsboard.server.transport.mqtt.MqttTransportContext; +import org.thingsboard.server.transport.mqtt.MqttTransportHandler; +import org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor; +import org.thingsboard.server.transport.mqtt.adaptors.ProtoMqttAdaptor; + +import javax.annotation.Nullable; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_CLOSED; +import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_OPEN; +import static org.thingsboard.server.common.transport.service.DefaultTransportService.SUBSCRIBE_TO_ATTRIBUTE_UPDATES_ASYNC_MSG; +import static org.thingsboard.server.common.transport.service.DefaultTransportService.SUBSCRIBE_TO_RPC_ASYNC_MSG; +import static org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugTopicUtil.parseTopic; + +/** + * Created by nickAS21 on 12.12.22 + */ +@Slf4j +public class SparkplugNodeSessionHandler { + + private static final String DEFAULT_DEVICE_TYPE = "default"; + private static final String CAN_T_PARSE_VALUE = "Can't parse value: "; + private static final String DEVICE_PROPERTY = "device"; + + private final MqttTransportContext context; + private final TransportService transportService; + private final TransportDeviceInfo nodeSparkplugInfo; + private final UUID sessionId; + private final ConcurrentMap deviceCreationLockMap; + private final ConcurrentMap devices = new ConcurrentHashMap<>(); + private final ConcurrentMap> deviceFutures = new ConcurrentHashMap<>(); + private final ConcurrentMap mqttQoSMap; + private final ChannelHandlerContext channel; + private final DeviceSessionCtx deviceSessionCtx; + private String nodeTopic; + + public SparkplugNodeSessionHandler(DeviceSessionCtx deviceSessionCtx, UUID sessionId, String nodeTopic) { + this.context = deviceSessionCtx.getContext(); + this.transportService = context.getTransportService(); + this.deviceSessionCtx = deviceSessionCtx; + this.nodeSparkplugInfo = deviceSessionCtx.getDeviceInfo(); + this.sessionId = sessionId; + this.deviceCreationLockMap = createWeakMap(); + this.mqttQoSMap = deviceSessionCtx.getMqttQoSMap(); + this.channel = deviceSessionCtx.getChannel(); + this.nodeTopic = nodeTopic; + } + + ConcurrentReferenceHashMap createWeakMap() { + return new ConcurrentReferenceHashMap<>(16, ConcurrentReferenceHashMap.ReferenceType.WEAK); + } + + public String getNodeId() { + return context.getNodeId(); + } + + public UUID getSessionId() { + return sessionId; + } + + public String getNodeTopic() { + return nodeTopic; + } + + public int nextMsgId() { + return deviceSessionCtx.nextMsgId(); + } + + public void deregisterSession(String deviceName) { + SparkplugSessionCtx deviceSessionCtx = devices.remove(deviceName); + if (deviceSessionCtx != null) { + deregisterSession(deviceName, deviceSessionCtx); + } else { + log.debug("[{}] Device [{}] was already removed from the gateway session", sessionId, deviceName); + } + } + + private void deregisterSession(String deviceName, SparkplugSessionCtx deviceSessionCtx) { + transportService.deregisterSession(deviceSessionCtx.getSessionInfo()); + transportService.process(deviceSessionCtx.getSessionInfo(), SESSION_EVENT_MSG_CLOSED, null); + log.debug("[{}] Removed device [{}] from the gateway session", sessionId, deviceName); + } + + public void onDeviceDeleted(String deviceName) { + deregisterSession(deviceName); + } + + private int getMsgId(MqttPublishMessage mqttMsg) { + return mqttMsg.variableHeader().packetId(); + } + + public void onDeviceConnectProto(MqttPublishMessage mqttMsg) throws AdaptorException { + try { + String deviceName = parseTopic(mqttMsg.variableHeader().topicName()).getDeviceId(); + String deviceType = StringUtils.isEmpty(nodeSparkplugInfo.getDeviceType()) ? DEFAULT_DEVICE_TYPE : nodeSparkplugInfo.getDeviceType(); + processOnConnect(mqttMsg, deviceName, deviceType); + } catch (Exception e) { + throw new AdaptorException(e); + } + } + + private void onDeviceDisconnectProto(MqttPublishMessage mqttMsg) throws AdaptorException { + try { + TransportApiProtos.DisconnectMsg connectProto = TransportApiProtos.DisconnectMsg.parseFrom(getBytes(mqttMsg.payload())); + String deviceName = checkDeviceName(connectProto.getDeviceName()); + processOnDisconnect(mqttMsg, deviceName); + } catch (RuntimeException | InvalidProtocolBufferException e) { + throw new AdaptorException(e); + } + } + + private void processOnDisconnect(MqttPublishMessage msg, String deviceName) { + deregisterSession(deviceName); + ack(msg); + } + + + private JsonElement getJson(MqttPublishMessage mqttMsg) throws AdaptorException { + return JsonMqttAdaptor.validateJsonPayload(sessionId, mqttMsg.payload()); + } + + private byte[] getBytes(ByteBuf payload) { + return ProtoMqttAdaptor.toBytes(payload); + } + + private void ack(MqttPublishMessage msg) { + int msgId = getMsgId(msg); + if (msgId > 0) { + writeAndFlush(MqttTransportHandler.createMqttPubAckMsg(msgId)); + } + } + + ChannelFuture writeAndFlush(MqttMessage mqttMessage) { + return channel.writeAndFlush(mqttMessage); + } + + private String checkDeviceName(String deviceName) { + if (StringUtils.isEmpty(deviceName)) { + throw new RuntimeException("Device name is empty!"); + } else { + return deviceName; + } + } + + private String getDeviceName(JsonElement json) { + return json.getAsJsonObject().get(DEVICE_PROPERTY).getAsString(); + } + + + private String getDeviceType(JsonElement json) { + JsonElement type = json.getAsJsonObject().get("type"); + return type == null || type instanceof JsonNull ? DEFAULT_DEVICE_TYPE : type.getAsString(); + } + + + private void processOnConnect(MqttPublishMessage msg, String deviceName, String deviceType) { + log.trace("[{}] onDeviceConnect: {}", sessionId, deviceName); + Futures.addCallback(onDeviceConnect(deviceName, deviceType), new FutureCallback<>() { + @Override + public void onSuccess(@Nullable SparkplugSessionCtx result) { + ack(msg); + log.trace("[{}] onDeviceConnectOk: {}", sessionId, deviceName); + } + + @Override + public void onFailure(Throwable t) { + log.warn("[{}] Failed to process device connect command: {}", sessionId, deviceName, t); + + } + }, context.getExecutor()); + } + + + private ListenableFuture onDeviceConnect(String deviceName, String deviceType) { + SparkplugSessionCtx result = devices.get(deviceName); + if (result == null) { + Lock deviceCreationLock = deviceCreationLockMap.computeIfAbsent(deviceName, s -> new ReentrantLock()); + deviceCreationLock.lock(); + try { + result = devices.get(deviceName); + if (result == null) { + return getDeviceCreationFuture(deviceName, deviceType); + } else { + return Futures.immediateFuture(result); + } + } finally { + deviceCreationLock.unlock(); + } + } else { + return Futures.immediateFuture(result); + } + } + + private ListenableFuture getDeviceCreationFuture(String deviceName, String deviceType) { + final SettableFuture futureToSet = SettableFuture.create(); + ListenableFuture future = deviceFutures.putIfAbsent(deviceName, futureToSet); + if (future != null) { + return future; + } + try { + transportService.process(TransportProtos.GetOrCreateDeviceFromSparkplugRequestMsg.newBuilder() + .setDeviceName(deviceName) + .setDeviceType(deviceType) + .setSparkplugIdMSB(nodeSparkplugInfo.getDeviceId().getId().getMostSignificantBits()) + .setSparkplugIdLSB(nodeSparkplugInfo.getDeviceId().getId().getLeastSignificantBits()) + .build(), + new TransportServiceCallback<>() { + @Override + public void onSuccess(GetOrCreateDeviceFromSparkplugResponse msg) { + if (msg.getDeviceInfo() == null) { + System.out.println("DeviceInfo == null"); + } + SparkplugSessionCtx nodeSparkplugSessionCtx = new SparkplugSessionCtx(SparkplugNodeSessionHandler.this, msg.getDeviceInfo(), msg.getDeviceProfile(), mqttQoSMap, transportService); + if (devices.putIfAbsent(deviceName, nodeSparkplugSessionCtx) == null) { + log.trace("[{}] First got or created device [{}], type [{}] for the gateway session", sessionId, deviceName, deviceType); + SessionInfoProto deviceSessionInfo = nodeSparkplugSessionCtx.getSessionInfo(); + transportService.registerAsyncSession(deviceSessionInfo, nodeSparkplugSessionCtx); + transportService.process(TransportProtos.TransportToDeviceActorMsg.newBuilder() + .setSessionInfo(deviceSessionInfo) + .setSessionEvent(SESSION_EVENT_MSG_OPEN) + .setSubscribeToAttributes(SUBSCRIBE_TO_ATTRIBUTE_UPDATES_ASYNC_MSG) + .setSubscribeToRPC(SUBSCRIBE_TO_RPC_ASYNC_MSG) + .build(), null); + } + futureToSet.set(devices.get(deviceName)); + deviceFutures.remove(deviceName); + } + + @Override + public void onError(Throwable e) { + log.warn("[{}] Failed to process device connect command: {}", sessionId, deviceName, e); + futureToSet.setException(e); + deviceFutures.remove(deviceName); + } + }); + return futureToSet; + } catch (Throwable e) { + deviceFutures.remove(deviceName); + throw e; + } + } + + +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugBPayload.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugBPayload.java new file mode 100644 index 0000000000..467b4d8d7a --- /dev/null +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugBPayload.java @@ -0,0 +1,175 @@ +/** + * 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.transport.mqtt.util.sparkplug; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Metric; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +/** + * Created by nickAS21 on 13.12.22 + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SparkplugBPayload { + + private Date timestamp; + private List metrics; + private long seq = -1; + private String uuid; + private byte[] body; + + public SparkplugBPayload() {}; + + public SparkplugBPayload(Date timestamp, List metrics, long seq, String uuid, byte[] body) { + this.timestamp = timestamp; + this.metrics = metrics; + this.seq = seq; + this.uuid = uuid; + this.body = body; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + + public void addMetric(Metric metric) { + metrics.add(metric); + } + + public void addMetric(int index, Metric metric) { + metrics.add(index, metric); + } + + public void addMetrics(List metrics) { + this.metrics.addAll(metrics); + } + + public Metric removeMetric(int index) { + return metrics.remove(index); + } + + public boolean removeMetric(Metric metric) { + return metrics.remove(metric); + } + + public List getMetrics() { + return metrics; + } + + @JsonIgnore + public Integer getMetricCount() { + return metrics.size(); + } + + public void setMetrics(List metrics) { + this.metrics = metrics; + } + + public long getSeq() { + return seq; + } + + public void setSeq(long seq) { + this.seq = seq; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public byte[] getBody() { + return body; + } + + public void setBody(byte[] body) { + this.body = body; + } + + @Override + public String toString() { + return "SparkplugBPayload [timestamp=" + timestamp + ", metrics=" + metrics + ", seq=" + seq + ", uuid=" + uuid + + ", body=" + Arrays.toString(body) + "]"; + } + + /** + * A builder for creating a {@link SparkplugBPayload} instance. + */ + public static class SparkplugBPayloadBuilder { + + private Date timestamp; + private List metrics; + private long seq = -1; + private String uuid; + private byte[] body; + + public SparkplugBPayloadBuilder(long sequenceNumber) { + this.seq = sequenceNumber; + metrics = new ArrayList(); + } + + public SparkplugBPayloadBuilder() { + metrics = new ArrayList(); + } + + public SparkplugBPayloadBuilder addMetric(Metric metric) { + this.metrics.add(metric); + return this; + } + + public SparkplugBPayloadBuilder addMetrics(Collection metrics) { + this.metrics.addAll(metrics); + return this; + } + + public SparkplugBPayloadBuilder setTimestamp(Date timestamp) { + this.timestamp = timestamp; + return this; + } + + public SparkplugBPayloadBuilder setSeq(long seq) { + this.seq = seq; + return this; + } + + public SparkplugBPayloadBuilder setUuid(String uuid) { + this.uuid = uuid; + return this; + } + + public SparkplugBPayloadBuilder setBody(byte[] body) { + this.body = body; + return this; + } + + public SparkplugBPayload createPayload() { + return new SparkplugBPayload(timestamp, metrics, seq, uuid, body); + } + } +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugBPayloadDecoder.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugBPayloadDecoder.java new file mode 100644 index 0000000000..910a47286d --- /dev/null +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugBPayloadDecoder.java @@ -0,0 +1,371 @@ +/** + * 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.transport.mqtt.util.sparkplug; + +/** + * Created by nickAS21 on 13.12.22 + */ + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.DataSet; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.DataSetDataType; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.File; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.MetaData; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Metric; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.MetricDataType; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Parameter; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.ParameterDataType; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.PropertyDataType; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.PropertySet; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.PropertyValue; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Row; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Template; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.SparkplugValue; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A {@link SparkplugPayloadDecoder} implementation for decoding Sparkplug B payloads. + */ +@Slf4j +public class SparkplugBPayloadDecoder implements SparkplugPayloadDecoder { + + + public SparkplugBPayloadDecoder() { + super(); + } + + public SparkplugBPayload buildFromByteArray(byte[] bytes) throws Exception { + SparkplugBProto.Payload protoPayload = SparkplugBProto.Payload.parseFrom(bytes); + SparkplugBPayload.SparkplugBPayloadBuilder builder = new SparkplugBPayload.SparkplugBPayloadBuilder(protoPayload.getSeq()); + + // Set the timestamp + if (protoPayload.hasTimestamp()) { + builder.setTimestamp(new Date(protoPayload.getTimestamp())); + } + + // Set the sequence number + if (protoPayload.hasSeq()) { + builder.setSeq(protoPayload.getSeq()); + } + + // Set the Metrics + for (SparkplugBProto.Payload.Metric protoMetric : protoPayload.getMetricsList()) { + builder.addMetric(convertMetric(protoMetric)); + } + + // Set the body + if (protoPayload.hasBody()) { + builder.setBody(protoPayload.getBody().toByteArray()); + } + + // Set the body + if (protoPayload.hasUuid()) { + builder.setUuid(protoPayload.getUuid()); + } + + return builder.createPayload(); + } + + private Metric convertMetric(SparkplugBProto.Payload.Metric protoMetric) throws Exception { + // Convert the dataType + MetricDataType dataType = MetricDataType.fromInteger((protoMetric.getDatatype())); + + // Build and return the Metric + return new Metric.MetricBuilder(protoMetric.getName(), dataType, getMetricValue(protoMetric)) + .isHistorical( + protoMetric.hasIsHistorical() ? protoMetric.getIsHistorical() : null) + .isTransient(protoMetric + .hasIsTransient() ? protoMetric.getIsTransient() : null) + .timestamp(protoMetric.hasTimestamp() ? new Date(protoMetric.getTimestamp()) : null) + .alias(protoMetric.hasAlias() ? protoMetric.getAlias() : null) + .metaData(protoMetric.hasMetadata() + ? new MetaData.MetaDataBuilder().contentType(protoMetric.getMetadata().getContentType()) + .size(protoMetric.getMetadata().getSize()).seq(protoMetric.getMetadata().getSeq()) + .fileName(protoMetric.getMetadata().getFileName()) + .fileType(protoMetric.getMetadata().getFileType()) + .md5(protoMetric.getMetadata().getMd5()) + .description(protoMetric.getMetadata().getDescription()).createMetaData() + : null) + .properties(protoMetric.hasProperties() + ? new PropertySet.PropertySetBuilder().addProperties(convertProperties(protoMetric.getProperties())) + .createPropertySet() + : null) + .createMetric(); + } + + private Map convertProperties(SparkplugBProto.Payload.PropertySet decodedPropSet) + throws Exception { + Map map = new HashMap(); + List keys = decodedPropSet.getKeysList(); + List values = decodedPropSet.getValuesList(); + for (int i = 0; i < keys.size(); i++) { + SparkplugBProto.Payload.PropertyValue value = values.get(i); + map.put(keys.get(i), + new PropertyValue(PropertyDataType.fromInteger(value.getType()), getPropertyValue(value))); + } + return map; + } + + private Object getPropertyValue(SparkplugBProto.Payload.PropertyValue value) throws Exception { + PropertyDataType type = PropertyDataType.fromInteger(value.getType()); + if (value.getIsNull()) { + return null; + } + switch (type) { + case Boolean: + return value.getBooleanValue(); + case DateTime: + return new Date(value.getLongValue()); + case Float: + return value.getFloatValue(); + case Double: + return value.getDoubleValue(); + case Int8: + return (byte) value.getIntValue(); + case Int16: + case UInt8: + return (short) value.getIntValue(); + case Int32: + case UInt16: + return value.getIntValue(); + case UInt32: + case Int64: + return value.getLongValue(); + case UInt64: + return BigInteger.valueOf(value.getLongValue()); + case String: + case Text: + return value.getStringValue(); + case PropertySet: + return new PropertySet.PropertySetBuilder().addProperties(convertProperties(value.getPropertysetValue())) + .createPropertySet(); + case PropertySetList: + List propertySetList = new ArrayList(); + List list = value.getPropertysetsValue().getPropertysetList(); + for (SparkplugBProto.Payload.PropertySet decodedPropSet : list) { + propertySetList.add(new PropertySet.PropertySetBuilder().addProperties(convertProperties(decodedPropSet)) + .createPropertySet()); + } + return propertySetList; + case Unknown: + default: + throw new Exception("Failed to decode: Unknown Property Data Type " + type); + } + } + + private Object getMetricValue(SparkplugBProto.Payload.Metric protoMetric) throws Exception { + // Check if the null flag has been set indicating that the value is null + if (protoMetric.getIsNull()) { + return null; + } + // Otherwise convert the value based on the type + int metricType = protoMetric.getDatatype(); + switch (MetricDataType.fromInteger(metricType)) { + case Boolean: + return protoMetric.getBooleanValue(); + case DateTime: + return new Date(protoMetric.getLongValue()); + case File: + String filename = protoMetric.getMetadata().getFileName(); + byte[] fileBytes = protoMetric.getBytesValue().toByteArray(); + return new File(filename, fileBytes); + case Float: + return protoMetric.getFloatValue(); + case Double: + return protoMetric.getDoubleValue(); + case Int8: + return (byte) protoMetric.getIntValue(); + case Int16: + case UInt8: + return (short) protoMetric.getIntValue(); + case Int32: + case UInt16: + return protoMetric.getIntValue(); + case UInt32: + case Int64: + return protoMetric.getLongValue(); + case UInt64: + return BigInteger.valueOf(protoMetric.getLongValue()); + case String: + case Text: + case UUID: + return protoMetric.getStringValue(); + case Bytes: + return protoMetric.getBytesValue().toByteArray(); + case DataSet: + SparkplugBProto.Payload.DataSet protoDataSet = protoMetric.getDatasetValue(); + // Build the and create the DataSet + return new DataSet.DataSetBuilder(protoDataSet.getNumOfColumns()).addColumnNames(protoDataSet.getColumnsList()) + .addTypes(convertDataSetDataTypes(protoDataSet.getTypesList())) + .addRows(convertDataSetRows(protoDataSet.getRowsList(), protoDataSet.getTypesList())) + .createDataSet(); + case Template: + SparkplugBProto.Payload.Template protoTemplate = protoMetric.getTemplateValue(); + List metrics = new ArrayList(); + List parameters = new ArrayList(); + + for (SparkplugBProto.Payload.Template.Parameter protoParameter : protoTemplate.getParametersList()) { + String name = protoParameter.getName(); + ParameterDataType type = ParameterDataType.fromInteger(protoParameter.getType()); + Object value = getParameterValue(protoParameter); + if (log.isTraceEnabled()) { + log.trace("Setting template parameter name: " + name + ", type: " + type + ", value: " + + value + ", valueType" + value.getClass()); + } + + parameters.add(new Parameter(name, type, value)); + } + + for (SparkplugBProto.Payload.Metric protoTemplateMetric : protoTemplate.getMetricsList()) { + Metric templateMetric = convertMetric(protoTemplateMetric); + if (log.isTraceEnabled()) { + log.trace("Setting template parameter name: " + templateMetric.getName() + ", type: " + + templateMetric.getDataType() + ", value: " + templateMetric.getValue()); + } + metrics.add(templateMetric); + } + + Template template = new Template.TemplateBuilder().version(protoTemplate.getVersion()) + .templateRef(protoTemplate.getTemplateRef()).definition(protoTemplate.getIsDefinition()) + .addMetrics(metrics).addParameters(parameters).createTemplate(); + + if (log.isTraceEnabled()) { + log.trace( + "Setting template - name: " + protoMetric.getName() + ", version: " + template.getVersion() + + ", ref: " + template.getTemplateRef() + ", isDef: " + template.isDefinition() + + ", metrics: " + metrics.size() + ", params: " + parameters.size()); + } + + return template; + case Unknown: + default: + throw new Exception("Failed to decode: Unknown Metric DataType " + metricType); + + } + } + + private Collection convertDataSetRows(List protoRows, + List protoTypes) throws Exception { + Collection rows = new ArrayList(); + if (protoRows != null) { + for (SparkplugBProto.Payload.DataSet.Row protoRow : protoRows) { + List protoValues = protoRow.getElementsList(); + List> values = new ArrayList>(); + for (int index = 0; index < protoRow.getElementsCount(); index++) { + values.add(convertDataSetValue(protoTypes.get(index), protoValues.get(index))); + } + // Add the values to the row and the row to the rows + rows.add(new Row.RowBuilder().addValues(values).createRow()); + } + } + return rows; + } + + private Collection convertDataSetDataTypes(List protoTypes) { + List types = new ArrayList(); + // Build up a List of column types + for (int type : protoTypes) { + types.add(DataSetDataType.fromInteger(type)); + } + return types; + } + + private Object getParameterValue(SparkplugBProto.Payload.Template.Parameter protoParameter) throws Exception { + // Otherwise convert the value based on the type + int type = protoParameter.getType(); + switch (MetricDataType.fromInteger(type)) { + case Boolean: + return protoParameter.getBooleanValue(); + case DateTime: + return new Date(protoParameter.getLongValue()); + case Float: + return protoParameter.getFloatValue(); + case Double: + return protoParameter.getDoubleValue(); + case Int8: + return (byte) protoParameter.getIntValue(); + case Int16: + case UInt8: + return (short) protoParameter.getIntValue(); + case Int32: + case UInt16: + return protoParameter.getIntValue(); + case UInt32: + case Int64: + return protoParameter.getLongValue(); + case UInt64: + return BigInteger.valueOf(protoParameter.getLongValue()); + case String: + case Text: + return protoParameter.getStringValue(); + case Unknown: + default: + throw new Exception("Failed to decode: Unknown Parameter Type " + type); + } + } + + private SparkplugValue convertDataSetValue(int protoType, SparkplugBProto.Payload.DataSet.DataSetValue protoValue) + throws Exception { + + DataSetDataType type = DataSetDataType.fromInteger(protoType); + switch (type) { + case Boolean: + return new SparkplugValue(type, protoValue.getBooleanValue()); + case DateTime: + // FIXME - remove after is_null is supported for dataset values + if (protoValue.getLongValue() == -9223372036854775808L) { + return new SparkplugValue(type, null); + } else { + return new SparkplugValue(type, new Date(protoValue.getLongValue())); + } + case Float: + return new SparkplugValue(type, protoValue.getFloatValue()); + case Double: + return new SparkplugValue(type, protoValue.getDoubleValue()); + case Int8: + return new SparkplugValue(type, (byte) protoValue.getIntValue()); + case UInt8: + case Int16: + return new SparkplugValue(type, (short) protoValue.getIntValue()); + case UInt16: + case Int32: + return new SparkplugValue(type, protoValue.getIntValue()); + case UInt32: + case Int64: + return new SparkplugValue(type, protoValue.getLongValue()); + case UInt64: + return new SparkplugValue(type, BigInteger.valueOf(protoValue.getLongValue())); + case String: + case Text: + if (protoValue.getStringValue().equals("null")) { + return new SparkplugValue(type, null); + } else { + return new SparkplugValue(type, protoValue.getStringValue()); + } + case Unknown: + default: + log.error("Unknown DataType: " + protoType); + throw new Exception("Failed to decode"); + } + } +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugBPayloadEncoder.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugBPayloadEncoder.java new file mode 100644 index 0000000000..03ab231f42 --- /dev/null +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugBPayloadEncoder.java @@ -0,0 +1,545 @@ +/** + * 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.transport.mqtt.util.sparkplug; + +import com.google.protobuf.ByteString; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.DataSet; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.DataSetDataType; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.File; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.MetaData; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Metric; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Parameter; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.ParameterDataType; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.PropertyDataType; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.PropertySet; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.PropertyValue; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Row; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Template; +import org.thingsboard.server.transport.mqtt.util.sparkplug.message.SparkplugValue; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * Created by nickAS21 on 13.12.22 + */ +public class SparkplugBPayloadEncoder implements SparkplugPayloadEncoder { + + private static Logger logger = LogManager.getLogger(SparkplugBPayloadEncoder.class.getName()); + + public SparkplugBPayloadEncoder() { + super(); + } + + public byte[] getBytes(SparkplugBPayload payload) throws IOException { + + SparkplugBProto.Payload.Builder protoMsg = SparkplugBProto.Payload.newBuilder(); + + // Set the timestamp + if (payload.getTimestamp() != null) { + protoMsg.setTimestamp(payload.getTimestamp().getTime()); + } + + // Set the sequence number + protoMsg.setSeq(payload.getSeq()); + + // Set the UUID if defined + if (payload.getUuid() != null) { + protoMsg.setUuid(payload.getUuid()); + } + + // Set the metrics + for (Metric metric : payload.getMetrics()) { + try { + protoMsg.addMetrics(convertMetric(metric)); + } catch(Exception e) { + logger.error("Failed to add metric: " + metric.getName()); + throw new RuntimeException(e); + } + } + + // Set the body + if (payload.getBody() != null) { + protoMsg.setBody(ByteString.copyFrom(payload.getBody())); + } + + return protoMsg.build().toByteArray(); + } + + private SparkplugBProto.Payload.Metric.Builder convertMetric(Metric metric) throws Exception { + + // build a metric + SparkplugBProto.Payload.Metric.Builder builder = SparkplugBProto.Payload.Metric.newBuilder(); + + // set the basic parameters + builder.setDatatype(metric.getDataType().toIntValue()); + builder = setMetricValue(builder, metric); + + // Set the name, data type, and value + if (metric.hasName()) { + builder.setName(metric.getName()); + } + + // Set the alias + if (metric.hasAlias()) { + builder.setAlias(metric.getAlias()); + } + + // Set the timestamp + if (metric.getTimestamp() != null) { + builder.setTimestamp(metric.getTimestamp().getTime()); + } + + // Set isHistorical + if (metric.getIsHistorical() != null) { + builder.setIsHistorical(metric.isHistorical()); + } + + // Set isTransient + if (metric.getIsTransient() != null) { + builder.setIsTransient(metric.isTransient()); + } + + // Set isNull + if (metric.getIsNull() != null) { + builder.setIsNull(metric.isNull()); + } + + // Set the metadata + if (metric.getMetaData() != null) { + builder = setMetaData(builder, metric); + } + + // Set the property set + if (metric.getProperties() != null) { + builder.setProperties(convertPropertySet(metric.getProperties())); + } + + return builder; + } + + private SparkplugBProto.Payload.Template.Parameter.Builder convertParameter(Parameter parameter) throws Exception { + + // build a metric + SparkplugBProto.Payload.Template.Parameter.Builder builder = + SparkplugBProto.Payload.Template.Parameter.newBuilder(); + + if (logger.isTraceEnabled()) { + logger.trace("Adding parameter: " + parameter.getName()); + logger.trace(" type: " + parameter.getType()); + } + + // Set the name + builder.setName(parameter.getName()); + + // Set the type and value + builder = setParameterValue(builder, parameter); + + return builder; + } + + private SparkplugBProto.Payload.PropertySet.Builder convertPropertySet(PropertySet propertySet) throws Exception { + SparkplugBProto.Payload.PropertySet.Builder setBuilder = SparkplugBProto.Payload.PropertySet.newBuilder(); + + Map map = propertySet.getPropertyMap(); + for (String key : map.keySet()) { + SparkplugBProto.Payload.PropertyValue.Builder builder = SparkplugBProto.Payload.PropertyValue.newBuilder(); + PropertyValue value = map.get(key); + PropertyDataType type = value.getType(); + builder.setType(type.toIntValue()); + if (value.getValue() == null) { + builder.setIsNull(true); + } else { + switch (type) { + case Boolean: + builder.setBooleanValue((Boolean) value.getValue()); + break; + case DateTime: + builder.setLongValue(((Date) value.getValue()).getTime()); + break; + case Double: + builder.setDoubleValue((Double) value.getValue()); + break; + case Float: + builder.setFloatValue((Float) value.getValue()); + break; + case Int8: + builder.setIntValue((Byte) value.getValue()); + break; + case Int16: + case UInt8: + builder.setIntValue((Short) value.getValue()); + break; + case Int32: + case UInt16: + builder.setIntValue((Integer) value.getValue()); + break; + case Int64: + case UInt32: + builder.setLongValue((Long) value.getValue()); + break; + case UInt64: + builder.setLongValue(((BigInteger) value.getValue()).longValue()); + break; + case String: + case Text: + builder.setStringValue((String) value.getValue()); + break; + case PropertySet: + builder.setPropertysetValue(convertPropertySet((PropertySet) value.getValue())); + break; + case PropertySetList: + List setList = (List) value.getValue(); + SparkplugBProto.Payload.PropertySetList.Builder listBuilder = + SparkplugBProto.Payload.PropertySetList.newBuilder(); + for (Object obj : setList) { + listBuilder.addPropertyset(convertPropertySet((PropertySet) obj)); + } + builder.setPropertysetsValue(listBuilder); + break; + case Unknown: + default: + logger.error("Unknown DataType: " + value.getType()); + throw new Exception("Failed to convert value " + value.getType()); + } + } + setBuilder.addKeys(key); + setBuilder.addValues(builder); + } + return setBuilder; + } + + private SparkplugBProto.Payload.Template.Parameter.Builder setParameterValue( + SparkplugBProto.Payload.Template.Parameter.Builder builder, Parameter parameter) throws Exception { + ParameterDataType type = parameter.getType(); + builder.setType(type.toIntValue()); + Object value = parameter.getValue(); + switch (type) { + case Boolean: + builder.setBooleanValue(toBoolean(value)); + break; + case DateTime: + builder.setLongValue(((Date) value).getTime()); + break; + case Double: + builder.setDoubleValue((Double) value); + break; + case Float: + builder.setFloatValue((Float) value); + break; + case Int8: + builder.setIntValue((Byte) value); + break; + case Int16: + case UInt8: + builder.setIntValue((Short) value); + break; + case Int32: + case UInt16: + builder.setIntValue((Integer) value); + break; + case Int64: + case UInt32: + builder.setLongValue((Long) value); + break; + case UInt64: + builder.setLongValue(((BigInteger) value).longValue()); + break; + case Text: + case String: + if (value == null) { + builder.setStringValue(""); + } else { + builder.setStringValue((String) value); + } + break; + case Unknown: + default: + logger.error("Unknown Type: " + type); + throw new Exception("Failed to encode"); + + } + return builder; + } + + private SparkplugBProto.Payload.Metric.Builder setMetricValue(SparkplugBProto.Payload.Metric.Builder metricBuilder, + Metric metric) throws Exception { + + // Set the data type + metricBuilder.setDatatype(metric.getDataType().toIntValue()); + + if (metric.getValue() == null) { + metricBuilder.setIsNull(true); + } else { + switch (metric.getDataType()) { + case Boolean: + metricBuilder.setBooleanValue(toBoolean(metric.getValue())); + break; + case DateTime: + metricBuilder.setLongValue(((Date) metric.getValue()).getTime()); + break; + case File: + metricBuilder.setBytesValue(ByteString.copyFrom(((File) metric.getValue()).getBytes())); + SparkplugBProto.Payload.MetaData.Builder metaDataBuilder = + SparkplugBProto.Payload.MetaData.newBuilder(); + metaDataBuilder.setFileName(((File) metric.getValue()).getFileName()); + metricBuilder.setMetadata(metaDataBuilder); + break; + case Float: + metricBuilder.setFloatValue((Float) metric.getValue()); + break; + case Double: + metricBuilder.setDoubleValue((Double) metric.getValue()); + break; + case Int8: + metricBuilder.setIntValue(((Byte)metric.getValue()).intValue()); + break; + case Int16: + case UInt8: + metricBuilder.setIntValue(((Short)metric.getValue()).intValue()); + break; + case Int32: + case UInt16: + metricBuilder.setIntValue((int) metric.getValue()); + break; + case UInt32: + case Int64: + metricBuilder.setLongValue((Long) metric.getValue()); + break; + case UInt64: + metricBuilder.setLongValue(((BigInteger) metric.getValue()).longValue()); + break; + case String: + case Text: + case UUID: + metricBuilder.setStringValue((String) metric.getValue()); + break; + case Bytes: + metricBuilder.setBytesValue(ByteString.copyFrom((byte[]) metric.getValue())); + break; + case DataSet: + DataSet dataSet = (DataSet) metric.getValue(); + SparkplugBProto.Payload.DataSet.Builder dataSetBuilder = + SparkplugBProto.Payload.DataSet.newBuilder(); + + dataSetBuilder.setNumOfColumns(dataSet.getNumOfColumns()); + + // Column names + List columnNames = dataSet.getColumnNames(); + if (columnNames != null && !columnNames.isEmpty()) { + for (String name : columnNames) { + // Add the column name + dataSetBuilder.addColumns(name); + } + } + + // Column types + List columnTypes = dataSet.getTypes(); + if (columnTypes != null && !columnTypes.isEmpty()) { + for (DataSetDataType type : columnTypes) { + // Add the column type + dataSetBuilder.addTypes(type.toIntValue()); + } + } + + // Dataset rows + List rows = dataSet.getRows(); + if (rows != null && !rows.isEmpty()) { + for (Row row : rows) { + SparkplugBProto.Payload.DataSet.Row.Builder protoRowBuilder = + SparkplugBProto.Payload.DataSet.Row.newBuilder(); + List> values = row.getValues(); + if (values != null && !values.isEmpty()) { + for (SparkplugValue value : values) { + // Add the converted element + protoRowBuilder.addElements(convertDataSetValue(value)); + } + + dataSetBuilder.addRows(protoRowBuilder); + } + } + } + + // Finally add the dataset + metricBuilder.setDatasetValue(dataSetBuilder); + break; + case Template: + Template template = (Template) metric.getValue(); + SparkplugBProto.Payload.Template.Builder templateBuilder = + SparkplugBProto.Payload.Template.newBuilder(); + + // Set isDefinition + templateBuilder.setIsDefinition(template.isDefinition()); + + // Set Version + if (template.getVersion() != null) { + templateBuilder.setVersion(template.getVersion()); + } + + // Set Template Reference + if (template.getTemplateRef() != null) { + templateBuilder.setTemplateRef(template.getTemplateRef()); + } + + // Set the template metrics + if (template.getMetrics() != null) { + for (Metric templateMetric : template.getMetrics()) { + templateBuilder.addMetrics(convertMetric(templateMetric)); + } + } + + // Set the template parameters + if (template.getParameters() != null) { + for (Parameter parameter : template.getParameters()) { + templateBuilder.addParameters(convertParameter(parameter)); + } + } + + // Add the template to the metric + metricBuilder.setTemplateValue(templateBuilder); + break; + case Unknown: + default: + logger.error("Unknown DataType: " + metric.getDataType()); + throw new Exception("Failed to encode"); + + } + } + return metricBuilder; + } + + private SparkplugBProto.Payload.Metric.Builder setMetaData(SparkplugBProto.Payload.Metric.Builder metricBuilder, + Metric metric) throws Exception { + + // If the builder has been built already - use it + SparkplugBProto.Payload.MetaData.Builder metaDataBuilder = metricBuilder.getMetadataBuilder() != null + ? metricBuilder.getMetadataBuilder() + : SparkplugBProto.Payload.MetaData.newBuilder(); + + MetaData metaData = metric.getMetaData(); + if (metaData.getContentType() != null) { + metaDataBuilder.setContentType(metaData.getContentType()); + } + if (metaData.getSize() != null) { + metaDataBuilder.setSize(metaData.getSize()); + } + if (metaData.getSeq() != null) { + metaDataBuilder.setSeq(metaData.getSeq()); + } + if (metaData.getFileName() != null) { + metaDataBuilder.setFileName(metaData.getFileName()); + } + if (metaData.getFileType() != null) { + metaDataBuilder.setFileType(metaData.getFileType()); + } + if (metaData.getMd5() != null) { + metaDataBuilder.setMd5(metaData.getMd5()); + } + if (metaData.getDescription() != null) { + metaDataBuilder.setDescription(metaData.getDescription()); + } + metricBuilder.setMetadata(metaDataBuilder); + + return metricBuilder; + } + + private SparkplugBProto.Payload.DataSet.DataSetValue.Builder convertDataSetValue(SparkplugValue value) throws Exception { + SparkplugBProto.Payload.DataSet.DataSetValue.Builder protoValueBuilder = + SparkplugBProto.Payload.DataSet.DataSetValue.newBuilder(); + + // Set the value + DataSetDataType type = value.getType(); + switch (type) { + case Int8: + protoValueBuilder.setIntValue((Byte) value.getValue()); + break; + case Int16: + case UInt8: + protoValueBuilder.setIntValue((Short) value.getValue()); + break; + case Int32: + case UInt16: + protoValueBuilder.setIntValue((Integer) value.getValue()); + break; + case Int64: + case UInt32: + protoValueBuilder.setLongValue((Long) value.getValue()); + break; + case UInt64: + protoValueBuilder.setLongValue(((BigInteger) value.getValue()).longValue()); + break; + case Float: + protoValueBuilder.setFloatValue((Float) value.getValue()); + break; + case Double: + protoValueBuilder.setDoubleValue((Double) value.getValue()); + break; + case String: + case Text: + if (value.getValue() != null) { + protoValueBuilder.setStringValue((String) value.getValue()); + } else { + logger.warn("String value for dataset is null"); + protoValueBuilder.setStringValue("null"); + } + break; + case Boolean: + protoValueBuilder.setBooleanValue(toBoolean(value.getValue())); + break; + case DateTime: + try { + protoValueBuilder.setLongValue(((Date) value.getValue()).getTime()); + } catch (NullPointerException npe) { + // FIXME - remove after is_null is supported for dataset values + logger.debug("Date in dataset was null - leaving it -9223372036854775808L"); + protoValueBuilder.setLongValue(-9223372036854775808L); + } + break; + default: + logger.error("Unknown DataType: " + value.getType()); + throw new Exception("Failed to convert value " + value.getType()); + } + + return protoValueBuilder; + } + + private Boolean toBoolean(Object value) { + if (value == null) { + return null; + } + if (value instanceof Integer) { + return ((Integer)value).intValue() == 0 ? Boolean.FALSE : Boolean.TRUE; + } else if (value instanceof Long) { + return ((Long)value).longValue() == 0 ? Boolean.FALSE : Boolean.TRUE; + } else if (value instanceof Float) { + return ((Float)value).floatValue() == 0 ? Boolean.FALSE : Boolean.TRUE; + } else if (value instanceof Double) { + return ((Double)value).doubleValue() == 0 ? Boolean.FALSE : Boolean.TRUE; + } else if (value instanceof Short) { + return ((Short)value).shortValue() == 0 ? Boolean.FALSE : Boolean.TRUE; + } else if (value instanceof Byte) { + return ((Byte)value).byteValue() == 0 ? Boolean.FALSE : Boolean.TRUE; + } else if (value instanceof String) { + return Boolean.parseBoolean(value.toString()); + } + return (Boolean)value; + } +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugBProto.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugBProto.java new file mode 100644 index 0000000000..cffdb133c6 --- /dev/null +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugBProto.java @@ -0,0 +1,17414 @@ +/** + * 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.transport.mqtt.util.sparkplug; + +/** + * Created by nickAS21 on 13.12.22 + */ +public final class SparkplugBProto { + private SparkplugBProto() {} + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistry registry) { + } + public interface PayloadOrBuilder extends + // @@protoc_insertion_point(interface_extends:org.thingsboard.server.transport.mqtt.util.sparkplug.Payload) + com.google.protobuf.GeneratedMessage. + ExtendableMessageOrBuilder { + + /** + * optional uint64 timestamp = 1; + * + *
+         * Timestamp at message sending time
+         * 
+ */ + boolean hasTimestamp(); + /** + * optional uint64 timestamp = 1; + * + *
+         * Timestamp at message sending time
+         * 
+ */ + long getTimestamp(); + + /** + * repeated .org.thingsboard.server.transport.mqtt.util.sparkplug.Payload.Metric metrics = 2; + * + *
+         * Repeated forever - no limit in Google Protobufs
+         * 
+ */ + java.util.List + getMetricsList(); + /** + * repeated .org.thingsboard.server.transport.mqtt.util.sparkplug.Payload.Metric metrics = 2; + * + *
+         * Repeated forever - no limit in Google Protobufs
+         * 
+ */ + org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugBProto.Payload.Metric getMetrics(int index); + /** + * repeated .org.thingsboard.server.transport.mqtt.util.sparkplug.Payload.Metric metrics = 2; + * + *
+         * Repeated forever - no limit in Google Protobufs
+         * 
+ */ + int getMetricsCount(); + /** + * repeated .org.thingsboard.server.transport.mqtt.util.sparkplug.Payload.Metric metrics = 2; + * + *
+         * Repeated forever - no limit in Google Protobufs
+         * 
+ */ + java.util.List + getMetricsOrBuilderList(); + /** + * repeated .org.thingsboard.server.transport.mqtt.util.sparkplug.Payload.Metric metrics = 2; + * + *
+         * Repeated forever - no limit in Google Protobufs
+         * 
+ */ + org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugBProto.Payload.MetricOrBuilder getMetricsOrBuilder( + int index); + + /** + * optional uint64 seq = 3; + * + *
+         * Sequence number
+         * 
+ */ + boolean hasSeq(); + /** + * optional uint64 seq = 3; + * + *
+         * Sequence number
+         * 
+ */ + long getSeq(); + + /** + * optional string uuid = 4; + * + *
+         * UUID to track message type in terms of schema definitions
+         * 
+ */ + boolean hasUuid(); + /** + * optional string uuid = 4; + * + *
+         * UUID to track message type in terms of schema definitions
+         * 
+ */ + java.lang.String getUuid(); + /** + * optional string uuid = 4; + * + *
+         * UUID to track message type in terms of schema definitions
+         * 
+ */ + com.google.protobuf.ByteString + getUuidBytes(); + + /** + * optional bytes body = 5; + * + *
+         * To optionally bypass the whole definition above
+         * 
+ */ + boolean hasBody(); + /** + * optional bytes body = 5; + * + *
+         * To optionally bypass the whole definition above
+         * 
+ */ + com.google.protobuf.ByteString getBody(); + } + /** + * Protobuf type {@code org.thingsboard.server.transport.mqtt.util.sparkplug.Payload} + * + *
+     * // Indexes of Data Types
+     * // Unknown placeholder for future expansion.
+     *Unknown         = 0;
+     * // Basic Types
+     *Int8            = 1;
+     *Int16           = 2;
+     *Int32           = 3;
+     *Int64           = 4;
+     *UInt8           = 5;
+     *UInt16          = 6;
+     *UInt32          = 7;
+     *UInt64          = 8;
+     *Float           = 9;
+     *Double          = 10;
+     *Boolean         = 11;
+     *String          = 12;
+     *DateTime        = 13;
+     *Text            = 14;
+     * // Additional Metric Types
+     *UUID            = 15;
+     *DataSet         = 16;
+     *Bytes           = 17;
+     *File            = 18;
+     *Template        = 19;
+     * // Additional PropertyValue Types
+     *PropertySet     = 20;
+     *PropertySetList = 21;
+     * 
+ */ + public static final class Payload extends + com.google.protobuf.GeneratedMessage.ExtendableMessage< + Payload> implements + // @@protoc_insertion_point(message_implements:org.thingsboard.server.transport.mqtt.util.sparkplug.Payload) + PayloadOrBuilder { + // Use Payload.newBuilder() to construct. + private Payload(com.google.protobuf.GeneratedMessage.ExtendableBuilder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private Payload(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final Payload defaultInstance; + public static Payload getDefaultInstance() { + return defaultInstance; + } + + public Payload getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private Payload( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 8: { + bitField0_ |= 0x00000001; + timestamp_ = input.readUInt64(); + break; + } + case 18: { + if (!((mutable_bitField0_ & 0x00000002) == 0x00000002)) { + metrics_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000002; + } + metrics_.add(input.readMessage(org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugBProto.Payload.Metric.PARSER, extensionRegistry)); + break; + } + case 24: { + bitField0_ |= 0x00000002; + seq_ = input.readUInt64(); + break; + } + case 34: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000004; + uuid_ = bs; + break; + } + case 42: { + bitField0_ |= 0x00000008; + body_ = input.readBytes(); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000002) == 0x00000002)) { + metrics_ = java.util.Collections.unmodifiableList(metrics_); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugBProto.internal_static_com_cirruslink_sparkplug_protobuf_Payload_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugBProto.internal_static_com_cirruslink_sparkplug_protobuf_Payload_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugBProto.Payload.class, org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugBProto.Payload.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public Payload parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Payload(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + public interface TemplateOrBuilder extends + // @@protoc_insertion_point(interface_extends:org.thingsboard.server.transport.mqtt.util.sparkplug.Payload.Template) + com.google.protobuf.GeneratedMessage. + ExtendableMessageOrBuilder