From 0dcde6044392b8f6f22766a54d7999c80b4f8d96 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Wed, 30 Nov 2022 13:06:00 +0200 Subject: [PATCH 001/258] Added alarm assignment feature and tests for it --- .../main/data/upgrade/3.4.2/schema_update.sql | 27 +++++++ .../server/controller/AlarmController.java | 73 +++++++++++++++-- .../controller/ControllerConstants.java | 2 + .../install/ThingsboardInstallService.java | 4 + .../service/action/EntityActionService.java | 6 ++ .../entitiy/AbstractTbEntityService.java | 2 +- .../DefaultTbNotificationEntityService.java | 4 + .../entitiy/alarm/DefaultTbAlarmService.java | 29 +++++++ .../service/entitiy/alarm/TbAlarmService.java | 5 ++ .../install/SqlDatabaseUpgradeService.java | 12 +++ .../update/DefaultDataUpdateService.java | 2 +- .../DefaultAlarmSubscriptionService.java | 19 ++++- .../controller/BaseAlarmControllerTest.java | 79 +++++++++++++++++++ .../server/dao/alarm/AlarmService.java | 7 +- .../server/common/data/DataConstants.java | 2 + .../server/common/data/alarm/Alarm.java | 25 +++--- .../server/common/data/alarm/AlarmQuery.java | 2 + .../server/common/data/alarm/EntityAlarm.java | 2 + .../server/common/data/audit/ActionType.java | 2 + .../common/data/edge/EdgeEventActionType.java | 2 + .../server/dao/alarm/AlarmDao.java | 3 +- .../server/dao/alarm/BaseAlarmService.java | 44 ++++++++++- .../server/dao/audit/AuditLogServiceImpl.java | 2 + .../server/dao/model/ModelConstants.java | 3 + .../dao/model/sql/AbstractAlarmEntity.java | 21 +++++ .../dao/model/sql/EntityAlarmEntity.java | 11 +++ .../server/dao/sql/alarm/AlarmRepository.java | 15 +++- .../server/dao/sql/alarm/JpaAlarmDao.java | 20 ++++- .../resources/sql/schema-entities-idx.sql | 2 + .../main/resources/sql/schema-entities.sql | 3 + .../dao/service/BaseAlarmServiceTest.java | 12 +-- .../engine/api/RuleEngineAlarmService.java | 7 +- .../components/alarm/alarm-table-config.ts | 2 +- .../home/models/widget-component.models.ts | 5 +- ui-ngx/src/app/shared/models/alarm.models.ts | 17 +++- .../src/app/shared/models/audit-log.models.ts | 4 + .../assets/locale/locale.constant-en_US.json | 3 + 37 files changed, 442 insertions(+), 38 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..e47a6bf2f3 --- /dev/null +++ b/application/src/main/data/upgrade/3.4.2/schema_update.sql @@ -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. +-- + + +-- ALARM ASSIGN TO USER START + +ALTER TABLE alarm ADD COLUMN assign_ts BIGINT; +ALTER TABLE alarm ADD COLUMN assignee_id UUID; + +ALTER TABLE entity_alarm ADD COLUMN assignee_id UUID; + +CREATE INDEX IF NOT EXISTS idx_entity_alarm_assignee_id ON entity_alarm(assignee_id); + +-- ALARM ASSIGN TO USER END \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 858a7ee3c4..cb1093c745 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -41,6 +41,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -48,9 +49,13 @@ import org.thingsboard.server.service.entitiy.alarm.TbAlarmService; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; +import java.util.UUID; + import static org.thingsboard.server.controller.ControllerConstants.ALARM_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ALARM_INFO_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ALARM_SORT_PROPERTY_ALLOWABLE_VALUES; +import static org.thingsboard.server.controller.ControllerConstants.ASSIGNEE_ID; +import static org.thingsboard.server.controller.ControllerConstants.ASSIGN_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE; @@ -79,6 +84,7 @@ public class AlarmController extends BaseController { private static final String ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES = "ANY, ACTIVE, CLEARED, ACK, UNACK"; private static final String ALARM_QUERY_STATUS_DESCRIPTION = "A string value representing one of the AlarmStatus enumeration value"; private static final String ALARM_QUERY_STATUS_ALLOWABLE_VALUES = "ACTIVE_UNACK, ACTIVE_ACK, CLEARED_UNACK, CLEARED_ACK"; + private static final String ALARM_QUERY_ASSIGNEE_DESCRIPTION = "A string value representing the assignee user id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; private static final String ALARM_QUERY_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on of next alarm fields: type, severity or status"; private static final String ALARM_QUERY_START_TIME_DESCRIPTION = "The start timestamp in milliseconds of the search time range over the Alarm class field: 'createdTime'."; private static final String ALARM_QUERY_END_TIME_DESCRIPTION = "The end timestamp in milliseconds of the search time range over the Alarm class field: 'createdTime'."; @@ -179,6 +185,45 @@ public class AlarmController extends BaseController { tbAlarmService.clear(alarm, getCurrentUser()).get(); } + @ApiOperation(value = "Assign/Reassign Alarm (assignAlarm)", + notes = "Assign the Alarm. " + + "Once assigned, the 'assign_ts' field will be set to current timestamp and special rule chain event 'ALARM_ASSIGNED' " + + "(or ALARM_REASSIGNED in case of assigning already assigned alarm) will be generated. " + + "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/alarm/{alarmId}/assign/{assigneeId}", method = RequestMethod.POST) + @ResponseStatus(value = HttpStatus.OK) + public void assignAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) + @PathVariable(ALARM_ID) String strAlarmId, + @ApiParam(value = ASSIGN_ID_PARAM_DESCRIPTION) + @PathVariable(ASSIGNEE_ID) String strAssigneeId + ) throws Exception { + checkParameter(ALARM_ID, strAlarmId); + checkParameter(ASSIGNEE_ID, strAssigneeId); + AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); + // TODO Add special permissions for assignment + Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); + UserId assigneeId = new UserId(UUID.fromString(strAssigneeId)); + tbAlarmService.assign(alarm, getCurrentUser(), assigneeId).get(); + } + + @ApiOperation(value = "Unassign Alarm (unassignAlarm)", + notes = "Unassign the Alarm. " + + "Once unassigned, the 'assign_ts' field will be set to current timestamp and special rule chain event 'ALARM_UNASSIGNED' will be generated. " + + "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/alarm/{alarmId}/assign", method = RequestMethod.DELETE) + @ResponseStatus(value = HttpStatus.OK) + public void assignAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) + @PathVariable(ALARM_ID) String strAlarmId + ) throws Exception { + checkParameter(ALARM_ID, strAlarmId); + AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); + // TODO Add special permissions for unassignment + Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); + tbAlarmService.unassign(alarm, getCurrentUser()).get(); + } + @ApiOperation(value = "Get Alarms (getAlarms)", notes = "Returns a page of alarms for the selected entity. Specifying both parameters 'searchStatus' and 'status' at the same time will cause an error. " + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) @@ -194,6 +239,8 @@ public class AlarmController extends BaseController { @RequestParam(required = false) String searchStatus, @ApiParam(value = ALARM_QUERY_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES) @RequestParam(required = false) String status, + @ApiParam(value = ALARM_QUERY_ASSIGNEE_DESCRIPTION) + @RequestParam(required = false) String assigneeId, @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) @@ -221,10 +268,14 @@ public class AlarmController extends BaseController { "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } checkEntityId(entityId, Operation.READ); + UserId assigneeUserId = null; + if (assigneeId != null) { + assigneeUserId = new UserId(UUID.fromString(assigneeId)); + } TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); try { - return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get()); + return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, assigneeUserId, fetchOriginator)).get()); } catch (Exception e) { throw handleException(e); } @@ -244,6 +295,8 @@ public class AlarmController extends BaseController { @RequestParam(required = false) String searchStatus, @ApiParam(value = ALARM_QUERY_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES) @RequestParam(required = false) String status, + @ApiParam(value = ALARM_QUERY_ASSIGNEE_DESCRIPTION) + @RequestParam(required = false) String assigneeId, @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) @@ -267,13 +320,17 @@ public class AlarmController extends BaseController { throw new ThingsboardException("Invalid alarms search query: Both parameters 'searchStatus' " + "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } + UserId assigneeUserId = null; + if (assigneeId != null) { + assigneeUserId = new UserId(UUID.fromString(assigneeId)); + } TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); try { if (getCurrentUser().isCustomerUser()) { - return checkNotNull(alarmService.findCustomerAlarms(getCurrentUser().getTenantId(), getCurrentUser().getCustomerId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get()); + return checkNotNull(alarmService.findCustomerAlarms(getCurrentUser().getTenantId(), getCurrentUser().getCustomerId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, assigneeUserId, fetchOriginator)).get()); } else { - return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get()); + return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, assigneeUserId, fetchOriginator)).get()); } } catch (Exception e) { throw handleException(e); @@ -295,7 +352,9 @@ public class AlarmController extends BaseController { @ApiParam(value = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES) @RequestParam(required = false) String searchStatus, @ApiParam(value = ALARM_QUERY_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES) - @RequestParam(required = false) String status + @RequestParam(required = false) String status, + @ApiParam(value = ALARM_QUERY_ASSIGNEE_DESCRIPTION) + @RequestParam(required = false) String assigneeId ) throws ThingsboardException { checkParameter("EntityId", strEntityId); checkParameter("EntityType", strEntityType); @@ -306,9 +365,13 @@ public class AlarmController extends BaseController { throw new ThingsboardException("Invalid alarms search query: Both parameters 'searchStatus' " + "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } + UserId assigneeUserId = null; + if (assigneeId != null) { + assigneeUserId = new UserId(UUID.fromString(assigneeId)); + } checkEntityId(entityId, Operation.READ); try { - return alarmService.findHighestAlarmSeverity(getCurrentUser().getTenantId(), entityId, alarmSearchStatus, alarmStatus); + return alarmService.findHighestAlarmSeverity(getCurrentUser().getTenantId(), entityId, alarmSearchStatus, alarmStatus, assigneeUserId); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java index e00ccaa4bc..27d26164df 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java +++ b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java @@ -27,6 +27,7 @@ public class ControllerConstants { protected static final String EDGE_ID = "edgeId"; protected static final String RPC_ID = "rpcId"; protected static final String ENTITY_ID = "entityId"; + protected static final String ASSIGNEE_ID = "assigneeId"; protected static final String PAGE_DATA_PARAMETERS = "You can specify parameters to filter the results. " + "The result is wrapped with PageData object that allows you to iterate over result set using pagination. " + "See the 'Model' tab of the Response Class for more details. "; @@ -44,6 +45,7 @@ public class ControllerConstants { protected static final String USER_ID_PARAM_DESCRIPTION = "A string value representing the user id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String ASSET_ID_PARAM_DESCRIPTION = "A string value representing the asset id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String ALARM_ID_PARAM_DESCRIPTION = "A string value representing the alarm id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; + protected static final String ASSIGN_ID_PARAM_DESCRIPTION = "A string value representing the user id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String ENTITY_ID_PARAM_DESCRIPTION = "A string value representing the entity id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String OTA_PACKAGE_ID_PARAM_DESCRIPTION = "A string value representing the ota package id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String ENTITY_TYPE_PARAM_DESCRIPTION = "A string value representing the entity type. For example, 'DEVICE'"; diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 9facea6dde..6b6104b0e2 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -236,6 +236,10 @@ public class ThingsboardInstallService { log.info("Updating system data..."); systemDataLoaderService.updateSystemWidgets(); break; + case "3.4.2": + log.info("Upgrading ThingsBoard from version 3.4.2 to 3.5 ..."); + databaseEntitiesUpgradeService.upgradeDatabase("3.4.2"); + break; //TODO update CacheCleanupService on the next version upgrade diff --git a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java index 7057f903be..fc8b71947c 100644 --- a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java +++ b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java @@ -86,6 +86,12 @@ public class EntityActionService { case ALARM_CLEAR: msgType = DataConstants.ALARM_CLEAR; break; + case ALARM_ASSIGN: + msgType = DataConstants.ALARM_CLEAR; + break; + case ALARM_UNASSIGN: + msgType = DataConstants.ALARM_CLEAR; + break; case ALARM_DELETE: msgType = DataConstants.ALARM_DELETE; break; diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java index 9f34c061fe..f4885c56cc 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java @@ -73,7 +73,7 @@ public abstract class AbstractTbEntityService { protected ListenableFuture removeAlarmsByEntityId(TenantId tenantId, EntityId entityId) { ListenableFuture> alarmsFuture = - alarmService.findAlarms(tenantId, new AlarmQuery(entityId, new TimePageLink(Integer.MAX_VALUE), null, null, false)); + alarmService.findAlarms(tenantId, new AlarmQuery(entityId, new TimePageLink(Integer.MAX_VALUE), null, null, null, false)); ListenableFuture> alarmIdsFuture = Futures.transform(alarmsFuture, page -> page.getData().stream().map(AlarmInfo::getId).collect(Collectors.toList()), dbExecutor); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java index 8f8531a5b4..94370d6ff2 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java @@ -306,6 +306,10 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS return EdgeEventActionType.ALARM_ACK; case ALARM_CLEAR: return EdgeEventActionType.ALARM_CLEAR; + case ALARM_ASSIGN: + return EdgeEventActionType.ALARM_ASSIGN; + case ALARM_UNASSIGN: + return EdgeEventActionType.ALARM_UNASSIGN; case DELETED: return EdgeEventActionType.DELETED; case RELATION_ADD_OR_UPDATE: diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java index 8157eff3e5..023e144bf0 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import java.util.List; @@ -75,6 +76,34 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb }, MoreExecutors.directExecutor()); } + @Override + public ListenableFuture assign(Alarm alarm, User user, UserId assigneeId) { + long assignTs = System.currentTimeMillis(); + ListenableFuture future = alarmSubscriptionService.assignAlarm(alarm.getTenantId(), alarm.getId(), assigneeId, assignTs); + return Futures.transform(future, result -> { + if (result != null && result) { + alarm.setAssignTs(assignTs); + alarm.setAssigneeId(assigneeId); + notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_ASSIGN, user); + } + return null; + }, MoreExecutors.directExecutor()); + } + + @Override + public ListenableFuture unassign(Alarm alarm, User user) { + long assignTs = System.currentTimeMillis(); + ListenableFuture future = alarmSubscriptionService.unassignAlarm(alarm.getTenantId(), alarm.getId(), assignTs); + return Futures.transform(future, result -> { + if (result != null && result) { + alarm.setAssignTs(assignTs); + alarm.setAssigneeId(null); + notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_UNASSIGN, user); + } + return null; + }, MoreExecutors.directExecutor()); + } + @Override public Boolean delete(Alarm alarm, User user) { TenantId tenantId = alarm.getTenantId(); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java index 8cd8d0b49d..0cf2de5ba9 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.UserId; public interface TbAlarmService { @@ -28,5 +29,9 @@ public interface TbAlarmService { ListenableFuture clear(Alarm alarm, User user); + ListenableFuture assign(Alarm alarm, User user, UserId assigneeId); + + ListenableFuture unassign(Alarm alarm, User user); + Boolean delete(Alarm alarm, User user); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 80665d544f..349400ecd1 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -677,6 +677,18 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService log.error("Failed updating schema!!!", e); } break; + case "3.4.2": + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + log.info("Updating schema ..."); + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.4.2", SCHEMA_UPDATE_SQL); + loadSql(schemaUpdateFile, conn); + log.info("Updating schema settings..."); + conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3004003;"); + log.info("Schema updated."); + } catch (Exception e) { + log.error("Failed updating schema!!!", e); + } + break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index ee34bd7f23..a831a0af5e 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -555,7 +555,7 @@ public class DefaultDataUpdateService implements DataUpdateService { }; private void updateTenantAlarmsCustomer(TenantId tenantId, String name, AtomicLong processed) { - AlarmQuery alarmQuery = new AlarmQuery(null, new TimePageLink(1000), null, null, false); + AlarmQuery alarmQuery = new AlarmQuery(null, new TimePageLink(1000), null, null, null, false); PageData alarms = alarmDao.findAlarms(tenantId, alarmQuery); boolean hasNext = true; while (hasNext) { 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 956dde8024..f7d121d9ff 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 @@ -34,6 +34,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.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.AlarmDataQuery; @@ -124,6 +125,20 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService return result; } + @Override + public ListenableFuture assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTs) { + ListenableFuture result = alarmService.assignAlarm(tenantId, alarmId, assigneeId, assignTs); + Futures.addCallback(result, new AlarmUpdateCallback(), wsCallBackExecutor); + return Futures.transform(result, AlarmOperationResult::isSuccessful, wsCallBackExecutor); + } + + @Override + public ListenableFuture unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTs) { + ListenableFuture result = alarmService.unassignAlarm(tenantId, alarmId, assignTs); + Futures.addCallback(result, new AlarmUpdateCallback(), wsCallBackExecutor); + return Futures.transform(result, AlarmOperationResult::isSuccessful, wsCallBackExecutor); + } + @Override public ListenableFuture findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId) { return alarmService.findAlarmByIdAsync(tenantId, alarmId); @@ -150,8 +165,8 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService } @Override - public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus) { - return alarmService.findHighestAlarmSeverity(tenantId, entityId, alarmSearchStatus, alarmStatus); + public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus, UserId assigneeUserId) { + return alarmService.findHighestAlarmSeverity(tenantId, entityId, alarmSearchStatus, alarmStatus, assigneeUserId); } @Override diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java index 493b1818d9..05b6f15c61 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java @@ -347,6 +347,85 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { .andExpect(statusReason(containsString(msgErrorPermission))); } + @Test + public void testAssignAlarm() throws Exception { + loginTenantAdmin(); + Alarm alarm = createAlarm(TEST_ALARM_TYPE); + Mockito.reset(tbClusterService, auditLogService); + long beforeAssignmentTs = System.currentTimeMillis(); + + doPost("/api/alarm/" + alarm.getId() + "/assign/" + tenantAdminUserId.getId()).andExpect(status().isOk()); + Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class); + Assert.assertNotNull(foundAlarm); + Assert.assertEquals(tenantAdminUserId, foundAlarm.getAssigneeId()); + Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis()); + + testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(), + tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN); + } + + @Test + public void testReassignAlarm() throws Exception { + loginTenantAdmin(); + Alarm alarm = createAlarm(TEST_ALARM_TYPE); + Mockito.reset(tbClusterService, auditLogService); + long beforeAssignmentTs = System.currentTimeMillis(); + + doPost("/api/alarm/" + alarm.getId() + "/assign/" + tenantAdminUserId.getId()).andExpect(status().isOk()); + + Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class); + Assert.assertNotNull(foundAlarm); + Assert.assertEquals(tenantAdminUserId, foundAlarm.getAssigneeId()); + Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis()); + + testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(), + tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN); + + logout(); + + loginCustomerUser(); + Mockito.reset(tbClusterService, auditLogService); + beforeAssignmentTs = System.currentTimeMillis(); + + doPost("/api/alarm/" + alarm.getId() + "/assign/" + customerUserId.getId()).andExpect(status().isOk()); + + foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class); + Assert.assertNotNull(foundAlarm); + Assert.assertEquals(customerUserId, foundAlarm.getAssigneeId()); + Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis()); + + testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(), + tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_ASSIGN); + } + + @Test + public void testUnassignAlarm() throws Exception { + loginTenantAdmin(); + Alarm alarm = createAlarm(TEST_ALARM_TYPE); + Mockito.reset(tbClusterService, auditLogService); + long beforeAssignmentTs = System.currentTimeMillis(); + + doPost("/api/alarm/" + alarm.getId() + "/assign/" + tenantAdminUserId.getId()).andExpect(status().isOk()); + Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class); + Assert.assertNotNull(foundAlarm); + Assert.assertEquals(tenantAdminUserId, foundAlarm.getAssigneeId()); + Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis()); + + testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(), + tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN); + + beforeAssignmentTs = System.currentTimeMillis(); + + doDelete("/api/alarm/" + alarm.getId() + "/assign").andExpect(status().isOk()); + foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class); + Assert.assertNotNull(foundAlarm); + Assert.assertNull(foundAlarm.getAssigneeId()); + Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis()); + + testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(), + tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_UNASSIGN); + } + @Test public void testFindAlarmsViaCustomerUser() throws Exception { loginCustomerUser(); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java index b8041aa3fc..81b4de263a 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; 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.AlarmDataQuery; @@ -48,6 +49,10 @@ public interface AlarmService { ListenableFuture clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs); + ListenableFuture assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTs); + + ListenableFuture unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTs); + Alarm findAlarmById(TenantId tenantId, AlarmId alarmId); ListenableFuture findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId); @@ -59,7 +64,7 @@ public interface AlarmService { ListenableFuture> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query); AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, - AlarmStatus alarmStatus); + AlarmStatus alarmStatus, UserId assigneeUserId); ListenableFuture findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type); 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 69c36697d3..099a452712 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 @@ -73,6 +73,8 @@ public class DataConstants { public static final String TIMESERIES_DELETED = "TIMESERIES_DELETED"; public static final String ALARM_ACK = "ALARM_ACK"; public static final String ALARM_CLEAR = "ALARM_CLEAR"; + public static final String ALARM_ASSIGN = "ALARM_ASSIGN"; + public static final String ALARM_UNASSIGN = "ALARM_UNASSIGN"; public static final String ALARM_DELETE = "ALARM_DELETE"; public static final String ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT"; public static final String ENTITY_ASSIGNED_TO_TENANT = "ENTITY_ASSIGNED_TO_TENANT"; 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..8eaca6b567 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 @@ -30,6 +30,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.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.validation.Length; import java.util.List; @@ -58,23 +59,27 @@ public class Alarm extends BaseData implements HasName, HasTenantId, Ha private AlarmSeverity severity; @ApiModelProperty(position = 9, required = true, value = "Alarm status", example = "CLEARED_UNACK") private AlarmStatus status; - @ApiModelProperty(position = 10, value = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565") + @ApiModelProperty(position = 10, value = "Alarm assignee user id") + private UserId assigneeId; + @ApiModelProperty(position = 11, value = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565") private long startTs; - @ApiModelProperty(position = 11, value = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522") + @ApiModelProperty(position = 12, value = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522") private long endTs; - @ApiModelProperty(position = 12, value = "Timestamp of the alarm acknowledgement, in milliseconds", example = "1634115221948") + @ApiModelProperty(position = 13, value = "Timestamp of the alarm acknowledgement, in milliseconds", example = "1634115221948") private long ackTs; - @ApiModelProperty(position = 13, value = "Timestamp of the alarm clearing, in milliseconds", example = "1634114528465") + @ApiModelProperty(position = 14, value = "Timestamp of the alarm clearing, in milliseconds", example = "1634114528465") private long clearTs; - @ApiModelProperty(position = 14, value = "JSON object with alarm details") + @ApiModelProperty(position = 15, value = "Timestamp of the alarm assigning0, in milliseconds", example = "1634115928465") + private long assignTs; + @ApiModelProperty(position = 16, value = "JSON object with alarm details") private transient JsonNode details; - @ApiModelProperty(position = 15, value = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true") + @ApiModelProperty(position = 17, value = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true") private boolean propagate; - @ApiModelProperty(position = 16, value = "Propagation flag to specify if alarm should be propagated to the owner (tenant or customer) of alarm originator", example = "true") + @ApiModelProperty(position = 18, value = "Propagation flag to specify if alarm should be propagated to the owner (tenant or customer) of alarm originator", example = "true") private boolean propagateToOwner; - @ApiModelProperty(position = 17, value = "Propagation flag to specify if alarm should be propagated to the tenant entity", example = "true") + @ApiModelProperty(position = 19, value = "Propagation flag to specify if alarm should be propagated to the tenant entity", example = "true") private boolean propagateToTenant; - @ApiModelProperty(position = 18, value = "JSON array of relation types that should be used for propagation. " + + @ApiModelProperty(position = 20, value = "JSON array of relation types that should be used for propagation. " + "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; @@ -96,10 +101,12 @@ public class Alarm extends BaseData implements HasName, HasTenantId, Ha this.originator = alarm.getOriginator(); this.severity = alarm.getSeverity(); this.status = alarm.getStatus(); + this.assigneeId = alarm.getAssigneeId(); this.startTs = alarm.getStartTs(); this.endTs = alarm.getEndTs(); this.ackTs = alarm.getAckTs(); this.clearTs = alarm.getClearTs(); + this.assignTs = alarm.getAssignTs(); this.details = alarm.getDetails(); this.propagate = alarm.isPropagate(); this.propagateToOwner = alarm.isPropagateToOwner(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java index 25bfefd4b2..945442dc9b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java @@ -19,6 +19,7 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.TimePageLink; /** @@ -33,6 +34,7 @@ public class AlarmQuery { private TimePageLink pageLink; private AlarmSearchStatus searchStatus; private AlarmStatus status; + private UserId assigneeId; private Boolean fetchOriginator; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/EntityAlarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/EntityAlarm.java index e7606d852b..b75adf7ebb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/EntityAlarm.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/EntityAlarm.java @@ -23,6 +23,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.TenantId; +import org.thingsboard.server.common.data.id.UserId; @Data @NoArgsConstructor @@ -35,6 +36,7 @@ public class EntityAlarm implements HasTenantId { private String alarmType; private CustomerId customerId; + private UserId assigneeId; private AlarmId alarmId; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java index 41f6f30dbe..4b666fa425 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java @@ -40,6 +40,8 @@ public enum ActionType { ALARM_ACK(false), ALARM_CLEAR(false), ALARM_DELETE(false), + ALARM_ASSIGN(false), + ALARM_UNASSIGN(false), LOGIN(false), LOGOUT(false), LOCKOUT(false), diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java index bba8767fca..79a689f2e6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java @@ -31,6 +31,8 @@ public enum EdgeEventActionType { RPC_CALL, ALARM_ACK, ALARM_CLEAR, + ALARM_ASSIGN, + ALARM_UNASSIGN, ASSIGNED_TO_EDGE, UNASSIGNED_FROM_EDGE, CREDENTIALS_REQUEST, diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java index 87788d1b2b..11c3e47599 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java @@ -26,6 +26,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.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.query.AlarmData; @@ -58,7 +59,7 @@ public interface AlarmDao extends Dao { PageData findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection orderedEntityIds); - Set findAlarmSeverities(TenantId tenantId, EntityId entityId, Set status); + Set findAlarmSeverities(TenantId tenantId, EntityId entityId, Set status, UserId assigneeUserId); PageData findAlarmsIdsByEndTsBeforeAndTenantId(Long time, TenantId tenantId, PageLink pageLink); 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 e6012602f1..3abefb780b 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 @@ -39,6 +39,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.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.AlarmDataQuery; @@ -261,6 +262,42 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ }); } + @Override + public ListenableFuture assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTime) { + return getAndUpdateAsync(tenantId, alarmId, new Function() { + @Nullable + @Override + public AlarmOperationResult apply(@Nullable Alarm alarm) { + if (alarm == null || assigneeId.equals(alarm.getAssigneeId())) { + return new AlarmOperationResult(alarm, false); + } else { + alarm.setAssigneeId(assigneeId); + alarm.setAssignTs(assignTime); + alarm = alarmDao.save(alarm.getTenantId(), alarm); + return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm))); + } + } + }); + } + + @Override + public ListenableFuture unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTime) { + return getAndUpdateAsync(tenantId, alarmId, new Function() { + @Nullable + @Override + public AlarmOperationResult apply(@Nullable Alarm alarm) { + if (alarm == null || alarm.getAssigneeId() == null) { + return new AlarmOperationResult(alarm, false); + } else { + alarm.setAssigneeId(null); + alarm.setAssignTs(assignTime); + alarm = alarmDao.save(alarm.getTenantId(), alarm); + return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm))); + } + } + }); + } + @Override public Alarm findAlarmById(TenantId tenantId, AlarmId alarmId) { log.trace("Executing findAlarmById [{}]", alarmId); @@ -328,7 +365,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @Override public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, - AlarmStatus alarmStatus) { + AlarmStatus alarmStatus, UserId assigneeUserId) { Set statusList = null; if (alarmSearchStatus != null) { statusList = alarmSearchStatus.getStatuses(); @@ -336,7 +373,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ statusList = Collections.singleton(alarmStatus); } - Set alarmSeverities = alarmDao.findAlarmSeverities(tenantId, entityId, statusList); + Set alarmSeverities = alarmDao.findAlarmSeverities(tenantId, entityId, statusList, assigneeUserId); return alarmSeverities.stream().min(AlarmSeverity::compareTo).orElse(null); } @@ -390,7 +427,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ } private void createEntityAlarmRecord(TenantId tenantId, EntityId entityId, Alarm alarm) { - EntityAlarm entityAlarm = new EntityAlarm(tenantId, entityId, alarm.getCreatedTime(), alarm.getType(), alarm.getCustomerId(), alarm.getId()); + // 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()); try { alarmDao.createEntityAlarmRecord(entityAlarm); } catch (Exception e) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java index 8bb862bea5..8a08abcf3b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java @@ -165,6 +165,8 @@ public class AuditLogServiceImpl implements AuditLogService { case UPDATED: case ALARM_ACK: case ALARM_CLEAR: + case ALARM_ASSIGN: + case ALARM_UNASSIGN: case RELATIONS_DELETED: case ASSIGNED_TO_TENANT: if (entity != 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..c64dea0c40 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 @@ -41,6 +41,7 @@ public class ModelConstants { public static final String USER_ID_PROPERTY = "user_id"; public static final String TENANT_ID_PROPERTY = "tenant_id"; public static final String CUSTOMER_ID_PROPERTY = "customer_id"; + public static final String ASSIGNEE_ID_PROPERTY = "assignee_id"; public static final String DEVICE_ID_PROPERTY = "device_id"; public static final String TITLE_PROPERTY = "title"; public static final String ALIAS_PROPERTY = "alias"; @@ -286,10 +287,12 @@ public class ModelConstants { 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_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"; public static final String ALARM_CLEAR_TS_PROPERTY = "clear_ts"; + public static final String ALARM_ASSIGN_TS_PROPERTY = "assign_ts"; public static final String ALARM_PROPAGATE_PROPERTY = "propagate"; public static final String ALARM_PROPAGATE_TO_OWNER_PROPERTY = "propagate_to_owner"; public static final String ALARM_PROPAGATE_TO_TENANT_PROPERTY = "propagate_to_tenant"; 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..c01b145174 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 @@ -20,6 +20,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; +import org.springframework.data.annotation.Id; import org.springframework.util.CollectionUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.StringUtils; @@ -30,6 +31,7 @@ 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.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; @@ -44,6 +46,8 @@ import java.util.Collections; import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ASSIGNEE_ID_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ASSIGN_TS_PROPERTY; 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; @@ -88,6 +92,10 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity @Column(name = ALARM_STATUS_PROPERTY) private AlarmStatus status; + @Type(type="pg-uuid") + @Column(name = ALARM_ASSIGNEE_ID_PROPERTY) + private UUID assigneeId; + @Column(name = ALARM_START_TS_PROPERTY) private Long startTs; @@ -100,6 +108,9 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity @Column(name = ALARM_CLEAR_TS_PROPERTY) private Long clearTs; + @Column(name = ALARM_ASSIGN_TS_PROPERTY) + private Long assignTs; + @Type(type = "json") @Column(name = ModelConstants.ASSET_ADDITIONAL_INFO_PROPERTY) private JsonNode details; @@ -137,6 +148,9 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity this.type = alarm.getType(); this.severity = alarm.getSeverity(); this.status = alarm.getStatus(); + if (alarm.getAssigneeId() != null) { + this.assigneeId = alarm.getAssigneeId().getId(); + } this.propagate = alarm.isPropagate(); this.propagateToOwner = alarm.isPropagateToOwner(); this.propagateToTenant = alarm.isPropagateToTenant(); @@ -144,6 +158,7 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity this.endTs = alarm.getEndTs(); this.ackTs = alarm.getAckTs(); this.clearTs = alarm.getClearTs(); + this.assignTs = alarm.getAssignTs(); this.details = alarm.getDetails(); if (!CollectionUtils.isEmpty(alarm.getPropagateRelationTypes())) { this.propagateRelationTypes = String.join(",", alarm.getPropagateRelationTypes()); @@ -163,6 +178,7 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity this.type = alarmEntity.getType(); this.severity = alarmEntity.getSeverity(); this.status = alarmEntity.getStatus(); + this.assigneeId = alarmEntity.getAssigneeId(); this.propagate = alarmEntity.getPropagate(); this.propagateToOwner = alarmEntity.getPropagateToOwner(); this.propagateToTenant = alarmEntity.getPropagateToTenant(); @@ -170,6 +186,7 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity this.endTs = alarmEntity.getEndTs(); this.ackTs = alarmEntity.getAckTs(); this.clearTs = alarmEntity.getClearTs(); + this.assignTs = alarmEntity.getAssignTs(); this.details = alarmEntity.getDetails(); this.propagateRelationTypes = alarmEntity.getPropagateRelationTypes(); } @@ -187,6 +204,9 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity alarm.setType(type); alarm.setSeverity(severity); alarm.setStatus(status); + if (assigneeId != null) { + alarm.setAssigneeId(new UserId(assigneeId)); + } alarm.setPropagate(propagate); alarm.setPropagateToOwner(propagateToOwner); alarm.setPropagateToTenant(propagateToTenant); @@ -194,6 +214,7 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity alarm.setEndTs(endTs); alarm.setAckTs(ackTs); alarm.setClearTs(clearTs); + alarm.setAssignTs(assignTs); alarm.setDetails(details); if (!StringUtils.isEmpty(propagateRelationTypes)) { alarm.setPropagateRelationTypes(Arrays.asList(propagateRelationTypes.split(","))); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java index 1f1a40b25a..434a4662fc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java @@ -21,6 +21,7 @@ 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.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.dao.model.ToData; import javax.persistence.Column; @@ -30,6 +31,7 @@ import javax.persistence.IdClass; import javax.persistence.Table; import java.util.UUID; +import static org.thingsboard.server.dao.model.ModelConstants.ASSIGNEE_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.CREATED_TIME_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ALARM_COLUMN_FAMILY_NAME; @@ -66,6 +68,9 @@ public final class EntityAlarmEntity implements ToData { @Column(name = CUSTOMER_ID_PROPERTY, columnDefinition = "uuid") private UUID customerId; + @Column(name = ASSIGNEE_ID_PROPERTY, columnDefinition = "uuid") + private UUID assigneeId; + public EntityAlarmEntity() { super(); } @@ -80,6 +85,9 @@ public final class EntityAlarmEntity implements ToData { if (entityAlarm.getCustomerId() != null) { customerId = entityAlarm.getCustomerId().getId(); } + if (entityAlarm.getAssigneeId() != null) { + assigneeId = entityAlarm.getAssigneeId().getId(); + } } @Override @@ -93,6 +101,9 @@ public final class EntityAlarmEntity implements ToData { if (customerId != null) { result.setCustomerId(new CustomerId(customerId)); } + if (assigneeId != null) { + result.setAssigneeId(new UserId(assigneeId)); + } return result; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java index beadcddf31..24bfe88656 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java @@ -48,6 +48,7 @@ public interface AlarmRepository extends JpaRepository { "AND (:startTime IS NULL OR (a.createdTime >= :startTime AND ea.createdTime >= :startTime)) " + "AND (:endTime IS NULL OR (a.createdTime <= :endTime AND ea.createdTime <= :endTime)) " + "AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " + + "AND (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId))" + "AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) " @@ -63,6 +64,7 @@ public interface AlarmRepository extends JpaRepository { "AND (:startTime IS NULL OR (a.createdTime >= :startTime AND ea.createdTime >= :startTime)) " + "AND (:endTime IS NULL OR (a.createdTime <= :endTime AND ea.createdTime <= :endTime)) " + "AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " + + "AND (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId))" + "AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) ") @@ -72,6 +74,7 @@ public interface AlarmRepository extends JpaRepository { @Param("startTime") Long startTime, @Param("endTime") Long endTime, @Param("alarmStatuses") Set alarmStatuses, + @Param("assigneeId") UUID assigneeId, @Param("searchText") String searchText, Pageable pageable); @@ -80,6 +83,7 @@ public interface AlarmRepository extends JpaRepository { "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + "AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " + + "AND (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId))" + "AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) ", @@ -90,6 +94,7 @@ public interface AlarmRepository extends JpaRepository { "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + "AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " + + "AND (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId))" + "AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) ") @@ -97,6 +102,7 @@ public interface AlarmRepository extends JpaRepository { @Param("startTime") Long startTime, @Param("endTime") Long endTime, @Param("alarmStatuses") Set alarmStatuses, + @Param("assigneeId") UUID assigneeId, @Param("searchText") String searchText, Pageable pageable); @@ -105,6 +111,7 @@ public interface AlarmRepository extends JpaRepository { "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + "AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " + + "AND (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId))" + "AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) " @@ -116,6 +123,7 @@ public interface AlarmRepository extends JpaRepository { "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + "AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " + + "AND (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId))" + "AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) ") @@ -124,6 +132,7 @@ public interface AlarmRepository extends JpaRepository { @Param("startTime") Long startTime, @Param("endTime") Long endTime, @Param("alarmStatuses") Set alarmStatuses, + @Param("assigneeId") UUID assigneeId, @Param("searchText") String searchText, Pageable pageable); @@ -133,11 +142,13 @@ public interface AlarmRepository extends JpaRepository { "AND ea.tenantId = :tenantId " + "AND ea.entityId = :affectedEntityId " + "AND ea.entityType = :affectedEntityType " + - "AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses))") + "AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " + + "AND (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId))") Set findAlarmSeverities(@Param("tenantId") UUID tenantId, @Param("affectedEntityId") UUID affectedEntityId, @Param("affectedEntityType") String affectedEntityType, - @Param("alarmStatuses") Set alarmStatuses); + @Param("alarmStatuses") Set alarmStatuses, + @Param("assigneeId") UUID assigneeId); @Query("SELECT a.id FROM AlarmEntity a WHERE a.tenantId = :tenantId AND a.createdTime < :time AND a.endTs < :time") Page findAlarmsIdsByEndTsBeforeAndTenantId(@Param("time") Long time, @Param("tenantId") UUID tenantId, Pageable pageable); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index 407f7658fe..b862f1c7e7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java @@ -32,6 +32,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.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.query.AlarmData; @@ -112,6 +113,10 @@ public class JpaAlarmDao extends JpaAbstractDao implements A } else if (query.getStatus() != null) { statusSet = Collections.singleton(query.getStatus()); } + UUID assigneeId = null; + if (query.getAssigneeId() != null) { + assigneeId = query.getAssigneeId().getId(); + } if (affectedEntity != null) { return DaoUtil.toPageData( alarmRepository.findAlarms( @@ -121,6 +126,7 @@ public class JpaAlarmDao extends JpaAbstractDao implements A query.getPageLink().getStartTime(), query.getPageLink().getEndTime(), statusSet, + assigneeId, Objects.toString(query.getPageLink().getTextSearch(), ""), DaoUtil.toPageable(query.getPageLink()) ) @@ -132,6 +138,7 @@ public class JpaAlarmDao extends JpaAbstractDao implements A query.getPageLink().getStartTime(), query.getPageLink().getEndTime(), statusSet, + assigneeId, Objects.toString(query.getPageLink().getTextSearch(), ""), DaoUtil.toPageable(query.getPageLink()) ) @@ -148,6 +155,10 @@ public class JpaAlarmDao extends JpaAbstractDao implements A } else if (query.getStatus() != null) { statusSet = Collections.singleton(query.getStatus()); } + UUID assigneeId = null; + if (query.getAssigneeId() != null) { + assigneeId = query.getAssigneeId().getId(); + } return DaoUtil.toPageData( alarmRepository.findCustomerAlarms( tenantId.getId(), @@ -155,6 +166,7 @@ public class JpaAlarmDao extends JpaAbstractDao implements A query.getPageLink().getStartTime(), query.getPageLink().getEndTime(), statusSet, + assigneeId, Objects.toString(query.getPageLink().getTextSearch(), ""), DaoUtil.toPageable(query.getPageLink()) ) @@ -167,8 +179,12 @@ public class JpaAlarmDao extends JpaAbstractDao implements A } @Override - public Set findAlarmSeverities(TenantId tenantId, EntityId entityId, Set statuses) { - return alarmRepository.findAlarmSeverities(tenantId.getId(), entityId.getId(), entityId.getEntityType().name(), statuses); + public Set findAlarmSeverities(TenantId tenantId, EntityId entityId, Set statuses, UserId assigneeUserId) { + UUID assigneeId = null; + if (assigneeUserId != null) { + assigneeId = assigneeUserId.getId(); + } + return alarmRepository.findAlarmSeverities(tenantId.getId(), entityId.getId(), entityId.getEntityType().name(), statuses, assigneeId); } @Override diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql index e7586b17a8..2e1a830eb0 100644 --- a/dao/src/main/resources/sql/schema-entities-idx.sql +++ b/dao/src/main/resources/sql/schema-entities-idx.sql @@ -28,6 +28,8 @@ CREATE INDEX IF NOT EXISTS idx_entity_alarm_created_time ON entity_alarm(tenant_ CREATE INDEX IF NOT EXISTS idx_entity_alarm_alarm_id ON entity_alarm(alarm_id); +CREATE INDEX IF NOT EXISTS idx_entity_alarm_assignee_id ON entity_alarm(assignee_id); + CREATE INDEX IF NOT EXISTS idx_relation_to_id ON relation(relation_type_group, to_type, to_id); CREATE INDEX IF NOT EXISTS idx_relation_from_id ON relation(relation_type_group, from_type, from_id); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 73039274a6..5ceb03288f 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -52,7 +52,9 @@ CREATE TABLE IF NOT EXISTS alarm ( propagate boolean, severity varchar(255), start_ts bigint, + assign_ts bigint, status varchar(255), + assignee_id uuid, tenant_id uuid, customer_id uuid, propagate_relation_types varchar, @@ -69,6 +71,7 @@ CREATE TABLE IF NOT EXISTS entity_alarm ( alarm_type varchar(255) NOT NULL, customer_id uuid, alarm_id uuid, + assignee_id uuid, CONSTRAINT entity_alarm_pkey PRIMARY KEY (entity_id, alarm_id), CONSTRAINT fk_entity_alarm_id FOREIGN KEY (alarm_id) REFERENCES alarm(id) ON DELETE CASCADE ); 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 f56936f87e..ba22a57110 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 @@ -425,7 +425,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { customerDevice = deviceService.saveDevice(customerDevice); // no one alarms was created - Assert.assertNull(alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, null)); + Assert.assertNull(alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, null, null)); Alarm alarm1 = Alarm.builder() .tenantId(tenantId) @@ -459,11 +459,11 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .build(); alarm3 = alarmService.createOrUpdateAlarm(alarm3).getAlarm(); - Assert.assertEquals(AlarmSeverity.MAJOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), AlarmSearchStatus.UNACK, null)); - Assert.assertEquals(AlarmSeverity.CRITICAL, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, null)); - Assert.assertEquals(AlarmSeverity.MAJOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, AlarmStatus.CLEARED_UNACK)); - Assert.assertEquals(AlarmSeverity.CRITICAL, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), AlarmSearchStatus.ACTIVE, null)); - Assert.assertEquals(AlarmSeverity.MINOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, AlarmStatus.CLEARED_ACK)); + 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)); + Assert.assertEquals(AlarmSeverity.MAJOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, AlarmStatus.CLEARED_UNACK, null)); + Assert.assertEquals(AlarmSeverity.CRITICAL, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), AlarmSearchStatus.ACTIVE, null, null)); + Assert.assertEquals(AlarmSeverity.MINOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, AlarmStatus.CLEARED_ACK, null)); } @Test diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java index 21bab255f2..e41c7ad5d5 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.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.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.AlarmDataQuery; @@ -49,6 +50,10 @@ public interface RuleEngineAlarmService { ListenableFuture clearAlarmForResult(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs); + ListenableFuture assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTs); + + ListenableFuture unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTs); + ListenableFuture findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId); Alarm findAlarmById(TenantId tenantId, AlarmId alarmId); @@ -61,7 +66,7 @@ public interface RuleEngineAlarmService { ListenableFuture> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query); - AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus); + AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus, UserId assigneeId); PageData findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection orderedEntityIds); } diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts index 6cc51857e7..ba70a43c14 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts @@ -116,7 +116,7 @@ export class AlarmTableConfig extends EntityTableConfig } fetchAlarms(pageLink: TimePageLink): Observable> { - const query = new AlarmQuery(this.entityId, pageLink, this.searchStatus, null, true); + const query = new AlarmQuery(this.entityId, pageLink, this.searchStatus, null, null, true); return this.alarmService.getAlarms(query); } diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 813b3b6927..7a7a81e302 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -89,6 +89,7 @@ import { TbPopoverComponent } from '@shared/components/popover.component'; import { EntityId } from '@shared/models/id/entity-id'; import { AlarmQuery, AlarmSearchStatus, AlarmStatus} from '@app/shared/models/alarm.models'; import { TelemetrySubscriber } from '@app/shared/public-api'; +import { UserId } from '@shared/models/id/user-id'; export interface IWidgetAction { name: string; @@ -414,8 +415,8 @@ export class WidgetContext { return new TimePageLink(pageSize, page, textSearch, sortOrder, startTime, endTime); } - alarmQuery(entityId: EntityId, pageLink: TimePageLink, searchStatus: AlarmSearchStatus, status: AlarmStatus, fetchOriginator: boolean) { - return new AlarmQuery(entityId, pageLink, searchStatus, status, fetchOriginator); + alarmQuery(entityId: EntityId, pageLink: TimePageLink, searchStatus: AlarmSearchStatus, status: AlarmStatus, assigneeId: UserId, fetchOriginator: boolean) { + return new AlarmQuery(entityId, pageLink, searchStatus, status, assigneeId, fetchOriginator); } } diff --git a/ui-ngx/src/app/shared/models/alarm.models.ts b/ui-ngx/src/app/shared/models/alarm.models.ts index fd2e200090..8059a1f585 100644 --- a/ui-ngx/src/app/shared/models/alarm.models.ts +++ b/ui-ngx/src/app/shared/models/alarm.models.ts @@ -23,6 +23,7 @@ import { NULL_UUID } from '@shared/models/id/has-uuid'; import { EntityType } from '@shared/models/entity-type.models'; import { CustomerId } from '@shared/models/id/customer-id'; import { TableCellButtonActionDescriptor } from '@home/components/widget/lib/table-widget.models'; +import { UserId } from "@shared/models/id/user-id"; export enum AlarmSeverity { CRITICAL = 'CRITICAL', @@ -89,6 +90,7 @@ export const alarmSeverityColors = new Map( export interface Alarm extends BaseData { tenantId: TenantId; customerId: CustomerId; + assigneeId: UserId; type: string; originator: EntityId; severity: AlarmSeverity; @@ -97,6 +99,7 @@ export interface Alarm extends BaseData { endTs: number; ackTs: number; clearTs: number; + assignTs: number; propagate: boolean; details?: any; } @@ -115,11 +118,13 @@ export const simulatedAlarm: AlarmInfo = { id: new AlarmId(NULL_UUID), tenantId: new TenantId(NULL_UUID), customerId: new CustomerId(NULL_UUID), + assigneeId: new UserId(NULL_UUID), createdTime: new Date().getTime(), startTs: new Date().getTime(), endTs: 0, ackTs: 0, clearTs: 0, + assignTs: 0, originatorName: 'Simulated', originator: { entityType: EntityType.DEVICE, @@ -172,6 +177,12 @@ export const alarmFields: {[fieldName: string]: AlarmField} = { name: 'alarm.clear-time', time: true }, + assignTime: { + keyName: 'assignTime', + value: 'assignTs', + name: 'alarm.assign-time', + time: true + }, originator: { keyName: 'originator', value: 'originatorName', @@ -205,15 +216,17 @@ export class AlarmQuery { pageLink: TimePageLink; searchStatus: AlarmSearchStatus; status: AlarmStatus; + assigneeId: UserId; fetchOriginator: boolean; constructor(entityId: EntityId, pageLink: TimePageLink, searchStatus: AlarmSearchStatus, status: AlarmStatus, - fetchOriginator: boolean) { + assigneeId: UserId, fetchOriginator: boolean) { this.affectedEntityId = entityId; this.pageLink = pageLink; this.searchStatus = searchStatus; this.status = status; + this.assigneeId = assigneeId; this.fetchOriginator = fetchOriginator; } @@ -224,6 +237,8 @@ export class AlarmQuery { query += `&searchStatus=${this.searchStatus}`; } else if (this.status) { query += `&status=${this.status}`; + } else if (this.assigneeId) { + query += `&assigneeId=${this.assigneeId.id}`; } if (typeof this.fetchOriginator !== 'undefined' && this.fetchOriginator !== null) { query += `&fetchOriginator=${this.fetchOriginator}`; diff --git a/ui-ngx/src/app/shared/models/audit-log.models.ts b/ui-ngx/src/app/shared/models/audit-log.models.ts index 056c6aa569..fd9ca82275 100644 --- a/ui-ngx/src/app/shared/models/audit-log.models.ts +++ b/ui-ngx/src/app/shared/models/audit-log.models.ts @@ -47,6 +47,8 @@ export enum ActionType { RELATIONS_DELETED = 'RELATIONS_DELETED', ALARM_ACK = 'ALARM_ACK', ALARM_CLEAR = 'ALARM_CLEAR', + ALARM_ASSIGN = 'ALARM_ASSIGN', + ALARM_UNASSIGN = 'ALARM_UNASSIGN', LOGIN = 'LOGIN', LOGOUT = 'LOGOUT', LOCKOUT = 'LOCKOUT', @@ -85,6 +87,8 @@ export const actionTypeTranslations = new Map( [ActionType.RELATIONS_DELETED, 'audit-log.type-relations-delete'], [ActionType.ALARM_ACK, 'audit-log.type-alarm-ack'], [ActionType.ALARM_CLEAR, 'audit-log.type-alarm-clear'], + [ActionType.ALARM_ASSIGN, 'audit-log.type-alarm-assign'], + [ActionType.ALARM_UNASSIGN, 'audit-log.type-alarm-unassign'], [ActionType.LOGIN, 'audit-log.type-login'], [ActionType.LOGOUT, 'audit-log.type-logout'], [ActionType.LOCKOUT, 'audit-log.type-lockout'], 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 3df8a15e52..269e2e0f67 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -440,6 +440,7 @@ "end-time": "End time", "ack-time": "Acknowledged time", "clear-time": "Cleared time", + "assign-time": "Assign time", "alarm-severity-list": "Alarm severity list", "any-severity": "Any severity", "severity-critical": "Critical", @@ -727,6 +728,8 @@ "type-relations-delete": "All relation deleted", "type-alarm-ack": "Acknowledged", "type-alarm-clear": "Cleared", + "type-alarm-assign": "Assigned", + "type-alarm-unassign": "Unassigned", "type-login": "Login", "type-logout": "Logout", "type-lockout": "Lockout", From 6f5a116beda8846c17096b683828d14b36690884 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Wed, 30 Nov 2022 14:28:50 +0200 Subject: [PATCH 002/258] Added methods to REST client --- .../main/java/org/thingsboard/rest/client/RestClient.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index ec1ead3e6b..5f3cf943c0 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -414,6 +414,14 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { restTemplate.postForLocation(baseURL + "/api/alarm/{alarmId}/clear", null, alarmId.getId()); } + public void assignAlarm(AlarmId alarmId, UserId userId) { + restTemplate.postForLocation(baseURL + "/api/alarm/{alarmId}/assign/{userId}", null, alarmId.getId(), userId.getId()); + } + + public void unassignAlarm(AlarmId alarmId) { + restTemplate.delete(baseURL + "/api/alarm/{alarmId}/assign", alarmId.getId()); + } + public PageData getAlarms(EntityId entityId, AlarmSearchStatus searchStatus, AlarmStatus status, TimePageLink pageLink, Boolean fetchOriginator) { String urlSecondPart = "/api/alarm/{entityType}/{entityId}?fetchOriginator={fetchOriginator}"; Map params = new HashMap<>(); From 229b2973ccf88dc5ff525dfac4adcf7563d43c88 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 5 Dec 2022 15:17:31 +0200 Subject: [PATCH 003/258] Added ability to check permissions for different user than the currect one --- .../server/controller/AlarmController.java | 12 +- .../server/controller/BaseController.java | 183 +++++++++++++----- 2 files changed, 140 insertions(+), 55 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index cb1093c745..fd91abed3a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -46,6 +46,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.alarm.TbAlarmService; +import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -201,9 +202,15 @@ public class AlarmController extends BaseController { checkParameter(ALARM_ID, strAlarmId); checkParameter(ASSIGNEE_ID, strAssigneeId); AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); - // TODO Add special permissions for assignment - Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); UserId assigneeId = new UserId(UUID.fromString(strAssigneeId)); + Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); + if (!getCurrentUser().getId().equals(assigneeId)) { + try { + checkEntityId(alarm.getOriginator(), Operation.WRITE, new SecurityUser(assigneeId)); + } catch (Exception e) { + throw new ThingsboardException("Assignee user doesn't have permission for alarm originator", ThingsboardErrorCode.PERMISSION_DENIED); + } + } tbAlarmService.assign(alarm, getCurrentUser(), assigneeId).get(); } @@ -219,7 +226,6 @@ public class AlarmController extends BaseController { ) throws Exception { checkParameter(ALARM_ID, strAlarmId); AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); - // TODO Add special permissions for unassignment Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); tbAlarmService.unassign(alarm, getCurrentUser()).get(); } 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..957b6348a7 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -449,11 +449,15 @@ public abstract class BaseController { } Tenant checkTenantId(TenantId tenantId, Operation operation) throws ThingsboardException { + return checkTenantId(tenantId, operation, getCurrentUser()); + } + + Tenant checkTenantId(TenantId tenantId, Operation operation, SecurityUser securityUser) 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); + accessControlService.checkPermission(securityUser, Resource.TENANT, operation, tenantId, tenant); return tenant; } catch (Exception e) { throw handleException(e, false); @@ -473,11 +477,15 @@ public abstract class BaseController { } TenantProfile checkTenantProfileId(TenantProfileId tenantProfileId, Operation operation) throws ThingsboardException { + return checkTenantProfileId(tenantProfileId, operation, getCurrentUser()); + } + + TenantProfile checkTenantProfileId(TenantProfileId tenantProfileId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(tenantProfileId, "Incorrect tenantProfileId " + tenantProfileId); TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(getTenantId(), tenantProfileId); checkNotNull(tenantProfile, "Tenant profile with id [" + tenantProfileId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.TENANT_PROFILE, operation); + accessControlService.checkPermission(securityUser, Resource.TENANT_PROFILE, operation); return tenantProfile; } catch (Exception e) { throw handleException(e, false); @@ -489,11 +497,15 @@ public abstract class BaseController { } Customer checkCustomerId(CustomerId customerId, Operation operation) throws ThingsboardException { + return checkCustomerId(customerId, operation, getCurrentUser()); + } + + Customer checkCustomerId(CustomerId customerId, Operation operation, SecurityUser securityUser) 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); + accessControlService.checkPermission(securityUser, Resource.CUSTOMER, operation, customerId, customer); return customer; } catch (Exception e) { throw handleException(e, false); @@ -501,11 +513,15 @@ public abstract class BaseController { } User checkUserId(UserId userId, Operation operation) throws ThingsboardException { + return checkUserId(userId, operation, getCurrentUser()); + } + + User checkUserId(UserId userId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(userId, "Incorrect userId " + userId); - User user = userService.findUserById(getCurrentUser().getTenantId(), userId); + User user = userService.findUserById(securityUser.getTenantId(), userId); checkNotNull(user, "User with id [" + userId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.USER, operation, userId, user); + accessControlService.checkPermission(securityUser, Resource.USER, operation, userId, user); return user; } catch (Exception e) { throw handleException(e, false); @@ -522,6 +538,10 @@ public abstract class BaseController { } protected void checkEntityId(EntityId entityId, Operation operation) throws ThingsboardException { + checkEntityId(entityId, operation, getCurrentUser()); + } + + protected void checkEntityId(EntityId entityId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { if (entityId == null) { throw new ThingsboardException("Parameter entityId can't be empty!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); @@ -529,61 +549,61 @@ public abstract class BaseController { validateId(entityId.getId(), "Incorrect entityId " + entityId); switch (entityId.getEntityType()) { case ALARM: - checkAlarmId(new AlarmId(entityId.getId()), operation); + checkAlarmId(new AlarmId(entityId.getId()), operation, securityUser); return; case DEVICE: - checkDeviceId(new DeviceId(entityId.getId()), operation); + checkDeviceId(new DeviceId(entityId.getId()), operation, securityUser); return; case DEVICE_PROFILE: - checkDeviceProfileId(new DeviceProfileId(entityId.getId()), operation); + checkDeviceProfileId(new DeviceProfileId(entityId.getId()), operation, securityUser); return; case CUSTOMER: - checkCustomerId(new CustomerId(entityId.getId()), operation); + checkCustomerId(new CustomerId(entityId.getId()), operation, securityUser); return; case TENANT: - checkTenantId(TenantId.fromUUID(entityId.getId()), operation); + checkTenantId(TenantId.fromUUID(entityId.getId()), operation, securityUser); return; case TENANT_PROFILE: - checkTenantProfileId(new TenantProfileId(entityId.getId()), operation); + checkTenantProfileId(new TenantProfileId(entityId.getId()), operation, securityUser); return; case RULE_CHAIN: - checkRuleChain(new RuleChainId(entityId.getId()), operation); + checkRuleChain(new RuleChainId(entityId.getId()), operation, securityUser); return; case RULE_NODE: - checkRuleNode(new RuleNodeId(entityId.getId()), operation); + checkRuleNode(new RuleNodeId(entityId.getId()), operation, securityUser); return; case ASSET: - checkAssetId(new AssetId(entityId.getId()), operation); + checkAssetId(new AssetId(entityId.getId()), operation, securityUser); return; case ASSET_PROFILE: - checkAssetProfileId(new AssetProfileId(entityId.getId()), operation); + checkAssetProfileId(new AssetProfileId(entityId.getId()), operation, securityUser); return; case DASHBOARD: - checkDashboardId(new DashboardId(entityId.getId()), operation); + checkDashboardId(new DashboardId(entityId.getId()), operation, securityUser); return; case USER: - checkUserId(new UserId(entityId.getId()), operation); + checkUserId(new UserId(entityId.getId()), operation, securityUser); return; case ENTITY_VIEW: - checkEntityViewId(new EntityViewId(entityId.getId()), operation); + checkEntityViewId(new EntityViewId(entityId.getId()), operation, securityUser); return; case EDGE: - checkEdgeId(new EdgeId(entityId.getId()), operation); + checkEdgeId(new EdgeId(entityId.getId()), operation, securityUser); return; case WIDGETS_BUNDLE: - checkWidgetsBundleId(new WidgetsBundleId(entityId.getId()), operation); + checkWidgetsBundleId(new WidgetsBundleId(entityId.getId()), operation, securityUser); return; case WIDGET_TYPE: - checkWidgetTypeId(new WidgetTypeId(entityId.getId()), operation); + checkWidgetTypeId(new WidgetTypeId(entityId.getId()), operation, securityUser); return; case TB_RESOURCE: - checkResourceId(new TbResourceId(entityId.getId()), operation); + checkResourceId(new TbResourceId(entityId.getId()), operation, securityUser); return; case OTA_PACKAGE: - checkOtaPackageId(new OtaPackageId(entityId.getId()), operation); + checkOtaPackageId(new OtaPackageId(entityId.getId()), operation, securityUser); return; case QUEUE: - checkQueueId(new QueueId(entityId.getId()), operation); + checkQueueId(new QueueId(entityId.getId()), operation, securityUser); return; default: throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType()); @@ -592,13 +612,16 @@ public abstract class BaseController { throw handleException(e, false); } } - Device checkDeviceId(DeviceId deviceId, Operation operation) throws ThingsboardException { + return checkDeviceId(deviceId, operation, getCurrentUser()); + } + + Device checkDeviceId(DeviceId deviceId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(deviceId, "Incorrect deviceId " + deviceId); - Device device = deviceService.findDeviceById(getCurrentUser().getTenantId(), deviceId); + Device device = deviceService.findDeviceById(securityUser.getTenantId(), deviceId); checkNotNull(device, "Device with id [" + deviceId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, deviceId, device); + accessControlService.checkPermission(securityUser, Resource.DEVICE, operation, deviceId, device); return device; } catch (Exception e) { throw handleException(e, false); @@ -618,11 +641,15 @@ public abstract class BaseController { } DeviceProfile checkDeviceProfileId(DeviceProfileId deviceProfileId, Operation operation) throws ThingsboardException { + return checkDeviceProfileId(deviceProfileId, operation, getCurrentUser()); + } + + DeviceProfile checkDeviceProfileId(DeviceProfileId deviceProfileId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(deviceProfileId, "Incorrect deviceProfileId " + deviceProfileId); - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(getCurrentUser().getTenantId(), deviceProfileId); + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(securityUser.getTenantId(), deviceProfileId); checkNotNull(deviceProfile, "Device profile with id [" + deviceProfileId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE_PROFILE, operation, deviceProfileId, deviceProfile); + accessControlService.checkPermission(securityUser, Resource.DEVICE_PROFILE, operation, deviceProfileId, deviceProfile); return deviceProfile; } catch (Exception e) { throw handleException(e, false); @@ -630,11 +657,15 @@ public abstract class BaseController { } protected EntityView checkEntityViewId(EntityViewId entityViewId, Operation operation) throws ThingsboardException { + return checkEntityViewId(entityViewId, operation, getCurrentUser()); + } + + protected EntityView checkEntityViewId(EntityViewId entityViewId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(entityViewId, "Incorrect entityViewId " + entityViewId); - EntityView entityView = entityViewService.findEntityViewById(getCurrentUser().getTenantId(), entityViewId); + EntityView entityView = entityViewService.findEntityViewById(securityUser.getTenantId(), entityViewId); checkNotNull(entityView, "Entity view with id [" + entityViewId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation, entityViewId, entityView); + accessControlService.checkPermission(securityUser, Resource.ENTITY_VIEW, operation, entityViewId, entityView); return entityView; } catch (Exception e) { throw handleException(e, false); @@ -654,11 +685,15 @@ public abstract class BaseController { } Asset checkAssetId(AssetId assetId, Operation operation) throws ThingsboardException { + return checkAssetId(assetId, operation, getCurrentUser()); + } + + Asset checkAssetId(AssetId assetId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(assetId, "Incorrect assetId " + assetId); - Asset asset = assetService.findAssetById(getCurrentUser().getTenantId(), assetId); + Asset asset = assetService.findAssetById(securityUser.getTenantId(), assetId); checkNotNull(asset, "Asset with id [" + assetId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation, assetId, asset); + accessControlService.checkPermission(securityUser, Resource.ASSET, operation, assetId, asset); return asset; } catch (Exception e) { throw handleException(e, false); @@ -678,11 +713,15 @@ public abstract class BaseController { } AssetProfile checkAssetProfileId(AssetProfileId assetProfileId, Operation operation) throws ThingsboardException { + return checkAssetProfileId(assetProfileId, operation, getCurrentUser()); + } + + AssetProfile checkAssetProfileId(AssetProfileId assetProfileId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(assetProfileId, "Incorrect assetProfileId " + assetProfileId); - AssetProfile assetProfile = assetProfileService.findAssetProfileById(getCurrentUser().getTenantId(), assetProfileId); + AssetProfile assetProfile = assetProfileService.findAssetProfileById(securityUser.getTenantId(), assetProfileId); checkNotNull(assetProfile, "Asset profile with id [" + assetProfileId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ASSET_PROFILE, operation, assetProfileId, assetProfile); + accessControlService.checkPermission(securityUser, Resource.ASSET_PROFILE, operation, assetProfileId, assetProfile); return assetProfile; } catch (Exception e) { throw handleException(e, false); @@ -690,11 +729,15 @@ public abstract class BaseController { } Alarm checkAlarmId(AlarmId alarmId, Operation operation) throws ThingsboardException { + return checkAlarmId(alarmId, operation, getCurrentUser()); + } + + Alarm checkAlarmId(AlarmId alarmId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(alarmId, "Incorrect alarmId " + alarmId); - Alarm alarm = alarmService.findAlarmByIdAsync(getCurrentUser().getTenantId(), alarmId).get(); + Alarm alarm = alarmService.findAlarmByIdAsync(securityUser.getTenantId(), alarmId).get(); checkNotNull(alarm, "Alarm with id [" + alarmId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, operation, alarmId, alarm); + accessControlService.checkPermission(securityUser, Resource.ALARM, operation, alarmId, alarm); return alarm; } catch (Exception e) { throw handleException(e, false); @@ -714,11 +757,15 @@ public abstract class BaseController { } WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, Operation operation) throws ThingsboardException { + return checkWidgetsBundleId(widgetsBundleId, operation, getCurrentUser()); + } + + WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId); - WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleById(getCurrentUser().getTenantId(), widgetsBundleId); + WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleById(securityUser.getTenantId(), widgetsBundleId); checkNotNull(widgetsBundle, "Widgets bundle with id [" + widgetsBundleId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.WIDGETS_BUNDLE, operation, widgetsBundleId, widgetsBundle); + accessControlService.checkPermission(securityUser, Resource.WIDGETS_BUNDLE, operation, widgetsBundleId, widgetsBundle); return widgetsBundle; } catch (Exception e) { throw handleException(e, false); @@ -726,11 +773,15 @@ public abstract class BaseController { } WidgetTypeDetails checkWidgetTypeId(WidgetTypeId widgetTypeId, Operation operation) throws ThingsboardException { + return checkWidgetTypeId(widgetTypeId, operation, getCurrentUser()); + } + + WidgetTypeDetails checkWidgetTypeId(WidgetTypeId widgetTypeId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(widgetTypeId, "Incorrect widgetTypeId " + widgetTypeId); - WidgetTypeDetails widgetTypeDetails = widgetTypeService.findWidgetTypeDetailsById(getCurrentUser().getTenantId(), widgetTypeId); + WidgetTypeDetails widgetTypeDetails = widgetTypeService.findWidgetTypeDetailsById(securityUser.getTenantId(), widgetTypeId); checkNotNull(widgetTypeDetails, "Widget type with id [" + widgetTypeId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, operation, widgetTypeId, widgetTypeDetails); + accessControlService.checkPermission(securityUser, Resource.WIDGET_TYPE, operation, widgetTypeId, widgetTypeDetails); return widgetTypeDetails; } catch (Exception e) { throw handleException(e, false); @@ -738,11 +789,15 @@ public abstract class BaseController { } Dashboard checkDashboardId(DashboardId dashboardId, Operation operation) throws ThingsboardException { + return checkDashboardId(dashboardId, operation, getCurrentUser()); + } + + Dashboard checkDashboardId(DashboardId dashboardId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(dashboardId, "Incorrect dashboardId " + dashboardId); - Dashboard dashboard = dashboardService.findDashboardById(getCurrentUser().getTenantId(), dashboardId); + Dashboard dashboard = dashboardService.findDashboardById(securityUser.getTenantId(), dashboardId); checkNotNull(dashboard, "Dashboard with id [" + dashboardId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.DASHBOARD, operation, dashboardId, dashboard); + accessControlService.checkPermission(securityUser, Resource.DASHBOARD, operation, dashboardId, dashboard); return dashboard; } catch (Exception e) { throw handleException(e, false); @@ -750,11 +805,15 @@ public abstract class BaseController { } Edge checkEdgeId(EdgeId edgeId, Operation operation) throws ThingsboardException { + return checkEdgeId(edgeId, operation, getCurrentUser()); + } + + Edge checkEdgeId(EdgeId edgeId, Operation operation, SecurityUser securityUser) 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); + accessControlService.checkPermission(securityUser, Resource.EDGE, operation, edgeId, edge); return edge; } catch (Exception e) { throw handleException(e, false); @@ -813,14 +872,22 @@ public abstract class BaseController { } protected RuleChain checkRuleChain(RuleChainId ruleChainId, Operation operation) throws ThingsboardException { + return checkRuleChain(ruleChainId, operation, getCurrentUser()); + } + + protected RuleChain checkRuleChain(RuleChainId ruleChainId, Operation operation, SecurityUser securityUser) throws ThingsboardException { validateId(ruleChainId, "Incorrect ruleChainId " + ruleChainId); - RuleChain ruleChain = ruleChainService.findRuleChainById(getCurrentUser().getTenantId(), ruleChainId); + RuleChain ruleChain = ruleChainService.findRuleChainById(securityUser.getTenantId(), ruleChainId); checkNotNull(ruleChain, "Rule chain with id [" + ruleChainId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.RULE_CHAIN, operation, ruleChainId, ruleChain); + accessControlService.checkPermission(securityUser, Resource.RULE_CHAIN, operation, ruleChainId, ruleChain); return ruleChain; } protected RuleNode checkRuleNode(RuleNodeId ruleNodeId, Operation operation) throws ThingsboardException { + return checkRuleNode(ruleNodeId, operation, getCurrentUser()); + } + + protected RuleNode checkRuleNode(RuleNodeId ruleNodeId, Operation operation, SecurityUser securityUser) throws ThingsboardException { validateId(ruleNodeId, "Incorrect ruleNodeId " + ruleNodeId); RuleNode ruleNode = ruleChainService.findRuleNodeById(getTenantId(), ruleNodeId); checkNotNull(ruleNode, "Rule node with id [" + ruleNodeId + "] is not found"); @@ -829,11 +896,15 @@ public abstract class BaseController { } TbResource checkResourceId(TbResourceId resourceId, Operation operation) throws ThingsboardException { + return checkResourceId(resourceId, operation, getCurrentUser()); + } + + TbResource checkResourceId(TbResourceId resourceId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(resourceId, "Incorrect resourceId " + resourceId); - TbResource resource = resourceService.findResourceById(getCurrentUser().getTenantId(), resourceId); + TbResource resource = resourceService.findResourceById(securityUser.getTenantId(), resourceId); checkNotNull(resource, "Resource with id [" + resourceId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.TB_RESOURCE, operation, resourceId, resource); + accessControlService.checkPermission(securityUser, Resource.TB_RESOURCE, operation, resourceId, resource); return resource; } catch (Exception e) { throw handleException(e, false); @@ -853,11 +924,15 @@ public abstract class BaseController { } OtaPackage checkOtaPackageId(OtaPackageId otaPackageId, Operation operation) throws ThingsboardException { + return checkOtaPackageId(otaPackageId, operation, getCurrentUser()); + } + + OtaPackage checkOtaPackageId(OtaPackageId otaPackageId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(otaPackageId, "Incorrect otaPackageId " + otaPackageId); - OtaPackage otaPackage = otaPackageService.findOtaPackageById(getCurrentUser().getTenantId(), otaPackageId); + OtaPackage otaPackage = otaPackageService.findOtaPackageById(securityUser.getTenantId(), otaPackageId); checkNotNull(otaPackage, "OTA package with id [" + otaPackageId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.OTA_PACKAGE, operation, otaPackageId, otaPackage); + accessControlService.checkPermission(securityUser, Resource.OTA_PACKAGE, operation, otaPackageId, otaPackage); return otaPackage; } catch (Exception e) { throw handleException(e, false); @@ -889,10 +964,14 @@ public abstract class BaseController { } protected Queue checkQueueId(QueueId queueId, Operation operation) throws ThingsboardException { + return checkQueueId(queueId, operation, getCurrentUser()); + } + + protected Queue checkQueueId(QueueId queueId, Operation operation, SecurityUser securityUser) throws ThingsboardException { validateId(queueId, "Incorrect queueId " + queueId); - Queue queue = queueService.findQueueById(getCurrentUser().getTenantId(), queueId); + Queue queue = queueService.findQueueById(securityUser.getTenantId(), queueId); checkNotNull(queue); - accessControlService.checkPermission(getCurrentUser(), Resource.QUEUE, operation, queueId, queue); + accessControlService.checkPermission(securityUser, Resource.QUEUE, operation, queueId, queue); TenantId tenantId = getTenantId(); if (queue.getTenantId().isNullUid() && !tenantId.isNullUid()) { TenantProfile tenantProfile = tenantProfileCache.get(tenantId); From ac83e379ab26a3d715afc3a52396724ba6b2ca01 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 6 Dec 2022 15:33:21 +0200 Subject: [PATCH 004/258] Updated upgrade script --- application/src/main/data/upgrade/3.4.2/schema_update.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 e47a6bf2f3..de7cd5b8b1 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 @@ -17,10 +17,10 @@ -- ALARM ASSIGN TO USER START -ALTER TABLE alarm ADD COLUMN assign_ts BIGINT; -ALTER TABLE alarm ADD COLUMN assignee_id UUID; +ALTER TABLE alarm ADD COLUMN IF NOT EXISTS assign_ts BIGINT; +ALTER TABLE alarm ADD COLUMN IF NOT EXISTS assignee_id UUID; -ALTER TABLE entity_alarm ADD COLUMN assignee_id UUID; +ALTER TABLE entity_alarm ADD COLUMN IF NOT EXISTS assignee_id UUID; CREATE INDEX IF NOT EXISTS idx_entity_alarm_assignee_id ON entity_alarm(assignee_id); From f8f9746ce274f320d8ca1d4c4ee907a249262953 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 8 Dec 2022 13:09:28 +0200 Subject: [PATCH 005/258] UI: Add UI MQTT transport configuration sparkPlug settings --- ...rofile-transport-configuration.component.html | 13 ++++++++++--- ...-profile-transport-configuration.component.ts | 16 ++++++++++++++-- .../src/assets/locale/locale.constant-en_US.json | 2 ++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html index 00ecd46582..3e8038f77d 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html @@ -16,7 +16,14 @@ -->
-
+
+ + {{ 'device-profile.mqtt-device-topic-filters-spark-plug' | translate }} + +
+
+
device-profile.mqtt-device-topic-filters
@@ -59,7 +66,7 @@
-
+
device-profile.mqtt-device-payload-type
@@ -134,7 +141,7 @@
-
+
{{ 'device-profile.mqtt-send-ack-on-validation-exception' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index b51b42f1d2..4b3db82c7d 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -92,6 +92,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control this.mqttDeviceProfileTransportConfigurationFormGroup = this.fb.group({ deviceAttributesTopic: [null, [Validators.required, this.validationMQTTTopic()]], deviceTelemetryTopic: [null, [Validators.required, this.validationMQTTTopic()]], + sparkPlug: [false], sendAckOnValidationException: [false, Validators.required], transportPayloadTypeConfiguration: this.fb.group({ transportPayloadType: [TransportPayloadType.JSON, Validators.required], @@ -102,7 +103,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control enableCompatibilityWithJsonPayloadFormat: [false, Validators.required], useJsonPayloadFormatForDefaultDownlinkTopics: [false, Validators.required] }) - }, {validator: this.uniqueDeviceTopicValidator} + }, {validators: this.uniqueDeviceTopicValidator} ); this.mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.transportPayloadType').valueChanges.pipe( takeUntil(this.destroy$) @@ -117,6 +118,16 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control .patchValue(false, {emitEvent: false}); } }); + this.mqttDeviceProfileTransportConfigurationFormGroup.get('sparkPlug').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + if (value) { + this.mqttDeviceProfileTransportConfigurationFormGroup.disable({emitEvent: false}); + this.mqttDeviceProfileTransportConfigurationFormGroup.get('sparkPlug').enable({emitEvent: false}); + } else { + this.mqttDeviceProfileTransportConfigurationFormGroup.enable({emitEvent: false}); + } + }); this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.pipe( takeUntil(this.destroy$) ).subscribe(() => { @@ -135,6 +146,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control this.mqttDeviceProfileTransportConfigurationFormGroup.disable({emitEvent: false}); } else { this.mqttDeviceProfileTransportConfigurationFormGroup.enable({emitEvent: false}); + this.mqttDeviceProfileTransportConfigurationFormGroup.get('sparkPlug').updateValueAndValidity({onlySelf: true}); } } @@ -157,7 +169,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control private updateModel() { let configuration: DeviceProfileTransportConfiguration = null; if (this.mqttDeviceProfileTransportConfigurationFormGroup.valid) { - configuration = this.mqttDeviceProfileTransportConfigurationFormGroup.value; + configuration = this.mqttDeviceProfileTransportConfigurationFormGroup.getRawValue(); configuration.type = DeviceTransportType.MQTT; } this.propagateChange(configuration); 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 3df8a15e52..b8c96df26c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1352,6 +1352,8 @@ "create-new-device-profile": "Create a new one!", "mqtt-device-topic-filters": "MQTT device topic filters", "mqtt-device-topic-filters-unique": "MQTT device topic filters need to be unique.", + "mqtt-device-topic-filters-spark-plug": "MQTT device topic filters SparkPlug.", + "mqtt-device-topic-filters-spark-plug-hint": "Default - telemetry. Example: namespace/group_id/message_type/edge_node_id/[device_id].", "mqtt-device-payload-type": "MQTT device payload", "mqtt-device-payload-type-json": "JSON", "mqtt-device-payload-type-proto": "Protobuf", From e95fed13ed25ac34dfe4bd85daaed7a8056a5cd2 Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Thu, 8 Dec 2022 13:59:11 +0200 Subject: [PATCH 006/258] sparkPlug: add to TransportConfiguration sparkPlug --- .../device/profile/MqttDeviceProfileTransportConfiguration.java | 1 + 1 file changed, 1 insertion(+) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java index 1a4d2c72ad..a2cc91ff1d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java @@ -27,6 +27,7 @@ public class MqttDeviceProfileTransportConfiguration implements DeviceProfileTra @NoXss private String deviceAttributesTopic = MqttTopics.DEVICE_ATTRIBUTES_TOPIC; private TransportPayloadTypeConfiguration transportPayloadTypeConfiguration; + private boolean sparkPlug; private boolean sendAckOnValidationException; @Override From 43f2e0ce7f08bfd08b1d6b8ccb90e4de9739dd9a Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 8 Dec 2022 14:39:26 +0200 Subject: [PATCH 007/258] UI: Updated UI MQTT transport configuration sparkPlug settings --- ...mqtt-device-profile-transport-configuration.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html index 3e8038f77d..cf91c8b2c5 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html @@ -66,7 +66,7 @@
-
+
device-profile.mqtt-device-payload-type
@@ -141,7 +141,7 @@
-
+
{{ 'device-profile.mqtt-send-ack-on-validation-exception' | translate }} From a675a6a37763974b04502301caa6d46702c84369 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 8 Dec 2022 15:19:56 +0200 Subject: [PATCH 008/258] UI: Updated UI MQTT transport configuration sparkPlug settings --- ...tt-device-profile-transport-configuration.component.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index 4b3db82c7d..4b517a081d 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -122,10 +122,11 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control takeUntil(this.destroy$) ).subscribe((value) => { if (value) { - this.mqttDeviceProfileTransportConfigurationFormGroup.disable({emitEvent: false}); - this.mqttDeviceProfileTransportConfigurationFormGroup.get('sparkPlug').enable({emitEvent: false}); + this.mqttDeviceProfileTransportConfigurationFormGroup.get('deviceAttributesTopic').disable({emitEvent: false}); + this.mqttDeviceProfileTransportConfigurationFormGroup.get('deviceTelemetryTopic').disable({emitEvent: false}); } else { - this.mqttDeviceProfileTransportConfigurationFormGroup.enable({emitEvent: false}); + this.mqttDeviceProfileTransportConfigurationFormGroup.get('deviceAttributesTopic').enable({emitEvent: false}); + this.mqttDeviceProfileTransportConfigurationFormGroup.get('deviceTelemetryTopic').enable({emitEvent: false}); } }); this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.pipe( @@ -162,6 +163,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control writeValue(value: MqttDeviceProfileTransportConfiguration | null): void { if (isDefinedAndNotNull(value)) { this.mqttDeviceProfileTransportConfigurationFormGroup.patchValue(value, {emitEvent: false}); + this.mqttDeviceProfileTransportConfigurationFormGroup.get('sparkPlug').updateValueAndValidity({onlySelf: true}); this.updateTransportPayloadBasedControls(value.transportPayloadTypeConfiguration?.transportPayloadType); } } From 7d19d37a23ab0ce6bd4582096f1ec3cebfe55d14 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 8 Dec 2022 16:20:58 +0200 Subject: [PATCH 009/258] UI: Updated UI MQTT transport configuration models --- ui-ngx/src/app/shared/models/device.models.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index d06963544e..317b2899b4 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -242,6 +242,7 @@ export interface DefaultDeviceProfileTransportConfiguration { export interface MqttDeviceProfileTransportConfiguration { deviceTelemetryTopic?: string; deviceAttributesTopic?: string; + sparkPlug?: boolean; sendAckOnValidationException?: boolean; transportPayloadTypeConfiguration?: { transportPayloadType?: TransportPayloadType; @@ -359,6 +360,7 @@ export function createDeviceProfileTransportConfiguration(type: DeviceTransportT const mqttTransportConfiguration: MqttDeviceProfileTransportConfiguration = { deviceTelemetryTopic: 'v1/devices/me/telemetry', deviceAttributesTopic: 'v1/devices/me/attributes', + sparkPlug: false, sendAckOnValidationException: false, transportPayloadTypeConfiguration: { transportPayloadType: TransportPayloadType.JSON, From 3865e36fc8f578963e4802685bff03e70369afaa Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Thu, 8 Dec 2022 17:05:41 +0200 Subject: [PATCH 010/258] sparkPlug: start add Handler --- .../MqttDeviceAwareSessionContext.java | 7 - .../mqtt/session/SparkplugNodeSessionCtx.java | 149 ++++++++++++++++++ 2 files changed, 149 insertions(+), 7 deletions(-) create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/SparkplugNodeSessionCtx.java diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java index 74e555ae6b..8c8437be51 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java @@ -16,14 +16,7 @@ package org.thingsboard.server.transport.mqtt.session; import io.netty.handler.codec.mqtt.MqttQoS; -import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; -import org.thingsboard.server.transport.mqtt.util.MqttTopicFilter; -import org.thingsboard.server.transport.mqtt.util.MqttTopicFilterFactory; import java.util.List; import java.util.Map; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/SparkplugNodeSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/SparkplugNodeSessionCtx.java new file mode 100644 index 0000000000..7ba309e450 --- /dev/null +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/SparkplugNodeSessionCtx.java @@ -0,0 +1,149 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 io.netty.channel.ChannelFuture; +import io.netty.handler.codec.mqtt.MqttMessage; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.rpc.RpcStatus; +import org.thingsboard.server.common.transport.SessionMsgListener; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; + +import java.util.UUID; +import java.util.concurrent.ConcurrentMap; + +/** + * Created by ashvayka on 19.01.17. + */ +@Slf4j +public class SparkplugNodeSessionCtx extends MqttDeviceAwareSessionContext implements SessionMsgListener { + + private final GatewaySessionHandler parent; + private final TransportService transportService; + + public SparkplugNodeSessionCtx(GatewaySessionHandler parent, TransportDeviceInfo deviceInfo, + DeviceProfile deviceProfile, ConcurrentMap mqttQoSMap, + TransportService transportService) { + super(UUID.randomUUID(), mqttQoSMap); + this.parent = parent; + setSessionInfo(SessionInfoProto.newBuilder() + .setNodeId(parent.getNodeId()) + .setSessionIdMSB(sessionId.getMostSignificantBits()) + .setSessionIdLSB(sessionId.getLeastSignificantBits()) + .setDeviceIdMSB(deviceInfo.getDeviceId().getId().getMostSignificantBits()) + .setDeviceIdLSB(deviceInfo.getDeviceId().getId().getLeastSignificantBits()) + .setTenantIdMSB(deviceInfo.getTenantId().getId().getMostSignificantBits()) + .setTenantIdLSB(deviceInfo.getTenantId().getId().getLeastSignificantBits()) + .setCustomerIdMSB(deviceInfo.getCustomerId().getId().getMostSignificantBits()) + .setCustomerIdLSB(deviceInfo.getCustomerId().getId().getLeastSignificantBits()) + .setDeviceName(deviceInfo.getDeviceName()) + .setDeviceType(deviceInfo.getDeviceType()) + .setGwSessionIdMSB(parent.getSessionId().getMostSignificantBits()) + .setGwSessionIdLSB(parent.getSessionId().getLeastSignificantBits()) + .setDeviceProfileIdMSB(deviceInfo.getDeviceProfileId().getId().getMostSignificantBits()) + .setDeviceProfileIdLSB(deviceInfo.getDeviceProfileId().getId().getLeastSignificantBits()) + .build()); + setDeviceInfo(deviceInfo); + setConnected(true); + setDeviceProfile(deviceProfile); + this.transportService = transportService; + } + + @Override + public UUID getSessionId() { + return sessionId; + } + + @Override + public int nextMsgId() { + return parent.nextMsgId(); + } + + @Override + public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg response) { + try { + parent.getPayloadAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), response).ifPresent(parent::writeAndFlush); + } catch (Exception e) { + log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); + } + } + + @Override + public void onAttributeUpdate(UUID sessionId, TransportProtos.AttributeUpdateNotificationMsg notification) { + log.trace("[{}] Received attributes update notification to device", sessionId); + try { + parent.getPayloadAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), notification).ifPresent(parent::writeAndFlush); + } catch (Exception e) { + log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); + } + } + + @Override + public void onToDeviceRpcRequest(UUID sessionId, TransportProtos.ToDeviceRpcRequestMsg request) { + log.trace("[{}] Received RPC command to device", sessionId); + try { + parent.getPayloadAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), request).ifPresent( + payload -> { + ChannelFuture channelFuture = parent.writeAndFlush(payload); + if (request.getPersisted()) { + channelFuture.addListener(result -> { + if (result.cause() == null) { + if (!isAckExpected(payload)) { + transportService.process(getSessionInfo(), request, RpcStatus.DELIVERED, TransportServiceCallback.EMPTY); + } else if (request.getPersisted()) { + transportService.process(getSessionInfo(), request, RpcStatus.SENT, TransportServiceCallback.EMPTY); + + } + } + }); + } + } + ); + } catch (Exception e) { + transportService.process(getSessionInfo(), + TransportProtos.ToDeviceRpcResponseMsg.newBuilder() + .setRequestId(request.getRequestId()).setError("Failed to convert device RPC command to MQTT msg").build(), TransportServiceCallback.EMPTY); + log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); + } + } + + @Override + public void onRemoteSessionCloseCommand(UUID sessionId, TransportProtos.SessionCloseNotificationProto sessionCloseNotification) { + log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage()); + parent.deregisterSession(getDeviceInfo().getDeviceName()); + } + + @Override + public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse) { + // This feature is not supported in the TB IoT Gateway yet. + } + + @Override + public void onDeviceDeleted(DeviceId deviceId) { + parent.onDeviceDeleted(this.getSessionInfo().getDeviceName()); + } + + private boolean isAckExpected(MqttMessage message) { + return message.fixedHeader().qosLevel().value() > 0; + } + +} From 08e356a4152aef41c895c3fb0206ab3707a48d0c Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 12 Dec 2022 13:00:22 +0200 Subject: [PATCH 011/258] 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 012/258] 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 1bf02b75f20a1fc9e6e18b4e2e7982cffbae2c61 Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Thu, 15 Dec 2022 11:40:04 +0200 Subject: [PATCH 013/258] 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