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