From ac20551bf41d43a5e094f394a9f1d889c36e0b02 Mon Sep 17 00:00:00 2001 From: IrynaMatveieva Date: Tue, 5 Nov 2024 17:05:10 +0200 Subject: [PATCH] added support of housekeeper deletion --- .../controller/CalculatedFieldController.java | 13 +++- ...CalculatedFieldsDeletionTaskProcessor.java | 43 +++++++++++ .../permission/CustomerUserPermissions.java | 31 +++++++- .../security/permission/Operation.java | 2 +- .../permission/TenantAdminPermissions.java | 21 +++-- .../CalculatedFieldControllerTest.java | 1 - .../CalculatedFieldService.java | 5 ++ .../data/housekeeper/HousekeeperTask.java | 4 + .../data/housekeeper/HousekeeperTaskType.java | 3 +- .../dao/asset/AssetProfileServiceImpl.java | 4 +- .../server/dao/asset/BaseAssetService.java | 4 +- .../BaseCalculatedFieldService.java | 31 +++++++- .../calculated_field/CalculatedFieldDao.java | 9 +++ .../dao/device/DeviceProfileServiceImpl.java | 4 +- .../server/dao/device/DeviceServiceImpl.java | 4 +- .../dao/entity/AbstractEntityService.java | 5 ++ .../dao/housekeeper/CleanUpService.java | 1 + .../CalculatedFieldRepository.java | 6 ++ .../JpaCalculatedFieldDao.java | 16 ++++ .../dao/service/AssetProfileServiceTest.java | 21 +++++ .../server/dao/service/AssetServiceTest.java | 77 ++++++++++++------- .../CalculatedFieldServiceTest.java | 5 +- .../dao/service/DeviceProfileServiceTest.java | 25 +++++- .../server/dao/service/DeviceServiceTest.java | 20 +++++ .../CalculatedFieldLinkDataValidatorTest.java | 2 +- 25 files changed, 300 insertions(+), 57 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/housekeeper/processor/CalculatedFieldsDeletionTaskProcessor.java rename dao/src/test/java/org/thingsboard/server/dao/{calculated_field => service}/CalculatedFieldServiceTest.java (97%) diff --git a/application/src/main/java/org/thingsboard/server/controller/CalculatedFieldController.java b/application/src/main/java/org/thingsboard/server/controller/CalculatedFieldController.java index 26a3539a46..b3f2c018cb 100644 --- a/application/src/main/java/org/thingsboard/server/controller/CalculatedFieldController.java +++ b/application/src/main/java/org/thingsboard/server/controller/CalculatedFieldController.java @@ -30,11 +30,11 @@ import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.calculated_field.CalculatedField; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CalculatedFieldId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.calculated_field.CalculatedFieldService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; -import org.thingsboard.server.service.security.permission.Resource; import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; @@ -63,7 +63,7 @@ public class CalculatedFieldController extends BaseController { public CalculatedField saveCalculatedField(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the calculated field.") @RequestBody CalculatedField calculatedField) throws Exception { calculatedField.setTenantId(getTenantId()); - checkEntity(calculatedField.getId(), calculatedField, Resource.CALCULATED_FIELD); + checkEntityId(calculatedField.getEntityId(), Operation.WRITE_CALCULATED_FIELD); return calculatedFieldService.save(calculatedField); } @@ -76,7 +76,10 @@ public class CalculatedFieldController extends BaseController { public CalculatedField getCalculatedFieldById(@Parameter @PathVariable(CALCULATED_FIELD_ID) String strCalculatedFieldId) throws ThingsboardException { checkParameter(CALCULATED_FIELD_ID, strCalculatedFieldId); CalculatedFieldId calculatedFieldId = new CalculatedFieldId(toUUID(strCalculatedFieldId)); - return checkCalculatedFieldId(calculatedFieldId, Operation.READ); + CalculatedField calculatedField = calculatedFieldService.findById(getTenantId(), calculatedFieldId); + checkNotNull(calculatedField); + checkEntityId(calculatedField.getEntityId(), Operation.READ_CALCULATED_FIELD); + return calculatedField; } @@ -88,7 +91,9 @@ public class CalculatedFieldController extends BaseController { public void deleteCalculatedField(@PathVariable(CALCULATED_FIELD_ID) String strCalculatedField) throws Exception { checkParameter(CALCULATED_FIELD_ID, strCalculatedField); CalculatedFieldId calculatedFieldId = new CalculatedFieldId(toUUID(strCalculatedField)); - checkCalculatedFieldId(calculatedFieldId, Operation.DELETE); + TenantId tenantId = getTenantId(); + CalculatedField calculatedField = calculatedFieldService.findById(tenantId, calculatedFieldId); + checkEntityId(calculatedField.getEntityId(), Operation.WRITE_CALCULATED_FIELD); calculatedFieldService.deleteCalculatedField(getTenantId(), calculatedFieldId); } diff --git a/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/CalculatedFieldsDeletionTaskProcessor.java b/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/CalculatedFieldsDeletionTaskProcessor.java new file mode 100644 index 0000000000..18d84b02ce --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/CalculatedFieldsDeletionTaskProcessor.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.housekeeper.processor; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTask; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType; +import org.thingsboard.server.dao.calculated_field.CalculatedFieldService; + +@Component +@RequiredArgsConstructor +@Slf4j +public class CalculatedFieldsDeletionTaskProcessor extends HousekeeperTaskProcessor { + + private final CalculatedFieldService calculatedFieldService; + + @Override + public void process(HousekeeperTask task) throws Exception { + int deletedCount = calculatedFieldService.deleteAllCalculatedFieldsByEntityId(task.getTenantId(), task.getEntityId()); + log.debug("[{}][{}][{}] Deleted {} calculated fields", task.getTenantId(), task.getEntityId().getEntityType(), task.getEntityId(), deletedCount); + } + + @Override + public HousekeeperTaskType getTaskType() { + return HousekeeperTaskType.DELETE_CALCULATED_FIELDS; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java index 46cd1c2979..0c6dbc1599 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java @@ -34,8 +34,8 @@ public class CustomerUserPermissions extends AbstractPermissions { public CustomerUserPermissions() { super(); put(Resource.ALARM, customerAlarmPermissionChecker); - put(Resource.ASSET, customerEntityPermissionChecker); - put(Resource.DEVICE, customerEntityPermissionChecker); + put(Resource.ASSET, customerEntityWithCalculatedFieldPermissionChecker); + put(Resource.DEVICE, customerEntityWithCalculatedFieldPermissionChecker); put(Resource.CUSTOMER, customerPermissionChecker); put(Resource.DASHBOARD, customerDashboardPermissionChecker); put(Resource.ENTITY_VIEW, customerEntityPermissionChecker); @@ -85,6 +85,29 @@ public class CustomerUserPermissions extends AbstractPermissions { } }; + private static final PermissionChecker customerEntityWithCalculatedFieldPermissionChecker = + new PermissionChecker.GenericPermissionChecker(Operation.READ, Operation.READ_CREDENTIALS, + Operation.READ_ATTRIBUTES, Operation.READ_TELEMETRY, Operation.RPC_CALL, Operation.CLAIM_DEVICES, + Operation.WRITE, Operation.WRITE_ATTRIBUTES, Operation.WRITE_TELEMETRY, Operation.READ_CALCULATED_FIELD, + Operation.WRITE_CALCULATED_FIELD) { + + @Override + @SuppressWarnings("unchecked") + public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { + + if (!super.hasPermission(user, operation, entityId, entity)) { + return false; + } + if (!user.getTenantId().equals(entity.getTenantId())) { + return false; + } + if (!(entity instanceof HasCustomerId)) { + return false; + } + return operation.equals(Operation.CLAIM_DEVICES) || user.getCustomerId().equals(((HasCustomerId) entity).getCustomerId()); + } + }; + private static final PermissionChecker customerPermissionChecker = new PermissionChecker.GenericPermissionChecker(Operation.READ, Operation.READ_ATTRIBUTES, Operation.READ_TELEMETRY) { @@ -188,7 +211,8 @@ public class CustomerUserPermissions extends AbstractPermissions { } }; - private static final PermissionChecker profilePermissionChecker = new PermissionChecker.GenericPermissionChecker(Operation.READ) { + private static final PermissionChecker profilePermissionChecker = new PermissionChecker.GenericPermissionChecker( + Operation.READ, Operation.READ_CALCULATED_FIELD, Operation.WRITE_CALCULATED_FIELD) { @Override @SuppressWarnings("unchecked") @@ -202,4 +226,5 @@ public class CustomerUserPermissions extends AbstractPermissions { return user.getTenantId().equals(entity.getTenantId()); } }; + } diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Operation.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Operation.java index 9465a91cc8..d029d78cff 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/Operation.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Operation.java @@ -19,6 +19,6 @@ public enum Operation { ALL, CREATE, READ, WRITE, DELETE, ASSIGN_TO_CUSTOMER, UNASSIGN_FROM_CUSTOMER, RPC_CALL, READ_CREDENTIALS, WRITE_CREDENTIALS, READ_ATTRIBUTES, WRITE_ATTRIBUTES, READ_TELEMETRY, WRITE_TELEMETRY, CLAIM_DEVICES, - ASSIGN_TO_TENANT + ASSIGN_TO_TENANT, READ_CALCULATED_FIELD, WRITE_CALCULATED_FIELD } diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java index 897581cc91..c730637148 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java @@ -23,15 +23,15 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.service.security.model.SecurityUser; -@Component(value="tenantAdminPermissions") +@Component(value = "tenantAdminPermissions") public class TenantAdminPermissions extends AbstractPermissions { public TenantAdminPermissions() { super(); put(Resource.ADMIN_SETTINGS, PermissionChecker.allowAllPermissionChecker); put(Resource.ALARM, tenantEntityPermissionChecker); - put(Resource.ASSET, tenantEntityPermissionChecker); - put(Resource.DEVICE, tenantEntityPermissionChecker); + put(Resource.ASSET, tenantEntityWithCalculatedFieldPermissionChecker); + put(Resource.DEVICE, tenantEntityWithCalculatedFieldPermissionChecker); put(Resource.CUSTOMER, tenantEntityPermissionChecker); put(Resource.DASHBOARD, tenantEntityPermissionChecker); put(Resource.ENTITY_VIEW, tenantEntityPermissionChecker); @@ -40,8 +40,8 @@ public class TenantAdminPermissions extends AbstractPermissions { put(Resource.USER, userPermissionChecker); put(Resource.WIDGETS_BUNDLE, widgetsPermissionChecker); put(Resource.WIDGET_TYPE, widgetsPermissionChecker); - put(Resource.DEVICE_PROFILE, tenantEntityPermissionChecker); - put(Resource.ASSET_PROFILE, tenantEntityPermissionChecker); + put(Resource.DEVICE_PROFILE, tenantEntityWithCalculatedFieldPermissionChecker); + put(Resource.ASSET_PROFILE, tenantEntityWithCalculatedFieldPermissionChecker); put(Resource.API_USAGE_STATE, tenantEntityPermissionChecker); put(Resource.TB_RESOURCE, tbResourcePermissionChecker); put(Resource.OTA_PACKAGE, tenantEntityPermissionChecker); @@ -51,14 +51,23 @@ public class TenantAdminPermissions extends AbstractPermissions { put(Resource.VERSION_CONTROL, PermissionChecker.allowAllPermissionChecker); put(Resource.NOTIFICATION, tenantEntityPermissionChecker); put(Resource.MOBILE_APP_SETTINGS, new PermissionChecker.GenericPermissionChecker(Operation.READ)); - put(Resource.CALCULATED_FIELD, tenantEntityPermissionChecker); } public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() { @Override public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { + if (!user.getTenantId().equals(entity.getTenantId())) { + return false; + } + return !Operation.READ_CALCULATED_FIELD.equals(operation) && !Operation.WRITE_CALCULATED_FIELD.equals(operation); + } + }; + + public static final PermissionChecker tenantEntityWithCalculatedFieldPermissionChecker = new PermissionChecker() { + @Override + public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { if (!user.getTenantId().equals(entity.getTenantId())) { return false; } diff --git a/application/src/test/java/org/thingsboard/server/controller/CalculatedFieldControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/CalculatedFieldControllerTest.java index 685a58d48c..d9f4ac0377 100644 --- a/application/src/test/java/org/thingsboard/server/controller/CalculatedFieldControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/CalculatedFieldControllerTest.java @@ -115,7 +115,6 @@ public class CalculatedFieldControllerTest extends AbstractControllerTest { doDelete("/api/calculatedField/" + savedCalculatedField.getId().getId().toString()) .andExpect(status().isOk()); doGet("/api/calculatedField/" + savedCalculatedField.getId().getId()).andExpect(status().isNotFound()); - } private CalculatedField getCalculatedField(DeviceId deviceId) { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/calculated_field/CalculatedFieldService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/calculated_field/CalculatedFieldService.java index e1779e1d33..f271f4ed24 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/calculated_field/CalculatedFieldService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/calculated_field/CalculatedFieldService.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.calculated_field; import org.thingsboard.server.common.data.calculated_field.CalculatedField; import org.thingsboard.server.common.data.calculated_field.CalculatedFieldLink; import org.thingsboard.server.common.data.id.CalculatedFieldId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.entity.EntityDaoService; @@ -29,6 +30,10 @@ public interface CalculatedFieldService extends EntityDaoService { void deleteCalculatedField(TenantId tenantId, CalculatedFieldId calculatedFieldId); + int deleteAllCalculatedFieldsByEntityId(TenantId tenantId, EntityId entityId); + CalculatedFieldLink saveCalculatedFieldLink(TenantId tenantId, CalculatedFieldLink calculatedFieldLink); + boolean existsByEntityId(TenantId tenantId, EntityId entityId); + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/HousekeeperTask.java b/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/HousekeeperTask.java index 8757b1121e..7a87528553 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/HousekeeperTask.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/HousekeeperTask.java @@ -81,6 +81,10 @@ public class HousekeeperTask implements Serializable { return new TenantEntitiesDeletionHousekeeperTask(tenantId, entityType); } + public static HousekeeperTask deleteCalculatedFields(TenantId tenantId, EntityId entityId) { + return new HousekeeperTask(tenantId, entityId, HousekeeperTaskType.DELETE_CALCULATED_FIELDS); + } + @JsonIgnore public String getDescription() { return taskType.getDescription() + " for " + entityId.getEntityType().getNormalName().toLowerCase() + " " + entityId.getId(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/HousekeeperTaskType.java b/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/HousekeeperTaskType.java index edd13b3d5d..236844c17d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/HousekeeperTaskType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/HousekeeperTaskType.java @@ -30,7 +30,8 @@ public enum HousekeeperTaskType { DELETE_ALARMS("alarms deletion"), UNASSIGN_ALARMS("alarms unassigning"), DELETE_TENANT_ENTITIES("tenant entities deletion"), - DELETE_ENTITIES("entities deletion"); + DELETE_ENTITIES("entities deletion"), + DELETE_CALCULATED_FIELDS("calculated fields deletion"); private final String description; diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java index 7d4d5300f9..baf20219ac 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java @@ -192,8 +192,8 @@ public class AssetProfileServiceImpl extends CachedVersionedEntityService calculatedFieldDataValidator; private final DataValidator calculatedFieldLinkDataValidator; @@ -72,7 +79,7 @@ public class BaseCalculatedFieldService implements CalculatedFieldService { @Override public CalculatedField findById(TenantId tenantId, CalculatedFieldId calculatedFieldId) { - log.trace("Executing findById, tenantId [{}], rpcId [{}]", tenantId, calculatedFieldId); + log.trace("Executing findById, tenantId [{}], calculatedFieldId [{}]", tenantId, calculatedFieldId); validateId(tenantId, id -> INCORRECT_TENANT_ID + id); validateId(calculatedFieldId, id -> INCORRECT_CALCULATED_FIELD_ID + id); return calculatedFieldDao.findById(tenantId, calculatedFieldId.getId()); @@ -80,12 +87,21 @@ public class BaseCalculatedFieldService implements CalculatedFieldService { @Override public void deleteCalculatedField(TenantId tenantId, CalculatedFieldId calculatedFieldId) { - log.trace("Executing deleteRpc, tenantId [{}], rpcId [{}]", tenantId, calculatedFieldId); + log.trace("Executing deleteCalculatedField, tenantId [{}], calculatedFieldId [{}]", tenantId, calculatedFieldId); validateId(tenantId, id -> INCORRECT_TENANT_ID + id); validateId(calculatedFieldId, id -> INCORRECT_CALCULATED_FIELD_ID + id); calculatedFieldDao.removeById(tenantId, calculatedFieldId.getId()); } + @Override + public int deleteAllCalculatedFieldsByEntityId(TenantId tenantId, EntityId entityId) { + log.trace("Executing deleteAllCalculatedFieldsByEntityId, tenantId [{}], entityId [{}]", tenantId, entityId); + validateId(tenantId, id -> INCORRECT_TENANT_ID + id); + validateId(entityId.getId(), id -> "Incorrect entityId " + id); + List calculatedFields = calculatedFieldDao.removeAllByEntityId(tenantId, entityId); + return calculatedFields.size(); + } + @Override public CalculatedFieldLink saveCalculatedFieldLink(TenantId tenantId, CalculatedFieldLink calculatedFieldLink) { calculatedFieldLinkDataValidator.validate(calculatedFieldLink, CalculatedFieldLink::getTenantId); @@ -98,6 +114,11 @@ public class BaseCalculatedFieldService implements CalculatedFieldService { } } + @Override + public boolean existsByEntityId(TenantId tenantId, EntityId entityId) { + return calculatedFieldDao.existsByTenantIdAndEntityId(tenantId, entityId); + } + @Override public Optional> findEntity(TenantId tenantId, EntityId entityId) { return Optional.ofNullable(findById(tenantId, new CalculatedFieldId(entityId.getId()))); @@ -114,6 +135,12 @@ public class BaseCalculatedFieldService implements CalculatedFieldService { .orElseThrow(() -> new IllegalArgumentException("Asset with id [" + entityId.getId() + "] does not exist.")); case DEVICE -> Optional.ofNullable(deviceService.findDeviceById(tenantId, (DeviceId) entityId)) .orElseThrow(() -> new IllegalArgumentException("Device with id [" + entityId.getId() + "] does not exist.")); + case ASSET_PROFILE -> + Optional.ofNullable(assetProfileService.findAssetProfileById(tenantId, (AssetProfileId) entityId)) + .orElseThrow(() -> new IllegalArgumentException("Asset Profile with id [" + entityId.getId() + "] does not exist.")); + case DEVICE_PROFILE -> + Optional.ofNullable(deviceProfileService.findDeviceProfileById(tenantId, (DeviceProfileId) entityId)) + .orElseThrow(() -> new IllegalArgumentException("Device Profile with id [" + entityId.getId() + "] does not exist.")); default -> throw new IllegalArgumentException("Entity type '" + entityId.getEntityType() + "' is not supported."); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/calculated_field/CalculatedFieldDao.java b/dao/src/main/java/org/thingsboard/server/dao/calculated_field/CalculatedFieldDao.java index e4bd019b18..d0d0a3c386 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/calculated_field/CalculatedFieldDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/calculated_field/CalculatedFieldDao.java @@ -16,7 +16,16 @@ package org.thingsboard.server.dao.calculated_field; import org.thingsboard.server.common.data.calculated_field.CalculatedField; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.Dao; +import java.util.List; + public interface CalculatedFieldDao extends Dao { + + boolean existsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId); + + List removeAllByEntityId(TenantId tenantId, EntityId entityId); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index 09e2be01e0..84dc1a7d14 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -230,8 +230,8 @@ public class DeviceProfileServiceImpl extends CachedVersionedEntityService { + + boolean existsByTenantIdAndEntityId(UUID tenantId, UUID entityId); + + List removeAllByTenantIdAndEntityId(UUID tenantId, UUID entityId); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/calculated_field/JpaCalculatedFieldDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/calculated_field/JpaCalculatedFieldDao.java index 33cf926eaf..9a0eef26c3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/calculated_field/JpaCalculatedFieldDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/calculated_field/JpaCalculatedFieldDao.java @@ -15,16 +15,21 @@ */ package org.thingsboard.server.dao.sql.calculated_field; +import jakarta.transaction.Transactional; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.calculated_field.CalculatedField; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.calculated_field.CalculatedFieldDao; import org.thingsboard.server.dao.model.sql.CalculatedFieldEntity; import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.util.SqlDao; +import java.util.List; import java.util.UUID; @Slf4j @@ -35,6 +40,17 @@ public class JpaCalculatedFieldDao extends JpaAbstractDao removeAllByEntityId(TenantId tenantId, EntityId entityId) { + return DaoUtil.convertDataList(calculatedFieldRepository.removeAllByTenantIdAndEntityId(tenantId.getId(), entityId.getId())); + } + @Override protected Class getEntityClass() { return CalculatedFieldEntity.class; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AssetProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AssetProfileServiceTest.java index 7271a0fc8a..c11d8c6e1c 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AssetProfileServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AssetProfileServiceTest.java @@ -28,10 +28,12 @@ import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.asset.AssetProfileInfo; +import org.thingsboard.server.common.data.calculated_field.CalculatedField; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetService; +import org.thingsboard.server.dao.calculated_field.CalculatedFieldService; import org.thingsboard.server.dao.exception.DataValidationException; import java.util.ArrayList; @@ -43,6 +45,7 @@ import java.util.concurrent.Executors; import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; @DaoSqlTest public class AssetProfileServiceTest extends AbstractServiceTest { @@ -54,6 +57,8 @@ public class AssetProfileServiceTest extends AbstractServiceTest { AssetProfileService assetProfileService; @Autowired AssetService assetService; + @Autowired + private CalculatedFieldService calculatedFieldService; @Test public void testSaveAssetProfile() { @@ -380,5 +385,21 @@ public class AssetProfileServiceTest extends AbstractServiceTest { assertThat(assetProfileInfos).isEqualTo(expected); } + @Test + public void testDeleteAssetProfileIfCalculatedFieldExists() { + AssetProfile assetProfile = this.createAssetProfile(tenantId, "Asset Profile"); + AssetProfile savedAssetProfile = assetProfileService.saveAssetProfile(assetProfile); + + CalculatedField calculatedField = new CalculatedField(); + calculatedField.setTenantId(tenantId); + calculatedField.setName("Test CF"); + calculatedField.setType("Simple"); + calculatedField.setEntityId(savedAssetProfile.getId()); + calculatedFieldService.save(calculatedField); + + assertThatThrownBy(() -> assetProfileService.deleteAssetProfile(tenantId, savedAssetProfile.getId())) + .isInstanceOf(DataValidationException.class) + .hasMessage("Deletion of Asset Profile is prohibited!"); + } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AssetServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AssetServiceTest.java index 1976f173a5..e1e1bf70ed 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AssetServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AssetServiceTest.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.asset.AssetProfile; +import org.thingsboard.server.common.data.calculated_field.CalculatedField; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -39,6 +40,7 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.dao.asset.AssetDao; import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetService; +import org.thingsboard.server.dao.calculated_field.CalculatedFieldService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.relation.RelationService; @@ -47,6 +49,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; @DaoSqlTest @@ -63,6 +66,8 @@ public class AssetServiceTest extends AbstractServiceTest { @Autowired private AssetProfileService assetProfileService; @Autowired + private CalculatedFieldService calculatedFieldService; + @Autowired private PlatformTransactionManager platformTransactionManager; private IdComparator idComparator = new IdComparator<>(); @@ -214,24 +219,24 @@ public class AssetServiceTest extends AbstractServiceTest { public void testFindAssetTypesByTenantId() throws Exception { List assets = new ArrayList<>(); try { - for (int i=0;i<3;i++) { + for (int i = 0; i < 3; i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); - asset.setName("My asset B"+i); + asset.setName("My asset B" + i); asset.setType("typeB"); assets.add(assetService.saveAsset(asset)); } - for (int i=0;i<7;i++) { + for (int i = 0; i < 7; i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); - asset.setName("My asset C"+i); + asset.setName("My asset C" + i); asset.setType("typeC"); assets.add(assetService.saveAsset(asset)); } - for (int i=0;i<9;i++) { + for (int i = 0; i < 9; i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); - asset.setName("My asset A"+i); + asset.setName("My asset A" + i); asset.setType("typeA"); assets.add(assetService.saveAsset(asset)); } @@ -267,10 +272,10 @@ public class AssetServiceTest extends AbstractServiceTest { @Test public void testFindAssetsByTenantId() { List assets = new ArrayList<>(); - for (int i=0;i<13;i++) { + for (int i = 0; i < 13; i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); - asset.setName("Asset"+i); + asset.setName("Asset" + i); asset.setType("default"); assets.add(assetService.saveAsset(asset)); } @@ -303,11 +308,11 @@ public class AssetServiceTest extends AbstractServiceTest { public void testFindAssetsByTenantIdAndName() { String title1 = "Asset title 1"; List assetsTitle1 = new ArrayList<>(); - for (int i=0;i<13;i++) { + for (int i = 0; i < 13; i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); String suffix = StringUtils.randomAlphanumeric(15); - String name = title1+suffix; + String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); asset.setType("default"); @@ -315,11 +320,11 @@ public class AssetServiceTest extends AbstractServiceTest { } String title2 = "Asset title 2"; List assetsTitle2 = new ArrayList<>(); - for (int i=0;i<17;i++) { + for (int i = 0; i < 17; i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); String suffix = StringUtils.randomAlphanumeric(15); - String name = title2+suffix; + String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); asset.setType("default"); @@ -381,11 +386,11 @@ public class AssetServiceTest extends AbstractServiceTest { String title1 = "Asset title 1"; String type1 = "typeA"; List assetsType1 = new ArrayList<>(); - for (int i=0;i<13;i++) { + for (int i = 0; i < 13; i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); String suffix = StringUtils.randomAlphanumeric(15); - String name = title1+suffix; + String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); asset.setType(type1); @@ -394,11 +399,11 @@ public class AssetServiceTest extends AbstractServiceTest { String title2 = "Asset title 2"; String type2 = "typeB"; List assetsType2 = new ArrayList<>(); - for (int i=0;i<17;i++) { + for (int i = 0; i < 17; i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); String suffix = StringUtils.randomAlphanumeric(15); - String name = title2+suffix; + String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); asset.setType(type2); @@ -464,10 +469,10 @@ public class AssetServiceTest extends AbstractServiceTest { CustomerId customerId = customer.getId(); List assets = new ArrayList<>(); - for (int i=0;i<13;i++) { + for (int i = 0; i < 13; i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); - asset.setName("Asset"+i); + asset.setName("Asset" + i); asset.setType("default"); asset = assetService.saveAsset(asset); assets.add(new AssetInfo(assetService.assignAssetToCustomer(tenantId, asset.getId(), customerId), customer.getTitle(), customer.isPublic(), "default")); @@ -508,11 +513,11 @@ public class AssetServiceTest extends AbstractServiceTest { String title1 = "Asset title 1"; List assetsTitle1 = new ArrayList<>(); - for (int i=0;i<17;i++) { + for (int i = 0; i < 17; i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); String suffix = StringUtils.randomAlphanumeric(15); - String name = title1+suffix; + String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); asset.setType("default"); @@ -521,11 +526,11 @@ public class AssetServiceTest extends AbstractServiceTest { } String title2 = "Asset title 2"; List assetsTitle2 = new ArrayList<>(); - for (int i=0;i<13;i++) { + for (int i = 0; i < 13; i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); String suffix = StringUtils.randomAlphanumeric(15); - String name = title2+suffix; + String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); asset.setType("default"); @@ -596,11 +601,11 @@ public class AssetServiceTest extends AbstractServiceTest { String title1 = "Asset title 1"; String type1 = "typeC"; List assetsType1 = new ArrayList<>(); - for (int i=0;i<17;i++) { + for (int i = 0; i < 17; i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); String suffix = StringUtils.randomAlphanumeric(15); - String name = title1+suffix; + String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); asset.setType(type1); @@ -610,11 +615,11 @@ public class AssetServiceTest extends AbstractServiceTest { String title2 = "Asset title 2"; String type2 = "typeD"; List assetsType2 = new ArrayList<>(); - for (int i=0;i<13;i++) { + for (int i = 0; i < 13; i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); String suffix = StringUtils.randomAlphanumeric(15); - String name = title2+suffix; + String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); asset.setType(type2); @@ -848,4 +853,24 @@ public class AssetServiceTest extends AbstractServiceTest { ); } + @Test + public void testDeleteAssetIfCalculatedFieldExists() { + Asset asset = new Asset(); + asset.setTenantId(tenantId); + asset.setName("My asset"); + asset.setType("default"); + Asset savedAsset = assetService.saveAsset(asset); + + CalculatedField calculatedField = new CalculatedField(); + calculatedField.setTenantId(tenantId); + calculatedField.setName("Test CF"); + calculatedField.setType("Simple"); + calculatedField.setEntityId(savedAsset.getId()); + calculatedFieldService.save(calculatedField); + + assertThatThrownBy(() -> assetService.deleteAsset(tenantId, savedAsset.getId())) + .isInstanceOf(DataValidationException.class) + .hasMessage("Can't delete asset that has entity views or calculated fields!"); + } + } diff --git a/dao/src/test/java/org/thingsboard/server/dao/calculated_field/CalculatedFieldServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/CalculatedFieldServiceTest.java similarity index 97% rename from dao/src/test/java/org/thingsboard/server/dao/calculated_field/CalculatedFieldServiceTest.java rename to dao/src/test/java/org/thingsboard/server/dao/service/CalculatedFieldServiceTest.java index fdff4bc2f6..46cc478904 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/calculated_field/CalculatedFieldServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/CalculatedFieldServiceTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.calculated_field; +package org.thingsboard.server.dao.service; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; @@ -28,10 +28,9 @@ import org.thingsboard.server.common.data.calculated_field.CalculatedField; import org.thingsboard.server.common.data.calculated_field.CalculatedFieldLink; import org.thingsboard.server.common.data.id.CalculatedFieldId; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.dao.calculated_field.CalculatedFieldService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.service.AbstractServiceTest; -import org.thingsboard.server.dao.service.DaoSqlTest; import java.util.UUID; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceProfileServiceTest.java index aa14e20151..9bb1abc3c8 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceProfileServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceProfileServiceTest.java @@ -31,9 +31,11 @@ import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.calculated_field.CalculatedField; import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.calculated_field.CalculatedFieldService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.exception.DataValidationException; @@ -49,6 +51,7 @@ import java.util.concurrent.Executors; import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; @DaoSqlTest @@ -60,6 +63,9 @@ public class DeviceProfileServiceTest extends AbstractServiceTest { DeviceService deviceService; @Autowired OtaPackageService otaPackageService; + @Autowired + private CalculatedFieldService calculatedFieldService; + private IdComparator idComparator = new IdComparator<>(); private IdComparator deviceProfileInfoIdComparator = new IdComparator<>(); @@ -397,7 +403,7 @@ public class DeviceProfileServiceTest extends AbstractServiceTest { var profileA = deviceProfileService.saveDeviceProfile( - createDeviceProfile(tenantId, "profile A")); + createDeviceProfile(tenantId, "profile A")); deviceProfiles.add(deviceProfileService.saveDeviceProfile(profileA)); @@ -478,4 +484,21 @@ public class DeviceProfileServiceTest extends AbstractServiceTest { assertThat(deviceProfileInfos).isEqualTo(expected); } + @Test + public void testDeleteDeviceProfileIfCalculatedFieldExists() { + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile"); + DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); + + CalculatedField calculatedField = new CalculatedField(); + calculatedField.setTenantId(tenantId); + calculatedField.setName("Test CF"); + calculatedField.setType("Simple"); + calculatedField.setEntityId(savedDeviceProfile.getId()); + calculatedFieldService.save(calculatedField); + + assertThatThrownBy(() -> deviceProfileService.deleteDeviceProfile(tenantId, savedDeviceProfile.getId())) + .isInstanceOf(DataValidationException.class) + .hasMessage("Deletion of Device Profile is prohibited!"); + } + } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java index 9fe3f85463..f2508090b2 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.calculated_field.CalculatedField; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.OtaPackageId; @@ -50,6 +51,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.dao.calculated_field.CalculatedFieldService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; @@ -87,6 +89,8 @@ public class DeviceServiceTest extends AbstractServiceTest { @Autowired TenantProfileService tenantProfileService; @Autowired + private CalculatedFieldService calculatedFieldService; + @Autowired private PlatformTransactionManager platformTransactionManager; @SpyBean private DeviceCredentialsDataValidator validator; @@ -1198,4 +1202,20 @@ public class DeviceServiceTest extends AbstractServiceTest { ); } + @Test + public void testDeleteDeviceIfCalculatedFieldExists() { + Device device = saveDevice(tenantId, "Test"); + + CalculatedField calculatedField = new CalculatedField(); + calculatedField.setTenantId(tenantId); + calculatedField.setName("Test CF"); + calculatedField.setType("Simple"); + calculatedField.setEntityId(device.getId()); + calculatedFieldService.save(calculatedField); + + assertThatThrownBy(() -> deviceService.deleteDevice(tenantId, device.getId())) + .isInstanceOf(DataValidationException.class) + .hasMessage("Can't delete device that has entity views or calculated fields!"); + } + } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/CalculatedFieldLinkDataValidatorTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/CalculatedFieldLinkDataValidatorTest.java index a74c59d7ad..c9fa57a751 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/validator/CalculatedFieldLinkDataValidatorTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/validator/CalculatedFieldLinkDataValidatorTest.java @@ -54,4 +54,4 @@ public class CalculatedFieldLinkDataValidatorTest { .hasMessage("Can't update non existing calculated field link!"); } -} \ No newline at end of file +}