committed by
GitHub
125 changed files with 5244 additions and 115 deletions
@ -0,0 +1,28 @@ |
|||
-- |
|||
-- 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. |
|||
-- |
|||
|
|||
ALTER TABLE device |
|||
ADD COLUMN IF NOT EXISTS external_id UUID; |
|||
ALTER TABLE device_profile |
|||
ADD COLUMN IF NOT EXISTS external_id UUID; |
|||
ALTER TABLE asset |
|||
ADD COLUMN IF NOT EXISTS external_id UUID; |
|||
ALTER TABLE rule_chain |
|||
ADD COLUMN IF NOT EXISTS external_id UUID; |
|||
ALTER TABLE dashboard |
|||
ADD COLUMN IF NOT EXISTS external_id UUID; |
|||
ALTER TABLE customer |
|||
ADD COLUMN IF NOT EXISTS external_id UUID; |
|||
@ -0,0 +1,336 @@ |
|||
/** |
|||
* 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.controller; |
|||
|
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import io.swagger.annotations.ApiOperation; |
|||
import lombok.Data; |
|||
import lombok.RequiredArgsConstructor; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.security.access.prepost.PreAuthorize; |
|||
import org.springframework.web.bind.annotation.GetMapping; |
|||
import org.springframework.web.bind.annotation.PathVariable; |
|||
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.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.EntityIdFactory; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; |
|||
import org.thingsboard.server.service.sync.vc.data.EntitiesVersionControlSettings; |
|||
import org.thingsboard.server.service.sync.vc.data.EntityVersion; |
|||
import org.thingsboard.server.service.sync.vc.data.VersionCreationResult; |
|||
import org.thingsboard.server.service.sync.vc.data.VersionLoadResult; |
|||
import org.thingsboard.server.service.sync.vc.data.VersionedEntityInfo; |
|||
import org.thingsboard.server.service.sync.vc.data.request.create.VersionCreateRequest; |
|||
import org.thingsboard.server.service.sync.vc.data.request.load.VersionLoadRequest; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE; |
|||
|
|||
@RestController |
|||
@RequestMapping("/api/entities/vc") |
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@RequiredArgsConstructor |
|||
public class EntitiesVersionControlController extends BaseController { |
|||
|
|||
private final EntitiesVersionControlService versionControlService; |
|||
|
|||
|
|||
@ApiOperation(value = "", notes = "" + |
|||
"SINGLE_ENTITY:" + NEW_LINE + |
|||
"```\n{\n" + |
|||
" \"type\": \"SINGLE_ENTITY\",\n" + |
|||
"\n" + |
|||
" \"versionName\": \"Version 1.0\",\n" + |
|||
" \"branch\": \"dev\",\n" + |
|||
"\n" + |
|||
" \"entityId\": {\n" + |
|||
" \"entityType\": \"DEVICE\",\n" + |
|||
" \"id\": \"b79448e0-d4f4-11ec-847b-0f432358ab48\"\n" + |
|||
" },\n" + |
|||
" \"config\": {\n" + |
|||
" \"saveRelations\": true\n" + |
|||
" }\n" + |
|||
"}\n```" + NEW_LINE + |
|||
"ENTITY_LIST:" + NEW_LINE + |
|||
"```\n{\n" + |
|||
" \"type\": \"ENTITY_LIST\",\n" + |
|||
"\n" + |
|||
" \"versionName\": \"Version 1.0\",\n" + |
|||
" \"branch\": \"dev\",\n" + |
|||
"\n" + |
|||
" \"entitiesIds\": [\n" + |
|||
" {\n" + |
|||
" \"entityType\": \"DEVICE\",\n" + |
|||
" \"id\": \"b79448e0-d4f4-11ec-847b-0f432358ab48\"\n" + |
|||
" },\n" + |
|||
" {\n" + |
|||
" \"entityType\": \"DEVICE_PROFILE\",\n" + |
|||
" \"id\": \"b7944123-d4f4-11ec-847b-0f432358ab48\"\n" + |
|||
" }\n" + |
|||
" ],\n" + |
|||
" \"config\": {\n" + |
|||
" \"saveRelations\": true,\n" + |
|||
" \"syncStrategy\": \"MERGE\"\n" + |
|||
" }\n" + |
|||
"}\n```" + NEW_LINE + |
|||
"ENTITY_TYPE:" + NEW_LINE + |
|||
"```\n{\n" + |
|||
" \"type\": \"ENTITY_TYPE\",\n" + |
|||
"\n" + |
|||
" \"versionName\": \"Version 1.0\",\n" + |
|||
" \"branch\": \"dev\",\n" + |
|||
"\n" + |
|||
" \"entityTypes\": {\n" + |
|||
" \"DEVICE\": {\n" + |
|||
" \"saveRelations\": true,\n" + |
|||
" \"syncStrategy\": \"MERGE\"\n" + |
|||
" },\n" + |
|||
" \"DEVICE_PROFILE\": {\n" + |
|||
" \"saveRelations\": true,\n" + |
|||
" \"syncStrategy\": \"OVERWRITE\"\n" + |
|||
" }\n" + |
|||
" }\n" + |
|||
"}\n```") |
|||
@PostMapping("/version") |
|||
public VersionCreationResult saveEntitiesVersion(@RequestBody VersionCreateRequest request) throws ThingsboardException { |
|||
SecurityUser user = getCurrentUser(); |
|||
try { |
|||
return versionControlService.saveEntitiesVersion(user, request); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
|
|||
@ApiOperation(value = "", notes = "" + |
|||
"```\n[\n" + |
|||
" {\n" + |
|||
" \"id\": \"c30c8bcaed3f0813649f0dee51a89d04d0a12b28\",\n" + |
|||
" \"name\": \"Device profile 1 version 1.0\"\n" + |
|||
" }\n" + |
|||
"]\n```") |
|||
@GetMapping("/version/{branch}/{entityType}/{externalEntityUuid}") |
|||
public List<EntityVersion> listEntityVersions(@PathVariable String branch, |
|||
@PathVariable EntityType entityType, |
|||
@PathVariable UUID externalEntityUuid) throws ThingsboardException { |
|||
try { |
|||
EntityId externalEntityId = EntityIdFactory.getByTypeAndUuid(entityType, externalEntityUuid); |
|||
return versionControlService.listEntityVersions(getTenantId(), branch, externalEntityId); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@ApiOperation(value = "", notes = "" + |
|||
"```\n[\n" + |
|||
" {\n" + |
|||
" \"id\": \"c30c8bcaed3f0813649f0dee51a89d04d0a12b28\",\n" + |
|||
" \"name\": \"Device profiles from dev\"\n" + |
|||
" }\n" + |
|||
"]\n```") |
|||
@GetMapping("/version/{branch}/{entityType}") |
|||
public List<EntityVersion> listEntityTypeVersions(@PathVariable String branch, |
|||
@PathVariable EntityType entityType) throws ThingsboardException { |
|||
try { |
|||
return versionControlService.listEntityTypeVersions(getTenantId(), branch, entityType); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@ApiOperation(value = "", notes = "" + |
|||
"```\n[\n" + |
|||
" {\n" + |
|||
" \"id\": \"ba9baaca1742b730e7331f31a6a51da5fc7da7f7\",\n" + |
|||
" \"name\": \"Device 1 removed\"\n" + |
|||
" },\n" + |
|||
" {\n" + |
|||
" \"id\": \"b3c28d722d328324c7c15b0b30047b0c40011cf7\",\n" + |
|||
" \"name\": \"Device profiles added\"\n" + |
|||
" },\n" + |
|||
" {\n" + |
|||
" \"id\": \"c30c8bcaed3f0813649f0dee51a89d04d0a12b28\",\n" + |
|||
" \"name\": \"Devices added\"\n" + |
|||
" }\n" + |
|||
"]\n```") |
|||
@GetMapping("/version/{branch}") |
|||
public List<EntityVersion> listVersions(@PathVariable String branch) throws ThingsboardException { |
|||
try { |
|||
return versionControlService.listVersions(getTenantId(), branch); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
|
|||
@GetMapping("/entity/{branch}/{entityType}/{versionId}") |
|||
public List<VersionedEntityInfo> listEntitiesAtVersion(@PathVariable String branch, |
|||
@PathVariable EntityType entityType, |
|||
@PathVariable String versionId) throws ThingsboardException { |
|||
try { |
|||
return versionControlService.listEntitiesAtVersion(getTenantId(), entityType, branch, versionId); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@GetMapping("/entity/{branch}/{versionId}") |
|||
public List<VersionedEntityInfo> listAllEntitiesAtVersion(@PathVariable String branch, |
|||
@PathVariable String versionId) throws ThingsboardException { |
|||
try { |
|||
return versionControlService.listAllEntitiesAtVersion(getTenantId(), branch, versionId); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
|
|||
@ApiOperation(value = "", notes = "" + |
|||
"SINGLE_ENTITY:" + NEW_LINE + |
|||
"```\n{\n" + |
|||
" \"type\": \"SINGLE_ENTITY\",\n" + |
|||
" \n" + |
|||
" \"branch\": \"dev\",\n" + |
|||
" \"versionId\": \"b3c28d722d328324c7c15b0b30047b0c40011cf7\",\n" + |
|||
" \n" + |
|||
" \"externalEntityId\": {\n" + |
|||
" \"entityType\": \"DEVICE\",\n" + |
|||
" \"id\": \"b7944123-d4f4-11ec-847b-0f432358ab48\"\n" + |
|||
" },\n" + |
|||
" \"config\": {\n" + |
|||
" \"loadRelations\": false,\n" + |
|||
" \"findExistingEntityByName\": false\n" + |
|||
" }\n" + |
|||
"}\n```" + NEW_LINE + |
|||
"ENTITY_TYPE:" + NEW_LINE + |
|||
"```\n{\n" + |
|||
" \"type\": \"ENTITY_TYPE\",\n" + |
|||
"\n" + |
|||
" \"branch\": \"dev\",\n" + |
|||
" \"versionId\": \"b3c28d722d328324c7c15b0b30047b0c40011cf7\",\n" + |
|||
"\n" + |
|||
" \"entityTypes\": {\n" + |
|||
" \"DEVICE\": {\n" + |
|||
" \"loadRelations\": false,\n" + |
|||
" \"findExistingEntityByName\": false,\n" + |
|||
" \"removeOtherEntities\": true\n" + |
|||
" }\n" + |
|||
" }\n" + |
|||
"}\n```") |
|||
@PostMapping("/entity") |
|||
public List<VersionLoadResult> loadEntitiesVersion(@RequestBody VersionLoadRequest request) throws ThingsboardException { |
|||
SecurityUser user = getCurrentUser(); |
|||
try { |
|||
String versionId = request.getVersionId(); |
|||
if (versionId == null) { |
|||
List<EntityVersion> versions = versionControlService.listVersions(user.getTenantId(), request.getBranch()); |
|||
if (versions.size() > 0) { |
|||
versionId = versions.get(0).getId(); |
|||
} else { |
|||
throw new IllegalArgumentException("No versions available in branch"); |
|||
} |
|||
} |
|||
|
|||
return versionControlService.loadEntitiesVersion(user, request); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
|
|||
@ApiOperation(value = "", notes = "" + |
|||
"```\n[\n" + |
|||
" {\n" + |
|||
" \"name\": \"master\",\n" + |
|||
" \"default\": true\n" + |
|||
" },\n" + |
|||
" {\n" + |
|||
" \"name\": \"dev\",\n" + |
|||
" \"default\": false\n" + |
|||
" },\n" + |
|||
" {\n" + |
|||
" \"name\": \"dev-2\",\n" + |
|||
" \"default\": false\n" + |
|||
" }\n" + |
|||
"]\n\n```") |
|||
@GetMapping("/branches") |
|||
public List<BranchInfo> listBranches() throws ThingsboardException { |
|||
try { |
|||
List<String> remoteBranches = versionControlService.listBranches(getTenantId()); |
|||
List<BranchInfo> infos = new ArrayList<>(); |
|||
|
|||
String defaultBranch = getSettings().getDefaultBranch(); |
|||
if (StringUtils.isNotEmpty(defaultBranch)) { |
|||
remoteBranches.remove(defaultBranch); |
|||
infos.add(new BranchInfo(defaultBranch, true)); |
|||
} |
|||
|
|||
remoteBranches.forEach(branch -> infos.add(new BranchInfo(branch, false))); |
|||
return infos; |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
|
|||
@ApiOperation(value = "", notes = "" + |
|||
"```\n{\n" + |
|||
" \"repositoryUri\": \"https://github.com/User/repo.git\",\n" + |
|||
" \"username\": \"User\",\n" + |
|||
" \"password\": \"api_key\",\n" + |
|||
" \"defaultBranch\": \"master\"\n" + |
|||
"}\n```") |
|||
@GetMapping("/settings") |
|||
public EntitiesVersionControlSettings getSettings() throws ThingsboardException { |
|||
try { |
|||
return versionControlService.getSettings(getTenantId()); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@ApiOperation(value = "", notes = "" + |
|||
"```\n{\n" + |
|||
" \"repositoryUri\": \"https://github.com/User/repo.git\",\n" + |
|||
" \"username\": \"User\",\n" + |
|||
" \"password\": \"api_key\",\n" + |
|||
" \"defaultBranch\": \"master\"\n" + |
|||
"}\n```") |
|||
@PostMapping("/settings") |
|||
public void saveSettings(@RequestBody EntitiesVersionControlSettings settings) throws ThingsboardException { |
|||
try { |
|||
versionControlService.saveSettings(getTenantId(), settings); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
|
|||
@Data |
|||
public static class BranchInfo { |
|||
private final String name; |
|||
private final boolean isDefault; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,174 @@ |
|||
/** |
|||
* 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.service.sync.exportimport; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.transaction.annotation.Transactional; |
|||
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.EntityId; |
|||
import org.thingsboard.server.dao.exception.DataValidationException; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.EntityExportService; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportData; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportSettings; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.impl.BaseEntityExportService; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.impl.DefaultEntityExportService; |
|||
import org.thingsboard.server.service.sync.exportimport.importing.EntityImportService; |
|||
import org.thingsboard.server.service.sync.exportimport.importing.data.EntityImportResult; |
|||
import org.thingsboard.server.service.sync.exportimport.importing.data.EntityImportSettings; |
|||
import org.thingsboard.server.utils.ThrowingRunnable; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Collection; |
|||
import java.util.Comparator; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Objects; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Service |
|||
@TbCoreComponent |
|||
@RequiredArgsConstructor |
|||
@Slf4j |
|||
public class DefaultEntitiesExportImportService implements EntitiesExportImportService { |
|||
|
|||
private final Map<EntityType, EntityExportService<?, ?, ?>> exportServices = new HashMap<>(); |
|||
private final Map<EntityType, EntityImportService<?, ?, ?>> importServices = new HashMap<>(); |
|||
|
|||
protected static final List<EntityType> SUPPORTED_ENTITY_TYPES = List.of( |
|||
EntityType.CUSTOMER, EntityType.ASSET, EntityType.RULE_CHAIN, |
|||
EntityType.DASHBOARD, EntityType.DEVICE_PROFILE, EntityType.DEVICE |
|||
); |
|||
|
|||
|
|||
@Override |
|||
public <E extends ExportableEntity<I>, I extends EntityId> EntityExportData<E> exportEntity(SecurityUser user, I entityId, EntityExportSettings exportSettings) throws ThingsboardException { |
|||
EntityType entityType = entityId.getEntityType(); |
|||
EntityExportService<I, E, EntityExportData<E>> exportService = getExportService(entityType); |
|||
|
|||
return exportService.getExportData(user, entityId, exportSettings); |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public <E extends ExportableEntity<I>, I extends EntityId> EntityImportResult<E> importEntity(SecurityUser user, EntityExportData<E> exportData, EntityImportSettings importSettings, |
|||
boolean saveReferences, boolean sendEvents) throws ThingsboardException { |
|||
if (exportData.getEntity() == null || exportData.getEntity().getId() == null) { |
|||
throw new DataValidationException("Invalid entity data"); |
|||
} |
|||
|
|||
EntityType entityType = exportData.getEntityType(); |
|||
EntityImportService<I, E, EntityExportData<E>> importService = getImportService(entityType); |
|||
|
|||
EntityImportResult<E> importResult = importService.importEntity(user, exportData, importSettings); |
|||
|
|||
if (saveReferences) { |
|||
importResult.getSaveReferencesCallback().run(); |
|||
} |
|||
if (sendEvents) { |
|||
importResult.getSendEventsCallback().run(); |
|||
} |
|||
|
|||
return importResult; |
|||
} |
|||
|
|||
@Transactional(rollbackFor = Exception.class, timeout = 120) |
|||
@Override |
|||
public List<EntityImportResult<?>> importEntities(SecurityUser user, List<EntityExportData<?>> exportDataList, EntityImportSettings importSettings) throws ThingsboardException { |
|||
fixDataOrderForImport(exportDataList); |
|||
|
|||
List<EntityImportResult<?>> importResults = new ArrayList<>(); |
|||
|
|||
for (EntityExportData exportData : exportDataList) { |
|||
EntityImportResult<?> importResult = importEntity(user, exportData, importSettings, false, false); |
|||
importResults.add(importResult); |
|||
} |
|||
|
|||
for (ThrowingRunnable saveReferencesCallback : importResults.stream() |
|||
.map(EntityImportResult::getSaveReferencesCallback) |
|||
.filter(Objects::nonNull) |
|||
.collect(Collectors.toList())) { |
|||
saveReferencesCallback.run(); |
|||
} |
|||
|
|||
importResults.stream() |
|||
.map(EntityImportResult::getSendEventsCallback) |
|||
.filter(Objects::nonNull) |
|||
.forEach(sendEventsCallback -> { |
|||
try { |
|||
sendEventsCallback.run(); |
|||
} catch (Exception e) { |
|||
log.error("Failed to send event for entity", e); |
|||
} |
|||
}); |
|||
|
|||
return importResults; |
|||
} |
|||
|
|||
@Override |
|||
public void fixDataOrderForImport(List<EntityExportData<?>> exportDataList) { |
|||
exportDataList.sort(Comparator.comparing(exportData -> SUPPORTED_ENTITY_TYPES.indexOf(exportData.getEntityType()))); |
|||
} |
|||
|
|||
|
|||
@SuppressWarnings("unchecked") |
|||
private <I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> EntityExportService<I, E, D> getExportService(EntityType entityType) { |
|||
EntityExportService<?, ?, ?> exportService = exportServices.get(entityType); |
|||
if (exportService == null) { |
|||
throw new IllegalArgumentException("Export for entity type " + entityType + " is not supported"); |
|||
} |
|||
return (EntityExportService<I, E, D>) exportService; |
|||
} |
|||
|
|||
@SuppressWarnings("unchecked") |
|||
private <I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> EntityImportService<I, E, D> getImportService(EntityType entityType) { |
|||
EntityImportService<?, ?, ?> importService = importServices.get(entityType); |
|||
if (importService == null) { |
|||
throw new IllegalArgumentException("Import for entity type " + entityType + " is not supported"); |
|||
} |
|||
return (EntityImportService<I, E, D>) importService; |
|||
} |
|||
|
|||
@Autowired |
|||
private void setExportServices(DefaultEntityExportService<?, ?, ?> defaultExportService, |
|||
Collection<BaseEntityExportService<?, ?, ?>> exportServices) { |
|||
exportServices.stream() |
|||
.sorted(Comparator.comparing(exportService -> exportService.getSupportedEntityTypes().size(), Comparator.reverseOrder())) |
|||
.forEach(exportService -> { |
|||
exportService.getSupportedEntityTypes().forEach(entityType -> { |
|||
this.exportServices.put(entityType, exportService); |
|||
}); |
|||
}); |
|||
SUPPORTED_ENTITY_TYPES.forEach(entityType -> { |
|||
this.exportServices.putIfAbsent(entityType, defaultExportService); |
|||
}); |
|||
} |
|||
|
|||
@Autowired |
|||
private void setImportServices(Collection<EntityImportService<?, ?, ?>> importServices) { |
|||
importServices.forEach(entityImportService -> { |
|||
this.importServices.put(entityImportService.getEntityType(), entityImportService); |
|||
}); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
/** |
|||
* 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.service.sync.exportimport; |
|||
|
|||
import org.thingsboard.server.common.data.ExportableEntity; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportData; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportSettings; |
|||
import org.thingsboard.server.service.sync.exportimport.importing.data.EntityImportResult; |
|||
import org.thingsboard.server.service.sync.exportimport.importing.data.EntityImportSettings; |
|||
|
|||
import java.util.List; |
|||
|
|||
public interface EntitiesExportImportService { |
|||
|
|||
<E extends ExportableEntity<I>, I extends EntityId> EntityExportData<E> exportEntity(SecurityUser user, I entityId, EntityExportSettings exportSettings) throws ThingsboardException; |
|||
|
|||
<E extends ExportableEntity<I>, I extends EntityId> EntityImportResult<E> importEntity(SecurityUser user, EntityExportData<E> exportData, EntityImportSettings importSettings, |
|||
boolean saveReferences, boolean sendEvents) throws ThingsboardException; |
|||
|
|||
List<EntityImportResult<?>> importEntities(SecurityUser user, List<EntityExportData<?>> exportDataList, EntityImportSettings importSettings) throws ThingsboardException; |
|||
|
|||
void fixDataOrderForImport(List<EntityExportData<?>> exportDataList); |
|||
|
|||
} |
|||
@ -0,0 +1,204 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.exporting; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.dao.DataAccessException; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.ExportableEntity; |
|||
import org.thingsboard.server.common.data.HasTenantId; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.CustomerId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.HasId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
import org.thingsboard.server.common.data.query.EntityData; |
|||
import org.thingsboard.server.common.data.query.EntityDataPageLink; |
|||
import org.thingsboard.server.common.data.query.EntityDataQuery; |
|||
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.dao.Dao; |
|||
import org.thingsboard.server.dao.ExportableEntityDao; |
|||
import org.thingsboard.server.dao.entity.EntityService; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.security.permission.AccessControlService; |
|||
import org.thingsboard.server.service.security.permission.Operation; |
|||
import org.thingsboard.server.service.security.permission.Resource; |
|||
|
|||
import java.util.Collection; |
|||
import java.util.Collections; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import static org.thingsboard.server.dao.sql.query.EntityKeyMapping.CREATED_TIME; |
|||
|
|||
@Service |
|||
@TbCoreComponent |
|||
@RequiredArgsConstructor |
|||
@Slf4j |
|||
public class DefaultExportableEntitiesService implements ExportableEntitiesService { |
|||
|
|||
private final Map<EntityType, Dao<?>> daos = new HashMap<>(); |
|||
|
|||
private final EntityService entityService; |
|||
private final AccessControlService accessControlService; |
|||
|
|||
|
|||
@Override |
|||
public <E extends ExportableEntity<I>, I extends EntityId> E findEntityByTenantIdAndExternalId(TenantId tenantId, I externalId) { |
|||
EntityType entityType = externalId.getEntityType(); |
|||
Dao<E> dao = getDao(entityType); |
|||
|
|||
E entity = null; |
|||
|
|||
if (dao instanceof ExportableEntityDao) { |
|||
ExportableEntityDao<E> exportableEntityDao = (ExportableEntityDao<E>) dao; |
|||
entity = exportableEntityDao.findByTenantIdAndExternalId(tenantId.getId(), externalId.getId()); |
|||
} |
|||
if (entity == null || !belongsToTenant(entity, tenantId)) { |
|||
return null; |
|||
} |
|||
|
|||
return entity; |
|||
} |
|||
|
|||
@Override |
|||
public <E extends HasId<I>, I extends EntityId> E findEntityByTenantIdAndId(TenantId tenantId, I id) { |
|||
EntityType entityType = id.getEntityType(); |
|||
Dao<E> dao = getDao(entityType); |
|||
if (dao == null) { |
|||
throw new IllegalArgumentException("Unsupported entity type " + entityType); |
|||
} |
|||
|
|||
E entity = dao.findById(tenantId, id.getId()); |
|||
|
|||
if (entity == null || !belongsToTenant(entity, tenantId)) { |
|||
return null; |
|||
} |
|||
|
|||
return entity; |
|||
} |
|||
|
|||
@Override |
|||
public <E extends ExportableEntity<I>, I extends EntityId> E findEntityByTenantIdAndName(TenantId tenantId, EntityType entityType, String name) { |
|||
Dao<E> dao = getDao(entityType); |
|||
|
|||
E entity = null; |
|||
|
|||
if (dao instanceof ExportableEntityDao) { |
|||
ExportableEntityDao<E> exportableEntityDao = (ExportableEntityDao<E>) dao; |
|||
try { |
|||
entity = exportableEntityDao.findByTenantIdAndName(tenantId.getId(), name); |
|||
} catch (UnsupportedOperationException ignored) { |
|||
} |
|||
} |
|||
if (entity == null || !belongsToTenant(entity, tenantId)) { |
|||
return null; |
|||
} |
|||
|
|||
return entity; |
|||
} |
|||
|
|||
@Override |
|||
public <E extends ExportableEntity<I>, I extends EntityId> PageData<E> findEntitiesByTenantId(TenantId tenantId, EntityType entityType, PageLink pageLink) { |
|||
Dao<E> dao = getDao(entityType); |
|||
if (dao instanceof ExportableEntityDao) { |
|||
ExportableEntityDao<E> exportableEntityDao = (ExportableEntityDao<E>) dao; |
|||
return exportableEntityDao.findByTenantId(tenantId.getId(), pageLink); |
|||
} |
|||
return new PageData<>(); |
|||
} |
|||
|
|||
private boolean belongsToTenant(HasId<? extends EntityId> entity, TenantId tenantId) { |
|||
return tenantId.equals(((HasTenantId) entity).getTenantId()); |
|||
} |
|||
|
|||
|
|||
private List<EntityId> findEntitiesByFilter(TenantId tenantId, CustomerId customerId, EntityFilter filter, int page, int pageSize) { |
|||
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)); |
|||
|
|||
EntityDataQuery query = new EntityDataQuery(filter, pageLink, List.of(sortProperty), Collections.emptyList(), Collections.emptyList()); |
|||
return findEntitiesByQuery(tenantId, customerId, query); |
|||
} |
|||
|
|||
private List<EntityId> findEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityDataQuery query) { |
|||
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"); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public <I extends EntityId> void deleteByTenantIdAndId(TenantId tenantId, I id) { |
|||
EntityType entityType = id.getEntityType(); |
|||
Dao<Object> dao = getDao(entityType); |
|||
if (dao == null) { |
|||
throw new IllegalArgumentException("Unsupported entity type " + entityType); |
|||
} |
|||
|
|||
dao.removeById(tenantId, id.getId()); |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public void checkPermission(SecurityUser user, HasId<? extends EntityId> entity, EntityType entityType, Operation operation) throws ThingsboardException { |
|||
if (entity instanceof HasTenantId) { |
|||
accessControlService.checkPermission(user, Resource.of(entityType), operation, entity.getId(), (HasTenantId) entity); |
|||
} else { |
|||
accessControlService.checkPermission(user, Resource.of(entityType), operation); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void checkPermission(SecurityUser user, EntityId entityId, Operation operation) throws ThingsboardException { |
|||
HasId<EntityId> entity = findEntityByTenantIdAndId(user.getTenantId(), entityId); |
|||
checkPermission(user, entity, entityId.getEntityType(), operation); |
|||
} |
|||
|
|||
|
|||
@SuppressWarnings("unchecked") |
|||
private <E> Dao<E> getDao(EntityType entityType) { |
|||
return (Dao<E>) daos.get(entityType); |
|||
} |
|||
|
|||
@Autowired |
|||
private void setDaos(Collection<Dao<?>> daos) { |
|||
daos.forEach(dao -> { |
|||
if (dao.getEntityType() != null) { |
|||
this.daos.put(dao.getEntityType(), dao); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.exporting; |
|||
|
|||
import org.thingsboard.server.common.data.ExportableEntity; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportSettings; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportData; |
|||
|
|||
public interface EntityExportService<I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> { |
|||
|
|||
D getExportData(SecurityUser user, I entityId, EntityExportSettings exportSettings) throws ThingsboardException; |
|||
|
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.exporting; |
|||
|
|||
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.EntityId; |
|||
import org.thingsboard.server.common.data.id.HasId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.security.permission.Operation; |
|||
import org.thingsboard.server.service.sync.vc.data.request.create.VersionCreateConfig; |
|||
|
|||
import java.util.List; |
|||
|
|||
public interface ExportableEntitiesService { |
|||
|
|||
<E extends ExportableEntity<I>, I extends EntityId> E findEntityByTenantIdAndExternalId(TenantId tenantId, I externalId); |
|||
|
|||
<E extends HasId<I>, I extends EntityId> E findEntityByTenantIdAndId(TenantId tenantId, I id); |
|||
|
|||
<E extends ExportableEntity<I>, I extends EntityId> E findEntityByTenantIdAndName(TenantId tenantId, EntityType entityType, String name); |
|||
|
|||
<E extends ExportableEntity<I>, I extends EntityId> PageData<E> findEntitiesByTenantId(TenantId tenantId, EntityType entityType, PageLink pageLink); |
|||
|
|||
<I extends EntityId> void deleteByTenantIdAndId(TenantId tenantId, I id); |
|||
|
|||
|
|||
void checkPermission(SecurityUser user, HasId<? extends EntityId> entity, EntityType entityType, Operation operation) throws ThingsboardException; |
|||
|
|||
void checkPermission(SecurityUser user, EntityId entityId, Operation operation) throws ThingsboardException; |
|||
|
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.exporting.data; |
|||
|
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.ToString; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.security.DeviceCredentials; |
|||
|
|||
@EqualsAndHashCode(callSuper = true) |
|||
@ToString(callSuper = true) |
|||
@Data |
|||
public class DeviceExportData extends EntityExportData<Device> { |
|||
|
|||
private DeviceCredentials credentials; |
|||
|
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.exporting.data; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import com.fasterxml.jackson.annotation.JsonInclude; |
|||
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.EntityType; |
|||
import org.thingsboard.server.common.data.ExportableEntity; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.relation.EntityRelation; |
|||
import org.thingsboard.server.utils.JsonTbEntity; |
|||
|
|||
import java.util.List; |
|||
|
|||
@JsonIgnoreProperties(ignoreUnknown = 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) |
|||
}) |
|||
@JsonInclude(JsonInclude.Include.NON_NULL) |
|||
@Data |
|||
public class EntityExportData<E extends ExportableEntity<? extends EntityId>> { |
|||
|
|||
@JsonTbEntity |
|||
private E entity; |
|||
private EntityType entityType; |
|||
|
|||
private List<EntityRelation> relations; |
|||
|
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.exporting.data; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
|
|||
@Data |
|||
@AllArgsConstructor |
|||
@NoArgsConstructor |
|||
@Builder |
|||
public class EntityExportSettings { |
|||
private boolean exportRelations; |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.exporting.data; |
|||
|
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.ToString; |
|||
import org.thingsboard.server.common.data.rule.RuleChain; |
|||
import org.thingsboard.server.common.data.rule.RuleChainMetaData; |
|||
|
|||
@EqualsAndHashCode(callSuper = true) |
|||
@ToString(callSuper = true) |
|||
@Data |
|||
public class RuleChainExportData extends EntityExportData<RuleChain> { |
|||
|
|||
private RuleChainMetaData metaData; |
|||
|
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.exporting.impl; |
|||
|
|||
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.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportSettings; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportData; |
|||
|
|||
import java.util.Set; |
|||
|
|||
public abstract class BaseEntityExportService<I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> extends DefaultEntityExportService<I, E, D> { |
|||
|
|||
@Override |
|||
protected void setAdditionalExportData(SecurityUser user, E entity, D exportData, EntityExportSettings exportSettings) throws ThingsboardException { |
|||
setRelatedEntities(user.getTenantId(), entity, (D) exportData); |
|||
super.setAdditionalExportData(user, entity, exportData, exportSettings); |
|||
} |
|||
|
|||
protected void setRelatedEntities(TenantId tenantId, E mainEntity, D exportData) {} |
|||
|
|||
protected abstract D newExportData(); |
|||
|
|||
public abstract Set<EntityType> getSupportedEntityTypes(); |
|||
|
|||
} |
|||
@ -0,0 +1,90 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.exporting.impl; |
|||
|
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.context.annotation.Lazy; |
|||
import org.springframework.context.annotation.Primary; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.ExportableEntity; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.relation.EntityRelation; |
|||
import org.thingsboard.server.common.data.relation.RelationTypeGroup; |
|||
import org.thingsboard.server.dao.relation.RelationService; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.security.permission.Operation; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportSettings; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.EntityExportService; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.ExportableEntitiesService; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportData; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
@Service |
|||
@TbCoreComponent |
|||
@Primary |
|||
public class DefaultEntityExportService<I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> implements EntityExportService<I, E, D> { |
|||
|
|||
@Autowired @Lazy |
|||
private ExportableEntitiesService exportableEntitiesService; |
|||
@Autowired |
|||
private RelationService relationService; |
|||
|
|||
@Override |
|||
public final D getExportData(SecurityUser user, I entityId, EntityExportSettings exportSettings) throws ThingsboardException { |
|||
D exportData = newExportData(); |
|||
|
|||
E entity = exportableEntitiesService.findEntityByTenantIdAndId(user.getTenantId(), entityId); |
|||
if (entity == null) { |
|||
throw new IllegalArgumentException(entityId.getEntityType() + " [" + entityId.getId() + "] not found"); |
|||
} |
|||
exportableEntitiesService.checkPermission(user, entity, entity.getId().getEntityType(), Operation.READ); |
|||
|
|||
exportData.setEntity(entity); |
|||
exportData.setEntityType(entityId.getEntityType()); |
|||
setAdditionalExportData(user, entity, exportData, exportSettings); |
|||
|
|||
return exportData; |
|||
} |
|||
|
|||
protected void setAdditionalExportData(SecurityUser user, E entity, D exportData, EntityExportSettings exportSettings) throws ThingsboardException { |
|||
if (exportSettings.isExportRelations()) { |
|||
List<EntityRelation> relations = new ArrayList<>(); |
|||
|
|||
List<EntityRelation> inboundRelations = relationService.findByTo(user.getTenantId(), entity.getId(), RelationTypeGroup.COMMON); |
|||
for (EntityRelation relation : inboundRelations) { |
|||
exportableEntitiesService.checkPermission(user, relation.getFrom(), Operation.READ); |
|||
} |
|||
relations.addAll(inboundRelations); |
|||
|
|||
List<EntityRelation> outboundRelations = relationService.findByFrom(user.getTenantId(), entity.getId(), RelationTypeGroup.COMMON); |
|||
for (EntityRelation relation : outboundRelations) { |
|||
exportableEntitiesService.checkPermission(user, relation.getTo(), Operation.READ); |
|||
} |
|||
relations.addAll(outboundRelations); |
|||
|
|||
exportData.setRelations(relations); |
|||
} |
|||
} |
|||
|
|||
protected D newExportData() { |
|||
return (D) new EntityExportData<E>(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.exporting.impl; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.dao.device.DeviceCredentialsService; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.DeviceExportData; |
|||
|
|||
import java.util.Set; |
|||
|
|||
@Service |
|||
@TbCoreComponent |
|||
@RequiredArgsConstructor |
|||
public class DeviceExportService extends BaseEntityExportService<DeviceId, Device, DeviceExportData> { |
|||
|
|||
private final DeviceCredentialsService deviceCredentialsService; |
|||
|
|||
@Override |
|||
protected void setRelatedEntities(TenantId tenantId, Device device, DeviceExportData exportData) { |
|||
exportData.setCredentials(deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, device.getId())); |
|||
} |
|||
|
|||
@Override |
|||
protected DeviceExportData newExportData() { |
|||
return new DeviceExportData(); |
|||
} |
|||
|
|||
@Override |
|||
public Set<EntityType> getSupportedEntityTypes() { |
|||
return Set.of(EntityType.DEVICE); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.exporting.impl; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.id.RuleChainId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.rule.RuleChain; |
|||
import org.thingsboard.server.dao.rule.RuleChainService; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.RuleChainExportData; |
|||
|
|||
import java.util.Set; |
|||
|
|||
@Service |
|||
@TbCoreComponent |
|||
@RequiredArgsConstructor |
|||
public class RuleChainExportService extends BaseEntityExportService<RuleChainId, RuleChain, RuleChainExportData> { |
|||
|
|||
private final RuleChainService ruleChainService; |
|||
|
|||
@Override |
|||
protected void setRelatedEntities(TenantId tenantId, RuleChain ruleChain, RuleChainExportData exportData) { |
|||
exportData.setMetaData(ruleChainService.loadRuleChainMetaData(tenantId, ruleChain.getId())); |
|||
} |
|||
|
|||
@Override |
|||
protected RuleChainExportData newExportData() { |
|||
return new RuleChainExportData(); |
|||
} |
|||
|
|||
@Override |
|||
public Set<EntityType> getSupportedEntityTypes() { |
|||
return Set.of(EntityType.RULE_CHAIN); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.importing; |
|||
|
|||
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.EntityId; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.sync.exportimport.importing.data.EntityImportResult; |
|||
import org.thingsboard.server.service.sync.exportimport.importing.data.EntityImportSettings; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportData; |
|||
|
|||
public interface EntityImportService<I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> { |
|||
|
|||
EntityImportResult<E> importEntity(SecurityUser user, D exportData, EntityImportSettings importSettings) throws ThingsboardException; |
|||
|
|||
EntityType getEntityType(); |
|||
|
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.importing.data; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnore; |
|||
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<E extends ExportableEntity<? extends EntityId>> { |
|||
|
|||
@JsonTbEntity |
|||
private E savedEntity; |
|||
@JsonTbEntity |
|||
private E oldEntity; |
|||
private EntityType entityType; |
|||
|
|||
@JsonIgnore |
|||
private ThrowingRunnable saveReferencesCallback = () -> {}; |
|||
@JsonIgnore |
|||
private ThrowingRunnable sendEventsCallback = () -> {}; |
|||
|
|||
public void addSaveReferencesCallback(ThrowingRunnable callback) { |
|||
this.saveReferencesCallback = this.saveReferencesCallback.andThen(callback); |
|||
} |
|||
|
|||
public void addSendEventsCallback(ThrowingRunnable callback) { |
|||
this.sendEventsCallback = this.sendEventsCallback.andThen(callback); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.importing.data; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
|
|||
@Data |
|||
@AllArgsConstructor |
|||
@NoArgsConstructor |
|||
@Builder |
|||
public class EntityImportSettings { |
|||
private boolean findExistingByName; |
|||
private boolean updateRelations; |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.importing.impl; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.asset.Asset; |
|||
import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.AssetId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.dao.asset.AssetService; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportData; |
|||
|
|||
@Service |
|||
@TbCoreComponent |
|||
@RequiredArgsConstructor |
|||
public class AssetImportService extends BaseEntityImportService<AssetId, Asset, EntityExportData<Asset>> { |
|||
|
|||
private final AssetService assetService; |
|||
|
|||
@Override |
|||
protected void setOwner(TenantId tenantId, Asset asset, IdProvider idProvider) { |
|||
asset.setTenantId(tenantId); |
|||
asset.setCustomerId(idProvider.getInternalId(asset.getCustomerId())); |
|||
} |
|||
|
|||
@Override |
|||
protected Asset prepareAndSave(TenantId tenantId, Asset asset, EntityExportData<Asset> exportData, IdProvider idProvider) { |
|||
return assetService.saveAsset(asset); |
|||
} |
|||
|
|||
@Override |
|||
protected void onEntitySaved(SecurityUser user, Asset savedAsset, Asset oldAsset) throws ThingsboardException { |
|||
super.onEntitySaved(user, savedAsset, oldAsset); |
|||
if (oldAsset != null) { |
|||
entityActionService.sendEntityNotificationMsgToEdgeService(user.getTenantId(), savedAsset.getId(), EdgeEventActionType.UPDATED); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public EntityType getEntityType() { |
|||
return EntityType.ASSET; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,230 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.importing.impl; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
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; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.EntityIdFactory; |
|||
import org.thingsboard.server.common.data.id.HasId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.relation.EntityRelation; |
|||
import org.thingsboard.server.common.data.relation.RelationTypeGroup; |
|||
import org.thingsboard.server.dao.relation.RelationService; |
|||
import org.thingsboard.server.service.action.EntityActionService; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.security.permission.Operation; |
|||
import org.thingsboard.server.service.sync.exportimport.importing.data.EntityImportResult; |
|||
import org.thingsboard.server.service.sync.exportimport.importing.data.EntityImportSettings; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.ExportableEntitiesService; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportData; |
|||
import org.thingsboard.server.service.sync.exportimport.importing.EntityImportService; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.Optional; |
|||
import java.util.UUID; |
|||
|
|||
public abstract class BaseEntityImportService<I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> implements EntityImportService<I, E, D> { |
|||
|
|||
@Autowired @Lazy |
|||
private ExportableEntitiesService exportableEntitiesService; |
|||
@Autowired |
|||
private RelationService relationService; |
|||
@Autowired |
|||
protected EntityActionService entityActionService; |
|||
@Autowired |
|||
protected TbClusterService clusterService; |
|||
|
|||
@Transactional(rollbackFor = Exception.class) |
|||
@Override |
|||
public EntityImportResult<E> importEntity(SecurityUser user, D exportData, EntityImportSettings importSettings) throws ThingsboardException { |
|||
E entity = exportData.getEntity(); |
|||
E existingEntity = findExistingEntity(user.getTenantId(), entity, importSettings); |
|||
|
|||
entity.setExternalId(entity.getId()); |
|||
|
|||
IdProvider idProvider = new IdProvider(user); |
|||
setOwner(user.getTenantId(), entity, idProvider); |
|||
if (existingEntity == null) { |
|||
entity.setId(null); |
|||
exportableEntitiesService.checkPermission(user, entity, getEntityType(), Operation.CREATE); |
|||
} else { |
|||
entity.setId(existingEntity.getId()); |
|||
entity.setCreatedTime(existingEntity.getCreatedTime()); |
|||
exportableEntitiesService.checkPermission(user, existingEntity, getEntityType(), Operation.WRITE); |
|||
} |
|||
|
|||
E savedEntity = prepareAndSave(user.getTenantId(), entity, exportData, idProvider); |
|||
|
|||
EntityImportResult<E> importResult = new EntityImportResult<>(); |
|||
importResult.setSavedEntity(savedEntity); |
|||
importResult.setOldEntity(existingEntity); |
|||
importResult.setEntityType(getEntityType()); |
|||
|
|||
processAfterSaved(user, importResult, exportData, idProvider, importSettings); |
|||
|
|||
return importResult; |
|||
} |
|||
|
|||
protected abstract void setOwner(TenantId tenantId, E entity, IdProvider idProvider); |
|||
|
|||
protected abstract E prepareAndSave(TenantId tenantId, E entity, D exportData, IdProvider idProvider); |
|||
|
|||
|
|||
protected void processAfterSaved(SecurityUser user, EntityImportResult<E> importResult, D exportData, |
|||
IdProvider idProvider, EntityImportSettings importSettings) throws ThingsboardException { |
|||
E savedEntity = importResult.getSavedEntity(); |
|||
E oldEntity = importResult.getOldEntity(); |
|||
|
|||
importResult.addSendEventsCallback(() -> { |
|||
onEntitySaved(user, savedEntity, oldEntity); |
|||
}); |
|||
|
|||
importResult.addSaveReferencesCallback(() -> { |
|||
if (!importSettings.isUpdateRelations() || exportData.getRelations() == null) { |
|||
return; |
|||
} |
|||
|
|||
List<EntityRelation> relations = new ArrayList<>(exportData.getRelations()); |
|||
|
|||
for (EntityRelation relation : relations) { |
|||
if (!relation.getTo().equals(savedEntity.getId())) { |
|||
HasId<EntityId> to = findInternalEntity(user.getTenantId(), relation.getTo()); |
|||
exportableEntitiesService.checkPermission(user, to, to.getId().getEntityType(), Operation.WRITE); |
|||
relation.setTo(to.getId()); |
|||
} |
|||
if (!relation.getFrom().equals(savedEntity.getId())) { |
|||
HasId<EntityId> from = findInternalEntity(user.getTenantId(), relation.getFrom()); |
|||
exportableEntitiesService.checkPermission(user, from, from.getId().getEntityType(), Operation.WRITE); |
|||
relation.setFrom(from.getId()); |
|||
} |
|||
} |
|||
|
|||
if (oldEntity != null) { |
|||
List<EntityRelation> existingRelations = new ArrayList<>(); |
|||
existingRelations.addAll(relationService.findByTo(user.getTenantId(), savedEntity.getId(), RelationTypeGroup.COMMON)); |
|||
existingRelations.addAll(relationService.findByFrom(user.getTenantId(), savedEntity.getId(), RelationTypeGroup.COMMON)); |
|||
|
|||
for (EntityRelation existingRelation : existingRelations) { |
|||
if (!relations.contains(existingRelation)) { |
|||
EntityId otherEntity = null; |
|||
if (!existingRelation.getTo().equals(savedEntity.getId())) { |
|||
otherEntity = existingRelation.getTo(); |
|||
} else if (!existingRelation.getFrom().equals(savedEntity.getId())) { |
|||
otherEntity = existingRelation.getFrom(); |
|||
} |
|||
if (otherEntity != null) { |
|||
exportableEntitiesService.checkPermission(user, otherEntity, Operation.WRITE); |
|||
} |
|||
relationService.deleteRelation(user.getTenantId(), existingRelation); |
|||
importResult.addSendEventsCallback(() -> { |
|||
entityActionService.logEntityAction(user, existingRelation.getFrom(), null, null, |
|||
ActionType.RELATION_DELETED, null, existingRelation); |
|||
entityActionService.logEntityAction(user, existingRelation.getTo(), null, null, |
|||
ActionType.RELATION_DELETED, null, existingRelation); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
for (EntityRelation relation : relations) { |
|||
relationService.saveRelation(user.getTenantId(), relation); |
|||
importResult.addSendEventsCallback(() -> { |
|||
entityActionService.logEntityAction(user, relation.getFrom(), null, null, |
|||
ActionType.RELATION_ADD_OR_UPDATE, null, relation); |
|||
entityActionService.logEntityAction(user, relation.getTo(), null, null, |
|||
ActionType.RELATION_ADD_OR_UPDATE, null, relation); |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
protected void onEntitySaved(SecurityUser user, E savedEntity, E oldEntity) throws ThingsboardException { |
|||
entityActionService.logEntityAction(user, savedEntity.getId(), savedEntity, |
|||
savedEntity instanceof HasCustomerId ? ((HasCustomerId) savedEntity).getCustomerId() : user.getCustomerId(), |
|||
oldEntity == null ? ActionType.ADDED : ActionType.UPDATED, null); |
|||
} |
|||
|
|||
|
|||
protected E findExistingEntity(TenantId tenantId, E entity, EntityImportSettings importSettings) { |
|||
return (E) Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndExternalId(tenantId, entity.getId())) |
|||
.or(() -> Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndId(tenantId, entity.getId()))) |
|||
.or(() -> { |
|||
if (importSettings.isFindExistingByName()) { |
|||
return Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndName(tenantId, getEntityType(), entity.getName())); |
|||
} else { |
|||
return Optional.empty(); |
|||
} |
|||
}) |
|||
.orElse(null); |
|||
} |
|||
|
|||
private <ID extends EntityId> HasId<ID> findInternalEntity(TenantId tenantId, ID externalId) { |
|||
return (HasId<ID>) Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndExternalId(tenantId, externalId)) |
|||
.or(() -> Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndId(tenantId, externalId))) |
|||
.orElseThrow(() -> new IllegalArgumentException("Cannot find " + externalId.getEntityType() + " by external id " + externalId)); |
|||
} |
|||
|
|||
|
|||
@RequiredArgsConstructor |
|||
protected class IdProvider { |
|||
private final SecurityUser user; |
|||
|
|||
public <ID extends EntityId> ID getInternalId(ID externalId) { |
|||
if (externalId == null || externalId.isNullUid()) return null; |
|||
|
|||
HasId<ID> entity = findInternalEntity(user.getTenantId(), externalId); |
|||
try { |
|||
exportableEntitiesService.checkPermission(user, entity, entity.getId().getEntityType(), Operation.READ); |
|||
} catch (ThingsboardException e) { |
|||
throw new IllegalArgumentException(e.getMessage(), e); |
|||
} |
|||
return entity.getId(); |
|||
} |
|||
|
|||
public Optional<EntityId> getInternalIdByUuid(UUID externalUuid) { |
|||
for (EntityType entityType : EntityType.values()) { |
|||
EntityId externalId; |
|||
try { |
|||
externalId = EntityIdFactory.getByTypeAndUuid(entityType, externalUuid); |
|||
} catch (Exception e) { |
|||
continue; |
|||
} |
|||
|
|||
EntityId internalId = null; |
|||
try { |
|||
internalId = getInternalId(externalId); |
|||
} catch (Exception ignored) {} |
|||
|
|||
if (internalId != null) { |
|||
return Optional.of(internalId); |
|||
} |
|||
} |
|||
return Optional.empty(); |
|||
} |
|||
|
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.importing.impl; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.Customer; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.CustomerId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.dao.customer.CustomerService; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportData; |
|||
|
|||
@Service |
|||
@TbCoreComponent |
|||
@RequiredArgsConstructor |
|||
public class CustomerImportService extends BaseEntityImportService<CustomerId, Customer, EntityExportData<Customer>> { |
|||
|
|||
private final CustomerService customerService; |
|||
|
|||
@Override |
|||
protected void setOwner(TenantId tenantId, Customer customer, IdProvider idProvider) { |
|||
customer.setTenantId(tenantId); |
|||
} |
|||
|
|||
@Override |
|||
protected Customer prepareAndSave(TenantId tenantId, Customer customer, EntityExportData<Customer> exportData, IdProvider idProvider) { |
|||
return customerService.saveCustomer(customer); |
|||
} |
|||
|
|||
@Override |
|||
protected void onEntitySaved(SecurityUser user, Customer savedCustomer, Customer oldCustomer) throws ThingsboardException { |
|||
super.onEntitySaved(user, savedCustomer, oldCustomer); |
|||
if (oldCustomer != null) { |
|||
entityActionService.sendEntityNotificationMsgToEdgeService(user.getTenantId(), savedCustomer.getId(), EdgeEventActionType.UPDATED); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public EntityType getEntityType() { |
|||
return EntityType.CUSTOMER; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,122 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.importing.impl; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import lombok.RequiredArgsConstructor; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.common.data.Dashboard; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.ShortCustomerInfo; |
|||
import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.CustomerId; |
|||
import org.thingsboard.server.common.data.id.DashboardId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.dao.dashboard.DashboardService; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.sync.exportimport.importing.data.EntityImportSettings; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportData; |
|||
import org.thingsboard.server.utils.RegexUtils; |
|||
|
|||
import java.util.Collections; |
|||
import java.util.HashSet; |
|||
import java.util.Optional; |
|||
import java.util.Set; |
|||
import java.util.UUID; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Service |
|||
@TbCoreComponent |
|||
@RequiredArgsConstructor |
|||
public class DashboardImportService extends BaseEntityImportService<DashboardId, Dashboard, EntityExportData<Dashboard>> { |
|||
|
|||
private final DashboardService dashboardService; |
|||
|
|||
|
|||
@Override |
|||
protected void setOwner(TenantId tenantId, Dashboard dashboard, IdProvider idProvider) { |
|||
dashboard.setTenantId(tenantId); |
|||
} |
|||
|
|||
@Override |
|||
protected Dashboard findExistingEntity(TenantId tenantId, Dashboard dashboard, EntityImportSettings importSettings) { |
|||
Dashboard existingDashboard = super.findExistingEntity(tenantId, dashboard, importSettings); |
|||
if (existingDashboard == null && importSettings.isFindExistingByName()) { |
|||
existingDashboard = dashboardService.findTenantDashboardsByTitle(tenantId, dashboard.getName()).stream().findFirst().orElse(null); |
|||
} |
|||
return existingDashboard; |
|||
} |
|||
|
|||
@Override |
|||
protected Dashboard prepareAndSave(TenantId tenantId, Dashboard dashboard, EntityExportData<Dashboard> exportData, IdProvider idProvider) { |
|||
JsonNode configuration = dashboard.getConfiguration(); |
|||
String newConfigurationJson = RegexUtils.replace(configuration.toString(), RegexUtils.UUID_PATTERN, uuid -> { |
|||
return idProvider.getInternalIdByUuid(UUID.fromString(uuid)) |
|||
.map(entityId -> entityId.getId().toString()).orElse(uuid); |
|||
}); |
|||
configuration = JacksonUtil.toJsonNode(newConfigurationJson); |
|||
dashboard.setConfiguration(configuration); |
|||
|
|||
Set<ShortCustomerInfo> assignedCustomers = Optional.ofNullable(dashboard.getAssignedCustomers()).orElse(Collections.emptySet()).stream() |
|||
.peek(customerInfo -> customerInfo.setCustomerId(idProvider.getInternalId(customerInfo.getCustomerId()))) |
|||
.collect(Collectors.toSet()); |
|||
|
|||
if (dashboard.getId() == null) { |
|||
dashboard.setAssignedCustomers(null); |
|||
dashboard = dashboardService.saveDashboard(dashboard); |
|||
for (ShortCustomerInfo customerInfo : assignedCustomers) { |
|||
dashboard = dashboardService.assignDashboardToCustomer(tenantId, dashboard.getId(), customerInfo.getCustomerId()); |
|||
} |
|||
} else { |
|||
Set<CustomerId> existingAssignedCustomers = Optional.ofNullable(dashboardService.findDashboardById(tenantId, dashboard.getId()).getAssignedCustomers()) |
|||
.orElse(Collections.emptySet()).stream().map(ShortCustomerInfo::getCustomerId).collect(Collectors.toSet()); |
|||
Set<CustomerId> newAssignedCustomers = assignedCustomers.stream().map(ShortCustomerInfo::getCustomerId).collect(Collectors.toSet()); |
|||
|
|||
Set<CustomerId> toUnassign = new HashSet<>(existingAssignedCustomers); |
|||
toUnassign.removeAll(newAssignedCustomers); |
|||
for (CustomerId customerId : toUnassign) { |
|||
assignedCustomers = dashboardService.unassignDashboardFromCustomer(tenantId, dashboard.getId(), customerId).getAssignedCustomers(); |
|||
} |
|||
|
|||
Set<CustomerId> toAssign = new HashSet<>(newAssignedCustomers); |
|||
toAssign.removeAll(existingAssignedCustomers); |
|||
for (CustomerId customerId : toAssign) { |
|||
assignedCustomers = dashboardService.assignDashboardToCustomer(tenantId, dashboard.getId(), customerId).getAssignedCustomers(); |
|||
} |
|||
|
|||
dashboard.setAssignedCustomers(assignedCustomers); |
|||
dashboard = dashboardService.saveDashboard(dashboard); |
|||
} |
|||
return dashboard; |
|||
} |
|||
|
|||
@Override |
|||
protected void onEntitySaved(SecurityUser user, Dashboard savedDashboard, Dashboard oldDashboard) throws ThingsboardException { |
|||
super.onEntitySaved(user, savedDashboard, oldDashboard); |
|||
if (oldDashboard != null) { |
|||
entityActionService.sendEntityNotificationMsgToEdgeService(user.getTenantId(), savedDashboard.getId(), EdgeEventActionType.UPDATED); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public EntityType getEntityType() { |
|||
return EntityType.DASHBOARD; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.importing.impl; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.dao.device.DeviceService; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.DeviceExportData; |
|||
|
|||
@Service |
|||
@TbCoreComponent |
|||
@RequiredArgsConstructor |
|||
public class DeviceImportService extends BaseEntityImportService<DeviceId, Device, DeviceExportData> { |
|||
|
|||
private final DeviceService deviceService; |
|||
|
|||
@Override |
|||
protected void setOwner(TenantId tenantId, Device device, IdProvider idProvider) { |
|||
device.setTenantId(tenantId); |
|||
device.setCustomerId(idProvider.getInternalId(device.getCustomerId())); |
|||
} |
|||
|
|||
@Override |
|||
protected Device prepareAndSave(TenantId tenantId, Device device, DeviceExportData exportData, IdProvider idProvider) { |
|||
device.setDeviceProfileId(idProvider.getInternalId(device.getDeviceProfileId())); |
|||
device.setFirmwareId(idProvider.getInternalId(device.getFirmwareId())); |
|||
device.setSoftwareId(idProvider.getInternalId(device.getSoftwareId())); |
|||
if (exportData.getCredentials() != null) { |
|||
exportData.getCredentials().setId(null); |
|||
exportData.getCredentials().setDeviceId(null); |
|||
return deviceService.saveDeviceWithCredentials(device, exportData.getCredentials()); |
|||
} else { |
|||
return deviceService.saveDevice(device); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void onEntitySaved(SecurityUser user, Device savedDevice, Device oldDevice) throws ThingsboardException { |
|||
super.onEntitySaved(user, savedDevice, oldDevice); |
|||
clusterService.onDeviceUpdated(savedDevice, oldDevice); |
|||
} |
|||
|
|||
@Override |
|||
public EntityType getEntityType() { |
|||
return EntityType.DEVICE; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.importing.impl; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.DeviceProfileId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
|||
import org.thingsboard.server.dao.device.DeviceProfileService; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.ota.OtaPackageStateService; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportData; |
|||
|
|||
import java.util.Objects; |
|||
|
|||
@Service |
|||
@TbCoreComponent |
|||
@RequiredArgsConstructor |
|||
public class DeviceProfileImportService extends BaseEntityImportService<DeviceProfileId, DeviceProfile, EntityExportData<DeviceProfile>> { |
|||
|
|||
private final DeviceProfileService deviceProfileService; |
|||
private final OtaPackageStateService otaPackageStateService; |
|||
|
|||
@Override |
|||
protected void setOwner(TenantId tenantId, DeviceProfile deviceProfile, IdProvider idProvider) { |
|||
deviceProfile.setTenantId(tenantId); |
|||
} |
|||
|
|||
@Override |
|||
protected DeviceProfile prepareAndSave(TenantId tenantId, DeviceProfile deviceProfile, EntityExportData<DeviceProfile> exportData, IdProvider idProvider) { |
|||
deviceProfile.setDefaultRuleChainId(idProvider.getInternalId(deviceProfile.getDefaultRuleChainId())); |
|||
deviceProfile.setDefaultDashboardId(idProvider.getInternalId(deviceProfile.getDefaultDashboardId())); |
|||
deviceProfile.setFirmwareId(idProvider.getInternalId(deviceProfile.getFirmwareId())); |
|||
deviceProfile.setSoftwareId(idProvider.getInternalId(deviceProfile.getSoftwareId())); |
|||
return deviceProfileService.saveDeviceProfile(deviceProfile); |
|||
} |
|||
|
|||
@Override |
|||
protected void onEntitySaved(SecurityUser user, DeviceProfile savedDeviceProfile, DeviceProfile oldDeviceProfile) throws ThingsboardException { |
|||
super.onEntitySaved(user, savedDeviceProfile, oldDeviceProfile); |
|||
clusterService.onDeviceProfileChange(savedDeviceProfile, null); |
|||
clusterService.broadcastEntityStateChangeEvent(user.getTenantId(), savedDeviceProfile.getId(), |
|||
oldDeviceProfile == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); |
|||
entityActionService.sendEntityNotificationMsgToEdgeService(user.getTenantId(), savedDeviceProfile.getId(), |
|||
oldDeviceProfile == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED); |
|||
otaPackageStateService.update(savedDeviceProfile, |
|||
oldDeviceProfile != null && !Objects.equals(oldDeviceProfile.getFirmwareId(), savedDeviceProfile.getFirmwareId()), |
|||
oldDeviceProfile != null && !Objects.equals(oldDeviceProfile.getSoftwareId(), savedDeviceProfile.getSoftwareId())); |
|||
} |
|||
|
|||
@Override |
|||
public EntityType getEntityType() { |
|||
return EntityType.DEVICE_PROFILE; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,110 @@ |
|||
/** |
|||
* 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.service.sync.exportimport.importing.impl; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import lombok.RequiredArgsConstructor; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.RuleChainId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
|||
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.RuleChainUpdateResult; |
|||
import org.thingsboard.server.dao.rule.RuleChainService; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.sync.exportimport.importing.data.EntityImportSettings; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.RuleChainExportData; |
|||
import org.thingsboard.server.utils.RegexUtils; |
|||
|
|||
import java.util.Collections; |
|||
import java.util.Optional; |
|||
import java.util.UUID; |
|||
|
|||
@Service |
|||
@TbCoreComponent |
|||
@RequiredArgsConstructor |
|||
public class RuleChainImportService extends BaseEntityImportService<RuleChainId, RuleChain, RuleChainExportData> { |
|||
|
|||
private final RuleChainService ruleChainService; |
|||
|
|||
@Override |
|||
protected void setOwner(TenantId tenantId, RuleChain ruleChain, IdProvider idProvider) { |
|||
ruleChain.setTenantId(tenantId); |
|||
} |
|||
|
|||
@Override |
|||
protected RuleChain findExistingEntity(TenantId tenantId, RuleChain ruleChain, EntityImportSettings importSettings) { |
|||
RuleChain existingRuleChain = super.findExistingEntity(tenantId, ruleChain, importSettings); |
|||
if (existingRuleChain == null && importSettings.isFindExistingByName()) { |
|||
existingRuleChain = ruleChainService.findTenantRuleChainsByTypeAndName(tenantId, ruleChain.getType(), ruleChain.getName()).stream().findFirst().orElse(null); |
|||
} |
|||
return existingRuleChain; |
|||
} |
|||
|
|||
@Override |
|||
protected RuleChain prepareAndSave(TenantId tenantId, RuleChain ruleChain, RuleChainExportData exportData, IdProvider idProvider) { |
|||
RuleChainMetaData metaData = exportData.getMetaData(); |
|||
Optional.ofNullable(metaData.getNodes()).orElse(Collections.emptyList()) |
|||
.forEach(ruleNode -> { |
|||
ruleNode.setId(null); |
|||
ruleNode.setRuleChainId(null); |
|||
|
|||
JsonNode ruleNodeConfig = ruleNode.getConfiguration(); |
|||
String newRuleNodeConfigJson = RegexUtils.replace(ruleNodeConfig.toString(), RegexUtils.UUID_PATTERN, uuid -> { |
|||
return idProvider.getInternalIdByUuid(UUID.fromString(uuid)) |
|||
.map(entityId -> entityId.getId().toString()) |
|||
.orElse(uuid); |
|||
}); |
|||
ruleNodeConfig = JacksonUtil.toJsonNode(newRuleNodeConfigJson); |
|||
ruleNode.setConfiguration(ruleNodeConfig); |
|||
}); |
|||
Optional.ofNullable(metaData.getRuleChainConnections()).orElse(Collections.emptyList()) |
|||
.forEach(ruleChainConnectionInfo -> { |
|||
ruleChainConnectionInfo.setTargetRuleChainId(idProvider.getInternalId(ruleChainConnectionInfo.getTargetRuleChainId())); |
|||
}); |
|||
ruleChain.setFirstRuleNodeId(null); |
|||
|
|||
ruleChain = ruleChainService.saveRuleChain(ruleChain); |
|||
exportData.getMetaData().setRuleChainId(ruleChain.getId()); |
|||
RuleChainUpdateResult updateResult = ruleChainService.saveRuleChainMetaData(tenantId, exportData.getMetaData()); |
|||
// FIXME [viacheslav]: send events for nodes
|
|||
return ruleChainService.findRuleChainById(tenantId, ruleChain.getId()); |
|||
} |
|||
|
|||
@Override |
|||
protected void onEntitySaved(SecurityUser user, RuleChain savedRuleChain, RuleChain oldRuleChain) throws ThingsboardException { |
|||
super.onEntitySaved(user, savedRuleChain, oldRuleChain); |
|||
if (savedRuleChain.getType() == RuleChainType.CORE) { |
|||
clusterService.broadcastEntityStateChangeEvent(user.getTenantId(), savedRuleChain.getId(), |
|||
oldRuleChain == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); |
|||
} else if (savedRuleChain.getType() == RuleChainType.EDGE && oldRuleChain != null) { |
|||
entityActionService.sendEntityNotificationMsgToEdgeService(user.getTenantId(), savedRuleChain.getId(), EdgeEventActionType.UPDATED); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public EntityType getEntityType() { |
|||
return EntityType.RULE_CHAIN; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,491 @@ |
|||
/** |
|||
* 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.service.sync.vc; |
|||
|
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import com.fasterxml.jackson.databind.ObjectWriter; |
|||
import com.fasterxml.jackson.databind.SerializationFeature; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.SneakyThrows; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.io.FileUtils; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.transaction.support.TransactionTemplate; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.common.data.DataConstants; |
|||
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; |
|||
import org.thingsboard.server.common.data.id.EntityIdFactory; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; |
|||
import org.thingsboard.server.common.data.kv.JsonDataEntry; |
|||
import org.thingsboard.server.common.data.kv.KvEntry; |
|||
import org.thingsboard.server.common.data.query.EntityDataPageLink; |
|||
import org.thingsboard.server.common.data.query.EntityDataQuery; |
|||
import org.thingsboard.server.common.data.query.EntityDataSortOrder; |
|||
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.dao.DaoUtil; |
|||
import org.thingsboard.server.dao.attributes.AttributesService; |
|||
import org.thingsboard.server.dao.entity.EntityService; |
|||
import org.thingsboard.server.dao.tenant.TenantDao; |
|||
import org.thingsboard.server.queue.util.AfterStartUp; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.security.permission.Operation; |
|||
import org.thingsboard.server.service.sync.exportimport.EntitiesExportImportService; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.ExportableEntitiesService; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportData; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportSettings; |
|||
import org.thingsboard.server.service.sync.exportimport.importing.data.EntityImportResult; |
|||
import org.thingsboard.server.service.sync.exportimport.importing.data.EntityImportSettings; |
|||
import org.thingsboard.server.service.sync.vc.data.EntitiesVersionControlSettings; |
|||
import org.thingsboard.server.service.sync.vc.data.EntityVersion; |
|||
import org.thingsboard.server.service.sync.vc.data.VersionCreationResult; |
|||
import org.thingsboard.server.service.sync.vc.data.VersionLoadResult; |
|||
import org.thingsboard.server.service.sync.vc.data.VersionedEntityInfo; |
|||
import org.thingsboard.server.service.sync.vc.data.request.create.EntityListVersionCreateRequest; |
|||
import org.thingsboard.server.service.sync.vc.data.request.create.EntityTypeVersionCreateRequest; |
|||
import org.thingsboard.server.service.sync.vc.data.request.create.SingleEntityVersionCreateRequest; |
|||
import org.thingsboard.server.service.sync.vc.data.request.create.SyncStrategy; |
|||
import org.thingsboard.server.service.sync.vc.data.request.create.VersionCreateConfig; |
|||
import org.thingsboard.server.service.sync.vc.data.request.create.VersionCreateRequest; |
|||
import org.thingsboard.server.service.sync.vc.data.request.load.EntityTypeVersionLoadRequest; |
|||
import org.thingsboard.server.service.sync.vc.data.request.load.SingleEntityVersionLoadRequest; |
|||
import org.thingsboard.server.service.sync.vc.data.request.load.VersionLoadConfig; |
|||
import org.thingsboard.server.service.sync.vc.data.request.load.VersionLoadRequest; |
|||
import org.thingsboard.server.utils.GitRepository; |
|||
import org.thingsboard.server.utils.ThrowingRunnable; |
|||
|
|||
import java.io.File; |
|||
import java.io.IOException; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.nio.file.Files; |
|||
import java.nio.file.Path; |
|||
import java.util.ArrayList; |
|||
import java.util.Collections; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Optional; |
|||
import java.util.Set; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import static org.thingsboard.server.dao.sql.query.EntityKeyMapping.CREATED_TIME; |
|||
|
|||
@Service |
|||
@TbCoreComponent |
|||
@RequiredArgsConstructor |
|||
@Slf4j |
|||
public class DefaultEntitiesVersionControlService implements EntitiesVersionControlService { |
|||
|
|||
private final EntitiesExportImportService exportImportService; |
|||
private final ExportableEntitiesService exportableEntitiesService; |
|||
private final AttributesService attributesService; |
|||
private final EntityService entityService; |
|||
private final TenantDao tenantDao; |
|||
private final TransactionTemplate transactionTemplate; |
|||
|
|||
// TODO [viacheslav]: concurrency
|
|||
private final Map<TenantId, GitRepository> repositories = new ConcurrentHashMap<>(); |
|||
@Value("${java.io.tmpdir}/repositories") |
|||
private String repositoriesFolder; |
|||
|
|||
private static final String SETTINGS_KEY = "vc"; |
|||
private final ObjectWriter jsonWriter = new ObjectMapper().writer(SerializationFeature.INDENT_OUTPUT); |
|||
|
|||
|
|||
@AfterStartUp |
|||
public void init() { |
|||
DaoUtil.processInBatches(tenantDao::findTenantsIds, 100, tenantId -> { |
|||
EntitiesVersionControlSettings settings = getSettings(tenantId); |
|||
if (settings != null) { |
|||
try { |
|||
initRepository(tenantId, settings); |
|||
} catch (Exception e) { |
|||
log.warn("Failed to init repository for tenant {}", tenantId, e); |
|||
} |
|||
} |
|||
}); |
|||
Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(() -> { |
|||
repositories.forEach((tenantId, repository) -> { |
|||
try { |
|||
repository.fetch(); |
|||
log.info("Fetching remote repository for tenant {}", tenantId); |
|||
} catch (Exception e) { |
|||
log.warn("Failed to fetch repository for tenant {}", tenantId, e); |
|||
} |
|||
}); |
|||
}, 5, 5, TimeUnit.SECONDS); |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public VersionCreationResult saveEntitiesVersion(SecurityUser user, VersionCreateRequest request) throws Exception { |
|||
GitRepository repository = checkRepository(user.getTenantId()); |
|||
|
|||
repository.fetch(); |
|||
if (repository.listBranches().contains(request.getBranch())) { |
|||
repository.checkout(request.getBranch()); |
|||
repository.merge(request.getBranch()); |
|||
} else { // TODO [viacheslav]: rollback orphan branch on failure
|
|||
repository.createAndCheckoutOrphanBranch(request.getBranch()); // FIXME [viacheslav]: Checkout returned unexpected result NO_CHANGE for master branch
|
|||
} |
|||
|
|||
switch (request.getType()) { |
|||
case SINGLE_ENTITY: { |
|||
SingleEntityVersionCreateRequest versionCreateRequest = (SingleEntityVersionCreateRequest) request; |
|||
saveEntityData(user, repository, versionCreateRequest.getEntityId(), versionCreateRequest.getConfig()); |
|||
break; |
|||
} |
|||
case ENTITY_LIST: { |
|||
EntityListVersionCreateRequest versionCreateRequest = (EntityListVersionCreateRequest) request; |
|||
if (versionCreateRequest.getConfig().getSyncStrategy() == SyncStrategy.OVERWRITE) { |
|||
versionCreateRequest.getEntitiesIds().stream() |
|||
.map(EntityId::getEntityType).distinct() |
|||
.forEach(entityType -> { |
|||
try { |
|||
FileUtils.deleteDirectory(Path.of(repository.getDirectory(), getRelativePath(entityType, null)).toFile()); |
|||
} catch (IOException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
}); |
|||
} |
|||
for (EntityId entityId : versionCreateRequest.getEntitiesIds()) { |
|||
saveEntityData(user, repository, entityId, versionCreateRequest.getConfig()); |
|||
} |
|||
break; |
|||
} |
|||
case ENTITY_TYPE: { |
|||
EntityTypeVersionCreateRequest versionCreateRequest = (EntityTypeVersionCreateRequest) request; |
|||
versionCreateRequest.getEntityTypes().forEach((entityType, config) -> { |
|||
if (config.getSyncStrategy() == SyncStrategy.OVERWRITE) { |
|||
try { |
|||
FileUtils.deleteDirectory(Path.of(repository.getDirectory(), getRelativePath(entityType, null)).toFile()); |
|||
} catch (IOException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
EntityTypeFilter entityTypeFilter = new EntityTypeFilter(); |
|||
entityTypeFilter.setEntityType(entityType); |
|||
EntityDataPageLink entityDataPageLink = new EntityDataPageLink(); |
|||
entityDataPageLink.setPage(-1); |
|||
entityDataPageLink.setPageSize(-1); |
|||
EntityKey sortProperty = new EntityKey(EntityKeyType.ENTITY_FIELD, CREATED_TIME); |
|||
entityDataPageLink.setSortOrder(new EntityDataSortOrder(sortProperty, EntityDataSortOrder.Direction.DESC)); |
|||
EntityDataQuery query = new EntityDataQuery(entityTypeFilter, entityDataPageLink, List.of(sortProperty), Collections.emptyList(), Collections.emptyList()); |
|||
|
|||
DaoUtil.processInBatches(pageLink -> { |
|||
entityDataPageLink.setPage(pageLink.getPage()); |
|||
entityDataPageLink.setPageSize(pageLink.getPageSize()); |
|||
return entityService.findEntityDataByQuery(user.getTenantId(), new CustomerId(EntityId.NULL_UUID), query); |
|||
}, 100, data -> { |
|||
EntityId entityId = data.getEntityId(); |
|||
try { |
|||
saveEntityData(user, repository, entityId, config); |
|||
} catch (Exception e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
}); |
|||
}); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
repository.add("."); |
|||
|
|||
VersionCreationResult result = new VersionCreationResult(); |
|||
GitRepository.Status status = repository.status(); |
|||
result.setAdded(status.getAdded().size()); |
|||
result.setModified(status.getModified().size()); |
|||
result.setRemoved(status.getRemoved().size()); |
|||
|
|||
GitRepository.Commit commit = repository.commit(request.getVersionName()); |
|||
repository.push(); |
|||
|
|||
result.setVersion(toVersion(commit)); |
|||
return result; |
|||
} |
|||
|
|||
private void saveEntityData(SecurityUser user, GitRepository repository, EntityId entityId, VersionCreateConfig config) throws Exception { |
|||
EntityExportData<ExportableEntity<EntityId>> entityData = exportImportService.exportEntity(user, entityId, EntityExportSettings.builder() |
|||
.exportRelations(config.isSaveRelations()) |
|||
.build()); |
|||
String entityDataJson = jsonWriter.writeValueAsString(entityData); |
|||
FileUtils.write(Path.of(repository.getDirectory(), getRelativePath(entityData.getEntityType(), |
|||
entityData.getEntity().getId().toString())).toFile(), entityDataJson, StandardCharsets.UTF_8); |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public List<EntityVersion> listEntityVersions(TenantId tenantId, String branch, EntityId externalId) throws Exception { |
|||
return listVersions(tenantId, branch, getRelativePath(externalId.getEntityType(), externalId.getId().toString())); |
|||
} |
|||
|
|||
@Override |
|||
public List<EntityVersion> listEntityTypeVersions(TenantId tenantId, String branch, EntityType entityType) throws Exception { |
|||
return listVersions(tenantId, branch, getRelativePath(entityType, null)); |
|||
} |
|||
|
|||
@Override |
|||
public List<EntityVersion> listVersions(TenantId tenantId, String branch) throws Exception { |
|||
return listVersions(tenantId, branch, null); |
|||
} |
|||
|
|||
private List<EntityVersion> listVersions(TenantId tenantId, String branch, String path) throws Exception { |
|||
GitRepository repository = checkRepository(tenantId); |
|||
return repository.listCommits(branch, path, Integer.MAX_VALUE).stream() |
|||
.map(this::toVersion) |
|||
.collect(Collectors.toList()); |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public List<VersionedEntityInfo> listEntitiesAtVersion(TenantId tenantId, EntityType entityType, String branch, String versionId) throws Exception { |
|||
return listEntitiesAtVersion(tenantId, branch, versionId, getRelativePath(entityType, null)); |
|||
} |
|||
|
|||
@Override |
|||
public List<VersionedEntityInfo> listAllEntitiesAtVersion(TenantId tenantId, String branch, String versionId) throws Exception { |
|||
return listEntitiesAtVersion(tenantId, branch, versionId, null); |
|||
} |
|||
|
|||
private List<VersionedEntityInfo> listEntitiesAtVersion(TenantId tenantId, String branch, String versionId, String path) throws Exception { |
|||
GitRepository repository = checkRepository(tenantId); |
|||
checkVersion(tenantId, branch, versionId); |
|||
return repository.listFilesAtCommit(versionId, path).stream() |
|||
.map(filePath -> { |
|||
EntityId entityId = fromRelativePath(filePath); |
|||
VersionedEntityInfo info = new VersionedEntityInfo(); |
|||
info.setExternalId(entityId); |
|||
return info; |
|||
}) |
|||
.collect(Collectors.toList()); |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public List<VersionLoadResult> loadEntitiesVersion(SecurityUser user, VersionLoadRequest request) throws Exception { |
|||
GitRepository repository = checkRepository(user.getTenantId()); |
|||
|
|||
EntityVersion version = checkVersion(user.getTenantId(), request.getBranch(), request.getVersionId()); |
|||
|
|||
switch (request.getType()) { |
|||
case SINGLE_ENTITY: { |
|||
SingleEntityVersionLoadRequest versionLoadRequest = (SingleEntityVersionLoadRequest) request; |
|||
EntityImportResult<?> importResult = transactionTemplate.execute(status -> { |
|||
try { |
|||
EntityImportResult<?> result = loadEntity(user, repository, versionLoadRequest.getExternalEntityId(), version.getId(), versionLoadRequest.getConfig()); |
|||
result.getSaveReferencesCallback().run(); |
|||
return result; |
|||
} catch (Exception e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
}); |
|||
try { |
|||
importResult.getSendEventsCallback().run(); |
|||
} catch (Exception e) { |
|||
log.error("Failed to send events for entity", e); |
|||
} |
|||
|
|||
return List.of(VersionLoadResult.builder() |
|||
.created(importResult.getOldEntity() == null ? 1 : 0) |
|||
.updated(importResult.getOldEntity() != null ? 1 : 0) |
|||
.deleted(0) |
|||
.build()); |
|||
} |
|||
case ENTITY_TYPE: { |
|||
EntityTypeVersionLoadRequest versionLoadRequest = (EntityTypeVersionLoadRequest) request; |
|||
return transactionTemplate.execute(status -> { |
|||
Map<EntityType, VersionLoadResult> results = new HashMap<>(); |
|||
List<ThrowingRunnable> saveReferencesCallbacks = new ArrayList<>(); |
|||
List<ThrowingRunnable> sendEventsCallbacks = new ArrayList<>(); |
|||
// order entity types..
|
|||
// or what
|
|||
versionLoadRequest.getEntityTypes().forEach((entityType, config) -> { |
|||
AtomicInteger created = new AtomicInteger(); |
|||
AtomicInteger updated = new AtomicInteger(); |
|||
AtomicInteger deleted = new AtomicInteger(); |
|||
|
|||
Set<EntityId> remoteEntities; |
|||
try { |
|||
remoteEntities = listEntitiesAtVersion(user.getTenantId(), request.getBranch(), request.getVersionId(), getRelativePath(entityType, null)).stream() |
|||
.map(VersionedEntityInfo::getExternalId) |
|||
.collect(Collectors.toSet()); |
|||
for (EntityId externalEntityId : remoteEntities) { |
|||
EntityImportResult<?> importResult = loadEntity(user, repository, externalEntityId, version.getId(), config); |
|||
|
|||
if (importResult.getOldEntity() == null) created.incrementAndGet(); |
|||
else updated.incrementAndGet(); |
|||
|
|||
saveReferencesCallbacks.add(importResult.getSaveReferencesCallback()); |
|||
sendEventsCallbacks.add(importResult.getSendEventsCallback()); |
|||
} |
|||
} catch (Exception e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
|
|||
if (config.isRemoveOtherEntities()) { |
|||
DaoUtil.processInBatches(pageLink -> { |
|||
return exportableEntitiesService.findEntitiesByTenantId(user.getTenantId(), entityType, pageLink); |
|||
}, 100, entity -> { |
|||
if (entity.getExternalId() == null || !remoteEntities.contains(entity.getExternalId())) { |
|||
try { |
|||
exportableEntitiesService.checkPermission(user, entity, entityType, Operation.DELETE); |
|||
} catch (ThingsboardException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
// need to delete entity types in a specific order?
|
|||
exportableEntitiesService.deleteByTenantIdAndId(user.getTenantId(), entity.getId()); |
|||
deleted.getAndIncrement(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
results.put(entityType, VersionLoadResult.builder() |
|||
.created(created.get()) |
|||
.updated(updated.get()) |
|||
.deleted(deleted.get()) |
|||
.build()); |
|||
}); |
|||
|
|||
for (ThrowingRunnable saveReferencesCallback : saveReferencesCallbacks) { |
|||
try { |
|||
saveReferencesCallback.run(); |
|||
} catch (ThingsboardException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
for (ThrowingRunnable sendEventsCallback : sendEventsCallbacks) { |
|||
try { |
|||
sendEventsCallback.run(); |
|||
} catch (Exception e) { |
|||
log.error("Failed to send events for entity", e); |
|||
} |
|||
} |
|||
return new ArrayList<>(results.values()); |
|||
}); |
|||
} |
|||
default: |
|||
throw new IllegalArgumentException("Unsupported version load request"); |
|||
} |
|||
} |
|||
|
|||
private EntityImportResult<?> loadEntity(SecurityUser user, GitRepository repository, EntityId externalId, String versionId, VersionLoadConfig config) throws Exception { |
|||
String entityDataJson = repository.getFileContentAtCommit(getRelativePath(externalId.getEntityType(), externalId.getId().toString()), versionId); |
|||
EntityExportData entityData = JacksonUtil.fromString(entityDataJson, EntityExportData.class); |
|||
|
|||
return exportImportService.importEntity(user, entityData, EntityImportSettings.builder() |
|||
.updateRelations(config.isLoadRelations()) |
|||
.findExistingByName(config.isFindExistingEntityByName()) |
|||
.build(), false, false); |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public List<String> listBranches(TenantId tenantId) throws Exception { |
|||
GitRepository repository = checkRepository(tenantId); |
|||
return repository.listBranches(); |
|||
} |
|||
|
|||
|
|||
private EntityVersion checkVersion(TenantId tenantId, String branch, String versionId) throws Exception { |
|||
return listVersions(tenantId, branch, null).stream() |
|||
.filter(version -> version.getId().equals(versionId)) |
|||
.findFirst().orElseThrow(() -> new IllegalArgumentException("Version not found")); |
|||
} |
|||
|
|||
private GitRepository checkRepository(TenantId tenantId) { |
|||
return Optional.ofNullable(repositories.get(tenantId)) |
|||
.orElseThrow(() -> new IllegalStateException("Repository is not initialized")); |
|||
} |
|||
|
|||
private void initRepository(TenantId tenantId, EntitiesVersionControlSettings settings) throws Exception { |
|||
Path repositoryDirectory = Path.of(repositoriesFolder, tenantId.getId().toString()); |
|||
GitRepository repository; |
|||
FileUtils.forceDelete(repositoryDirectory.toFile()); |
|||
|
|||
Files.createDirectories(repositoryDirectory); |
|||
repository = GitRepository.clone(settings.getRepositoryUri(), settings.getUsername(), settings.getPassword(), repositoryDirectory.toFile()); |
|||
repositories.put(tenantId, repository); |
|||
} |
|||
|
|||
private void clearRepository(TenantId tenantId) throws IOException { |
|||
GitRepository repository = repositories.get(tenantId); |
|||
if (repository != null) { |
|||
FileUtils.deleteDirectory(new File(repository.getDirectory())); |
|||
repositories.remove(tenantId); |
|||
} |
|||
} |
|||
|
|||
|
|||
@SneakyThrows |
|||
@Override |
|||
public void saveSettings(TenantId tenantId, EntitiesVersionControlSettings settings) { |
|||
attributesService.save(tenantId, tenantId, DataConstants.SERVER_SCOPE, List.of( |
|||
new BaseAttributeKvEntry(System.currentTimeMillis(), new JsonDataEntry(SETTINGS_KEY, JacksonUtil.toString(settings))) |
|||
)).get(); |
|||
|
|||
clearRepository(tenantId); |
|||
initRepository(tenantId, settings); |
|||
} |
|||
|
|||
@SneakyThrows |
|||
@Override |
|||
public EntitiesVersionControlSettings getSettings(TenantId tenantId) { |
|||
return attributesService.find(tenantId, tenantId, DataConstants.SERVER_SCOPE, SETTINGS_KEY).get() |
|||
.flatMap(KvEntry::getJsonValue) |
|||
.map(json -> { |
|||
try { |
|||
return JacksonUtil.fromString(json, EntitiesVersionControlSettings.class); |
|||
} catch (IllegalArgumentException e) { |
|||
return null; |
|||
} |
|||
}) |
|||
.orElse(null); |
|||
} |
|||
|
|||
|
|||
private EntityVersion toVersion(GitRepository.Commit commit) { |
|||
return new EntityVersion(commit.getId(), commit.getMessage()); |
|||
} |
|||
|
|||
private String getRelativePath(EntityType entityType, String entityId) { |
|||
String path = entityType.name().toLowerCase(); |
|||
if (entityId != null) { |
|||
path += "/" + entityId + ".json"; |
|||
} |
|||
return path; |
|||
} |
|||
|
|||
private EntityId fromRelativePath(String path) { |
|||
EntityType entityType = EntityType.valueOf(StringUtils.substringBefore(path, "/").toUpperCase()); |
|||
String entityId = StringUtils.substringBetween(path, "/", ".json"); |
|||
return EntityIdFactory.getByTypeAndUuid(entityType, entityId); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
/** |
|||
* 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.service.sync.vc; |
|||
|
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.sync.vc.data.EntitiesVersionControlSettings; |
|||
import org.thingsboard.server.service.sync.vc.data.EntityVersion; |
|||
import org.thingsboard.server.service.sync.vc.data.VersionCreationResult; |
|||
import org.thingsboard.server.service.sync.vc.data.VersionLoadResult; |
|||
import org.thingsboard.server.service.sync.vc.data.VersionedEntityInfo; |
|||
import org.thingsboard.server.service.sync.vc.data.request.load.VersionLoadRequest; |
|||
import org.thingsboard.server.service.sync.vc.data.request.create.VersionCreateRequest; |
|||
|
|||
import java.util.List; |
|||
|
|||
public interface EntitiesVersionControlService { |
|||
|
|||
VersionCreationResult saveEntitiesVersion(SecurityUser user, VersionCreateRequest request) throws Exception; |
|||
|
|||
|
|||
List<EntityVersion> listEntityVersions(TenantId tenantId, String branch, EntityId externalId) throws Exception; |
|||
|
|||
List<EntityVersion> listEntityTypeVersions(TenantId tenantId, String branch, EntityType entityType) throws Exception; |
|||
|
|||
List<EntityVersion> listVersions(TenantId tenantId, String branch) throws Exception; |
|||
|
|||
|
|||
List<VersionedEntityInfo> listEntitiesAtVersion(TenantId tenantId, EntityType entityType, String branch, String versionId) throws Exception; |
|||
|
|||
List<VersionedEntityInfo> listAllEntitiesAtVersion(TenantId tenantId, String branch, String versionId) throws Exception; |
|||
|
|||
|
|||
List<VersionLoadResult> loadEntitiesVersion(SecurityUser user, VersionLoadRequest request) throws Exception; |
|||
|
|||
|
|||
List<String> listBranches(TenantId tenantId) throws Exception; |
|||
|
|||
|
|||
void saveSettings(TenantId tenantId, EntitiesVersionControlSettings settings); |
|||
|
|||
EntitiesVersionControlSettings getSettings(TenantId tenantId); |
|||
|
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
/** |
|||
* 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.service.sync.vc.data; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class EntitiesVersionControlSettings { |
|||
private String repositoryUri; |
|||
private String username; |
|||
private String password; |
|||
private String defaultBranch; |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* 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.service.sync.vc.data; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
|
|||
@Data |
|||
@AllArgsConstructor |
|||
@NoArgsConstructor |
|||
public class EntityVersion { |
|||
private String id; |
|||
private String name; |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
/** |
|||
* 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.service.sync.vc.data; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class VersionCreationResult { |
|||
private EntityVersion version; |
|||
private int added; |
|||
private int modified; |
|||
private int removed; |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
/** |
|||
* 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.service.sync.vc.data; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
|
|||
@Data |
|||
@AllArgsConstructor |
|||
@NoArgsConstructor |
|||
@Builder |
|||
public class VersionLoadResult { |
|||
private EntityType entityType; |
|||
private int created; |
|||
private int updated; |
|||
private int deleted; |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
/** |
|||
* 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.service.sync.vc.data; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
|
|||
@Data |
|||
public class VersionedEntityInfo { |
|||
private EntityId externalId; |
|||
// etc..
|
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
/** |
|||
* 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.service.sync.vc.data.request.create; |
|||
|
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
|
|||
import java.util.List; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class EntityListVersionCreateRequest extends VersionCreateRequest { |
|||
|
|||
private List<EntityId> entitiesIds; |
|||
private MultipleEntitiesVersionCreateConfig config; |
|||
|
|||
@Override |
|||
public VersionCreateRequestType getType() { |
|||
return VersionCreateRequestType.ENTITY_LIST; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
/** |
|||
* 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.service.sync.vc.data.request.create; |
|||
|
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
|
|||
import java.util.Map; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class EntityTypeVersionCreateRequest extends VersionCreateRequest { |
|||
|
|||
private Map<EntityType, MultipleEntitiesVersionCreateConfig> entityTypes; |
|||
|
|||
@Override |
|||
public VersionCreateRequestType getType() { |
|||
return VersionCreateRequestType.ENTITY_TYPE; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
/** |
|||
* 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.service.sync.vc.data.request.create; |
|||
|
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class MultipleEntitiesVersionCreateConfig extends VersionCreateConfig { |
|||
private SyncStrategy syncStrategy; |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* 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.service.sync.vc.data.request.create; |
|||
|
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class SingleEntityVersionCreateRequest extends VersionCreateRequest { |
|||
|
|||
private EntityId entityId; |
|||
private VersionCreateConfig config; |
|||
|
|||
@Override |
|||
public VersionCreateRequestType getType() { |
|||
return VersionCreateRequestType.SINGLE_ENTITY; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
/** |
|||
* 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.service.sync.vc.data.request.create; |
|||
|
|||
public enum SyncStrategy { |
|||
MERGE, |
|||
OVERWRITE |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
/** |
|||
* 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.service.sync.vc.data.request.create; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class VersionCreateConfig { |
|||
private boolean saveRelations; |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
/** |
|||
* 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.service.sync.vc.data.request.create; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonSubTypes; |
|||
import com.fasterxml.jackson.annotation.JsonSubTypes.Type; |
|||
import com.fasterxml.jackson.annotation.JsonTypeInfo; |
|||
import lombok.Data; |
|||
|
|||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") |
|||
@JsonSubTypes({ |
|||
@Type(name = "SINGLE_ENTITY", value = SingleEntityVersionCreateRequest.class), |
|||
@Type(name = "ENTITY_LIST", value = EntityListVersionCreateRequest.class), |
|||
@Type(name = "ENTITY_TYPE", value = EntityTypeVersionCreateRequest.class) |
|||
}) |
|||
@Data |
|||
public abstract class VersionCreateRequest { |
|||
|
|||
private String versionName; |
|||
private String branch; |
|||
|
|||
public abstract VersionCreateRequestType getType(); |
|||
|
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.sync.vc.data.request.create; |
|||
|
|||
public enum VersionCreateRequestType { |
|||
SINGLE_ENTITY, |
|||
ENTITY_LIST, |
|||
ENTITY_TYPE |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.sync.vc.data.request.load; |
|||
|
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class EntityTypeVersionLoadConfig extends VersionLoadConfig { |
|||
|
|||
private boolean removeOtherEntities; |
|||
|
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
/** |
|||
* 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.service.sync.vc.data.request.load; |
|||
|
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
|
|||
import java.util.Map; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class EntityTypeVersionLoadRequest extends VersionLoadRequest { |
|||
|
|||
private Map<EntityType, EntityTypeVersionLoadConfig> entityTypes; |
|||
|
|||
@Override |
|||
public VersionLoadRequestType getType() { |
|||
return VersionLoadRequestType.ENTITY_TYPE; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
/** |
|||
* 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.service.sync.vc.data.request.load; |
|||
|
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class SingleEntityVersionLoadRequest extends VersionLoadRequest { |
|||
|
|||
private EntityId externalEntityId; |
|||
|
|||
private VersionLoadConfig config; |
|||
|
|||
@Override |
|||
public VersionLoadRequestType getType() { |
|||
return VersionLoadRequestType.SINGLE_ENTITY; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
/** |
|||
* 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.service.sync.vc.data.request.load; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class VersionLoadConfig { |
|||
|
|||
private boolean loadRelations; |
|||
private boolean findExistingEntityByName; |
|||
|
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
/** |
|||
* 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.service.sync.vc.data.request.load; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonSubTypes; |
|||
import com.fasterxml.jackson.annotation.JsonTypeInfo; |
|||
import lombok.Data; |
|||
|
|||
import static com.fasterxml.jackson.annotation.JsonSubTypes.Type; |
|||
|
|||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") |
|||
@JsonSubTypes({ |
|||
@Type(name = "SINGLE_ENTITY", value = SingleEntityVersionLoadRequest.class), |
|||
@Type(name = "ENTITY_TYPE", value = EntityTypeVersionLoadRequest.class) |
|||
}) |
|||
@Data |
|||
public abstract class VersionLoadRequest { |
|||
|
|||
private String branch; |
|||
private String versionId; |
|||
|
|||
public abstract VersionLoadRequestType getType(); |
|||
|
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
/** |
|||
* 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.service.sync.vc.data.request.load; |
|||
|
|||
public enum VersionLoadRequestType { |
|||
SINGLE_ENTITY, |
|||
ENTITY_TYPE |
|||
} |
|||
@ -0,0 +1,274 @@ |
|||
/** |
|||
* 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.google.common.collect.Streams; |
|||
import lombok.Data; |
|||
import lombok.Getter; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.eclipse.jgit.api.Git; |
|||
import org.eclipse.jgit.api.GitCommand; |
|||
import org.eclipse.jgit.api.ListBranchCommand; |
|||
import org.eclipse.jgit.api.LogCommand; |
|||
import org.eclipse.jgit.api.RmCommand; |
|||
import org.eclipse.jgit.api.Status; |
|||
import org.eclipse.jgit.api.TransportCommand; |
|||
import org.eclipse.jgit.api.errors.GitAPIException; |
|||
import org.eclipse.jgit.lib.Constants; |
|||
import org.eclipse.jgit.lib.ObjectId; |
|||
import org.eclipse.jgit.lib.ObjectLoader; |
|||
import org.eclipse.jgit.lib.ObjectReader; |
|||
import org.eclipse.jgit.revwalk.RevCommit; |
|||
import org.eclipse.jgit.revwalk.filter.RevFilter; |
|||
import org.eclipse.jgit.transport.CredentialsProvider; |
|||
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; |
|||
import org.eclipse.jgit.treewalk.TreeWalk; |
|||
import org.eclipse.jgit.treewalk.filter.PathFilter; |
|||
|
|||
import java.io.File; |
|||
import java.io.IOException; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.Set; |
|||
import java.util.concurrent.locks.ReadWriteLock; |
|||
import java.util.concurrent.locks.ReentrantReadWriteLock; |
|||
import java.util.stream.Collectors; |
|||
|
|||
public class GitRepository { |
|||
|
|||
private final Git git; |
|||
private final CredentialsProvider credentialsProvider; |
|||
|
|||
@Getter |
|||
private final String directory; |
|||
|
|||
private GitRepository(Git git, CredentialsProvider credentialsProvider, String directory) { |
|||
this.git = git; |
|||
this.credentialsProvider = credentialsProvider; |
|||
this.directory = directory; |
|||
} |
|||
|
|||
public static GitRepository clone(String uri, String username, String password, File directory) throws GitAPIException { |
|||
CredentialsProvider credentialsProvider = newCredentialsProvider(username, password); |
|||
Git git = Git.cloneRepository() |
|||
.setURI(uri) |
|||
.setDirectory(directory) |
|||
.setNoCheckout(true) |
|||
.setCredentialsProvider(credentialsProvider) |
|||
.call(); |
|||
return new GitRepository(git, credentialsProvider, directory.getAbsolutePath()); |
|||
} |
|||
|
|||
public static GitRepository open(File directory, String username, String password) throws IOException { |
|||
Git git = Git.open(directory); |
|||
return new GitRepository(git, newCredentialsProvider(username, password), directory.getAbsolutePath()); |
|||
} |
|||
|
|||
|
|||
public void fetch() throws GitAPIException { |
|||
execute(git.fetch() |
|||
.setRemoveDeletedRefs(true)); |
|||
} |
|||
|
|||
public void checkout(String branch) throws GitAPIException { |
|||
execute(git.checkout() |
|||
.setName("origin/" + branch)); |
|||
} |
|||
|
|||
public void merge(String branch) throws IOException, GitAPIException { |
|||
ObjectId branchId = resolve("origin/" + branch); |
|||
if (branchId == null) { |
|||
throw new IllegalArgumentException("Branch not found"); |
|||
} |
|||
execute(git.merge() |
|||
.include(branchId)); |
|||
} |
|||
|
|||
|
|||
public List<String> listBranches() throws GitAPIException { |
|||
return execute(git.branchList() |
|||
.setListMode(ListBranchCommand.ListMode.ALL)).stream() |
|||
.filter(ref -> !ref.getName().equals(Constants.HEAD)) |
|||
.map(ref -> org.eclipse.jgit.lib.Repository.shortenRefName(ref.getName())) |
|||
.map(name -> StringUtils.removeStart(name, "origin/")) |
|||
.distinct().collect(Collectors.toList()); |
|||
} |
|||
|
|||
|
|||
public List<Commit> listCommits(String branch, int limit) throws IOException, GitAPIException { |
|||
return listCommits(branch, null, limit); |
|||
} |
|||
|
|||
public List<Commit> listCommits(String branch, String path, int limit) throws IOException, GitAPIException { |
|||
ObjectId branchId = resolve("origin/" + branch); |
|||
if (branchId == null) { |
|||
throw new IllegalArgumentException("Branch not found"); |
|||
} |
|||
LogCommand command = git.log() |
|||
.add(branchId).setMaxCount(limit) |
|||
.setRevFilter(RevFilter.NO_MERGES); |
|||
if (StringUtils.isNotEmpty(path)) { |
|||
command.addPath(path); |
|||
} |
|||
return Streams.stream(execute(command)) |
|||
.map(this::toCommit) |
|||
.collect(Collectors.toList()); |
|||
} |
|||
|
|||
|
|||
public List<String> listFilesAtCommit(String commitId) throws IOException { |
|||
return listFilesAtCommit(commitId, null); |
|||
} |
|||
|
|||
public List<String> listFilesAtCommit(String commitId, String path) throws IOException { |
|||
List<String> files = new ArrayList<>(); |
|||
RevCommit revCommit = resolveCommit(commitId); |
|||
try (TreeWalk treeWalk = new TreeWalk(git.getRepository())) { |
|||
treeWalk.reset(revCommit.getTree().getId()); |
|||
if (StringUtils.isNotEmpty(path)) { |
|||
treeWalk.setFilter(PathFilter.create(path)); |
|||
} |
|||
treeWalk.setRecursive(true); |
|||
while (treeWalk.next()) { |
|||
files.add(treeWalk.getPathString()); |
|||
} |
|||
} |
|||
return files; |
|||
} |
|||
|
|||
|
|||
public String getFileContentAtCommit(String file, String commitId) throws IOException { |
|||
RevCommit revCommit = resolveCommit(commitId); |
|||
try (TreeWalk treeWalk = TreeWalk.forPath(git.getRepository(), file, revCommit.getTree())) { |
|||
if (treeWalk == null) { |
|||
throw new IllegalArgumentException("Not found"); |
|||
} |
|||
ObjectId blobId = treeWalk.getObjectId(0); |
|||
try (ObjectReader objectReader = git.getRepository().newObjectReader()) { |
|||
ObjectLoader objectLoader = objectReader.open(blobId); |
|||
byte[] bytes = objectLoader.getBytes(); |
|||
return new String(bytes, StandardCharsets.UTF_8); |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
public void createAndCheckoutOrphanBranch(String name) throws GitAPIException { |
|||
execute(git.checkout() |
|||
.setOrphan(true) |
|||
.setName(name)); |
|||
Set<String> uncommittedChanges = git.status().call().getUncommittedChanges(); |
|||
if (!uncommittedChanges.isEmpty()) { |
|||
RmCommand rm = git.rm(); |
|||
uncommittedChanges.forEach(rm::addFilepattern); |
|||
execute(rm); |
|||
} |
|||
execute(git.clean()); |
|||
} |
|||
|
|||
public void add(String filesPattern) throws GitAPIException { // FIXME [viacheslav]
|
|||
execute(git.add().setUpdate(true).addFilepattern(filesPattern)); |
|||
execute(git.add().addFilepattern(filesPattern)); |
|||
} |
|||
|
|||
public Status status() throws GitAPIException { |
|||
org.eclipse.jgit.api.Status status = execute(git.status()); |
|||
return new Status(status.getAdded(), status.getModified(), status.getRemoved()); |
|||
} |
|||
|
|||
public Commit commit(String message) throws GitAPIException { |
|||
RevCommit revCommit = execute(git.commit() |
|||
.setMessage(message)); // TODO [viacheslav]: set configurable author for commit
|
|||
return toCommit(revCommit); |
|||
} |
|||
|
|||
|
|||
public void push() throws GitAPIException { |
|||
execute(git.push()); |
|||
} |
|||
|
|||
|
|||
// public List<Diff> getCommitChanges(Commit commit) throws IOException, GitAPIException {
|
|||
// RevCommit revCommit = resolveCommit(commit.getId());
|
|||
// if (revCommit.getParentCount() == 0) {
|
|||
// return null; // just takes the first parent of a commit, but should find a parent in branch provided
|
|||
// }
|
|||
// return execute(git.diff()
|
|||
// .setOldTree(prepareTreeParser(git.getRepository().parseCommit(revCommit.getParent(0))))
|
|||
// .setNewTree(prepareTreeParser(revCommit))).stream()
|
|||
// .map(diffEntry -> new Diff(diffEntry.getChangeType().name(), diffEntry.getOldPath(), diffEntry.getNewPath()))
|
|||
// .collect(Collectors.toList());
|
|||
// }
|
|||
//
|
|||
//
|
|||
// private AbstractTreeIterator prepareTreeParser(RevCommit revCommit) throws IOException {
|
|||
// // from the commit we can build the tree which allows us to construct the TreeParser
|
|||
// //noinspection Duplicates
|
|||
// org.eclipse.jgit.lib.Repository repository = git.getRepository();
|
|||
// try (RevWalk walk = new RevWalk(repository)) {
|
|||
// RevTree tree = walk.parseTree(revCommit.getTree().getId());
|
|||
//
|
|||
// CanonicalTreeParser treeParser = new CanonicalTreeParser();
|
|||
// try (ObjectReader reader = repository.newObjectReader()) {
|
|||
// treeParser.reset(reader, tree.getId());
|
|||
// }
|
|||
//
|
|||
// walk.dispose();
|
|||
//
|
|||
// return treeParser;
|
|||
// }
|
|||
// }
|
|||
|
|||
private Commit toCommit(RevCommit revCommit) { |
|||
return new Commit(revCommit.getName(), revCommit.getFullMessage(), revCommit.getAuthorIdent().getName()); |
|||
} |
|||
|
|||
private RevCommit resolveCommit(String id) throws IOException { |
|||
return git.getRepository().parseCommit(resolve(id)); |
|||
} |
|||
|
|||
private ObjectId resolve(String rev) throws IOException { |
|||
return git.getRepository().resolve(rev); |
|||
} |
|||
|
|||
private <C extends GitCommand<T>, T> T execute(C command) throws GitAPIException { |
|||
if (command instanceof TransportCommand && credentialsProvider != null) { |
|||
((TransportCommand<?, ?>) command).setCredentialsProvider(credentialsProvider); |
|||
} |
|||
return command.call(); |
|||
} |
|||
|
|||
private static CredentialsProvider newCredentialsProvider(String username, String password) { |
|||
return new UsernamePasswordCredentialsProvider(username, password); |
|||
} |
|||
|
|||
|
|||
@Data |
|||
public static class Commit { |
|||
private final String id; |
|||
private final String message; |
|||
private final String authorName; |
|||
} |
|||
|
|||
@Data |
|||
public static class Status { |
|||
private final Set<String> added; |
|||
private final Set<String> modified; |
|||
private final Set<String> removed; |
|||
} |
|||
|
|||
} |
|||
@ -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 { |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
/** |
|||
* 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 lombok.AccessLevel; |
|||
import lombok.NoArgsConstructor; |
|||
|
|||
import java.util.function.UnaryOperator; |
|||
import java.util.regex.Pattern; |
|||
|
|||
@NoArgsConstructor(access = AccessLevel.PRIVATE) |
|||
public class RegexUtils { |
|||
|
|||
public static final Pattern UUID_PATTERN = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); |
|||
|
|||
|
|||
public static String replace(String s, Pattern pattern, UnaryOperator<String> replacer) { |
|||
return pattern.matcher(s).replaceAll(matchResult -> { |
|||
return replacer.apply(matchResult.group()); |
|||
}); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
/** |
|||
* 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 org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
|
|||
public interface ThrowingRunnable { |
|||
|
|||
void run() throws ThingsboardException; |
|||
|
|||
default ThrowingRunnable andThen(ThrowingRunnable after) { |
|||
return () -> { |
|||
this.run(); |
|||
after.run(); |
|||
}; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,389 @@ |
|||
/** |
|||
* 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.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; |
|||
import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; |
|||
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.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; |
|||
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; |
|||
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.exportimport.exporting.data.EntityExportData; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportSettings; |
|||
import org.thingsboard.server.service.sync.vc.data.request.create.VersionCreateConfig; |
|||
import org.thingsboard.server.service.sync.vc.data.request.create.SingleEntityVersionCreateConfig; |
|||
import org.thingsboard.server.service.sync.exportimport.importing.data.EntityImportResult; |
|||
import org.thingsboard.server.service.sync.exportimport.importing.data.EntityImportSettings; |
|||
import org.thingsboard.server.service.sync.exportimport.importing.data.ImportRequest; |
|||
|
|||
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 { |
|||
|
|||
@Autowired |
|||
protected DeviceService deviceService; |
|||
@Autowired |
|||
protected OtaPackageService otaPackageService; |
|||
@Autowired |
|||
protected DeviceProfileService deviceProfileService; |
|||
@Autowired |
|||
protected AssetService assetService; |
|||
@Autowired |
|||
protected CustomerService customerService; |
|||
@Autowired |
|||
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(); |
|||
device.setTenantId(tenantId); |
|||
device.setCustomerId(customerId); |
|||
device.setName(name); |
|||
device.setLabel("lbl"); |
|||
device.setDeviceProfileId(deviceProfileId); |
|||
DeviceData deviceData = new DeviceData(); |
|||
deviceData.setTransportConfiguration(new DefaultDeviceTransportConfiguration()); |
|||
device.setDeviceData(deviceData); |
|||
return deviceService.saveDevice(device); |
|||
} |
|||
|
|||
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()); |
|||
deviceProfile.setProfileData(profileData); |
|||
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); |
|||
asset.setCustomerId(customerId); |
|||
asset.setType(type); |
|||
asset.setName(name); |
|||
asset.setLabel("lbl"); |
|||
asset.setAdditionalInfo(JacksonUtil.newObjectNode().set("a", new TextNode("b"))); |
|||
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); |
|||
customer.setTitle(name); |
|||
customer.setCountry("ua"); |
|||
customer.setAddress("abb"); |
|||
customer.setEmail("ccc@aa.org"); |
|||
customer.setAdditionalInfo(JacksonUtil.newObjectNode().set("a", new TextNode("b"))); |
|||
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); |
|||
dashboard.setTitle(name); |
|||
dashboard.setConfiguration(JacksonUtil.newObjectNode().set("a", new TextNode("b"))); |
|||
dashboard.setImage("abvregewrg"); |
|||
dashboard.setMobileHide(true); |
|||
dashboard = dashboardService.saveDashboard(dashboard); |
|||
if (customerId != null) { |
|||
dashboardService.assignDashboardToCustomer(tenantId, dashboard.getId(), customerId); |
|||
return dashboardService.findDashboardById(tenantId, dashboard.getId()); |
|||
} |
|||
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); |
|||
ruleChain.setName(name); |
|||
ruleChain.setType(RuleChainType.CORE); |
|||
ruleChain.setDebugMode(true); |
|||
ruleChain.setConfiguration(JacksonUtil.newObjectNode().set("a", new TextNode("b"))); |
|||
ruleChain = ruleChainService.saveRuleChain(ruleChain); |
|||
|
|||
RuleChainMetaData metaData = new RuleChainMetaData(); |
|||
metaData.setRuleChainId(ruleChain.getId()); |
|||
|
|||
RuleNode ruleNode1 = new RuleNode(); |
|||
ruleNode1.setName("Simple Rule Node 1"); |
|||
ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); |
|||
ruleNode1.setDebugMode(true); |
|||
TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration(); |
|||
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1")); |
|||
ruleNode1.setConfiguration(mapper.valueToTree(configuration1)); |
|||
|
|||
RuleNode ruleNode2 = new RuleNode(); |
|||
ruleNode2.setName("Simple Rule Node 2"); |
|||
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); |
|||
ruleNode2.setDebugMode(true); |
|||
TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration(); |
|||
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2")); |
|||
ruleNode2.setConfiguration(mapper.valueToTree(configuration2)); |
|||
|
|||
metaData.setNodes(Arrays.asList(ruleNode1, ruleNode2)); |
|||
metaData.setFirstNodeIndex(0); |
|||
metaData.addConnectionInfo(0, 1, "Success"); |
|||
ruleChainService.saveRuleChainMetaData(tenantId, metaData); |
|||
|
|||
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 <E extends ExportableEntity<?> & 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 <E extends ExportableEntity<I>, I extends EntityId> EntityExportData<E> exportSingleEntity(I entityId) throws Exception { |
|||
SingleEntityVersionCreateConfig exportRequest = new SingleEntityVersionCreateConfig(); |
|||
exportRequest.setEntityId(entityId); |
|||
exportRequest.setExportSettings(new EntityExportSettings()); |
|||
return (EntityExportData<E>) exportEntities(exportRequest).get(0); |
|||
} |
|||
|
|||
protected List<EntityExportData<?>> exportEntities(VersionCreateConfig exportRequest) throws Exception { |
|||
return getResponse(doPost("/api/entities/export", exportRequest), new TypeReference<List<EntityExportData<?>>>() {}); |
|||
} |
|||
|
|||
protected List<EntityExportData<?>> exportEntities(List<VersionCreateConfig> exportRequests) throws Exception { |
|||
return getResponse(doPost("/api/entities/export?multiple", exportRequests), new TypeReference<List<EntityExportData<?>>>() {}); |
|||
} |
|||
|
|||
|
|||
protected <E extends ExportableEntity<I>, I extends EntityId> EntityImportResult<E> importEntity(EntityExportData<E> exportData) throws Exception { |
|||
return (EntityImportResult<E>) importEntities(List.of((EntityExportData<ExportableEntity<EntityId>>) exportData)).get(0); |
|||
} |
|||
|
|||
protected List<EntityImportResult<?>> importEntities(List<EntityExportData<?>> exportDataList) throws Exception { |
|||
ImportRequest importRequest = new ImportRequest(); |
|||
importRequest.setImportSettings(new EntityImportSettings()); |
|||
importRequest.setExportDataList(exportDataList); |
|||
return importEntities(importRequest); |
|||
} |
|||
|
|||
protected List<EntityImportResult<?>> importEntities(ImportRequest importRequest) throws Exception { |
|||
return getResponse(doPost("/api/entities/import", importRequest), new TypeReference<List<EntityImportResult<?>>>() {}); |
|||
} |
|||
|
|||
protected <T> T getResponse(ResultActions resultActions, TypeReference<T> typeReference) throws Exception { |
|||
try { |
|||
return readResponse(resultActions.andExpect(status().isOk()), typeReference); |
|||
} catch (AssertionError e) { |
|||
throw new AssertionError(readResponse(resultActions, String.class), e); |
|||
} |
|||
} |
|||
|
|||
|
|||
protected void logInAsTenantAdmin1() throws Exception { |
|||
login(tenantAdmin1.getEmail(), "12345678"); |
|||
} |
|||
|
|||
protected void logInAsTenantAdmin2() throws Exception { |
|||
login(tenantAdmin2.getEmail(), "12345678"); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,738 @@ |
|||
/** |
|||
* 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.controller.sql; |
|||
|
|||
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.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.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.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.service.action.EntityActionService; |
|||
import org.thingsboard.server.service.ota.OtaPackageStateService; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.DeviceExportData; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportData; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.RuleChainExportData; |
|||
import org.thingsboard.server.service.sync.vc.data.request.create.EntitiesByCustomFilterVersionCreateConfig; |
|||
import org.thingsboard.server.service.sync.exportimport.exporting.data.EntityExportSettings; |
|||
import org.thingsboard.server.service.sync.vc.data.request.create.EntityListVersionCreateConfig; |
|||
import org.thingsboard.server.service.sync.vc.data.request.create.EntityTypeVersionCreateConfig; |
|||
import org.thingsboard.server.service.sync.vc.data.request.create.VersionCreateConfig; |
|||
import org.thingsboard.server.service.sync.vc.data.request.create.SingleEntityVersionCreateConfig; |
|||
import org.thingsboard.server.service.sync.exportimport.importing.data.EntityImportResult; |
|||
import org.thingsboard.server.service.sync.exportimport.importing.data.EntityImportSettings; |
|||
import org.thingsboard.server.service.sync.exportimport.importing.data.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 DeviceCredentialsService deviceCredentialsService; |
|||
@SpyBean |
|||
private EntityActionService entityActionService; |
|||
@SpyBean |
|||
private TbClusterService clusterService; |
|||
@SpyBean |
|||
private OtaPackageStateService otaPackageStateService; |
|||
|
|||
@Test |
|||
public void testExportImportAsset_betweenTenants() throws Exception { |
|||
logInAsTenantAdmin1(); |
|||
Asset asset = createAsset(tenantId1, null, "AB", "Asset of tenant 1"); |
|||
EntityExportData<Asset> exportData = exportSingleEntity(asset.getId()); |
|||
assertThat(exportData.getEntity()).isEqualTo(asset); |
|||
|
|||
logInAsTenantAdmin2(); |
|||
EntityImportResult<Asset> importResult = importEntity(exportData); |
|||
|
|||
checkImportedEntity(tenantId1, asset, tenantId2, importResult.getSavedEntity()); |
|||
checkImportedAssetData(asset, importResult.getSavedEntity()); |
|||
} |
|||
|
|||
@Test |
|||
public void testExportImportAsset_sameTenant() throws Exception { |
|||
logInAsTenantAdmin1(); |
|||
Asset asset = createAsset(tenantId1, null, "AB", "Asset v1.0"); |
|||
EntityExportData<Asset> exportData = exportSingleEntity(asset.getId()); |
|||
|
|||
EntityImportResult<Asset> importResult = importEntity(exportData); |
|||
|
|||
checkImportedEntity(tenantId1, asset, tenantId1, importResult.getSavedEntity()); |
|||
checkImportedAssetData(asset, importResult.getSavedEntity()); |
|||
} |
|||
|
|||
@Test |
|||
public void testExportImportAsset_sameTenant_withCustomer() throws Exception { |
|||
logInAsTenantAdmin1(); |
|||
Customer customer = createCustomer(tenantId1, "My customer"); |
|||
Asset asset = createAsset(tenantId1, customer.getId(), "AB", "My asset"); |
|||
|
|||
Asset importedAsset = importEntity(this.<Asset, AssetId>exportSingleEntity(asset.getId())).getSavedEntity(); |
|||
|
|||
assertThat(importedAsset.getCustomerId()).isEqualTo(asset.getCustomerId()); |
|||
} |
|||
|
|||
|
|||
@Test |
|||
public void testExportImportCustomer_betweenTenants() throws Exception { |
|||
logInAsTenantAdmin1(); |
|||
Customer customer = createCustomer(tenantAdmin1.getTenantId(), "Customer of tenant 1"); |
|||
EntityExportData<Customer> exportData = exportSingleEntity(customer.getId()); |
|||
assertThat(exportData.getEntity()).isEqualTo(customer); |
|||
|
|||
logInAsTenantAdmin2(); |
|||
EntityImportResult<Customer> importResult = importEntity(exportData); |
|||
|
|||
checkImportedEntity(tenantId1, customer, tenantId2, importResult.getSavedEntity()); |
|||
checkImportedCustomerData(customer, importResult.getSavedEntity()); |
|||
} |
|||
|
|||
@Test |
|||
public void testExportImportCustomer_sameTenant() throws Exception { |
|||
logInAsTenantAdmin1(); |
|||
Customer customer = createCustomer(tenantAdmin1.getTenantId(), "Customer v1.0"); |
|||
EntityExportData<Customer> exportData = exportSingleEntity(customer.getId()); |
|||
|
|||
EntityImportResult<Customer> importResult = importEntity(exportData); |
|||
|
|||
checkImportedEntity(tenantId1, customer, tenantId1, importResult.getSavedEntity()); |
|||
checkImportedCustomerData(customer, importResult.getSavedEntity()); |
|||
} |
|||
|
|||
|
|||
@Test |
|||
public void testExportImportDeviceWithProfile_betweenTenants() throws Exception { |
|||
logInAsTenantAdmin1(); |
|||
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()); |
|||
|
|||
EntityExportData<DeviceProfile> profileExportData = exportSingleEntity(deviceProfile.getId()); |
|||
assertThat(profileExportData.getEntity()).isEqualTo(deviceProfile); |
|||
|
|||
EntityExportData<Device> deviceExportData = exportSingleEntity(device.getId()); |
|||
assertThat(deviceExportData.getEntity()).isEqualTo(device); |
|||
assertThat(((DeviceExportData) deviceExportData).getCredentials()).isEqualTo(credentials); |
|||
DeviceCredentials exportedCredentials = ((DeviceExportData) deviceExportData).getCredentials(); |
|||
exportedCredentials.setCredentialsId(credentials.getCredentialsId() + "a"); |
|||
|
|||
logInAsTenantAdmin2(); |
|||
EntityImportResult<DeviceProfile> profileImportResult = importEntity(profileExportData); |
|||
checkImportedEntity(tenantId1, deviceProfile, tenantId2, profileImportResult.getSavedEntity()); |
|||
checkImportedDeviceProfileData(deviceProfile, profileImportResult.getSavedEntity()); |
|||
|
|||
|
|||
EntityImportResult<Device> deviceImportResult = importEntity(deviceExportData); |
|||
Device importedDevice = deviceImportResult.getSavedEntity(); |
|||
checkImportedEntity(tenantId1, device, tenantId2, deviceImportResult.getSavedEntity()); |
|||
checkImportedDeviceData(device, importedDevice); |
|||
|
|||
assertThat(importedDevice.getDeviceProfileId()).isEqualTo(profileImportResult.getSavedEntity().getId()); |
|||
|
|||
DeviceCredentials importedCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId2, importedDevice.getId()); |
|||
assertThat(importedCredentials.getId()).isNotEqualTo(credentials.getId()); |
|||
assertThat(importedCredentials.getCredentialsId()).isEqualTo(exportedCredentials.getCredentialsId()); |
|||
assertThat(importedCredentials.getCredentialsValue()).isEqualTo(credentials.getCredentialsValue()); |
|||
assertThat(importedCredentials.getCredentialsType()).isEqualTo(credentials.getCredentialsType()); |
|||
} |
|||
|
|||
@Test |
|||
public void testExportImportDevice_sameTenant() throws Exception { |
|||
logInAsTenantAdmin1(); |
|||
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<Device> deviceExportData = exportSingleEntity(device.getId()); |
|||
|
|||
EntityImportResult<Device> importResult = importEntity(deviceExportData); |
|||
Device importedDevice = importResult.getSavedEntity(); |
|||
|
|||
checkImportedEntity(tenantId1, device, tenantId1, importResult.getSavedEntity()); |
|||
assertThat(importedDevice.getDeviceProfileId()).isEqualTo(device.getDeviceProfileId()); |
|||
assertThat(deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId1, device.getId())).isEqualTo(credentials); |
|||
assertThat(importedDevice.getFirmwareId()).isEqualTo(firmware.getId()); |
|||
assertThat(importedDevice.getSoftwareId()).isEqualTo(software.getId()); |
|||
} |
|||
|
|||
|
|||
@Test |
|||
public void testExportImportDashboard_betweenTenants() throws Exception { |
|||
logInAsTenantAdmin1(); |
|||
Dashboard dashboard = createDashboard(tenantAdmin1.getTenantId(), null, "Dashboard of tenant 1"); |
|||
|
|||
EntityExportData<Dashboard> exportData = exportSingleEntity(dashboard.getId()); |
|||
assertThat(exportData.getEntity()).isEqualTo(dashboard); |
|||
|
|||
logInAsTenantAdmin2(); |
|||
EntityImportResult<Dashboard> 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<Dashboard> exportData = exportSingleEntity(dashboard.getId()); |
|||
|
|||
EntityImportResult<Dashboard> 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<Dashboard> 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<Customer> 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); |
|||
|
|||
EntityTypeVersionCreateConfig assetsExportRequest = new EntityTypeVersionCreateConfig(); |
|||
assetsExportRequest.setEntityType(EntityType.ASSET); |
|||
assetsExportRequest.setPageSize(10); |
|||
assetsExportRequest.setExportSettings(new EntityExportSettings()); |
|||
EntityTypeVersionCreateConfig dashboardsExportRequest = new EntityTypeVersionCreateConfig(); |
|||
dashboardsExportRequest.setEntityType(EntityType.DASHBOARD); |
|||
dashboardsExportRequest.setPageSize(10); |
|||
dashboardsExportRequest.setExportSettings(new EntityExportSettings()); |
|||
List<EntityExportData<?>> exportDataList = exportEntities(List.of(assetsExportRequest, dashboardsExportRequest)); |
|||
|
|||
logInAsTenantAdmin2(); |
|||
Map<EntityType, List<EntityImportResult<?>>> 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<String> 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()); |
|||
|
|||
EntityExportData<RuleChain> exportData = exportSingleEntity(ruleChain.getId()); |
|||
assertThat(exportData.getEntity()).isEqualTo(ruleChain); |
|||
assertThat(((RuleChainExportData) exportData).getMetaData()).isEqualTo(metaData); |
|||
|
|||
logInAsTenantAdmin2(); |
|||
EntityImportResult<RuleChain> importResult = importEntity(exportData); |
|||
RuleChain importedRuleChain = importResult.getSavedEntity(); |
|||
RuleChainMetaData importedMetaData = ruleChainService.loadRuleChainMetaData(tenantId2, importedRuleChain.getId()); |
|||
|
|||
checkImportedEntity(tenantId1, ruleChain, tenantId2, importResult.getSavedEntity()); |
|||
checkImportedRuleChainData(ruleChain, metaData, importedRuleChain, importedMetaData); |
|||
} |
|||
|
|||
@Test |
|||
public void testExportImportRuleChain_sameTenant() throws Exception { |
|||
logInAsTenantAdmin1(); |
|||
RuleChain ruleChain = createRuleChain(tenantId1, "Rule chain v1.0"); |
|||
RuleChainMetaData metaData = ruleChainService.loadRuleChainMetaData(tenantId1, ruleChain.getId()); |
|||
|
|||
EntityExportData<RuleChain> exportData = exportSingleEntity(ruleChain.getId()); |
|||
|
|||
EntityImportResult<RuleChain> importResult = importEntity(exportData); |
|||
RuleChain importedRuleChain = importResult.getSavedEntity(); |
|||
RuleChainMetaData importedMetaData = ruleChainService.loadRuleChainMetaData(tenantId1, importedRuleChain.getId()); |
|||
|
|||
checkImportedEntity(tenantId1, ruleChain, tenantId1, importResult.getSavedEntity()); |
|||
checkImportedRuleChainData(ruleChain, metaData, importedRuleChain, importedMetaData); |
|||
} |
|||
|
|||
|
|||
@Test |
|||
public void testExportImportBatch_betweenTenants() throws Exception { |
|||
logInAsTenantAdmin1(); |
|||
|
|||
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"); |
|||
|
|||
EntityListVersionCreateConfig exportRequest = new EntityListVersionCreateConfig(); |
|||
exportRequest.setExportSettings(new EntityExportSettings()); |
|||
exportRequest.setEntitiesIds(List.of(customer.getId(), asset.getId(), ruleChain.getId(), deviceProfile.getId(), dashboard.getId())); |
|||
List<EntityExportData<?>> 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(); |
|||
ImportRequest importRequest = new ImportRequest(); |
|||
importRequest.setImportSettings(new EntityImportSettings()); |
|||
importRequest.setExportDataList(exportDataList); |
|||
Map<EntityType, EntityImportResult<?>> 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()); |
|||
|
|||
EntityListVersionCreateConfig exportRequest = new EntityListVersionCreateConfig(); |
|||
exportRequest.setEntitiesIds(List.of(asset.getId(), device.getId())); |
|||
exportRequest.setExportSettings(EntityExportSettings.builder() |
|||
.exportRelations(true) |
|||
.build()); |
|||
List<EntityExportData<?>> exportDataList = exportEntities(exportRequest); |
|||
|
|||
EntityExportData<?> deviceExportData = exportDataList.stream().filter(exportData -> exportData.getEntityType() == EntityType.DEVICE).findFirst().orElse(null); |
|||
assertThat(deviceExportData.getRelations()).size().isOne(); |
|||
assertThat(deviceExportData.getRelations().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() |
|||
.updateRelations(true) |
|||
.build()); |
|||
Map<EntityType, EntityImportResult<?>> 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<EntityRelation> 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 testExportImportWithRelations_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()); |
|||
|
|||
EntityListVersionCreateConfig exportRequest = new EntityListVersionCreateConfig(); |
|||
exportRequest.setEntitiesIds(List.of(asset.getId(), device.getId())); |
|||
exportRequest.setExportSettings(EntityExportSettings.builder() |
|||
.exportRelations(true) |
|||
.build()); |
|||
List<EntityExportData<?>> exportDataList = exportEntities(exportRequest); |
|||
|
|||
assertThat(exportDataList).allMatch(exportData -> exportData.getRelations().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() |
|||
.updateRelations(true) |
|||
.build()); |
|||
Map<EntityType, EntityImportResult<?>> 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<EntityRelation> 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 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()); |
|||
|
|||
SingleEntityVersionCreateConfig exportRequest = new SingleEntityVersionCreateConfig(); |
|||
exportRequest.setEntityId(asset.getId()); |
|||
exportRequest.setExportSettings(EntityExportSettings.builder() |
|||
.exportRelations(true) |
|||
.build()); |
|||
EntityExportData<Asset> assetExportData = (EntityExportData<Asset>) exportEntities(exportRequest).get(0); |
|||
assertThat(assetExportData.getRelations()).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() |
|||
.updateRelations(true) |
|||
.build()); |
|||
|
|||
importEntities(importRequest); |
|||
|
|||
List<EntityRelation> relations = relationService.findByFrom(TenantId.SYS_TENANT_ID, asset.getId(), RelationTypeGroup.COMMON); |
|||
assertThat(relations).contains(relation1); |
|||
assertThat(relations).doesNotContain(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()); |
|||
|
|||
SingleEntityVersionCreateConfig exportRequest = new SingleEntityVersionCreateConfig(); |
|||
exportRequest.setEntityId(device.getId()); |
|||
exportRequest.setExportSettings(EntityExportSettings.builder() |
|||
.exportRelations(true) |
|||
.build()); |
|||
EntityExportData<?> deviceExportData = exportEntities(exportRequest).get(0); |
|||
assertThat(deviceExportData.getRelations()).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() |
|||
.updateRelations(true) |
|||
.build()); |
|||
|
|||
importEntities(importRequest); |
|||
|
|||
List<EntityRelation> 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); |
|||
|
|||
EntityListVersionCreateConfig exportRequest = new EntityListVersionCreateConfig(); |
|||
exportRequest.setEntitiesIds(List.of(defaultDeviceProfile.getId())); |
|||
exportRequest.setExportSettings(new EntityExportSettings()); |
|||
List<EntityExportData<?>> exportDataList = exportEntities(exportRequest); |
|||
|
|||
logInAsTenantAdmin2(); |
|||
ImportRequest importRequest = new ImportRequest(); |
|||
importRequest.setExportDataList(exportDataList); |
|||
importRequest.setImportSettings(EntityImportSettings.builder() |
|||
.findExistingByName(false) |
|||
.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 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<EntityType, ExportableEntity<?>> 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 { |
|||
EntityTypeVersionCreateConfig exportRequest = new EntityTypeVersionCreateConfig(); |
|||
exportRequest.setExportSettings(new EntityExportSettings()); |
|||
exportRequest.setPageSize(10); |
|||
exportRequest.setEntityType(entity.getId().getEntityType()); |
|||
|
|||
List<EntityExportData<?>> exportDataList = exportEntities(exportRequest); |
|||
assertThat(exportDataList).size().isNotZero(); |
|||
assertThat(exportDataList).anySatisfy(exportData -> { |
|||
assertThat(exportData.getEntity()).isEqualTo(entity); |
|||
}); |
|||
} |
|||
|
|||
private void testCustomEntityFilterExportRequest(ExportableEntity<?> entity) throws Exception { |
|||
EntitiesByCustomFilterVersionCreateConfig exportRequest = new EntitiesByCustomFilterVersionCreateConfig(); |
|||
exportRequest.setExportSettings(new EntityExportSettings()); |
|||
exportRequest.setPageSize(10); |
|||
|
|||
org.thingsboard.server.common.data.query.EntityListFilter filter = new org.thingsboard.server.common.data.query.EntityListFilter(); |
|||
filter.setEntityType(entity.getId().getEntityType()); |
|||
filter.setEntityList(List.of(entity.getId().toString())); |
|||
exportRequest.setFilter(filter); |
|||
|
|||
List<EntityExportData<?>> 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<VersionCreateConfig> exportRequests = new ArrayList<>(); |
|||
|
|||
for (EntityType entityType : Set.of(EntityType.DEVICE, EntityType.ASSET)) { |
|||
EntityTypeVersionCreateConfig exportRequest = new EntityTypeVersionCreateConfig(); |
|||
exportRequest.setExportSettings(new EntityExportSettings()); |
|||
exportRequest.setPageSize(10); |
|||
exportRequest.setEntityType(entityType); |
|||
exportRequest.setCustomerId(customer.getUuidId()); |
|||
exportRequests.add(exportRequest); |
|||
} |
|||
|
|||
List<EntityExportData<?>> 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); |
|||
}); |
|||
} |
|||
|
|||
|
|||
@Test |
|||
public void testEntityEventsOnImport() throws Exception { |
|||
logInAsTenantAdmin1(); |
|||
|
|||
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"); |
|||
|
|||
EntityListVersionCreateConfig exportRequest = new EntityListVersionCreateConfig(); |
|||
exportRequest.setEntitiesIds(List.of(customer.getId(), asset.getId(), device.getId(), ruleChain.getId(), dashboard.getId(), deviceProfile.getId())); |
|||
exportRequest.setExportSettings(new EntityExportSettings()); |
|||
|
|||
Map<EntityType, EntityExportData> 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)); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.common.data; |
|||
|
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.HasId; |
|||
|
|||
public interface ExportableEntity<I extends EntityId> extends HasId<I>, HasName { |
|||
|
|||
void setId(I id); |
|||
|
|||
I getExternalId(); |
|||
void setExternalId(I externalId); |
|||
|
|||
long getCreatedTime(); |
|||
void setCreatedTime(long createdTime); |
|||
|
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao; |
|||
|
|||
import org.thingsboard.server.common.data.ExportableEntity; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
public interface ExportableEntityDao<T extends ExportableEntity<?>> extends Dao<T> { |
|||
|
|||
T findByTenantIdAndExternalId(UUID tenantId, UUID externalId); |
|||
|
|||
default T findByTenantIdAndName(UUID tenantId, String name) { throw new UnsupportedOperationException(); } |
|||
|
|||
PageData<T> findByTenantId(UUID tenantId, PageLink pageLink); |
|||
|
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
public interface ExportableEntityRepository<D> { |
|||
|
|||
D findByTenantIdAndExternalId(UUID tenantId, UUID externalId); |
|||
|
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue