@ -15,9 +15,7 @@
* /
package org.thingsboard.server.controller ;
import io.swagger.annotations.ApiParam ;
import lombok.RequiredArgsConstructor ;
import org.apache.commons.collections.CollectionUtils ;
import org.springframework.security.access.prepost.PreAuthorize ;
import org.springframework.web.bind.annotation.PathVariable ;
import org.springframework.web.bind.annotation.PostMapping ;
@ -27,10 +25,11 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController ;
import org.thingsboard.server.common.data.EntityType ;
import org.thingsboard.server.common.data.ExportableEntity ;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode ;
import org.thingsboard.server.common.data.exception.ThingsboardException ;
import org.thingsboard.server.common.data.id.CustomerId ;
import org.thingsboard.server.common.data.id.EntityId ;
import org.thingsboard.server.common.data.id.EntityIdFactory ;
import org.thingsboard.server.common.data.id.TenantId ;
import org.thingsboard.server.common.data.query.EntityData ;
import org.thingsboard.server.common.data.query.EntityDataPageLink ;
import org.thingsboard.server.common.data.query.EntityDataQuery ;
@ -38,169 +37,263 @@ import org.thingsboard.server.common.data.query.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityFilter ;
import org.thingsboard.server.common.data.query.EntityKey ;
import org.thingsboard.server.common.data.query.EntityKeyType ;
import org.thingsboard.server.common.data.query.EntityTypeFilter ;
import org.thingsboard.server.common.data.relation.EntityRelation ;
import org.thingsboard.server.common.data.relation.RelationTypeGroup ;
import org.thingsboard.server.dao.entity.EntityService ;
import org.thingsboard.server.queue.util.TbCoreComponent ;
import org.thingsboard.server.service.exportimport.EntitiesExportImportService ;
import org.thingsboard.server.service.exportimport.ExportableEntitiesService ;
import org.thingsboard.server.service.exportimport.exporting.EntityExportSettings ;
import org.thingsboard.server.service.exportimport.exporting.data.EntityExportData ;
import org.thingsboard.server.service.exportimport.importing.EntityImportResult ;
import org.thingsboard.server.service.exportimport.importing.EntityImportSettings ;
import org.thingsboard.server.service.query.EntityQueryService ;
import org.thingsboard.server.service.security.model.SecurityUser ;
import org.thingsboard.server.service.security.permission.Operation ;
import org.thingsboard.server.service.security.permission.Resource ;
import java.util.ArrayList ;
import java.util.Arrays ;
import java.util.Collections ;
import java.util.LinkedList ;
import java.util.List ;
import java.util.Map ;
import java.util.Optional ;
import java.util.UUID ;
import java.util.function.Function ;
import java.util.stream.Collectors ;
import static org.thingsboard.server.dao.sql.query.EntityKeyMapping.CREATED_TIME ;
@RestController
@RequestMapping ( "/api/entities" )
@PreAuthorize ( "hasAuthority('TENANT_ADMIN')" )
@TbCoreComponent
@RequiredArgsConstructor
public class EntitiesExportImportController extends BaseController {
private final EntitiesExportImportService exportImportService ;
private final EntityQueryService entityQueryService ;
private final ExportableEntitiesService exportableEntitiesService ;
private final EntityService entityService ;
// TODO [viacheslav]: export and import of batches
// TODO [viacheslav]: api to export and import whole customer, whole tenant
@PostMapping ( "/export/{entityType}/{id}" )
public EntityExportData < ExportableEntity < EntityId > > exportSingleEntity ( @PathVariable EntityType entityType ,
@PathVariable UUID id ,
@RequestParam Map < String , String > exportSettingsParams ) throws ThingsboardException {
EntityId entityId = EntityIdFactory . getByTypeAndUuid ( entityType , id ) ;
try {
return exportEntity ( getTenantId ( ) , entityId , toExportSettings ( exportSettingsParams ) ) ;
} catch ( Exception e ) {
throw handleException ( e ) ;
}
}
@PostMapping ( "/export/{entityType}/{entityId}" )
@PreAuthorize ( "hasAuthority('TENANT_ADMIN')" )
public EntityExportData < ? > exportEntity ( @ApiParam ( allowableValues = "DEVICE, DEVICE_PROFILE, ASSET, RULE_CHAIN, DASHBOARD, CUSTOMER" )
@PathVariable EntityType entityType ,
@PathVariable ( "entityId" ) UUID entityUuid ,
@RequestParam ( defaultValue = "false" ) boolean exportInboundRelations ) throws ThingsboardException {
EntityId entityId = EntityIdFactory . getByTypeAndUuid ( entityType , entityUuid ) ;
checkEntityId ( entityId , Operation . READ ) ;
@PostMapping ( value = "/export/{entityType}" , params = "ids" )
public List < EntityExportData < ExportableEntity < EntityId > > > exportEntitiesByIds ( @PathVariable EntityType entityType ,
@RequestParam UUID [ ] ids ,
@RequestParam Map < String , String > exportSettingsParams ) throws ThingsboardException {
List < EntityId > entitiesIds = Arrays . stream ( ids )
. map ( id - > EntityIdFactory . getByTypeAndUuid ( entityType , id ) )
. collect ( Collectors . toList ( ) ) ;
try {
return exportEntitiesByIds ( getTenantId ( ) , entitiesIds , toExportSettings ( exportSettingsParams ) ) ;
} catch ( Exception e ) {
throw handleException ( e ) ;
}
}
SecurityUser user = getCurrentUser ( ) ;
EntityExportSettings exportSettings = toExportSettings ( exportInboundRelations ) ;
@PostMapping ( value = "/export/{entityType}" , params = "ids" )
public List < EntityExportData < ExportableEntity < EntityId > > > exportAllEntitiesByEntityType ( @PathVariable EntityType entityType ,
@RequestParam Map < String , String > exportSettingsParams ,
@RequestParam ( defaultValue = "0" ) int page ,
@RequestParam ( defaultValue = "2147483647" ) int pageSize ,
@RequestParam ( name = "customerId" , required = false ) UUID customerUuid ) throws ThingsboardException {
TenantId tenantId = getTenantId ( ) ;
CustomerId customerId = toCustomerId ( customerUuid ) ;
try { // FIXME [viacheslav]: check read permission for relation fromId
return exportImportService . exportEntity ( user . getTenantId ( ) , entityId , exportSettings ) ;
EntityTypeFilter entityTypeFilter = new EntityTypeFilter ( ) ;
entityTypeFilter . setEntityType ( entityType ) ;
try {
return exportEntitiesByFilter ( tenantId , customerId , entityTypeFilter , page , pageSize , toExportSettings ( exportSettingsParams ) ) ;
} catch ( Exception e ) {
throw handleException ( e ) ;
}
}
@PostMapping ( "/exportByFilter" )
@PreAuthorize ( "hasAuthority('TENANT_ADMIN')" )
public List < EntityExportData < ? > > exportEntitiesByFilter ( @RequestBody EntityFilter filter , // TODO [viacheslav]: exportInboundRelations, exportOutboundRelations
@RequestParam ( defaultValue = "false" ) boolean exportInboundRelations ,
@RequestParam ( defaultValue = "0" ) int page ,
@RequestParam ( defaultValue = "100" ) int pageSize ) throws ThingsboardException {
EntityDataPageLink pageLink = new EntityDataPageLink ( ) ;
pageLink . setPage ( page ) ;
pageLink . setPageSize ( pageSize ) ;
pageLink . setSortOrder ( new EntityDataSortOrder ( new EntityKey ( EntityKeyType . ENTITY_FIELD , CREATED_TIME ) , EntityDataSortOrder . Direction . DESC ) ) ;
EntityDataQuery entityDataQuery = new EntityDataQuery ( filter , pageLink , List . of ( new EntityKey ( EntityKeyType . ENTITY_FIELD , CREATED_TIME ) ) ,
Collections . emptyList ( ) , Collections . emptyList ( ) ) ;
SecurityUser user = getCurrentUser ( ) ;
EntityExportSettings exportSettings = toExportSettings ( exportInboundRelations ) ;
public List < EntityExportData < ExportableEntity < EntityId > > > exportEntitiesByFilter ( @RequestBody EntityFilter filter ,
@RequestParam Map < String , String > exportSettingsParams ,
@RequestParam ( defaultValue = "0" ) int page ,
@RequestParam ( defaultValue = "2147483647" ) int pageSize ,
@RequestParam ( name = "customerId" , required = false ) UUID customerUuid ) throws ThingsboardException {
TenantId tenantId = getTenantId ( ) ;
CustomerId customerId = toCustomerId ( customerUuid ) ;
try {
return exportEntitiesByFilter ( tenantId , customerId , filter , page , pageSize , toExportSettings ( exportSettingsParams ) ) ;
} catch ( Exception e ) {
throw handleException ( e ) ;
}
}
// FIXME: too aggressive
@PostMapping ( "/exportByFilters" )
public List < EntityExportData < ExportableEntity < EntityId > > > exportAllEntitiesByFilters ( @RequestBody List < EntityFilter > filters ,
@RequestParam Map < String , String > exportSettingsParams ,
@RequestParam ( name = "customerId" , required = false ) UUID customerUuid ) throws ThingsboardException {
TenantId tenantId = getTenantId ( ) ;
CustomerId customerId = toCustomerId ( customerUuid ) ;
try {
// FIXME [viacheslav]: check read permission for relation fromId
return entityQueryService . findEntityDataByQuery ( user , entityDataQuery ) . getData ( ) . stream ( )
. map ( EntityData : : getEntityId )
. map ( entityId - > {
return exportImportService . exportEntity ( user . getTenantId ( ) , entityId , exportSettings ) ;
} )
. collect ( Collectors . toList ( ) ) ;
List < EntityExportData < ExportableEntity < EntityId > > > exportDataList = new ArrayList < > ( ) ;
for ( EntityFilter filter : filters ) {
exportDataList . addAll ( exportEntitiesByFilter ( tenantId , customerId , filter , 0 , Integer . MAX_VALUE , toExportSettings ( exportSettingsParams ) ) ) ;
}
return exportDataList ;
} catch ( Exception e ) {
throw handleException ( e ) ;
}
}
@PostMapping ( "/exportByQuery" )
@PreAuthorize ( "hasAuthority('TENANT_ADMIN')" )
public List < EntityExportData < ? > > exportEntitiesByQuery ( @RequestBody EntityDataQuery query ,
@RequestParam ( defaultValue = "false" ) boolean exportInboundRelations ) throws ThingsboardException {
SecurityUser user = getCurrentUser ( ) ;
EntityExportSettings exportSettings = toExportSettings ( exportInboundRelations ) ;
// FIXME [viacheslav]: check read permission for relation fromId
public List < EntityExportData < ExportableEntity < EntityId > > > exportEntitiesByQuery ( @RequestBody EntityDataQuery entitiesQuery ,
@RequestParam Map < String , String > exportSettingsParams ,
@RequestParam ( name = "customerId" , required = false ) UUID customerUuid ) throws ThingsboardException {
TenantId tenantId = getTenantId ( ) ;
CustomerId customerId = toCustomerId ( customerUuid ) ;
try {
return entityQueryService . findEntityDataByQuery ( user , query ) . getData ( ) . stream ( )
. map ( EntityData : : getEntityId )
. map ( entityId - > exportImportService . exportEntity ( user . getTenantId ( ) , entityId , exportSettings ) )
. collect ( Collectors . toList ( ) ) ;
return exportEntitiesByQuery ( tenantId , customerId , entitiesQuery , toExportSettings ( exportSettingsParams ) ) ;
} catch ( Exception e ) {
throw handleException ( e ) ;
}
}
@PostMapping ( "/import" )
@PreAuthorize ( "hasAuthority('TENANT_ADMIN')" )
public List < EntityImportResult < ExportableEntity < EntityId > > > importEntity ( @RequestBody List < EntityExportData < ExportableEntity < EntityId > > > exportDataList ,
@RequestParam ( defaultValue = "false" ) boolean importInboundRelations ) throws ThingsboardException {
SecurityUser user = getCurrentUser ( ) ;
EntityImportSettings importSettings = toImportSettings ( importInboundRelations ) ;
private List < EntityExportData < ExportableEntity < EntityId > > > exportEntitiesByFilter ( TenantId tenantId , CustomerId customerId , EntityFilter filter , int page , int pageSize , EntityExportSettings exportSettings ) throws ThingsboardException {
EntityDataPageLink pageLink = new EntityDataPageLink ( ) ;
pageLink . setPage ( page ) ;
pageLink . setPageSize ( pageSize ) ;
EntityKey sortProperty = new EntityKey ( EntityKeyType . ENTITY_FIELD , CREATED_TIME ) ;
pageLink . setSortOrder ( new EntityDataSortOrder ( sortProperty , EntityDataSortOrder . Direction . DESC ) ) ;
for ( EntityExportData < ExportableEntity < EntityId > > exportData : exportDataList ) {
checkPermissionsForImport ( user , exportData , im portSettings ) ;
}
EntityDataQuery query = new EntityDataQuery ( filter , pageLink , List . of ( sortProperty ) , Collections . emptyList ( ) , Collections . emptyList ( ) ) ;
return exportEntitiesByQuery ( tenantId , customerId , query , ex portSettings) ;
}
try {
List < EntityImportResult < ExportableEntity < EntityId > > > importResultList = exportImportService . importEntities ( user . getTenantId ( ) , exportDataList , importSettings ) ;
importResultList . forEach ( entityImportResult - > {
onEntityUpdatedOrCreated ( user , entityImportResult . getSavedEntity ( ) , entityImportResult . getOldEntity ( ) , entityImportResult . getOldEntity ( ) = = null ) ;
} ) ;
return importResultList ;
} catch ( Exception e ) {
throw handleException ( e ) ;
private List < EntityExportData < ExportableEntity < EntityId > > > exportEntitiesByQuery ( TenantId tenantId , CustomerId customerId , EntityDataQuery query , EntityExportSettings exportSettings ) throws ThingsboardException {
List < EntityId > entitiesIds = entityService . findEntityDataByQuery ( tenantId , customerId , query ) . getData ( ) . stream ( )
. map ( EntityData : : getEntityId )
. collect ( Collectors . toList ( ) ) ;
return exportEntitiesByIds ( tenantId , entitiesIds , exportSettings ) ;
}
private List < EntityExportData < ExportableEntity < EntityId > > > exportEntitiesByIds ( TenantId tenantId , List < EntityId > entitiesIds , EntityExportSettings exportSettings ) throws ThingsboardException {
List < EntityExportData < ExportableEntity < EntityId > > > exportDataList = new ArrayList < > ( ) ;
for ( EntityId entityId : entitiesIds ) {
exportDataList . add ( exportEntity ( tenantId , entityId , exportSettings ) ) ;
}
return exportDataList ;
}
public void checkPermissionsForExport ( SecurityUser user , EntityId entityId , EntityExportSettings exportSettings ) throws ThingsboardException {
private < E extends ExportableEntity < I > , I extends EntityId > EntityExportData < E > exportEntity ( TenantId tenantId , I entityId , EntityExportSettings exportSettings ) throws ThingsboardException {
checkEntityId ( entityId , Operation . READ ) ;
List < EntityRelation > relations = new LinkedList < > ( ) ;
if ( exportSettings . isExportInboundRelations ( ) ) {
for ( EntityRelation entityRelation : relationService . findByTo ( user . getTenantId ( ) , entityId , RelationTypeGroup . COMMON ) ) {
EntityId fromId = entityRelation . getFrom ( ) ;
checkEntityId ( fromId , Operation . READ ) ;
relations . addAll ( relationService . findByTo ( tenantId , entityId , RelationTypeGroup . COMMON ) ) ;
}
if ( exportSettings . isExportOutboundRelations ( ) ) {
relations . addAll ( relationService . findByFrom ( tenantId , entityId , RelationTypeGroup . COMMON ) ) ;
}
for ( EntityRelation relation : relations ) {
if ( ! relation . getFrom ( ) . equals ( entityId ) ) {
checkEntityId ( relation . getFrom ( ) , Operation . READ ) ;
} else if ( ! relation . getTo ( ) . equals ( entityId ) ) {
checkEntityId ( relation . getTo ( ) , Operation . READ ) ;
}
}
return exportImportService . exportEntity ( tenantId , entityId , exportSettings ) ;
}
public void checkPermissionsForImport ( SecurityUser user , EntityExportData < ExportableEntity < EntityId > > exportData , EntityImportSettings importSettings ) throws ThingsboardException {
ExportableEntity < EntityId > existingEntity = exportImportService . findEntityByExternalId ( user . getTenantId ( ) , exportData . getMainEntity ( ) . getId ( ) ) ;
if ( existingEntity ! = null ) {
checkEntityId ( existingEntity . getId ( ) , Operation . WRITE ) ;
} else {
accessControlService . checkPermission ( user , Resource . of ( exportData . getEntityType ( ) ) , Operation . CREATE ) ;
@PostMapping ( "/import" )
public List < EntityImportResult < ExportableEntity < EntityId > > > importEntity ( @RequestBody List < EntityExportData < ExportableEntity < EntityId > > > exportDataList ,
@RequestParam Map < String , String > importSettingsParams ) throws ThingsboardException {
SecurityUser user = getCurrentUser ( ) ;
EntityImportSettings importSettings = toImportSettings ( importSettingsParams ) ;
try {
return importEntities ( user , exportDataList , importSettings )
. stream ( ) . peek ( entityImportResult - > {
onEntityUpdatedOrCreated ( user , entityImportResult . getSavedEntity ( ) , entityImportResult . getOldEntity ( ) , entityImportResult . getOldEntity ( ) = = null ) ;
} )
. collect ( Collectors . toList ( ) ) ;
} catch ( Exception e ) {
throw handleException ( e ) ;
}
}
if ( importSettings . isImportInboundRelations ( ) & & CollectionUtils . isNotEmpty ( exportData . getInboundRelations ( ) ) ) {
for ( EntityRelation entityRelation : exportData . getInboundRelations ( ) ) {
ExportableEntity < EntityId > entityFrom = exportImportService . findEntityByExternalId ( user . getTenantId ( ) , entityRelation . getFrom ( ) ) ;
if ( entityFrom ! = null ) {
accessControlService . checkPermission ( user , Resource . of ( entityFrom . getId ( ) . getEntityType ( ) ) , Operation . WRITE , entityFrom . getId ( ) , entityFrom ) ;
} else {
throw new ThingsboardException ( "Relation with non-existing entity" , ThingsboardErrorCode . BAD_REQUEST_PARAMS ) ;
public List < EntityImportResult < ExportableEntity < EntityId > > > importEntities ( SecurityUser user , List < EntityExportData < ExportableEntity < EntityId > > > exportDataList , EntityImportSettings importSettings ) throws ThingsboardException {
for ( EntityExportData < ExportableEntity < EntityId > > exportData : exportDataList ) {
ExportableEntity < EntityId > existingEntity = exportableEntitiesService . findEntityByExternalId ( user . getTenantId ( ) , exportData . getMainEntity ( ) . getId ( ) ) ;
if ( existingEntity ! = null ) {
checkEntityId ( existingEntity . getId ( ) , Operation . WRITE ) ;
} else {
accessControlService . checkPermission ( user , Resource . of ( exportData . getEntityType ( ) ) , Operation . CREATE ) ;
}
List < EntityRelation > relations = new LinkedList < > ( ) ;
if ( importSettings . isImportInboundRelations ( ) & & exportData . getInboundRelations ( ) ! = null ) {
relations . addAll ( exportData . getInboundRelations ( ) ) ;
}
if ( importSettings . isImportOutboundRelations ( ) & & exportData . getOutboundRelations ( ) ! = null ) {
relations . addAll ( exportData . getOutboundRelations ( ) ) ;
}
for ( EntityRelation relation : relations ) {
EntityId otherEntityId = null ;
if ( ! relation . getFrom ( ) . equals ( exportData . getMainEntity ( ) . getId ( ) ) ) {
otherEntityId = relation . getFrom ( ) ;
} else if ( ! relation . getTo ( ) . equals ( exportData . getMainEntity ( ) . getId ( ) ) ) {
otherEntityId = relation . getTo ( ) ;
}
if ( otherEntityId ! = null ) {
ExportableEntity < EntityId > otherEntity = exportableEntitiesService . findEntityByExternalId ( user . getTenantId ( ) , otherEntityId ) ;
if ( otherEntity ! = null ) {
checkEntityId ( otherEntity . getId ( ) , Operation . WRITE ) ;
} else {
throw new IllegalArgumentException ( "Relation is referencing non-existing entity" ) ;
}
}
}
}
return exportImportService . importEntities ( user . getTenantId ( ) , exportDataList , importSettings ) ;
}
private EntityImportSettings toImportSettings ( boolean importInboundRelations ) {
return EntityImportSettings . builder ( )
. importInboundRelations ( importInboundRelations )
private EntityExportSettings toExportSettings ( Map < String , String > exportSettingsParams ) {
return EntityExportSettings . builder ( )
. exportInboundRelations ( getParam ( exportSettingsParams , "exportInboundRelations" , false , Boolean : : parseBoolean ) )
. exportOutboundRelations ( getParam ( exportSettingsParams , "exportOutboundRelations" , false , Boolean : : parseBoolean ) )
. build ( ) ;
}
private EntityExportSettings toExportSettings ( boolean exportInboundRelations ) {
return EntityExportSettings . builder ( )
. exportInboundRelations ( exportInboundRelations )
private EntityImportSettings toImportSettings ( Map < String , String > importSettingsParams ) {
return EntityImportSettings . builder ( )
. importInboundRelations ( getParam ( importSettingsParams , "importInboundRelations" , false , Boolean : : parseBoolean ) )
. importOutboundRelations ( getParam ( importSettingsParams , "importOutboundRelations" , false , Boolean : : parseBoolean ) )
. removeExistingRelations ( getParam ( importSettingsParams , "removeExistingRelations" , true , Boolean : : parseBoolean ) )
. updateReferencesToOtherEntities ( getParam ( importSettingsParams , "updateReferencesToOtherEntities" , true , Boolean : : parseBoolean ) )
. build ( ) ;
}
protected < T > T getParam ( Map < String , String > requestParams , String key , T defaultValue , Function < String , T > parsingFunction ) {
return parsingFunction . apply ( requestParams . getOrDefault ( key , defaultValue . toString ( ) ) ) ;
}
private CustomerId toCustomerId ( UUID customerUuid ) {
return new CustomerId ( Optional . ofNullable ( customerUuid ) . orElse ( EntityId . NULL_UUID ) ) ;
}
}