diff --git a/application/src/main/java/org/thingsboard/server/controller/EntitiesExportImportController.java b/application/src/main/java/org/thingsboard/server/controller/EntitiesExportImportController.java index 9f9e90b0ca..4a1badd38e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntitiesExportImportController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntitiesExportImportController.java @@ -17,13 +17,13 @@ package org.thingsboard.server.controller; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DataAccessException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; @@ -41,9 +41,9 @@ 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.data.EntityExportData; -import org.thingsboard.server.service.sync.exporting.data.request.EntityFilterExportRequest; +import org.thingsboard.server.service.sync.exporting.data.request.CustomEntityFilterExportRequest; +import org.thingsboard.server.service.sync.exporting.data.request.CustomEntityQueryExportRequest; import org.thingsboard.server.service.sync.exporting.data.request.EntityListExportRequest; -import org.thingsboard.server.service.sync.exporting.data.request.EntityQueryExportRequest; import org.thingsboard.server.service.sync.exporting.data.request.EntityTypeExportRequest; import org.thingsboard.server.service.sync.exporting.data.request.ExportRequest; import org.thingsboard.server.service.sync.exporting.data.request.SingleEntityExportRequest; @@ -71,7 +71,7 @@ public class EntitiesExportImportController extends BaseController { @PostMapping("/export") @PreAuthorize("hasAuthority('TENANT_ADMIN')") - public List>> exportEntities(@RequestBody ExportRequest exportRequest) throws ThingsboardException { + public List> exportEntities(@RequestBody ExportRequest exportRequest) throws ThingsboardException { SecurityUser user = getCurrentUser(); try { return exportEntitiesByRequest(user, exportRequest); @@ -82,10 +82,10 @@ public class EntitiesExportImportController extends BaseController { @PostMapping(value = "/export", params = {"multiple"}) @PreAuthorize("hasAuthority('TENANT_ADMIN')") - public List>> exportEntities(@RequestBody List exportRequests) throws ThingsboardException { + public List> exportEntities(@RequestBody List exportRequests) throws ThingsboardException { SecurityUser user = getCurrentUser(); try { - List>> exportDataList = new ArrayList<>(); + List> exportDataList = new ArrayList<>(); for (ExportRequest exportRequest : exportRequests) { exportDataList.addAll(exportEntitiesByRequest(user, exportRequest)); } @@ -96,10 +96,10 @@ public class EntitiesExportImportController extends BaseController { } - private List>> exportEntitiesByRequest(SecurityUser user, ExportRequest request) throws ThingsboardException { + private List> exportEntitiesByRequest(SecurityUser user, ExportRequest request) throws ThingsboardException { List entitiesIds = findEntitiesForRequest(user, request); - List>> exportDataList = new ArrayList<>(); + List> exportDataList = new ArrayList<>(); for (EntityId entityId : entitiesIds) { exportDataList.add(exportImportService.exportEntity(user, entityId, request.getExportSettings())); } @@ -122,15 +122,15 @@ public class EntitiesExportImportController extends BaseController { CustomerId customerId = Optional.ofNullable(exportRequest.getCustomerId()).orElse(emptyId(EntityType.CUSTOMER)); return findEntitiesByFilter(user.getTenantId(), customerId, entityTypeFilter, exportRequest.getPage(), exportRequest.getPageSize()); } - case ENTITY_FILTER: { - EntityFilterExportRequest exportRequest = (EntityFilterExportRequest) request; + case CUSTOM_ENTITY_FILTER: { + CustomEntityFilterExportRequest exportRequest = (CustomEntityFilterExportRequest) request; EntityFilter filter = exportRequest.getFilter(); CustomerId customerId = Optional.ofNullable(exportRequest.getCustomerId()).orElse(emptyId(EntityType.CUSTOMER)); return findEntitiesByFilter(user.getTenantId(), customerId, filter, exportRequest.getPage(), exportRequest.getPageSize()); } - case ENTITY_QUERY:{ - EntityQueryExportRequest exportRequest = (EntityQueryExportRequest) request; + case CUSTOM_ENTITY_QUERY: { + CustomEntityQueryExportRequest exportRequest = (CustomEntityQueryExportRequest) request; EntityDataQuery query = exportRequest.getQuery(); CustomerId customerId = Optional.ofNullable(exportRequest.getCustomerId()).orElse(emptyId(EntityType.CUSTOMER)); @@ -153,24 +153,29 @@ public class EntitiesExportImportController extends BaseController { } private List findEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityDataQuery query) { - return entityService.findEntityDataByQuery(tenantId, customerId, query).getData().stream() - .map(EntityData::getEntityId) - .collect(Collectors.toList()); + try { + return entityService.findEntityDataByQuery(tenantId, customerId, query).getData().stream() + .map(EntityData::getEntityId) + .collect(Collectors.toList()); + } catch (DataAccessException e) { + log.error("Failed to find entity data by query: {}", e.getMessage()); + throw new IllegalArgumentException("Entity filter cannot be processed"); + } } @PostMapping("/import") - public List>> importEntities(@RequestBody ImportRequest importRequest) throws ThingsboardException { + public List> importEntities(@RequestBody ImportRequest importRequest) throws ThingsboardException { SecurityUser user = getCurrentUser(); try { - List>> importResults = exportImportService.importEntities(user, importRequest.getExportDataList(), importRequest.getImportSettings()); + List> importResults = exportImportService.importEntities(user, importRequest.getExportDataList(), importRequest.getImportSettings()); importResults.stream() - .map(EntityImportResult::getPushEventsCallback) + .map(EntityImportResult::getSendEventsCallback) .filter(Objects::nonNull) - .forEach(pushEventsCallback -> { + .forEach(sendEventsCallback -> { try { - pushEventsCallback.run(); + sendEventsCallback.run(); } catch (Exception e) { log.error("Failed to send event for entity", e); } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/DefaultEntitiesExportImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/DefaultEntitiesExportImportService.java index eefa396d06..cfb895c36a 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/DefaultEntitiesExportImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/DefaultEntitiesExportImportService.java @@ -67,7 +67,7 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS protected static final List SUPPORTED_ENTITY_TYPES = List.of( EntityType.CUSTOMER, EntityType.ASSET, EntityType.RULE_CHAIN, - EntityType.DEVICE_PROFILE, EntityType.DEVICE, EntityType.DASHBOARD + EntityType.DASHBOARD, EntityType.DEVICE_PROFILE, EntityType.DEVICE ); @@ -82,12 +82,12 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS @Transactional(rollbackFor = Exception.class) @Override - public List>> importEntities(SecurityUser user, List>> exportDataList, EntityImportSettings importSettings) throws ThingsboardException { + public List> importEntities(SecurityUser user, List> exportDataList, EntityImportSettings importSettings) throws ThingsboardException { exportDataList.sort(Comparator.comparing(exportData -> SUPPORTED_ENTITY_TYPES.indexOf(exportData.getEntityType()))); - List>> importResults = new ArrayList<>(); + List> importResults = new ArrayList<>(); - for (EntityExportData> exportData : exportDataList) { - EntityImportResult> importResult = importEntity(user, exportData, importSettings); + for (EntityExportData exportData : exportDataList) { + EntityImportResult importResult = importEntity(user, exportData, importSettings); importResults.add(importResult); } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/EntitiesExportImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/EntitiesExportImportService.java index b0690ea9ca..eaaf01ba2c 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/EntitiesExportImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/EntitiesExportImportService.java @@ -30,6 +30,6 @@ public interface EntitiesExportImportService { , I extends EntityId> EntityExportData exportEntity(SecurityUser user, I entityId, EntityExportSettings exportSettings) throws ThingsboardException; - List>> importEntities(SecurityUser user, List>> exportDataList, EntityImportSettings importSettings) throws ThingsboardException; + List> importEntities(SecurityUser user, List> exportDataList, EntityImportSettings importSettings) throws ThingsboardException; } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/exporting/data/EntityExportData.java b/application/src/main/java/org/thingsboard/server/service/sync/exporting/data/EntityExportData.java index bf44b0dc37..4ab6dd57b1 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/exporting/data/EntityExportData.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/exporting/data/EntityExportData.java @@ -21,21 +21,16 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes.Type; import com.fasterxml.jackson.annotation.JsonTypeInfo; import lombok.Data; -import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.Dashboard; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.ExportableEntity; -import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.utils.JsonTbEntity; import java.util.List; @JsonIgnoreProperties(ignoreUnknown = true) -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "entityType", defaultImpl = EntityExportData.class, visible = true) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "entityType", defaultImpl = EntityExportData.class) @JsonSubTypes({ @Type(name = "DEVICE", value = DeviceExportData.class), @Type(name = "RULE_CHAIN", value = RuleChainExportData.class) @@ -44,15 +39,7 @@ import java.util.List; @Data public class EntityExportData> { - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "entityType", include = JsonTypeInfo.As.EXTERNAL_PROPERTY) - @JsonSubTypes({ - @Type(name = "DEVICE", value = Device.class), - @Type(name = "RULE_CHAIN", value = RuleChain.class), - @Type(name = "DEVICE_PROFILE", value = DeviceProfile.class), - @Type(name = "ASSET", value = Asset.class), - @Type(name = "DASHBOARD", value = Dashboard.class), - @Type(name = "CUSTOMER", value = Customer.class) - }) + @JsonTbEntity private E entity; private EntityType entityType; diff --git a/application/src/main/java/org/thingsboard/server/service/sync/exporting/data/request/EntityFilterExportRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/exporting/data/request/CustomEntityFilterExportRequest.java similarity index 89% rename from application/src/main/java/org/thingsboard/server/service/sync/exporting/data/request/EntityFilterExportRequest.java rename to application/src/main/java/org/thingsboard/server/service/sync/exporting/data/request/CustomEntityFilterExportRequest.java index 00ef0604e5..02cbca98d6 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/exporting/data/request/EntityFilterExportRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/exporting/data/request/CustomEntityFilterExportRequest.java @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.query.EntityFilter; @EqualsAndHashCode(callSuper = true) @Data -public class EntityFilterExportRequest extends ExportRequest { +public class CustomEntityFilterExportRequest extends ExportRequest { private EntityFilter filter; private int page; @@ -31,7 +31,7 @@ public class EntityFilterExportRequest extends ExportRequest { @Override public ExportRequestType getType() { - return ExportRequestType.ENTITY_FILTER; + return ExportRequestType.CUSTOM_ENTITY_FILTER; } } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/exporting/data/request/EntityQueryExportRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/exporting/data/request/CustomEntityQueryExportRequest.java similarity index 89% rename from application/src/main/java/org/thingsboard/server/service/sync/exporting/data/request/EntityQueryExportRequest.java rename to application/src/main/java/org/thingsboard/server/service/sync/exporting/data/request/CustomEntityQueryExportRequest.java index 85232821d7..76d258e891 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/exporting/data/request/EntityQueryExportRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/exporting/data/request/CustomEntityQueryExportRequest.java @@ -22,14 +22,14 @@ import org.thingsboard.server.common.data.query.EntityDataQuery; @EqualsAndHashCode(callSuper = true) @Data -public class EntityQueryExportRequest extends ExportRequest { +public class CustomEntityQueryExportRequest extends ExportRequest { private EntityDataQuery query; private CustomerId customerId; @Override public ExportRequestType getType() { - return ExportRequestType.ENTITY_QUERY; + return ExportRequestType.CUSTOM_ENTITY_QUERY; } } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/exporting/data/request/ExportRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/exporting/data/request/ExportRequest.java index c140786d06..af0f161227 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/exporting/data/request/ExportRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/exporting/data/request/ExportRequest.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.sync.exporting.data.request; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes.Type; import com.fasterxml.jackson.annotation.JsonTypeInfo; @@ -27,15 +26,14 @@ import lombok.Data; @Type(name = "SINGLE_ENTITY", value = SingleEntityExportRequest.class), @Type(name = "ENTITY_LIST", value = EntityListExportRequest.class), @Type(name = "ENTITY_TYPE", value = EntityTypeExportRequest.class), - @Type(name = "ENTITY_FILTER", value = EntityFilterExportRequest.class), - @Type(name = "ENTITY_QUERY", value = EntityQueryExportRequest.class) + @Type(name = "CUSTOM_ENTITY_FILTER", value = CustomEntityFilterExportRequest.class), + @Type(name = "CUSTOM_ENTITY_QUERY", value = CustomEntityQueryExportRequest.class) }) @Data public abstract class ExportRequest { private EntityExportSettings exportSettings; - @JsonIgnore public abstract ExportRequestType getType(); } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/exporting/data/request/ExportRequestType.java b/application/src/main/java/org/thingsboard/server/service/sync/exporting/data/request/ExportRequestType.java index 07e5c946ba..c36ac567e3 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/exporting/data/request/ExportRequestType.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/exporting/data/request/ExportRequestType.java @@ -20,6 +20,6 @@ public enum ExportRequestType { ENTITY_LIST, ENTITY_TYPE, - ENTITY_FILTER, - ENTITY_QUERY + CUSTOM_ENTITY_FILTER, + CUSTOM_ENTITY_QUERY } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/importing/data/EntityImportResult.java b/application/src/main/java/org/thingsboard/server/service/sync/importing/data/EntityImportResult.java index 7160a483b3..62c574d5f0 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/importing/data/EntityImportResult.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/importing/data/EntityImportResult.java @@ -16,32 +16,33 @@ package org.thingsboard.server.service.sync.importing.data; import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.Getter; -import lombok.Setter; +import lombok.Data; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.utils.JsonTbEntity; import org.thingsboard.server.utils.ThrowingRunnable; +@Data public class EntityImportResult> { - @Getter @Setter + + @JsonTbEntity private E savedEntity; - @Getter @Setter + @JsonTbEntity private E oldEntity; - @Getter @Setter private EntityType entityType; - @Getter @JsonIgnore + @JsonIgnore private ThrowingRunnable saveReferencesCallback = () -> {}; - @Getter @JsonIgnore - private ThrowingRunnable pushEventsCallback = () -> {}; + @JsonIgnore + private ThrowingRunnable sendEventsCallback = () -> {}; public void addSaveReferencesCallback(ThrowingRunnable callback) { this.saveReferencesCallback = this.saveReferencesCallback.andThen(callback); } - public void addPushEventsCallback(ThrowingRunnable callback) { - this.pushEventsCallback = this.pushEventsCallback.andThen(callback); + public void addSendEventsCallback(ThrowingRunnable callback) { + this.sendEventsCallback = this.sendEventsCallback.andThen(callback); } } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/importing/data/EntityImportSettings.java b/application/src/main/java/org/thingsboard/server/service/sync/importing/data/EntityImportSettings.java index 1b5a93c0d8..3b457e1f5a 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/importing/data/EntityImportSettings.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/importing/data/EntityImportSettings.java @@ -26,8 +26,10 @@ import lombok.NoArgsConstructor; @Builder public class EntityImportSettings { private boolean findExistingByName; + private boolean importInboundRelations; private boolean importOutboundRelations; private boolean removeExistingRelations; + private boolean updateReferencesToOtherEntities; } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/importing/data/request/ImportRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/importing/data/request/ImportRequest.java index 6e857958d6..7943e1a404 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/importing/data/request/ImportRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/importing/data/request/ImportRequest.java @@ -26,7 +26,7 @@ import java.util.List; @Data public class ImportRequest { - private List>> exportDataList; + private List> exportDataList; private EntityImportSettings importSettings; } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/importing/impl/BaseEntityImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/importing/impl/BaseEntityImportService.java index b576ae1e60..97547868d2 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/importing/impl/BaseEntityImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/importing/impl/BaseEntityImportService.java @@ -21,7 +21,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.cluster.TbClusterService; -import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.HasCustomerId; import org.thingsboard.server.common.data.audit.ActionType; @@ -76,6 +75,7 @@ public abstract class BaseEntityImportService { + importResult.addSendEventsCallback(() -> { onEntitySaved(user, savedEntity, oldEntity); }); } @@ -172,8 +172,6 @@ public abstract class BaseEntityImportService HasId findInternalEntity(TenantId tenantId, ID externalId) { - if (externalId == null || externalId.isNullUid()) return null; - return (HasId) Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndExternalId(tenantId, externalId)) .or(() -> Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndId(tenantId, externalId))) .orElseThrow(() -> new IllegalArgumentException("Cannot find " + externalId.getEntityType() + " by external id " + externalId)); @@ -187,19 +185,18 @@ public abstract class BaseEntityImportService ALWAYS_UPDATE_REFERENCED_IDS = Set.of( - EntityType.RULE_CHAIN - ); - public ID get(Function idExtractor) { - if (existingEntity == null || importSettings.isUpdateReferencesToOtherEntities() - || ALWAYS_UPDATE_REFERENCED_IDS.contains(getEntityType())) { + if (existingEntity == null || importSettings.isUpdateReferencesToOtherEntities()) { return getInternalId(idExtractor.apply(this.entity)); } else { return idExtractor.apply(existingEntity); } } + public ID getInternal(ID externalId) { + return getInternalId(externalId); + } + public Set get(Function> listExtractor, Function idGetter, BiConsumer idSetter) { if (existingEntity == null || importSettings.isUpdateReferencesToOtherEntities()) { return Optional.ofNullable(listExtractor.apply(entity)).orElse(Collections.emptySet()).stream() @@ -213,17 +210,15 @@ public abstract class BaseEntityImportService ID getInternalId(ID externalId) { + if (externalId == null || externalId.isNullUid()) return null; + HasId 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(); - } else { - return null; + try { + exportableEntitiesService.checkPermission(user, entity, entity.getId().getEntityType(), Operation.READ); + } catch (ThingsboardException e) { + throw new IllegalArgumentException(e.getMessage(), e); } + return entity.getId(); } } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/importing/impl/DashboardImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/importing/impl/DashboardImportService.java index 3e06480a2f..fda6904421 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/importing/impl/DashboardImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/importing/impl/DashboardImportService.java @@ -74,7 +74,7 @@ public class DashboardImportService extends BaseEntityImportService { String uuid = matchResult.group(); EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, uuid); - return idProvider.get(d -> entityId).toString(); + return idProvider.getInternal(entityId).toString(); }); ((ObjectNode) entityAlias).set("filter", JacksonUtil.toJsonNode(newFilterJson)); }); @@ -86,7 +86,7 @@ public class DashboardImportService extends BaseEntityImportService existingAssignedCustomers = Optional.ofNullable(dashboardService.findDashboardById(tenantId, dashboard.getId()).getAssignedCustomers()) diff --git a/application/src/main/java/org/thingsboard/server/service/sync/importing/impl/RuleChainImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/importing/impl/RuleChainImportService.java index f901fcfd17..5fef00cbed 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/importing/impl/RuleChainImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/importing/impl/RuleChainImportService.java @@ -63,14 +63,14 @@ public class RuleChainImportService extends BaseEntityImportService { ((ObjectNode) ruleNodeConfig).set("ruleChainId", new TextNode( - idProvider.get(rc -> new RuleChainId(otherRuleChainUuid)).toString() + idProvider.getInternal(new RuleChainId(otherRuleChainUuid)).toString() )); ruleNode.setConfiguration(ruleNodeConfig); }); }); Optional.ofNullable(metaData.getRuleChainConnections()).orElse(Collections.emptyList()) .forEach(ruleChainConnectionInfo -> { - ruleChainConnectionInfo.setTargetRuleChainId(idProvider.get(rc -> ruleChainConnectionInfo.getTargetRuleChainId())); + ruleChainConnectionInfo.setTargetRuleChainId(idProvider.getInternal(ruleChainConnectionInfo.getTargetRuleChainId())); }); ruleChain.setFirstRuleNodeId(null); diff --git a/application/src/main/java/org/thingsboard/server/utils/JsonTbEntity.java b/application/src/main/java/org/thingsboard/server/utils/JsonTbEntity.java new file mode 100644 index 0000000000..cfc38ff3c3 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/utils/JsonTbEntity.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.utils; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +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.DeviceProfile; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.rule.RuleChain; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@JacksonAnnotationsInside +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "entityType", include = JsonTypeInfo.As.EXTERNAL_PROPERTY) +@JsonSubTypes({ + @Type(name = "DEVICE", value = Device.class), + @Type(name = "RULE_CHAIN", value = RuleChain.class), + @Type(name = "DEVICE_PROFILE", value = DeviceProfile.class), + @Type(name = "ASSET", value = Asset.class), + @Type(name = "DASHBOARD", value = Dashboard.class), + @Type(name = "CUSTOMER", value = Customer.class) +}) +public @interface JsonTbEntity { +} diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEntitiesExportImportControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEntitiesExportImportControllerTest.java index 0d73ed9f09..a8a5bca534 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEntitiesExportImportControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEntitiesExportImportControllerTest.java @@ -17,6 +17,8 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.node.TextNode; +import org.junit.After; +import org.junit.Before; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.web.servlet.ResultActions; import org.thingsboard.common.util.JacksonUtil; @@ -28,6 +30,10 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.DeviceData; @@ -35,19 +41,29 @@ import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileCon import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.ota.OtaPackageService; +import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.service.sync.exporting.data.EntityExportData; import org.thingsboard.server.service.sync.exporting.data.request.EntityExportSettings; import org.thingsboard.server.service.sync.exporting.data.request.ExportRequest; @@ -56,11 +72,12 @@ import org.thingsboard.server.service.sync.importing.data.EntityImportResult; import org.thingsboard.server.service.sync.importing.data.EntityImportSettings; import org.thingsboard.server.service.sync.importing.data.request.ImportRequest; -import java.lang.reflect.Type; +import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collections; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public abstract class BaseEntitiesExportImportControllerTest extends AbstractControllerTest { @@ -68,6 +85,8 @@ public abstract class BaseEntitiesExportImportControllerTest extends AbstractCon @Autowired protected DeviceService deviceService; @Autowired + protected OtaPackageService otaPackageService; + @Autowired protected DeviceProfileService deviceProfileService; @Autowired protected AssetService assetService; @@ -77,6 +96,45 @@ public abstract class BaseEntitiesExportImportControllerTest extends AbstractCon protected RuleChainService ruleChainService; @Autowired protected DashboardService dashboardService; + @Autowired + protected RelationService relationService; + @Autowired + protected TenantService tenantService; + + protected TenantId tenantId1; + protected User tenantAdmin1; + + protected TenantId tenantId2; + protected User tenantAdmin2; + + @Before + public void beforeEach() throws Exception { + loginSysAdmin(); + Tenant tenant1 = new Tenant(); + tenant1.setTitle("Tenant 1"); + tenant1.setEmail("tenant1@thingsboard.org"); + this.tenantId1 = tenantService.saveTenant(tenant1).getId(); + User tenantAdmin1 = new User(); + tenantAdmin1.setTenantId(tenantId1); + tenantAdmin1.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin1.setEmail("tenant1-admin@thingsboard.org"); + this.tenantAdmin1 = createUser(tenantAdmin1, "12345678"); + Tenant tenant2 = new Tenant(); + tenant2.setTitle("Tenant 2"); + tenant2.setEmail("tenant2@thingsboard.org"); + this.tenantId2 = tenantService.saveTenant(tenant2).getId(); + User tenantAdmin2 = new User(); + tenantAdmin2.setTenantId(tenantId2); + tenantAdmin2.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin2.setEmail("tenant2-admin@thingsboard.org"); + this.tenantAdmin2 = createUser(tenantAdmin2, "12345678"); + } + + @After + public void afterEach() { + tenantService.deleteTenant(tenantId1); + tenantService.deleteTenant(tenantId2); + } protected Device createDevice(TenantId tenantId, CustomerId customerId, DeviceProfileId deviceProfileId, String name) { Device device = new Device(); @@ -91,13 +149,38 @@ public abstract class BaseEntitiesExportImportControllerTest extends AbstractCon return deviceService.saveDevice(device); } - protected DeviceProfile createDeviceProfile(TenantId tenantId, String name) { + protected OtaPackage createOtaPackage(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType type) { + OtaPackage otaPackage = new OtaPackage(); + otaPackage.setTenantId(tenantId); + otaPackage.setDeviceProfileId(deviceProfileId); + otaPackage.setType(type); + otaPackage.setTitle("My " + type); + otaPackage.setVersion("v1.0"); + otaPackage.setFileName("filename.txt"); + otaPackage.setContentType("text/plain"); + otaPackage.setChecksumAlgorithm(ChecksumAlgorithm.SHA256); + otaPackage.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"); + otaPackage.setDataSize(1L); + otaPackage.setData(ByteBuffer.wrap(new byte[]{(int) 1})); + return otaPackageService.saveOtaPackage(otaPackage); + } + + protected void checkImportedDeviceData(Device initialDevice, Device importedDevice) { + assertThat(importedDevice.getName()).isEqualTo(initialDevice.getName()); + assertThat(importedDevice.getType()).isEqualTo(initialDevice.getType()); + assertThat(importedDevice.getDeviceData()).isEqualTo(initialDevice.getDeviceData()); + assertThat(importedDevice.getLabel()).isEqualTo(initialDevice.getLabel()); + } + + protected DeviceProfile createDeviceProfile(TenantId tenantId, RuleChainId defaultRuleChainId, DashboardId defaultDashboardId, String name) { DeviceProfile deviceProfile = new DeviceProfile(); deviceProfile.setTenantId(tenantId); deviceProfile.setName(name); deviceProfile.setDescription("dscrptn"); deviceProfile.setType(DeviceProfileType.DEFAULT); deviceProfile.setTransportType(DeviceTransportType.DEFAULT); + deviceProfile.setDefaultRuleChainId(defaultRuleChainId); + deviceProfile.setDefaultDashboardId(defaultDashboardId); DeviceProfileData profileData = new DeviceProfileData(); profileData.setConfiguration(new DefaultDeviceProfileConfiguration()); profileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration()); @@ -105,6 +188,14 @@ public abstract class BaseEntitiesExportImportControllerTest extends AbstractCon return deviceProfileService.saveDeviceProfile(deviceProfile); } + protected void checkImportedDeviceProfileData(DeviceProfile initialProfile, DeviceProfile importedProfile) { + assertThat(initialProfile.getName()).isEqualTo(importedProfile.getName()); + assertThat(initialProfile.getType()).isEqualTo(importedProfile.getType()); + assertThat(initialProfile.getTransportType()).isEqualTo(importedProfile.getTransportType()); + assertThat(initialProfile.getProfileData()).isEqualTo(importedProfile.getProfileData()); + assertThat(initialProfile.getDescription()).isEqualTo(importedProfile.getDescription()); + } + protected Asset createAsset(TenantId tenantId, CustomerId customerId, String type, String name) { Asset asset = new Asset(); asset.setTenantId(tenantId); @@ -116,6 +207,13 @@ public abstract class BaseEntitiesExportImportControllerTest extends AbstractCon return assetService.saveAsset(asset); } + protected void checkImportedAssetData(Asset initialAsset, Asset importedAsset) { + assertThat(importedAsset.getName()).isEqualTo(initialAsset.getName()); + assertThat(importedAsset.getType()).isEqualTo(initialAsset.getType()); + assertThat(importedAsset.getLabel()).isEqualTo(initialAsset.getLabel()); + assertThat(importedAsset.getAdditionalInfo()).isEqualTo(initialAsset.getAdditionalInfo()); + } + protected Customer createCustomer(TenantId tenantId, String name) { Customer customer = new Customer(); customer.setTenantId(tenantId); @@ -127,6 +225,13 @@ public abstract class BaseEntitiesExportImportControllerTest extends AbstractCon return customerService.saveCustomer(customer); } + protected void checkImportedCustomerData(Customer initialCustomer, Customer importedCustomer) { + assertThat(importedCustomer.getTitle()).isEqualTo(initialCustomer.getTitle()); + assertThat(importedCustomer.getCountry()).isEqualTo(initialCustomer.getCountry()); + assertThat(importedCustomer.getAddress()).isEqualTo(initialCustomer.getAddress()); + assertThat(importedCustomer.getEmail()).isEqualTo(initialCustomer.getEmail()); + } + protected Dashboard createDashboard(TenantId tenantId, CustomerId customerId, String name) { Dashboard dashboard = new Dashboard(); dashboard.setTenantId(tenantId); @@ -142,6 +247,16 @@ public abstract class BaseEntitiesExportImportControllerTest extends AbstractCon return dashboard; } + protected void checkImportedDashboardData(Dashboard initialDashboard, Dashboard importedDashboard) { + assertThat(importedDashboard.getTitle()).isEqualTo(initialDashboard.getTitle()); + assertThat(importedDashboard.getConfiguration()).isEqualTo(initialDashboard.getConfiguration()); + assertThat(importedDashboard.getImage()).isEqualTo(initialDashboard.getImage()); + assertThat(importedDashboard.isMobileHide()).isEqualTo(initialDashboard.isMobileHide()); + if (initialDashboard.getAssignedCustomers() != null) { + assertThat(importedDashboard.getAssignedCustomers()).containsAll(initialDashboard.getAssignedCustomers()); + } + } + protected RuleChain createRuleChain(TenantId tenantId, String name) { RuleChain ruleChain = new RuleChain(); ruleChain.setTenantId(tenantId); @@ -178,6 +293,50 @@ public abstract class BaseEntitiesExportImportControllerTest extends AbstractCon return ruleChainService.findRuleChainById(tenantId, ruleChain.getId()); } + protected void checkImportedRuleChainData(RuleChain initialRuleChain, RuleChainMetaData initialMetaData, RuleChain importedRuleChain, RuleChainMetaData importedMetaData) { + assertThat(importedRuleChain.getType()).isEqualTo(initialRuleChain.getType()); + assertThat(importedRuleChain.getName()).isEqualTo(initialRuleChain.getName()); + assertThat(importedRuleChain.isDebugMode()).isEqualTo(initialRuleChain.isDebugMode()); + assertThat(importedRuleChain.getConfiguration()).isEqualTo(initialRuleChain.getConfiguration()); + + assertThat(importedMetaData.getConnections()).isEqualTo(initialMetaData.getConnections()); + assertThat(importedMetaData.getFirstNodeIndex()).isEqualTo(initialMetaData.getFirstNodeIndex()); + for (int i = 0; i < initialMetaData.getNodes().size(); i++) { + RuleNode initialNode = initialMetaData.getNodes().get(i); + RuleNode importedNode = importedMetaData.getNodes().get(i); + assertThat(importedNode.getRuleChainId()).isEqualTo(importedRuleChain.getId()); + assertThat(importedNode.getName()).isEqualTo(initialNode.getName()); + assertThat(importedNode.getType()).isEqualTo(initialNode.getType()); + assertThat(importedNode.getConfiguration()).isEqualTo(initialNode.getConfiguration()); + assertThat(importedNode.getAdditionalInfo()).isEqualTo(initialNode.getAdditionalInfo()); + } + } + + protected EntityRelation createRelation(EntityId from, EntityId to) { + EntityRelation relation = new EntityRelation(); + relation.setFrom(from); + relation.setTo(to); + relation.setType(EntityRelation.MANAGES_TYPE); + relation.setAdditionalInfo(JacksonUtil.newObjectNode().set("a", new TextNode("b"))); + relation.setTypeGroup(RelationTypeGroup.COMMON); + relationService.saveRelation(TenantId.SYS_TENANT_ID, relation); + return relation; + } + + protected & HasTenantId> void checkImportedEntity(TenantId tenantId1, E initialEntity, TenantId tenantId2, E importedEntity) { + assertThat(initialEntity.getTenantId()).isEqualTo(tenantId1); + assertThat(importedEntity.getTenantId()).isEqualTo(tenantId2); + + assertThat(importedEntity.getExternalId()).isEqualTo(initialEntity.getId()); + + boolean sameTenant = tenantId1.equals(tenantId2); + if (!sameTenant) { + assertThat(importedEntity.getId()).isNotEqualTo(initialEntity.getId()); + } else { + assertThat(importedEntity.getId()).isEqualTo(initialEntity.getId()); + } + } + protected , I extends EntityId> EntityExportData exportSingleEntity(I entityId) throws Exception { SingleEntityExportRequest exportRequest = new SingleEntityExportRequest(); @@ -186,33 +345,30 @@ public abstract class BaseEntitiesExportImportControllerTest extends AbstractCon return (EntityExportData) exportEntities(exportRequest).get(0); } - protected List>> exportEntities(ExportRequest exportRequest) throws Exception { - return getResponse(doPost("/api/entities/export", exportRequest), new TypeReference>>>() {}); + protected List> exportEntities(ExportRequest exportRequest) throws Exception { + return getResponse(doPost("/api/entities/export", exportRequest), new TypeReference>>() {}); } - protected List>> exportEntities(List exportRequests) throws Exception { - return getResponse(doPost("/api/entities/export?multiple", exportRequests), new TypeReference>>>() {}); + protected List> exportEntities(List exportRequests) throws Exception { + return getResponse(doPost("/api/entities/export?multiple", exportRequests), new TypeReference>>() {}); } protected , I extends EntityId> EntityImportResult importEntity(EntityExportData exportData) throws Exception { - return (EntityImportResult) importEntities(List.of((EntityExportData>)exportData)).get(0); + return (EntityImportResult) importEntities(List.of((EntityExportData>) exportData)).get(0); } - protected List>> importEntities(List>> exportDataList) throws Exception { + protected List> importEntities(List> exportDataList) throws Exception { ImportRequest importRequest = new ImportRequest(); importRequest.setImportSettings(EntityImportSettings.builder() .updateReferencesToOtherEntities(true) .build()); importRequest.setExportDataList(exportDataList); - return getResponse(doPost("/api/entities/import", importRequest), new TypeReference>>>() { - @Override - public Type getType() { - return mapper.getTypeFactory().constructCollectionType(List.class, - mapper.getTypeFactory().constructParametricType(EntityImportResult.class, - exportDataList.get(0).getEntity().getClass())); - } - }); + return importEntities(importRequest); + } + + protected List> importEntities(ImportRequest importRequest) throws Exception { + return getResponse(doPost("/api/entities/import", importRequest), new TypeReference>>() {}); } protected T getResponse(ResultActions resultActions, TypeReference typeReference) throws Exception { @@ -223,4 +379,13 @@ public abstract class BaseEntitiesExportImportControllerTest extends AbstractCon } } + + protected void logInAsTenantAdmin1() throws Exception { + login(tenantAdmin1.getEmail(), "12345678"); + } + + protected void logInAsTenantAdmin2() throws Exception { + login(tenantAdmin2.getEmail(), "12345678"); + } + } diff --git a/application/src/test/java/org/thingsboard/server/controller/sql/EntitiesExportImportControllerSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/sql/EntitiesExportImportControllerSqlTest.java index ccbf311bef..920d91e93d 100644 --- a/application/src/test/java/org/thingsboard/server/controller/sql/EntitiesExportImportControllerSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/sql/EntitiesExportImportControllerSqlTest.java @@ -15,86 +15,81 @@ */ package org.thingsboard.server.controller.sql; -import org.junit.After; -import org.junit.Before; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; +import com.google.common.collect.Streams; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.testcontainers.shaded.org.apache.commons.lang.RandomStringUtils; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.cluster.TbClusterService; 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.DeviceProfile; +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.Tenant; -import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.data.query.EntityListFilter; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; -import org.thingsboard.server.common.data.rule.RuleNode; -import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.controller.BaseEntitiesExportImportControllerTest; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.dao.tenant.TenantService; -import org.thingsboard.server.service.sync.exporting.ExportableEntitiesService; +import org.thingsboard.server.service.action.EntityActionService; +import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.sync.exporting.data.DeviceExportData; import org.thingsboard.server.service.sync.exporting.data.EntityExportData; import org.thingsboard.server.service.sync.exporting.data.RuleChainExportData; +import org.thingsboard.server.service.sync.exporting.data.request.CustomEntityFilterExportRequest; +import org.thingsboard.server.service.sync.exporting.data.request.EntityExportSettings; +import org.thingsboard.server.service.sync.exporting.data.request.EntityListExportRequest; +import org.thingsboard.server.service.sync.exporting.data.request.EntityTypeExportRequest; +import org.thingsboard.server.service.sync.exporting.data.request.ExportRequest; +import org.thingsboard.server.service.sync.exporting.data.request.SingleEntityExportRequest; import org.thingsboard.server.service.sync.importing.data.EntityImportResult; +import org.thingsboard.server.service.sync.importing.data.EntityImportSettings; +import org.thingsboard.server.service.sync.importing.data.request.ImportRequest; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.verify; @DaoSqlTest public class EntitiesExportImportControllerSqlTest extends BaseEntitiesExportImportControllerTest { - @Autowired - private TenantService tenantService; @Autowired private DeviceCredentialsService deviceCredentialsService; - @Autowired - private ExportableEntitiesService exportableEntitiesService; - - private TenantId tenantId1; - private User tenantAdmin1; - - private TenantId tenantId2; - private User tenantAdmin2; - - @Before - public void beforeEach() throws Exception { - loginSysAdmin(); - Tenant tenant1 = new Tenant(); - tenant1.setTitle("Tenant 1"); - tenant1.setEmail("tenant1@thingsboard.org"); - this.tenantId1 = tenantService.saveTenant(tenant1).getId(); - User tenantAdmin1 = new User(); - tenantAdmin1.setTenantId(tenantId1); - tenantAdmin1.setAuthority(Authority.TENANT_ADMIN); - tenantAdmin1.setEmail("tenant1-admin@thingsboard.org"); - this.tenantAdmin1 = createUser(tenantAdmin1, "12345678"); - Tenant tenant2 = new Tenant(); - tenant2.setTitle("Tenant 2"); - tenant2.setEmail("tenant2@thingsboard.org"); - this.tenantId2 = tenantService.saveTenant(tenant2).getId(); - User tenantAdmin2 = new User(); - tenantAdmin2.setTenantId(tenantId2); - tenantAdmin2.setAuthority(Authority.TENANT_ADMIN); - tenantAdmin2.setEmail("tenant2-admin@thingsboard.org"); - this.tenantAdmin2 = createUser(tenantAdmin2, "12345678"); - } - - @After - public void afterEach() { - tenantService.deleteTenant(tenantId1); - tenantService.deleteTenant(tenantId2); - } - + @SpyBean + private EntityActionService entityActionService; + @SpyBean + private TbClusterService clusterService; + @SpyBean + private OtaPackageStateService otaPackageStateService; @Test - public void testExportImportSingleAsset_betweenTenants() throws Exception { + public void testExportImportAsset_betweenTenants() throws Exception { logInAsTenantAdmin1(); Asset asset = createAsset(tenantId1, null, "AB", "Asset of tenant 1"); EntityExportData exportData = exportSingleEntity(asset.getId()); @@ -103,40 +98,24 @@ public class EntitiesExportImportControllerSqlTest extends BaseEntitiesExportImp logInAsTenantAdmin2(); EntityImportResult importResult = importEntity(exportData); - checkImportedEntity(tenantId1, asset, tenantId2, importResult); + checkImportedEntity(tenantId1, asset, tenantId2, importResult.getSavedEntity()); checkImportedAssetData(asset, importResult.getSavedEntity()); } @Test - public void testExportImportSingleAsset_sameTenant() throws Exception { + public void testExportImportAsset_sameTenant() throws Exception { logInAsTenantAdmin1(); Asset asset = createAsset(tenantId1, null, "AB", "Asset v1.0"); EntityExportData exportData = exportSingleEntity(asset.getId()); EntityImportResult importResult = importEntity(exportData); - checkImportedEntity(tenantId1, asset, tenantId1, importResult); + checkImportedEntity(tenantId1, asset, tenantId1, importResult.getSavedEntity()); checkImportedAssetData(asset, importResult.getSavedEntity()); } @Test - public void testExportImportAsset_withCustomer_betweenTenants() throws Exception { - logInAsTenantAdmin1(); - Customer customer = createCustomer(tenantId1, "My customer"); - Asset asset = createAsset(tenantId1, customer.getId(), "AB", "My asset"); - - EntityExportData customerExportData = exportSingleEntity(customer.getId()); - EntityExportData assetExportData = exportSingleEntity(asset.getId()); - - logInAsTenantAdmin2(); - Customer importedCustomer = importEntity(customerExportData).getSavedEntity(); - Asset importedAsset = importEntity(assetExportData).getSavedEntity(); - - assertThat(importedAsset.getCustomerId()).isEqualTo(importedCustomer.getId()); - } - - @Test - public void testExportImportAsset_withCustomer_sameTenant() throws Exception { + public void testExportImportAsset_sameTenant_withCustomer() throws Exception { logInAsTenantAdmin1(); Customer customer = createCustomer(tenantId1, "My customer"); Asset asset = createAsset(tenantId1, customer.getId(), "AB", "My asset"); @@ -146,16 +125,9 @@ public class EntitiesExportImportControllerSqlTest extends BaseEntitiesExportImp assertThat(importedAsset.getCustomerId()).isEqualTo(asset.getCustomerId()); } - private void checkImportedAssetData(Asset initialAsset, Asset importedAsset) { - assertThat(importedAsset.getName()).isEqualTo(initialAsset.getName()); - assertThat(importedAsset.getType()).isEqualTo(initialAsset.getType()); - assertThat(importedAsset.getLabel()).isEqualTo(initialAsset.getLabel()); - assertThat(importedAsset.getAdditionalInfo()).isEqualTo(initialAsset.getAdditionalInfo()); - } - @Test - public void testExportImportSingleCustomer_betweenTenants() throws Exception { + public void testExportImportCustomer_betweenTenants() throws Exception { logInAsTenantAdmin1(); Customer customer = createCustomer(tenantAdmin1.getTenantId(), "Customer of tenant 1"); EntityExportData exportData = exportSingleEntity(customer.getId()); @@ -164,34 +136,27 @@ public class EntitiesExportImportControllerSqlTest extends BaseEntitiesExportImp logInAsTenantAdmin2(); EntityImportResult importResult = importEntity(exportData); - checkImportedEntity(tenantId1, customer, tenantId2, importResult); + checkImportedEntity(tenantId1, customer, tenantId2, importResult.getSavedEntity()); checkImportedCustomerData(customer, importResult.getSavedEntity()); } @Test - public void testExportImportSingleCustomer_sameTenant() throws Exception { + public void testExportImportCustomer_sameTenant() throws Exception { logInAsTenantAdmin1(); Customer customer = createCustomer(tenantAdmin1.getTenantId(), "Customer v1.0"); EntityExportData exportData = exportSingleEntity(customer.getId()); EntityImportResult importResult = importEntity(exportData); - checkImportedEntity(tenantId1, customer, tenantId1, importResult); + checkImportedEntity(tenantId1, customer, tenantId1, importResult.getSavedEntity()); checkImportedCustomerData(customer, importResult.getSavedEntity()); } - private void checkImportedCustomerData(Customer initialCustomer, Customer importedCustomer) { - assertThat(importedCustomer.getTitle()).isEqualTo(initialCustomer.getTitle()); - assertThat(importedCustomer.getCountry()).isEqualTo(initialCustomer.getCountry()); - assertThat(importedCustomer.getAddress()).isEqualTo(initialCustomer.getAddress()); - assertThat(importedCustomer.getEmail()).isEqualTo(initialCustomer.getEmail()); - } - @Test public void testExportImportDeviceWithProfile_betweenTenants() throws Exception { logInAsTenantAdmin1(); - DeviceProfile deviceProfile = createDeviceProfile(tenantId1, "Device profile of tenant 1"); + DeviceProfile deviceProfile = createDeviceProfile(tenantId1, null, null, "Device profile of tenant 1"); Device device = createDevice(tenantId1, null, deviceProfile.getId(), "Device of tenant 1"); DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId1, device.getId()); @@ -206,13 +171,13 @@ public class EntitiesExportImportControllerSqlTest extends BaseEntitiesExportImp logInAsTenantAdmin2(); EntityImportResult profileImportResult = importEntity(profileExportData); - checkImportedEntity(tenantId1, deviceProfile, tenantId2, profileImportResult); + checkImportedEntity(tenantId1, deviceProfile, tenantId2, profileImportResult.getSavedEntity()); checkImportedDeviceProfileData(deviceProfile, profileImportResult.getSavedEntity()); EntityImportResult deviceImportResult = importEntity(deviceExportData); Device importedDevice = deviceImportResult.getSavedEntity(); - checkImportedEntity(tenantId1, device, tenantId2, deviceImportResult); + checkImportedEntity(tenantId1, device, tenantId2, deviceImportResult.getSavedEntity()); checkImportedDeviceData(device, importedDevice); assertThat(importedDevice.getDeviceProfileId()).isEqualTo(profileImportResult.getSavedEntity().getId()); @@ -227,8 +192,14 @@ public class EntitiesExportImportControllerSqlTest extends BaseEntitiesExportImp @Test public void testExportImportDevice_sameTenant() throws Exception { logInAsTenantAdmin1(); - DeviceProfile deviceProfile = createDeviceProfile(tenantId1, "Device profile v1.0"); + DeviceProfile deviceProfile = createDeviceProfile(tenantId1, null, null, "Device profile v1.0"); + OtaPackage firmware = createOtaPackage(tenantId1, deviceProfile.getId(), OtaPackageType.FIRMWARE); + OtaPackage software = createOtaPackage(tenantId1, deviceProfile.getId(), OtaPackageType.SOFTWARE); Device device = createDevice(tenantId1, null, deviceProfile.getId(), "Device v1.0"); + device.setFirmwareId(firmware.getId()); + device.setSoftwareId(software.getId()); + device = deviceService.saveDevice(device); + DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId1, device.getId()); EntityExportData deviceExportData = exportSingleEntity(device.getId()); @@ -236,29 +207,119 @@ public class EntitiesExportImportControllerSqlTest extends BaseEntitiesExportImp EntityImportResult importResult = importEntity(deviceExportData); Device importedDevice = importResult.getSavedEntity(); - checkImportedEntity(tenantId1, device, tenantId1, importResult); + checkImportedEntity(tenantId1, device, tenantId1, importResult.getSavedEntity()); assertThat(importedDevice.getDeviceProfileId()).isEqualTo(device.getDeviceProfileId()); assertThat(deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId1, device.getId())).isEqualTo(credentials); - } - - private void checkImportedDeviceProfileData(DeviceProfile initialProfile, DeviceProfile importedProfile) { - assertThat(initialProfile.getName()).isEqualTo(importedProfile.getName()); - assertThat(initialProfile.getType()).isEqualTo(importedProfile.getType()); - assertThat(initialProfile.getTransportType()).isEqualTo(importedProfile.getTransportType()); - assertThat(initialProfile.getProfileData()).isEqualTo(importedProfile.getProfileData()); - assertThat(initialProfile.getDescription()).isEqualTo(importedProfile.getDescription()); - } - - private void checkImportedDeviceData(Device initialDevice, Device importedDevice) { - assertThat(importedDevice.getName()).isEqualTo(initialDevice.getName()); - assertThat(importedDevice.getType()).isEqualTo(initialDevice.getType()); - assertThat(importedDevice.getDeviceData()).isEqualTo(initialDevice.getDeviceData()); - assertThat(importedDevice.getLabel()).isEqualTo(initialDevice.getLabel()); + assertThat(importedDevice.getFirmwareId()).isEqualTo(firmware.getId()); + assertThat(importedDevice.getSoftwareId()).isEqualTo(software.getId()); } @Test - public void testExportImportSingleRuleChain_betweenTenants() throws Exception { + public void testExportImportDashboard_betweenTenants() throws Exception { + logInAsTenantAdmin1(); + Dashboard dashboard = createDashboard(tenantAdmin1.getTenantId(), null, "Dashboard of tenant 1"); + + EntityExportData exportData = exportSingleEntity(dashboard.getId()); + assertThat(exportData.getEntity()).isEqualTo(dashboard); + + logInAsTenantAdmin2(); + EntityImportResult importResult = importEntity(exportData); + checkImportedEntity(tenantId1, dashboard, tenantId2, importResult.getSavedEntity()); + checkImportedDashboardData(dashboard, importResult.getSavedEntity()); + } + + @Test + public void testExportImportDashboard_sameTenant() throws Exception { + logInAsTenantAdmin1(); + Dashboard dashboard = createDashboard(tenantAdmin1.getTenantId(), null, "Dashboard v1.0"); + + EntityExportData exportData = exportSingleEntity(dashboard.getId()); + + EntityImportResult importResult = importEntity(exportData); + checkImportedEntity(tenantId1, dashboard, tenantId1, importResult.getSavedEntity()); + checkImportedDashboardData(dashboard, importResult.getSavedEntity()); + } + + @Test + public void testExportImportDashboard_betweenTenants_withCustomer_updated() throws Exception { + logInAsTenantAdmin1(); + Dashboard dashboard = createDashboard(tenantAdmin1.getTenantId(), null, "Dashboard of tenant 1"); + + EntityExportData exportData = exportSingleEntity(dashboard.getId()); + + logInAsTenantAdmin2(); + Dashboard importedDashboard = (Dashboard) importEntities(List.of(exportData)).get(0).getSavedEntity(); + checkImportedEntity(tenantId1, dashboard, tenantId2, importedDashboard); + + logInAsTenantAdmin1(); + Customer customer = createCustomer(tenantId1, "Customer 1"); + EntityExportData customerExportData = exportSingleEntity(customer.getId()); + dashboardService.assignDashboardToCustomer(tenantId1, dashboard.getId(), customer.getId()); + exportData = exportSingleEntity(dashboard.getId()); + + logInAsTenantAdmin2(); + Customer importedCustomer = (Customer) importEntities(List.of(customerExportData)).get(0).getSavedEntity(); + importedDashboard = (Dashboard) importEntities(List.of(exportData)).get(0).getSavedEntity(); + assertThat(importedDashboard.getAssignedCustomers()).hasOnlyOneElementSatisfying(customerInfo -> { + assertThat(customerInfo.getCustomerId()).isEqualTo(importedCustomer.getId()); + }); + } + + @Test + public void testExportImportDashboard_betweenTenants_withEntityAliases() throws Exception { + logInAsTenantAdmin1(); + Asset asset1 = createAsset(tenantId1, null, "A", "Asset 1"); + Asset asset2 = createAsset(tenantId1, null, "A", "Asset 2"); + Dashboard dashboard = createDashboard(tenantId1, null, "Dashboard 1"); + + String entityAliases = "{\n" + + "\t\"23c4185d-1497-9457-30b2-6d91e69a5b2c\": {\n" + + "\t\t\"alias\": \"assets\",\n" + + "\t\t\"filter\": {\n" + + "\t\t\t\"entityList\": [\n" + + "\t\t\t\t\"" + asset1.getId().toString() + "\",\n" + + "\t\t\t\t\"" + asset2.getId().toString() + "\"\n" + + "\t\t\t],\n" + + "\t\t\t\"entityType\": \"ASSET\",\n" + + "\t\t\t\"resolveMultiple\": true,\n" + + "\t\t\t\"type\": \"entityList\"\n" + + "\t\t},\n" + + "\t\t\"id\": \"23c4185d-1497-9457-30b2-6d91e69a5b2c\"\n" + + "\t}\n" + + "}"; + ObjectNode dashboardConfiguration = JacksonUtil.newObjectNode(); + dashboardConfiguration.set("entityAliases", JacksonUtil.toJsonNode(entityAliases)); + dashboardConfiguration.set("description", new TextNode("hallo")); + dashboard.setConfiguration(dashboardConfiguration); + dashboard = dashboardService.saveDashboard(dashboard); + + EntityTypeExportRequest assetsExportRequest = new EntityTypeExportRequest(); + assetsExportRequest.setEntityType(EntityType.ASSET); + assetsExportRequest.setPageSize(10); + assetsExportRequest.setExportSettings(new EntityExportSettings()); + EntityTypeExportRequest dashboardsExportRequest = new EntityTypeExportRequest(); + dashboardsExportRequest.setEntityType(EntityType.DASHBOARD); + dashboardsExportRequest.setPageSize(10); + dashboardsExportRequest.setExportSettings(new EntityExportSettings()); + List> exportDataList = exportEntities(List.of(assetsExportRequest, dashboardsExportRequest)); + + logInAsTenantAdmin2(); + Map>> importResults = importEntities(exportDataList).stream().collect(Collectors.groupingBy(EntityImportResult::getEntityType)); + Asset importedAsset1 = (Asset) importResults.get(EntityType.ASSET).get(0).getSavedEntity(); + Asset importedAsset2 = (Asset) importResults.get(EntityType.ASSET).get(1).getSavedEntity(); + Dashboard importedDashboard = (Dashboard) importResults.get(EntityType.DASHBOARD).get(0).getSavedEntity(); + + Set entityAliasEntitiesIds = Streams.stream(importedDashboard.getConfiguration() + .get("entityAliases").elements().next().get("filter").get("entityList").elements()) + .map(JsonNode::asText).collect(Collectors.toSet()); + assertThat(entityAliasEntitiesIds).doesNotContain(asset1.getId().toString(), asset2.getId().toString()); + assertThat(entityAliasEntitiesIds).contains(importedAsset1.getId().toString(), importedAsset2.getId().toString()); + } + + + @Test + public void testExportImportRuleChain_betweenTenants() throws Exception { logInAsTenantAdmin1(); RuleChain ruleChain = createRuleChain(tenantId1, "Rule chain of tenant 1"); RuleChainMetaData metaData = ruleChainService.loadRuleChainMetaData(tenantId1, ruleChain.getId()); @@ -272,12 +333,12 @@ public class EntitiesExportImportControllerSqlTest extends BaseEntitiesExportImp RuleChain importedRuleChain = importResult.getSavedEntity(); RuleChainMetaData importedMetaData = ruleChainService.loadRuleChainMetaData(tenantId2, importedRuleChain.getId()); - checkImportedEntity(tenantId1, ruleChain, tenantId2, importResult); + checkImportedEntity(tenantId1, ruleChain, tenantId2, importResult.getSavedEntity()); checkImportedRuleChainData(ruleChain, metaData, importedRuleChain, importedMetaData); } @Test - public void testExportImportSingleRuleChain_sameTenant() throws Exception { + public void testExportImportRuleChain_sameTenant() throws Exception { logInAsTenantAdmin1(); RuleChain ruleChain = createRuleChain(tenantId1, "Rule chain v1.0"); RuleChainMetaData metaData = ruleChainService.loadRuleChainMetaData(tenantId1, ruleChain.getId()); @@ -288,91 +349,439 @@ public class EntitiesExportImportControllerSqlTest extends BaseEntitiesExportImp RuleChain importedRuleChain = importResult.getSavedEntity(); RuleChainMetaData importedMetaData = ruleChainService.loadRuleChainMetaData(tenantId1, importedRuleChain.getId()); - checkImportedEntity(tenantId1, ruleChain, tenantId1, importResult); + checkImportedEntity(tenantId1, ruleChain, tenantId1, importResult.getSavedEntity()); checkImportedRuleChainData(ruleChain, metaData, importedRuleChain, importedMetaData); } - private void checkImportedRuleChainData(RuleChain initialRuleChain, RuleChainMetaData initialMetaData, RuleChain importedRuleChain, RuleChainMetaData importedMetaData) { - assertThat(importedRuleChain.getType()).isEqualTo(initialRuleChain.getType()); - assertThat(importedRuleChain.getName()).isEqualTo(initialRuleChain.getName()); - assertThat(importedRuleChain.isDebugMode()).isEqualTo(initialRuleChain.isDebugMode()); - assertThat(importedRuleChain.getConfiguration()).isEqualTo(initialRuleChain.getConfiguration()); - - assertThat(importedMetaData.getConnections()).isEqualTo(initialMetaData.getConnections()); - assertThat(importedMetaData.getFirstNodeIndex()).isEqualTo(initialMetaData.getFirstNodeIndex()); - for (int i = 0; i < initialMetaData.getNodes().size(); i++) { - RuleNode initialNode = initialMetaData.getNodes().get(i); - RuleNode importedNode = importedMetaData.getNodes().get(i); - assertThat(importedNode.getRuleChainId()).isEqualTo(importedRuleChain.getId()); - assertThat(importedNode.getName()).isEqualTo(initialNode.getName()); - assertThat(importedNode.getType()).isEqualTo(initialNode.getType()); - assertThat(importedNode.getConfiguration()).isEqualTo(initialNode.getConfiguration()); - assertThat(importedNode.getAdditionalInfo()).isEqualTo(initialNode.getAdditionalInfo()); - } - } - @Test - public void testExportImportSingleDashboard_betweenTenants() throws Exception { + public void testExportImportBatch_betweenTenants() throws Exception { logInAsTenantAdmin1(); - Dashboard dashboard = createDashboard(tenantAdmin1.getTenantId(), null, "Dashboard of tenant 1"); - EntityExportData exportData = exportSingleEntity(dashboard.getId()); - assertThat(exportData.getEntity()).isEqualTo(dashboard); + Customer customer = createCustomer(tenantId1, "Customer 1"); + Asset asset = createAsset(tenantId1, customer.getId(), "A", "Customer 1 - Asset 1"); + RuleChain ruleChain = createRuleChain(tenantId1, "Rule chain 1"); + Dashboard dashboard = createDashboard(tenantId1, customer.getId(), "Customer 1 - Dashboard 1"); + DeviceProfile deviceProfile = createDeviceProfile(tenantId1, ruleChain.getId(), dashboard.getId(), "Device profile 1"); + Device device = createDevice(tenantId1, customer.getId(), deviceProfile.getId(), "Customer 1 - Device 1"); + + EntityListExportRequest exportRequest = new EntityListExportRequest(); + exportRequest.setExportSettings(new EntityExportSettings()); + exportRequest.setEntitiesIds(List.of(customer.getId(), asset.getId(), ruleChain.getId(), deviceProfile.getId(), dashboard.getId())); + List> exportDataList = exportEntities(exportRequest); + exportRequest.setEntitiesIds(List.of(device.getId())); + DeviceExportData deviceExportData = (DeviceExportData) exportEntities(exportRequest).get(0); + deviceExportData.getCredentials().setCredentialsId(RandomStringUtils.randomAlphanumeric(10)); + exportDataList.add(deviceExportData); logInAsTenantAdmin2(); - EntityImportResult importResult = importEntity(exportData); - checkImportedEntity(tenantId1, dashboard, tenantId2, importResult); - checkImportedDashboardData(dashboard, importResult.getSavedEntity()); + ImportRequest importRequest = new ImportRequest(); + importRequest.setImportSettings(EntityImportSettings.builder() + .updateReferencesToOtherEntities(true) + .build()); + importRequest.setExportDataList(exportDataList); + Map> importResults = importEntities(importRequest).stream() + .collect(Collectors.toMap(EntityImportResult::getEntityType, r -> r)); + + Customer importedCustomer = (Customer) importResults.get(EntityType.CUSTOMER).getSavedEntity(); + checkImportedEntity(tenantId1, customer, tenantId2, importedCustomer); + + Asset importedAsset = (Asset) importResults.get(EntityType.ASSET).getSavedEntity(); + checkImportedEntity(tenantId1, asset, tenantId2, importedAsset); + assertThat(importedAsset.getCustomerId()).isEqualTo(importedCustomer.getId()); + + RuleChain importedRuleChain = (RuleChain) importResults.get(EntityType.RULE_CHAIN).getSavedEntity(); + checkImportedEntity(tenantId1, ruleChain, tenantId2, importedRuleChain); + + Dashboard importedDashboard = (Dashboard) importResults.get(EntityType.DASHBOARD).getSavedEntity(); + checkImportedEntity(tenantId1, dashboard, tenantId2, importedDashboard); + assertThat(importedDashboard.getAssignedCustomers()).size().isOne(); + assertThat(importedDashboard.getAssignedCustomers()).hasOnlyOneElementSatisfying(customerInfo -> { + assertThat(customerInfo.getCustomerId()).isEqualTo(importedCustomer.getId()); + }); + + DeviceProfile importedDeviceProfile = (DeviceProfile) importResults.get(EntityType.DEVICE_PROFILE).getSavedEntity(); + checkImportedEntity(tenantId1, deviceProfile, tenantId2, importedDeviceProfile); + assertThat(importedDeviceProfile.getDefaultRuleChainId()).isEqualTo(importedRuleChain.getId()); + assertThat(importedDeviceProfile.getDefaultDashboardId()).isEqualTo(importedDashboard.getId()); + + Device importedDevice = (Device) importResults.get(EntityType.DEVICE).getSavedEntity(); + checkImportedEntity(tenantId1, device, tenantId2, importedDevice); + assertThat(importedDevice.getCustomerId()).isEqualTo(importedCustomer.getId()); + assertThat(importedDevice.getDeviceProfileId()).isEqualTo(importedDeviceProfile.getId()); + } + + + @Test + public void testExportImportWithInboundRelations_betweenTenants() throws Exception { + logInAsTenantAdmin1(); + + Asset asset = createAsset(tenantId1, null, "A", "Asset 1"); + Device device = createDevice(tenantId1, null, null, "Device 1"); + EntityRelation relation = createRelation(asset.getId(), device.getId()); + + EntityListExportRequest exportRequest = new EntityListExportRequest(); + exportRequest.setEntitiesIds(List.of(asset.getId(), device.getId())); + exportRequest.setExportSettings(EntityExportSettings.builder() + .exportInboundRelations(true) + .exportOutboundRelations(false) + .build()); + List> exportDataList = exportEntities(exportRequest); + + EntityExportData deviceExportData = exportDataList.stream().filter(exportData -> exportData.getEntityType() == EntityType.DEVICE).findFirst().orElse(null); + assertThat(deviceExportData.getInboundRelations()).size().isOne(); + assertThat(deviceExportData.getInboundRelations().get(0)).matches(entityRelation -> { + return entityRelation.getFrom().equals(asset.getId()) && entityRelation.getTo().equals(device.getId()); + }); + ((DeviceExportData) deviceExportData).getCredentials().setCredentialsId("ab"); + ((Device) deviceExportData.getEntity()).setDeviceProfileId(null); + + logInAsTenantAdmin2(); + + ImportRequest importRequest = new ImportRequest(); + importRequest.setExportDataList(exportDataList); + importRequest.setImportSettings(EntityImportSettings.builder() + .importInboundRelations(true) + .build()); + Map> importResults = importEntities(importRequest).stream().collect(Collectors.toMap(EntityImportResult::getEntityType, r -> r)); + + Device importedDevice = (Device) importResults.get(EntityType.DEVICE).getSavedEntity(); + Asset importedAsset = (Asset) importResults.get(EntityType.ASSET).getSavedEntity(); + checkImportedEntity(tenantId1, device, tenantId2, importedDevice); + checkImportedEntity(tenantId1, asset, tenantId2, importedAsset); + + List importedRelations = relationService.findByTo(TenantId.SYS_TENANT_ID, importedDevice.getId(), RelationTypeGroup.COMMON); + assertThat(importedRelations).size().isOne(); + assertThat(importedRelations.get(0)).satisfies(importedRelation -> { + assertThat(importedRelation.getFrom()).isEqualTo(importedAsset.getId()); + assertThat(importedRelation.getType()).isEqualTo(relation.getType()); + assertThat(importedRelation.getAdditionalInfo()).isEqualTo(relation.getAdditionalInfo()); + }); } @Test - public void testExportImportSingleDashboard_sameTenant() throws Exception { + public void testExportImportWithRelations_betweenTenants() throws Exception { logInAsTenantAdmin1(); - Dashboard dashboard = createDashboard(tenantAdmin1.getTenantId(), null, "Dashboard v1.0"); - EntityExportData exportData = exportSingleEntity(dashboard.getId()); + Asset asset = createAsset(tenantId1, null, "A", "Asset 1"); + Device device = createDevice(tenantId1, null, null, "Device 1"); + EntityRelation relation = createRelation(asset.getId(), device.getId()); - EntityImportResult importResult = importEntity(exportData); - checkImportedEntity(tenantId1, dashboard, tenantId1, importResult); - checkImportedDashboardData(dashboard, importResult.getSavedEntity()); + EntityListExportRequest exportRequest = new EntityListExportRequest(); + exportRequest.setEntitiesIds(List.of(asset.getId(), device.getId())); + exportRequest.setExportSettings(EntityExportSettings.builder() + .exportInboundRelations(true) + .exportOutboundRelations(true) + .build()); + List> exportDataList = exportEntities(exportRequest); + + assertThat(exportDataList).allMatch(exportData -> exportData.getInboundRelations().size() + exportData.getOutboundRelations().size() == 1); + + EntityExportData deviceExportData = exportDataList.stream().filter(exportData -> exportData.getEntityType() == EntityType.DEVICE).findFirst().orElse(null); + ((DeviceExportData) deviceExportData).getCredentials().setCredentialsId("ab"); + ((Device) deviceExportData.getEntity()).setDeviceProfileId(null); + + logInAsTenantAdmin2(); + + ImportRequest importRequest = new ImportRequest(); + importRequest.setExportDataList(exportDataList); + importRequest.setImportSettings(EntityImportSettings.builder() + .importInboundRelations(true) + .importOutboundRelations(true) + .build()); + Map> importResults = importEntities(importRequest).stream().collect(Collectors.toMap(EntityImportResult::getEntityType, r -> r)); + + Device importedDevice = (Device) importResults.get(EntityType.DEVICE).getSavedEntity(); + Asset importedAsset = (Asset) importResults.get(EntityType.ASSET).getSavedEntity(); + + List importedRelations = relationService.findByTo(TenantId.SYS_TENANT_ID, importedDevice.getId(), RelationTypeGroup.COMMON); + assertThat(importedRelations).size().isOne(); + assertThat(importedRelations.get(0)).satisfies(importedRelation -> { + assertThat(importedRelation.getFrom()).isEqualTo(importedAsset.getId()); + assertThat(importedRelation.getType()).isEqualTo(relation.getType()); + assertThat(importedRelation.getAdditionalInfo()).isEqualTo(relation.getAdditionalInfo()); + }); } - private void checkImportedDashboardData(Dashboard initialDashboard, Dashboard importedDashboard) { - assertThat(importedDashboard.getTitle()).isEqualTo(initialDashboard.getTitle()); - assertThat(importedDashboard.getConfiguration()).isEqualTo(initialDashboard.getConfiguration()); - assertThat(importedDashboard.getImage()).isEqualTo(initialDashboard.getImage()); - assertThat(importedDashboard.isMobileHide()).isEqualTo(initialDashboard.isMobileHide()); - if (initialDashboard.getAssignedCustomers() != null) { - assertThat(importedDashboard.getAssignedCustomers()).containsAll(initialDashboard.getAssignedCustomers()); + @Test + public void testExportImportWithRelations_sameTenant() throws Exception { + logInAsTenantAdmin1(); + + Asset asset = createAsset(tenantId1, null, "A", "Asset 1"); + Device device1 = createDevice(tenantId1, null, null, "Device 1"); + EntityRelation relation1 = createRelation(asset.getId(), device1.getId()); + + SingleEntityExportRequest exportRequest = new SingleEntityExportRequest(); + exportRequest.setEntityId(asset.getId()); + exportRequest.setExportSettings(EntityExportSettings.builder() + .exportOutboundRelations(true) + .build()); + EntityExportData assetExportData = (EntityExportData) exportEntities(exportRequest).get(0); + assertThat(assetExportData.getOutboundRelations()).size().isOne(); + + Device device2 = createDevice(tenantId1, null, null, "Device 2"); + EntityRelation relation2 = createRelation(asset.getId(), device2.getId()); + + ImportRequest importRequest = new ImportRequest(); + importRequest.setExportDataList(List.of(assetExportData)); + importRequest.setImportSettings(EntityImportSettings.builder() + .importOutboundRelations(true) + .build()); + + importEntities(importRequest); + + List relations = relationService.findByFrom(TenantId.SYS_TENANT_ID, asset.getId(), RelationTypeGroup.COMMON); + assertThat(relations).contains(relation1, relation2); + } + + @Test + public void textExportImportWithRelations_sameTenant_removeExisting() throws Exception { + logInAsTenantAdmin1(); + + Asset asset1 = createAsset(tenantId1, null, "A", "Asset 1"); + Device device = createDevice(tenantId1, null, null, "Device 1"); + EntityRelation relation1 = createRelation(asset1.getId(), device.getId()); + + SingleEntityExportRequest exportRequest = new SingleEntityExportRequest(); + exportRequest.setEntityId(device.getId()); + exportRequest.setExportSettings(EntityExportSettings.builder() + .exportInboundRelations(true) + .build()); + EntityExportData deviceExportData = exportEntities(exportRequest).get(0); + assertThat(deviceExportData.getInboundRelations()).size().isOne(); + + Asset asset2 = createAsset(tenantId1, null, "A", "Asset 2"); + EntityRelation relation2 = createRelation(asset2.getId(), device.getId()); + + ImportRequest importRequest = new ImportRequest(); + importRequest.setExportDataList(List.of(deviceExportData)); + importRequest.setImportSettings(EntityImportSettings.builder() + .importInboundRelations(true) + .removeExistingRelations(true) + .build()); + + importEntities(importRequest); + + List relations = relationService.findByTo(TenantId.SYS_TENANT_ID, device.getId(), RelationTypeGroup.COMMON); + assertThat(relations).contains(relation1); + assertThat(relations).doesNotContain(relation2); + } + + + @Test + public void testExportImportDeviceProfile_betweenTenants_findExistingByName() throws Exception { + logInAsTenantAdmin1(); + DeviceProfile defaultDeviceProfile = deviceProfileService.findDefaultDeviceProfile(tenantId1); + + EntityListExportRequest exportRequest = new EntityListExportRequest(); + exportRequest.setEntitiesIds(List.of(defaultDeviceProfile.getId())); + exportRequest.setExportSettings(new EntityExportSettings()); + List> exportDataList = exportEntities(exportRequest); + + logInAsTenantAdmin2(); + ImportRequest importRequest = new ImportRequest(); + importRequest.setExportDataList(exportDataList); + importRequest.setImportSettings(EntityImportSettings.builder() + .findExistingByName(false) + .updateReferencesToOtherEntities(true) + .build()); + assertThatThrownBy(() -> { + importEntities(importRequest); + }).hasMessageContaining("default device profile is present"); + + importRequest.getImportSettings().setFindExistingByName(true); + importEntities(importRequest); + checkImportedEntity(tenantId1, defaultDeviceProfile, tenantId2, deviceProfileService.findDefaultDeviceProfile(tenantId2)); + } + + @Test + public void testExportImportDashboard_betweenTenants_doNotUpdateReferencesToOtherEntities() throws Exception { + logInAsTenantAdmin1(); + Customer customer = createCustomer(tenantId1, "Customer 1"); + Dashboard dashboard = createDashboard(tenantId1, customer.getId(), "Dashboard 1"); + + EntityListExportRequest exportRequest = new EntityListExportRequest(); + exportRequest.setEntitiesIds(List.of(customer.getId(), dashboard.getId())); + exportRequest.setExportSettings(new EntityExportSettings()); + List> exportDataList = exportEntities(exportRequest); + + logInAsTenantAdmin2(); + Map> importResults = importEntities(exportDataList).stream().collect(Collectors.toMap(EntityImportResult::getEntityType, r -> r)); + + Customer importedCustomer = (Customer) importResults.get(EntityType.CUSTOMER).getSavedEntity(); + Dashboard importedDashboard = (Dashboard) importResults.get(EntityType.DASHBOARD).getSavedEntity(); + assertThat(importedDashboard.getAssignedCustomers()).hasOnlyOneElementSatisfying(customerInfo -> { + assertThat(customerInfo.getCustomerId()).isEqualTo(importedCustomer.getId()); + }); + assertThat(importedDashboard.getConfiguration()).isEqualTo(dashboard.getConfiguration()); + + logInAsTenantAdmin1(); + dashboard.setConfiguration(JacksonUtil.newObjectNode().set("aaa", new TextNode("bbb"))); + dashboard = dashboardService.saveDashboard(dashboard); + dashboard = dashboardService.unassignDashboardFromCustomer(tenantId1, dashboard.getId(), customer.getId()); + EntityExportData updatedDashboardExportData = exportSingleEntity(dashboard.getId()); + assertThat(updatedDashboardExportData.getEntity().getAssignedCustomers()).isNullOrEmpty(); + + logInAsTenantAdmin2(); + ImportRequest importRequest = new ImportRequest(); + importRequest.setExportDataList(List.of(updatedDashboardExportData)); + importRequest.setImportSettings(EntityImportSettings.builder() + .updateReferencesToOtherEntities(false) + .build()); + importedDashboard = (Dashboard) importEntities(importRequest).get(0).getSavedEntity(); + + assertThat(importedDashboard.getConfiguration()).isEqualTo(dashboard.getConfiguration()); + assertThat(importedDashboard.getAssignedCustomers()).hasOnlyOneElementSatisfying(customerInfo -> { + assertThat(customerInfo.getCustomerId()).isEqualTo(importedCustomer.getId()); + }); + } + + + @Test + public void testExportRequests() throws Exception { + logInAsTenantAdmin1(); + + Device device = createDevice(tenantId1, null, null, "Device 1"); + DeviceProfile deviceProfile = createDeviceProfile(tenantId1, null, null, "Device profile 1"); + RuleChain ruleChain = createRuleChain(tenantId1, "Rule chain 1"); + Asset asset = createAsset(tenantId1, null, "A", "Asset 1"); + Dashboard dashboard = createDashboard(tenantId1, null, "Dashboard 1"); + Customer customer = createCustomer(tenantId1, "Customer 1"); + + Map> entities = Map.of( + EntityType.DEVICE, device, EntityType.DEVICE_PROFILE, deviceProfile, + EntityType.RULE_CHAIN, ruleChain, EntityType.ASSET, asset, + EntityType.DASHBOARD, dashboard, EntityType.CUSTOMER, customer + ); + + for (ExportableEntity entity : entities.values()) { + testEntityTypeExportRequest(entity); + testCustomEntityFilterExportRequest(entity); } } + private void testEntityTypeExportRequest(ExportableEntity entity) throws Exception { + EntityTypeExportRequest exportRequest = new EntityTypeExportRequest(); + exportRequest.setExportSettings(new EntityExportSettings()); + exportRequest.setPageSize(10); + exportRequest.setEntityType(entity.getId().getEntityType()); - private & HasTenantId> void checkImportedEntity(TenantId tenantId1, E initialEntity, TenantId tenantId2, EntityImportResult importResult) { - E importedEntity = importResult.getSavedEntity(); + List> exportDataList = exportEntities(exportRequest); + assertThat(exportDataList).size().isNotZero(); + assertThat(exportDataList).anySatisfy(exportData -> { + assertThat(exportData.getEntity()).isEqualTo(entity); + }); + } - assertThat(initialEntity.getTenantId()).isEqualTo(tenantId1); - assertThat(importedEntity.getTenantId()).isEqualTo(tenantId2); + private void testCustomEntityFilterExportRequest(ExportableEntity entity) throws Exception { + CustomEntityFilterExportRequest exportRequest = new CustomEntityFilterExportRequest(); + exportRequest.setExportSettings(new EntityExportSettings()); + exportRequest.setPageSize(10); - assertThat(importedEntity.getExternalId()).isEqualTo(initialEntity.getId()); + EntityListFilter filter = new EntityListFilter(); + filter.setEntityType(entity.getId().getEntityType()); + filter.setEntityList(List.of(entity.getId().toString())); + exportRequest.setFilter(filter); - boolean sameTenant = tenantId1.equals(tenantId2); - if (!sameTenant) { - assertThat(importedEntity.getId()).isNotEqualTo(initialEntity.getId()); - } else { - assertThat(importedEntity.getId()).isEqualTo(initialEntity.getId()); - assertThat(importResult.getOldEntity()).isEqualTo(initialEntity); + List> exportDataList = exportEntities(exportRequest); + assertThat(exportDataList).hasOnlyOneElementSatisfying(exportData -> { + assertThat(exportData.getEntity()).isEqualTo(entity); + }); + } + + + @Test + public void testExportImportCustomerEntities_betweenTenants() throws Exception { + logInAsTenantAdmin1(); + Customer customer = createCustomer(tenantId1, "Customer 1"); + + Device tenantDevice = createDevice(tenantId1, null, null, "Tenant device 1"); + Device customerDevice = createDevice(tenantId1, customer.getId(), null, "Customer device 1"); + Asset tenantAsset = createAsset(tenantId1, null, "A", "Tenant asset 1"); + Asset customerAsset = createAsset(tenantId1, customer.getId(), "A", "Customer asset 1"); + + List exportRequests = new ArrayList<>(); + + for (EntityType entityType : Set.of(EntityType.DEVICE, EntityType.ASSET)) { + EntityTypeExportRequest exportRequest = new EntityTypeExportRequest(); + exportRequest.setExportSettings(new EntityExportSettings()); + exportRequest.setPageSize(10); + exportRequest.setEntityType(entityType); + exportRequest.setCustomerId(customer.getId()); + exportRequests.add(exportRequest); } + + List> exportDataList = exportEntities(exportRequests); + assertThat(exportDataList).size().isEqualTo(2); + assertThat(exportDataList).anySatisfy(exportData -> { + assertThat(exportData.getEntity()).isEqualTo(customerDevice); + }); + assertThat(exportDataList).anySatisfy(exportData -> { + assertThat(exportData.getEntity()).isEqualTo(customerAsset); + }); } - private void logInAsTenantAdmin1() throws Exception { - login(tenantAdmin1.getEmail(), "12345678"); - } + @Test + public void testEntityEventsOnImport() throws Exception { + logInAsTenantAdmin1(); - private void logInAsTenantAdmin2() throws Exception { - login(tenantAdmin2.getEmail(), "12345678"); + Customer customer = createCustomer(tenantId1, "Customer 1"); + Asset asset = createAsset(tenantId1, null, "A", "Asset 1"); + RuleChain ruleChain = createRuleChain(tenantId1, "Rule chain 1"); + Dashboard dashboard = createDashboard(tenantId1, null, "Dashboard 1"); + DeviceProfile deviceProfile = createDeviceProfile(tenantId1, ruleChain.getId(), dashboard.getId(), "Device profile 1"); + Device device = createDevice(tenantId1, null, deviceProfile.getId(), "Device 1"); + + EntityListExportRequest exportRequest = new EntityListExportRequest(); + exportRequest.setEntitiesIds(List.of(customer.getId(), asset.getId(), device.getId(), ruleChain.getId(), dashboard.getId(), deviceProfile.getId())); + exportRequest.setExportSettings(new EntityExportSettings()); + + Map entitiesExportData = exportEntities(exportRequest).stream() + .collect(Collectors.toMap(EntityExportData::getEntityType, r -> r)); + + logInAsTenantAdmin2(); + + Customer importedCustomer = (Customer) importEntity(entitiesExportData.get(EntityType.CUSTOMER)).getSavedEntity(); + verify(entityActionService).logEntityAction(any(), eq(importedCustomer.getId()), eq(importedCustomer), + any(), eq(ActionType.ADDED), isNull()); + importEntity(entitiesExportData.get(EntityType.CUSTOMER)); + verify(entityActionService).logEntityAction(any(), eq(importedCustomer.getId()), eq(importedCustomer), + any(), eq(ActionType.UPDATED), isNull()); + verify(clusterService).sendNotificationMsgToEdgeService(any(), any(), eq(importedCustomer.getId()), any(), any(), eq(EdgeEventActionType.UPDATED)); + + Asset importedAsset = (Asset) importEntity(entitiesExportData.get(EntityType.ASSET)).getSavedEntity(); + verify(entityActionService).logEntityAction(any(), eq(importedAsset.getId()), eq(importedAsset), + any(), eq(ActionType.ADDED), isNull()); + importEntity(entitiesExportData.get(EntityType.ASSET)); + verify(entityActionService).logEntityAction(any(), eq(importedAsset.getId()), eq(importedAsset), + any(), eq(ActionType.UPDATED), isNull()); + verify(clusterService).sendNotificationMsgToEdgeService(any(), any(), eq(importedAsset.getId()), any(), any(), eq(EdgeEventActionType.UPDATED)); + + RuleChain importedRuleChain = (RuleChain) importEntity(entitiesExportData.get(EntityType.RULE_CHAIN)).getSavedEntity(); + verify(entityActionService).logEntityAction(any(), eq(importedRuleChain.getId()), eq(importedRuleChain), + any(), eq(ActionType.ADDED), isNull()); + verify(clusterService).broadcastEntityStateChangeEvent(any(), eq(importedRuleChain.getId()), eq(ComponentLifecycleEvent.CREATED)); + + Dashboard importedDashboard = (Dashboard) importEntity(entitiesExportData.get(EntityType.DASHBOARD)).getSavedEntity(); + verify(entityActionService).logEntityAction(any(), eq(importedDashboard.getId()), eq(importedDashboard), + any(), eq(ActionType.ADDED), isNull()); + + DeviceProfile importedDeviceProfile = (DeviceProfile) importEntity(entitiesExportData.get(EntityType.DEVICE_PROFILE)).getSavedEntity(); + verify(entityActionService).logEntityAction(any(), eq(importedDeviceProfile.getId()), eq(importedDeviceProfile), + any(), eq(ActionType.ADDED), isNull()); + verify(clusterService).onDeviceProfileChange(eq(importedDeviceProfile), any()); + verify(clusterService).broadcastEntityStateChangeEvent(any(), eq(importedDeviceProfile.getId()), eq(ComponentLifecycleEvent.CREATED)); + verify(clusterService).sendNotificationMsgToEdgeService(any(), any(), eq(importedDeviceProfile.getId()), any(), any(), eq(EdgeEventActionType.ADDED)); + verify(otaPackageStateService).update(eq(importedDeviceProfile), eq(false), eq(false)); + + ((DeviceExportData)entitiesExportData.get(EntityType.DEVICE)).getCredentials().setCredentialsId("abc"); + Device importedDevice = (Device) importEntity(entitiesExportData.get(EntityType.DEVICE)).getSavedEntity(); + verify(entityActionService).logEntityAction(any(), eq(importedDevice.getId()), eq(importedDevice), + any(), eq(ActionType.ADDED), isNull()); + verify(clusterService).onDeviceUpdated(eq(importedDevice), isNull()); + importEntity(entitiesExportData.get(EntityType.DEVICE)); + verify(clusterService).onDeviceUpdated(eq(importedDevice), eq(importedDevice)); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ExportableEntity.java b/common/data/src/main/java/org/thingsboard/server/common/data/ExportableEntity.java index 50569b7a02..4da7de93ee 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ExportableEntity.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ExportableEntity.java @@ -26,4 +26,7 @@ public interface ExportableEntity extends HasId, HasName I getExternalId(); void setExternalId(I externalId); + long getCreatedTime(); + void setCreatedTime(long createdTime); + }