Add option to find existing entity by name when importing; refactoring

This commit is contained in:
Viacheslav Klimov 2022-04-05 17:12:51 +03:00
parent 18b2fd4664
commit 7c00cd449f
16 changed files with 127 additions and 58 deletions

View File

@ -29,7 +29,6 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
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.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
@ -38,23 +37,18 @@ import org.thingsboard.server.common.data.query.EntityFilter;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.EntityTypeFilter;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.sync.EntitiesExportImportService;
import org.thingsboard.server.service.sync.exporting.ExportableEntitiesService;
import org.thingsboard.server.service.sync.exporting.EntityExportSettings;
import org.thingsboard.server.service.sync.exporting.data.EntityExportData;
import org.thingsboard.server.service.sync.importing.EntityImportResult;
import org.thingsboard.server.service.sync.importing.EntityImportSettings;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -72,7 +66,6 @@ import static org.thingsboard.server.dao.sql.query.EntityKeyMapping.CREATED_TIME
public class EntitiesExportImportController extends BaseController {
private final EntitiesExportImportService exportImportService;
private final ExportableEntitiesService exportableEntitiesService;
private final EntityService entityService;
@ -201,8 +194,8 @@ public class EntitiesExportImportController extends BaseController {
@PostMapping("/import")
public List<EntityImportResult<ExportableEntity<EntityId>>> importEntity(@RequestBody List<EntityExportData<ExportableEntity<EntityId>>> exportDataList,
@RequestParam Map<String, String> importSettingsParams) throws ThingsboardException {
public List<EntityImportResult<ExportableEntity<EntityId>>> importEntities(@RequestBody List<EntityExportData<ExportableEntity<EntityId>>> exportDataList,
@RequestParam Map<String, String> importSettingsParams) throws ThingsboardException {
SecurityUser user = getCurrentUser();
EntityImportSettings importSettings = toImportSettings(importSettingsParams);
@ -225,25 +218,31 @@ public class EntitiesExportImportController extends BaseController {
private EntityExportSettings toExportSettings(Map<String, String> exportSettingsParams) {
return EntityExportSettings.builder()
.exportInboundRelations(getParam(exportSettingsParams, "exportInboundRelations", false, Boolean::parseBoolean))
.exportOutboundRelations(getParam(exportSettingsParams, "exportOutboundRelations", false, Boolean::parseBoolean))
.exportInboundRelations(getBooleanParam(exportSettingsParams, "exportInboundRelations", false))
.exportOutboundRelations(getBooleanParam(exportSettingsParams, "exportOutboundRelations", false))
.build();
}
private EntityImportSettings toImportSettings(Map<String, String> importSettingsParams) {
return EntityImportSettings.builder()
.importInboundRelations(getParam(importSettingsParams, "importInboundRelations", false, Boolean::parseBoolean))
.importOutboundRelations(getParam(importSettingsParams, "importOutboundRelations", false, Boolean::parseBoolean))
.removeExistingRelations(getParam(importSettingsParams, "removeExistingRelations", true, Boolean::parseBoolean))
.updateReferencesToOtherEntities(getParam(importSettingsParams, "updateReferencesToOtherEntities", true, Boolean::parseBoolean))
.findExistingByName(getBooleanParam(importSettingsParams, "findExistingByName", false))
.importInboundRelations(getBooleanParam(importSettingsParams, "importInboundRelations", false))
.importOutboundRelations(getBooleanParam(importSettingsParams, "importOutboundRelations", false))
.removeExistingRelations(getBooleanParam(importSettingsParams, "removeExistingRelations", true))
.updateReferencesToOtherEntities(getBooleanParam(importSettingsParams, "updateReferencesToOtherEntities", true))
.build();
}
protected <T> T getParam(Map<String, String> requestParams, String key, T defaultValue, Function<String, T> parsingFunction) {
protected static boolean getBooleanParam(Map<String, String> requestParams, String key, boolean defaultValue) {
return getParam(requestParams, key, defaultValue, Boolean::parseBoolean);
}
protected static <T> T getParam(Map<String, String> requestParams, String key, T defaultValue, Function<String, T> parsingFunction) {
return parsingFunction.apply(requestParams.getOrDefault(key, defaultValue.toString()));
}
private CustomerId toCustomerId(UUID customerUuid) {
private static CustomerId toCustomerId(UUID customerUuid) {
return new CustomerId(Optional.ofNullable(customerUuid).orElse(EntityId.NULL_UUID));
}

View File

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -41,14 +42,11 @@ import org.thingsboard.server.service.sync.importing.EntityImportService;
import org.thingsboard.server.service.sync.importing.EntityImportSettings;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@TbCoreComponent
@ -100,35 +98,46 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS
@Override
public <E extends ExportableEntity<I>, I extends EntityId> E findEntityByExternalId(SecurityUser user, I externalId) {
public <E extends ExportableEntity<I>, I extends EntityId> E findEntityByTenantIdAndExternalId(TenantId tenantId, I externalId) {
EntityType entityType = externalId.getEntityType();
if (SUPPORTED_ENTITY_TYPES.contains(entityType)) {
ExportableEntityDao<E> dao = (ExportableEntityDao<E>) getDao(entityType);
return dao.findByTenantIdAndExternalId(user.getTenantId().getId(), externalId.getId());
return dao.findByTenantIdAndExternalId(tenantId.getId(), externalId.getId());
} else {
return null;
}
return findEntityById(user, externalId);
}
@Override
public <E extends HasId<I>, I extends EntityId> E findEntityById(SecurityUser user, I id) {
public <E extends HasId<I>, I extends EntityId> E findEntityByTenantIdAndId(TenantId tenantId, I id) {
Dao<E> dao = (Dao<E>) getDao(id.getEntityType());
return dao.findById(user.getTenantId(), id.getId());
E entity = dao.findById(tenantId, id.getId());
if (entity instanceof HasTenantId && !((HasTenantId) entity).getTenantId().equals(tenantId)) {
return null;
}
return entity;
}
@Override
public <E extends ExportableEntity<I>, I extends EntityId> E findEntityByTenantIdAndName(TenantId tenantId, EntityType entityType, String name) {
ExportableEntityDao<E> dao = (ExportableEntityDao<E>) getDao(entityType);
return dao.findFirstByTenantIdAndName(tenantId.getId(), name);
}
@Override
public void checkPermission(SecurityUser user, HasId<? extends EntityId> entity, Operation operation) throws ThingsboardException {
public void checkPermission(SecurityUser user, HasId<? extends EntityId> entity, EntityType entityType, Operation operation) throws ThingsboardException {
if (entity instanceof HasTenantId) {
accessControlService.checkPermission(user, Resource.of(entity.getId().getEntityType()), operation, entity.getId(), (HasTenantId) entity);
accessControlService.checkPermission(user, Resource.of(entityType), operation, entity.getId(), (HasTenantId) entity);
} else if (entity != null) {
accessControlService.checkPermission(user, Resource.of(entity.getId().getEntityType()), operation);
accessControlService.checkPermission(user, Resource.of(entityType), operation);
}
}
@Override
public void checkPermission(SecurityUser user, EntityId entityId, Operation operation) throws ThingsboardException {
HasId<EntityId> entity = findEntityById(user, entityId);
checkPermission(user, entity, operation);
HasId<EntityId> entity = findEntityByTenantIdAndId(user.getTenantId(), entityId);
checkPermission(user, entity, entityId.getEntityType(), operation);
}

View File

@ -15,22 +15,25 @@
*/
package org.thingsboard.server.service.sync.exporting;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
public interface ExportableEntitiesService {
<E extends ExportableEntity<I>, I extends EntityId> E findEntityByExternalId(SecurityUser user, I externalId);
<E extends ExportableEntity<I>, I extends EntityId> E findEntityByTenantIdAndExternalId(TenantId tenantId, I externalId);
<E extends HasId<I>, I extends EntityId> E findEntityById(SecurityUser user, I id);
<E extends HasId<I>, I extends EntityId> E findEntityByTenantIdAndId(TenantId tenantId, I id);
<E extends ExportableEntity<I>, I extends EntityId> E findEntityByTenantIdAndName(TenantId tenantId, EntityType entityType, String name);
void checkPermission(SecurityUser user, HasId<? extends EntityId> entity, Operation operation) throws ThingsboardException;
void checkPermission(SecurityUser user, HasId<? extends EntityId> entity, EntityType entityType, Operation operation) throws ThingsboardException;
void checkPermission(SecurityUser user, EntityId entityId, Operation operation) throws ThingsboardException;

View File

@ -44,8 +44,11 @@ public abstract class BaseEntityExportService<I extends EntityId, E extends Expo
public final D getExportData(SecurityUser user, I entityId, EntityExportSettings exportSettings) throws ThingsboardException {
D exportData = newExportData();
E entity = exportableEntitiesService.findEntityById(user, entityId);
exportableEntitiesService.checkPermission(user, entity, Operation.READ);
E entity = exportableEntitiesService.findEntityByTenantIdAndId(user.getTenantId(), entityId);
if (entity == null) {
throw new IllegalArgumentException("Entity not found");
}
exportableEntitiesService.checkPermission(user, entity, getEntityType(), Operation.READ);
exportData.setEntity(entity);
setRelatedEntities(user.getTenantId(), entity, exportData);

View File

@ -25,6 +25,7 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor
@Builder
public class EntityImportSettings {
private boolean findExistingByName;
private boolean importInboundRelations;
private boolean importOutboundRelations;
private boolean removeExistingRelations;

View File

@ -57,7 +57,7 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
@Override
public EntityImportResult<E> importEntity(SecurityUser user, D exportData, EntityImportSettings importSettings) throws ThingsboardException {
E entity = exportData.getEntity();
E existingEntity = exportableEntitiesService.findEntityByExternalId(user, entity.getId());
E existingEntity = findExistingEntity(user.getTenantId(), entity, importSettings);
entity.setExternalId(entity.getId());
@ -65,10 +65,10 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
setOwner(user.getTenantId(), entity, idProvider);
if (existingEntity == null) {
entity.setId(null);
exportableEntitiesService.checkPermission(user, entity, Operation.CREATE);
exportableEntitiesService.checkPermission(user, entity, getEntityType(), Operation.CREATE);
} else {
entity.setId(existingEntity.getId());
exportableEntitiesService.checkPermission(user, existingEntity, Operation.WRITE);
exportableEntitiesService.checkPermission(user, existingEntity, getEntityType(), Operation.WRITE);
}
E savedEntity = prepareAndSave(user.getTenantId(), entity, exportData, idProvider);
@ -85,6 +85,28 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
protected abstract E prepareAndSave(TenantId tenantId, E entity, D exportData, NewIdProvider idProvider);
private E findExistingEntity(TenantId tenantId, E entity, EntityImportSettings importSettings) {
return (E) Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndExternalId(tenantId, entity.getId()))
.or(() -> Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndId(tenantId, entity.getId())))
.or(() -> {
if (importSettings.isFindExistingByName()) {
return Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndName(tenantId, getEntityType(), entity.getName()));
} else {
return Optional.empty();
}
})
.orElse(null);
}
private <ID extends EntityId> HasId<ID> findInternalEntity(TenantId tenantId, ID externalId) {
if (externalId == null || externalId.isNullUid()) return null;
return (HasId<ID>) Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndExternalId(tenantId, externalId))
.or(() -> Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndId(tenantId, externalId)))
.orElseThrow(() -> new IllegalArgumentException("Cannot find " + externalId.getEntityType() + " by external id " + externalId));
}
private void importRelations(SecurityUser user, E savedEntity, E existingEntity, D exportData, EntityImportSettings importSettings) throws ThingsboardException {
List<EntityRelation> newRelations = new LinkedList<>();
@ -116,31 +138,21 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
for (EntityRelation relation : newRelations) {
HasId<EntityId> otherEntity = null;
if (!relation.getTo().equals(savedEntity.getId())) {
otherEntity = findInternalEntity(user, relation.getTo());
otherEntity = findInternalEntity(user.getTenantId(), relation.getTo());
relation.setTo(otherEntity.getId());
}
if (!relation.getFrom().equals(savedEntity.getId())) {
otherEntity = findInternalEntity(user, relation.getFrom());
otherEntity = findInternalEntity(user.getTenantId(), relation.getFrom());
relation.setFrom(otherEntity.getId());
}
if (otherEntity != null) {
exportableEntitiesService.checkPermission(user, otherEntity, Operation.WRITE);
exportableEntitiesService.checkPermission(user, otherEntity, otherEntity.getId().getEntityType(), Operation.WRITE);
}
relationService.saveRelation(user.getTenantId(), relation);
}
}
private <IE extends HasId<ID>, ID extends EntityId> IE findInternalEntity(SecurityUser user, ID externalId) {
if (externalId == null || externalId.isNullUid()) {
return null;
}
IE entity = exportableEntitiesService.findEntityByExternalId(user, externalId);
if (entity == null) {
throw new IllegalArgumentException("Cannot find " + externalId.getEntityType() + " by external id " + externalId);
}
return entity;
}
@RequiredArgsConstructor
protected class NewIdProvider {
@ -175,12 +187,16 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
}
private <ID extends EntityId> ID getInternalId(ID externalId) {
try {
HasId<ID> entity = findInternalEntity(user, externalId);
exportableEntitiesService.checkPermission(user, entity, Operation.READ);
HasId<ID> entity = findInternalEntity(user.getTenantId(), externalId);
if (entity != null) {
try {
exportableEntitiesService.checkPermission(user, entity, entity.getId().getEntityType(), Operation.READ);
} catch (ThingsboardException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
return entity.getId();
} catch (ThingsboardException e) {
throw new IllegalArgumentException(e.getMessage(), e);
} else {
return null;
}
}

View File

@ -23,4 +23,6 @@ public interface ExportableEntityDao<T extends ExportableEntity<?>> extends Dao<
T findByTenantIdAndExternalId(UUID tenantId, UUID externalId);
T findFirstByTenantIdAndName(UUID tenantId, String name);
}

View File

@ -25,6 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
@ -110,6 +111,7 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
.orElse(null);
}
@Transactional
@CacheEvict(cacheNames = ASSET_CACHE, key = "{#asset.tenantId, #asset.name}")
@Override
public Asset saveAsset(Asset asset) {

View File

@ -214,6 +214,11 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao<AssetEntity, Asset> im
return DaoUtil.getData(assetRepository.findByTenantIdAndExternalId(tenantId, externalId));
}
@Override
public Asset findFirstByTenantIdAndName(UUID tenantId, String name) {
return findAssetsByTenantIdAndName(tenantId, name).orElse(null);
}
@Override
public EntityType getEntityType() {
return EntityType.ASSET;

View File

@ -75,6 +75,11 @@ public class JpaCustomerDao extends JpaAbstractSearchTextDao<CustomerEntity, Cus
return DaoUtil.getData(customerRepository.findByTenantIdAndExternalId(tenantId, externalId));
}
@Override
public Customer findFirstByTenantIdAndName(UUID tenantId, String name) {
return findCustomersByTenantIdAndTitle(tenantId, name).orElse(null);
}
@Override
public EntityType getEntityType() {
return EntityType.CUSTOMER;

View File

@ -28,4 +28,6 @@ public interface DashboardRepository extends JpaRepository<DashboardEntity, UUID
Long countByTenantId(UUID tenantId);
DashboardEntity findFirstByTenantIdAndTitle(UUID tenantId, String title);
}

View File

@ -57,6 +57,11 @@ public class JpaDashboardDao extends JpaAbstractSearchTextDao<DashboardEntity, D
return DaoUtil.getData(dashboardRepository.findByTenantIdAndExternalId(tenantId, externalId));
}
@Override
public Dashboard findFirstByTenantIdAndName(UUID tenantId, String name) {
return DaoUtil.getData(dashboardRepository.findFirstByTenantIdAndTitle(tenantId, name));
}
@Override
public EntityType getEntityType() {
return EntityType.DASHBOARD;

View File

@ -308,6 +308,11 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device>
return DaoUtil.getData(deviceRepository.findByTenantIdAndExternalId(tenantId, externalId));
}
@Override
public Device findFirstByTenantIdAndName(UUID tenantId, String name) {
return findDeviceByTenantIdAndName(tenantId, name).orElse(null);
}
@Override
public EntityType getEntityType() {
return EntityType.DEVICE;

View File

@ -116,6 +116,11 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao<DeviceProfileE
return DaoUtil.getData(deviceProfileRepository.findByTenantIdAndExternalId(tenantId, externalId));
}
@Override
public DeviceProfile findFirstByTenantIdAndName(UUID tenantId, String name) {
return DaoUtil.getData(deviceProfileRepository.findByTenantIdAndName(tenantId, name));
}
@Override
public EntityType getEntityType() {
return EntityType.DEVICE_PROFILE;

View File

@ -114,6 +114,11 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao<RuleChainEntity, R
return DaoUtil.getData(ruleChainRepository.findByTenantIdAndExternalId(tenantId, externalId));
}
@Override
public RuleChain findFirstByTenantIdAndName(UUID tenantId, String name) {
return DaoUtil.getData(ruleChainRepository.findFirstByTenantIdAndName(tenantId, name));
}
@Override
public EntityType getEntityType() {
return EntityType.RULE_CHAIN;

View File

@ -67,4 +67,6 @@ public interface RuleChainRepository extends JpaRepository<RuleChainEntity, UUID
List<RuleChainEntity> findByTenantIdAndTypeAndName(UUID tenantId, RuleChainType type, String name);
RuleChainEntity findFirstByTenantIdAndName(UUID tenantId, String name);
}