Browse Source

Merge pull request #6558 from ViacheslavKlimov/feature/entities-vc

Entity Version Control
pull/6559/head
Andrew Shvayka 4 years ago
committed by GitHub
parent
commit
89ca417b10
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      application/pom.xml
  2. 28
      application/src/main/data/upgrade/3.3.4/schema_update.sql
  3. 4
      application/src/main/java/org/thingsboard/server/controller/AssetController.java
  4. 4
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  5. 4
      application/src/main/java/org/thingsboard/server/controller/EdgeController.java
  6. 336
      application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java
  7. 10
      application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
  8. 4
      application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java
  9. 4
      application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
  10. 4
      application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java
  11. 174
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/DefaultEntitiesExportImportService.java
  12. 40
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/EntitiesExportImportService.java
  13. 204
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/DefaultExportableEntitiesService.java
  14. 29
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/EntityExportService.java
  15. 49
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/ExportableEntitiesService.java
  16. 31
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/data/DeviceExportData.java
  17. 48
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/data/EntityExportData.java
  18. 29
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/data/EntityExportSettings.java
  19. 31
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/data/RuleChainExportData.java
  20. 43
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/impl/BaseEntityExportService.java
  21. 90
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/impl/DefaultEntityExportService.java
  22. 52
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/impl/DeviceExportService.java
  23. 52
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/impl/RuleChainExportService.java
  24. 33
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/EntityImportService.java
  25. 9
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/csv/AbstractBulkImportService.java
  26. 2
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/csv/BulkImportColumnType.java
  27. 2
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/csv/BulkImportRequest.java
  28. 2
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/csv/BulkImportResult.java
  29. 2
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/csv/ImportedEntityInfo.java
  30. 48
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/data/EntityImportResult.java
  31. 30
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/data/EntityImportSettings.java
  32. 62
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/impl/AssetImportService.java
  33. 230
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/impl/BaseEntityImportService.java
  34. 61
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/impl/CustomerImportService.java
  35. 122
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/impl/DashboardImportService.java
  36. 68
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/impl/DeviceImportService.java
  37. 75
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/impl/DeviceProfileImportService.java
  38. 110
      application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/impl/RuleChainImportService.java
  39. 491
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java
  40. 59
      application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java
  41. 26
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesVersionControlSettings.java
  42. 28
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntityVersion.java
  43. 26
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/VersionCreationResult.java
  44. 33
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/VersionLoadResult.java
  45. 25
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/VersionedEntityInfo.java
  46. 36
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/create/EntityListVersionCreateRequest.java
  47. 35
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/create/EntityTypeVersionCreateRequest.java
  48. 25
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/create/MultipleEntitiesVersionCreateConfig.java
  49. 34
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/create/SingleEntityVersionCreateRequest.java
  50. 21
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/create/SyncStrategy.java
  51. 23
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/create/VersionCreateConfig.java
  52. 37
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/create/VersionCreateRequest.java
  53. 22
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/create/VersionCreateRequestType.java
  54. 27
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/load/EntityTypeVersionLoadConfig.java
  55. 35
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/load/EntityTypeVersionLoadRequest.java
  56. 35
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/load/SingleEntityVersionLoadRequest.java
  57. 26
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/load/VersionLoadConfig.java
  58. 37
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/load/VersionLoadRequest.java
  59. 21
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/load/VersionLoadRequestType.java
  60. 274
      application/src/main/java/org/thingsboard/server/utils/GitRepository.java
  61. 47
      application/src/main/java/org/thingsboard/server/utils/JsonTbEntity.java
  62. 36
      application/src/main/java/org/thingsboard/server/utils/RegexUtils.java
  63. 31
      application/src/main/java/org/thingsboard/server/utils/ThrowingRunnable.java
  64. 1
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  65. 389
      application/src/test/java/org/thingsboard/server/controller/BaseEntitiesExportImportControllerTest.java
  66. 738
      application/src/test/java/org/thingsboard/server/controller/sql/EntitiesExportImportControllerSqlTest.java
  67. 5
      common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
  68. 7
      common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java
  69. 41
      common/data/src/main/java/org/thingsboard/server/common/data/Customer.java
  70. 39
      common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java
  71. 9
      common/data/src/main/java/org/thingsboard/server/common/data/Device.java
  72. 5
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java
  73. 31
      common/data/src/main/java/org/thingsboard/server/common/data/ExportableEntity.java
  74. 10
      common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
  75. 2
      common/data/src/main/java/org/thingsboard/server/common/data/id/IdBased.java
  76. 6
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java
  77. 5
      dao/src/main/java/org/thingsboard/server/dao/Dao.java
  78. 20
      dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java
  79. 32
      dao/src/main/java/org/thingsboard/server/dao/ExportableEntityDao.java
  80. 24
      dao/src/main/java/org/thingsboard/server/dao/ExportableEntityRepository.java
  81. 4
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
  82. 2
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  83. 5
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java
  84. 9
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java
  85. 7
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  86. 3
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
  87. 3
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java
  88. 2
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  89. 11
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java
  90. 10
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java
  91. 9
      dao/src/main/java/org/thingsboard/server/dao/model/sql/CustomerEntity.java
  92. 9
      dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java
  93. 9
      dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java
  94. 9
      dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java
  95. 12
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  96. 5
      dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java
  97. 8
      dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java
  98. 7
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  99. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java
  100. 21
      dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java

4
application/pom.xml

@ -337,6 +337,10 @@
<artifactId>Java-WebSocket</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
</dependency>
</dependencies>
<build>

28
application/src/main/data/upgrade/3.3.4/schema_update.sql

@ -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;

4
application/src/main/java/org/thingsboard/server/controller/AssetController.java

@ -54,8 +54,8 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.asset.AssetBulkImportService;
import org.thingsboard.server.service.importing.BulkImportRequest;
import org.thingsboard.server.service.importing.BulkImportResult;
import org.thingsboard.server.service.sync.exportimport.importing.csv.BulkImportRequest;
import org.thingsboard.server.service.sync.exportimport.importing.csv.BulkImportResult;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;

4
application/src/main/java/org/thingsboard/server/controller/DeviceController.java

@ -74,8 +74,8 @@ import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.device.DeviceBulkImportService;
import org.thingsboard.server.service.gateway_device.GatewayNotificationsService;
import org.thingsboard.server.service.importing.BulkImportRequest;
import org.thingsboard.server.service.importing.BulkImportResult;
import org.thingsboard.server.service.sync.exportimport.importing.csv.BulkImportRequest;
import org.thingsboard.server.service.sync.exportimport.importing.csv.BulkImportResult;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;

4
application/src/main/java/org/thingsboard/server/controller/EdgeController.java

@ -55,8 +55,8 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeBulkImportService;
import org.thingsboard.server.service.importing.BulkImportRequest;
import org.thingsboard.server.service.importing.BulkImportResult;
import org.thingsboard.server.service.sync.exportimport.importing.csv.BulkImportRequest;
import org.thingsboard.server.service.sync.exportimport.importing.csv.BulkImportResult;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;

336
application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java

@ -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;
}
}

10
application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java

@ -22,13 +22,14 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@ -41,7 +42,6 @@ import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.cluster.TbClusterService;
import java.util.List;
import java.util.Map;
@ -212,7 +212,7 @@ public class EntityActionService {
}
}
public <E extends HasName, I extends EntityId> void logEntityAction(User user, I entityId, E entity, CustomerId customerId,
public <E extends HasName, I extends EntityId> void logEntityAction(User user, I entityId, E entity, CustomerId customerId,
ActionType actionType, Exception e, Object... additionalInfo) {
if (customerId == null || customerId.isNullUid()) {
customerId = user.getCustomerId();
@ -223,6 +223,9 @@ public class EntityActionService {
auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo);
}
public void sendEntityNotificationMsgToEdgeService(TenantId tenantId, EntityId entityId, EdgeEventActionType action) {
tbClusterService.sendNotificationMsgToEdgeService(tenantId, null, entityId, null, null, action);
}
private <T> T extractParameter(Class<T> clazz, int index, Object... additionalInfo) {
T result = null;
@ -267,4 +270,5 @@ public class EntityActionService {
entityNode.put(kvEntry.getKey(), kvEntry.getValueAsString());
}
}
}

4
application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java

@ -25,8 +25,8 @@ import org.thingsboard.server.common.data.asset.Asset;
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.importing.AbstractBulkImportService;
import org.thingsboard.server.service.importing.BulkImportColumnType;
import org.thingsboard.server.service.sync.exportimport.importing.csv.AbstractBulkImportService;
import org.thingsboard.server.service.sync.exportimport.importing.csv.BulkImportColumnType;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.Map;

4
application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java

@ -49,8 +49,8 @@ import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.importing.AbstractBulkImportService;
import org.thingsboard.server.service.importing.BulkImportColumnType;
import org.thingsboard.server.service.sync.exportimport.importing.csv.AbstractBulkImportService;
import org.thingsboard.server.service.sync.exportimport.importing.csv.BulkImportColumnType;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.Collection;

4
application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java

@ -25,8 +25,8 @@ import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.importing.AbstractBulkImportService;
import org.thingsboard.server.service.importing.BulkImportColumnType;
import org.thingsboard.server.service.sync.exportimport.importing.csv.AbstractBulkImportService;
import org.thingsboard.server.service.sync.exportimport.importing.csv.BulkImportColumnType;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.Map;

174
application/src/main/java/org/thingsboard/server/service/sync/exportimport/DefaultEntitiesExportImportService.java

@ -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);
});
}
}

40
application/src/main/java/org/thingsboard/server/service/sync/exportimport/EntitiesExportImportService.java

@ -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);
}

204
application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/DefaultExportableEntitiesService.java

@ -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);
}
});
}
}

29
application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/EntityExportService.java

@ -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;
}

49
application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/ExportableEntitiesService.java

@ -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;
}

31
application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/data/DeviceExportData.java

@ -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;
}

48
application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/data/EntityExportData.java

@ -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;
}

29
application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/data/EntityExportSettings.java

@ -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;
}

31
application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/data/RuleChainExportData.java

@ -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;
}

43
application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/impl/BaseEntityExportService.java

@ -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();
}

90
application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/impl/DefaultEntityExportService.java

@ -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>();
}
}

52
application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/impl/DeviceExportService.java

@ -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);
}
}

52
application/src/main/java/org/thingsboard/server/service/sync/exportimport/exporting/impl/RuleChainExportService.java

@ -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);
}
}

33
application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/EntityImportService.java

@ -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();
}

9
application/src/main/java/org/thingsboard/server/service/importing/AbstractBulkImportService.java → application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/csv/AbstractBulkImportService.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.importing;
package org.thingsboard.server.service.sync.exportimport.importing.csv;
import com.google.common.util.concurrent.FutureCallback;
import com.google.gson.JsonObject;
@ -44,7 +44,6 @@ import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.controller.BaseController;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.service.action.EntityActionService;
import org.thingsboard.server.service.importing.BulkImportRequest.ColumnMapping;
import org.thingsboard.server.service.security.AccessValidator;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.AccessControlService;
@ -166,7 +165,7 @@ public abstract class AbstractBulkImportService<E extends HasId<? extends Entity
protected abstract EntityType getEntityType();
private void saveKvs(SecurityUser user, E entity, Map<ColumnMapping, ParsedValue> data) {
private void saveKvs(SecurityUser user, E entity, Map<BulkImportRequest.ColumnMapping, ParsedValue> data) {
Arrays.stream(BulkImportColumnType.values())
.filter(BulkImportColumnType::isKv)
.map(kvType -> {
@ -250,7 +249,7 @@ public abstract class AbstractBulkImportService<E extends HasId<? extends Entity
linesCounter.incrementAndGet();
}
List<ColumnMapping> columnsMappings = request.getMapping().getColumns();
List<BulkImportRequest.ColumnMapping> columnsMappings = request.getMapping().getColumns();
return records.stream()
.map(record -> {
EntityData entityData = new EntityData();
@ -281,7 +280,7 @@ public abstract class AbstractBulkImportService<E extends HasId<? extends Entity
@Data
protected static class EntityData {
private final Map<BulkImportColumnType, String> fields = new LinkedHashMap<>();
private final Map<ColumnMapping, ParsedValue> kvs = new LinkedHashMap<>();
private final Map<BulkImportRequest.ColumnMapping, ParsedValue> kvs = new LinkedHashMap<>();
private int lineNumber;
}

2
application/src/main/java/org/thingsboard/server/service/importing/BulkImportColumnType.java → application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/csv/BulkImportColumnType.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.importing;
package org.thingsboard.server.service.sync.exportimport.importing.csv;
import lombok.Getter;
import org.thingsboard.server.common.data.DataConstants;

2
application/src/main/java/org/thingsboard/server/service/importing/BulkImportRequest.java → application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/csv/BulkImportRequest.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.importing;
package org.thingsboard.server.service.sync.exportimport.importing.csv;
import lombok.Data;

2
application/src/main/java/org/thingsboard/server/service/importing/BulkImportResult.java → application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/csv/BulkImportResult.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.importing;
package org.thingsboard.server.service.sync.exportimport.importing.csv;
import lombok.Data;

2
application/src/main/java/org/thingsboard/server/service/importing/ImportedEntityInfo.java → application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/csv/ImportedEntityInfo.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.importing;
package org.thingsboard.server.service.sync.exportimport.importing.csv;
import lombok.Data;

48
application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/data/EntityImportResult.java

@ -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);
}
}

30
application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/data/EntityImportSettings.java

@ -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;
}

62
application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/impl/AssetImportService.java

@ -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;
}
}

230
application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/impl/BaseEntityImportService.java

@ -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();
}
}
}

61
application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/impl/CustomerImportService.java

@ -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;
}
}

122
application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/impl/DashboardImportService.java

@ -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;
}
}

68
application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/impl/DeviceImportService.java

@ -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;
}
}

75
application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/impl/DeviceProfileImportService.java

@ -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;
}
}

110
application/src/main/java/org/thingsboard/server/service/sync/exportimport/importing/impl/RuleChainImportService.java

@ -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;
}
}

491
application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java

@ -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);
}
}

59
application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java

@ -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);
}

26
application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesVersionControlSettings.java

@ -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;
}

28
application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntityVersion.java

@ -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;
}

26
application/src/main/java/org/thingsboard/server/service/sync/vc/data/VersionCreationResult.java

@ -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;
}

33
application/src/main/java/org/thingsboard/server/service/sync/vc/data/VersionLoadResult.java

@ -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;
}

25
application/src/main/java/org/thingsboard/server/service/sync/vc/data/VersionedEntityInfo.java

@ -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..
}

36
application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/create/EntityListVersionCreateRequest.java

@ -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;
}
}

35
application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/create/EntityTypeVersionCreateRequest.java

@ -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;
}
}

25
application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/create/MultipleEntitiesVersionCreateConfig.java

@ -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;
}

34
application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/create/SingleEntityVersionCreateRequest.java

@ -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;
}
}

21
application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/create/SyncStrategy.java

@ -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
}

23
application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/create/VersionCreateConfig.java

@ -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;
}

37
application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/create/VersionCreateRequest.java

@ -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();
}

22
application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/create/VersionCreateRequestType.java

@ -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
}

27
application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/load/EntityTypeVersionLoadConfig.java

@ -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;
}

35
application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/load/EntityTypeVersionLoadRequest.java

@ -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;
}
}

35
application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/load/SingleEntityVersionLoadRequest.java

@ -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;
}
}

26
application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/load/VersionLoadConfig.java

@ -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;
}

37
application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/load/VersionLoadRequest.java

@ -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();
}

21
application/src/main/java/org/thingsboard/server/service/sync/vc/data/request/load/VersionLoadRequestType.java

@ -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
}

274
application/src/main/java/org/thingsboard/server/utils/GitRepository.java

@ -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;
}
}

47
application/src/main/java/org/thingsboard/server/utils/JsonTbEntity.java

@ -0,0 +1,47 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.utils;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.rule.RuleChain;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "entityType", include = JsonTypeInfo.As.EXTERNAL_PROPERTY)
@JsonSubTypes({
@Type(name = "DEVICE", value = Device.class),
@Type(name = "RULE_CHAIN", value = RuleChain.class),
@Type(name = "DEVICE_PROFILE", value = DeviceProfile.class),
@Type(name = "ASSET", value = Asset.class),
@Type(name = "DASHBOARD", value = Dashboard.class),
@Type(name = "CUSTOMER", value = Customer.class)
})
public @interface JsonTbEntity {
}

36
application/src/main/java/org/thingsboard/server/utils/RegexUtils.java

@ -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());
});
}
}

31
application/src/main/java/org/thingsboard/server/utils/ThrowingRunnable.java

@ -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();
};
}
}

1
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -605,7 +605,6 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected <T> T readResponse(ResultActions result, TypeReference<T> type) throws Exception {
byte[] content = result.andReturn().getResponse().getContentAsByteArray();
ObjectMapper mapper = new ObjectMapper();
return mapper.readerFor(type).readValue(content);
}

389
application/src/test/java/org/thingsboard/server/controller/BaseEntitiesExportImportControllerTest.java

@ -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");
}
}

738
application/src/test/java/org/thingsboard/server/controller/sql/EntitiesExportImportControllerSqlTest.java

@ -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));
}
}

5
common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java

@ -25,6 +25,8 @@ 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 java.util.List;
public interface DashboardService {
Dashboard findDashboardById(TenantId tenantId, DashboardId dashboardId);
@ -64,4 +66,7 @@ public interface DashboardService {
PageData<DashboardInfo> findDashboardsByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, PageLink pageLink);
DashboardInfo findFirstDashboardInfoByTenantIdAndName(TenantId tenantId, String name);
List<Dashboard> findTenantDashboardsByTitle(TenantId tenantId, String title);
}

7
common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java

@ -28,11 +28,11 @@ import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainData;
import org.thingsboard.server.common.data.rule.RuleChainImportResult;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleChainUpdateResult;
import org.thingsboard.server.common.data.rule.RuleNode;
import java.util.Collection;
import java.util.List;
/**
@ -66,6 +66,8 @@ public interface RuleChainService {
PageData<RuleChain> findTenantRuleChainsByType(TenantId tenantId, RuleChainType type, PageLink pageLink);
Collection<RuleChain> findTenantRuleChainsByTypeAndName(TenantId tenantId, RuleChainType type, String name);
void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId);
void deleteRuleChainsByTenantId(TenantId tenantId);
@ -93,4 +95,7 @@ public interface RuleChainService {
List<RuleNode> findRuleNodesByTenantIdAndType(TenantId tenantId, String name, String toString);
RuleNode saveRuleNode(TenantId tenantId, RuleNode ruleNode);
void deleteRuleNodes(TenantId tenantId, RuleChainId ruleChainId);
}

41
common/data/src/main/java/org/thingsboard/server/common/data/Customer.java

@ -20,12 +20,16 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonProperty.Access;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModelProperty;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
public class Customer extends ContactBased<CustomerId> implements HasTenantId {
@EqualsAndHashCode(callSuper = false)
public class Customer extends ContactBased<CustomerId> implements HasTenantId, ExportableEntity<CustomerId> {
private static final long serialVersionUID = -1599722990298929275L;
@ -36,6 +40,9 @@ public class Customer extends ContactBased<CustomerId> implements HasTenantId {
@ApiModelProperty(position = 5, required = true, value = "JSON object with Tenant Id")
private TenantId tenantId;
@Getter @Setter
private CustomerId externalId;
public Customer() {
super();
}
@ -48,6 +55,7 @@ public class Customer extends ContactBased<CustomerId> implements HasTenantId {
super(customer);
this.tenantId = customer.getTenantId();
this.title = customer.getTitle();
this.externalId = customer.getExternalId();
}
public TenantId getTenantId() {
@ -161,37 +169,6 @@ public class Customer extends ContactBased<CustomerId> implements HasTenantId {
return getTitle();
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
result = prime * result + ((title == null) ? 0 : title.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
Customer other = (Customer) obj;
if (tenantId == null) {
if (other.tenantId != null)
return false;
} else if (!tenantId.equals(other.tenantId))
return false;
if (title == null) {
if (other.title != null)
return false;
} else if (!title.equals(other.title))
return false;
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();

39
common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java

@ -17,14 +17,21 @@ package org.thingsboard.server.common.data;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModelProperty;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.thingsboard.server.common.data.id.DashboardId;
public class Dashboard extends DashboardInfo {
@EqualsAndHashCode(callSuper = false)
public class Dashboard extends DashboardInfo implements ExportableEntity<DashboardId> {
private static final long serialVersionUID = 872682138346187503L;
private transient JsonNode configuration;
@Getter @Setter
private DashboardId externalId;
public Dashboard() {
super();
}
@ -40,6 +47,7 @@ public class Dashboard extends DashboardInfo {
public Dashboard(Dashboard dashboard) {
super(dashboard);
this.configuration = dashboard.getConfiguration();
this.externalId = dashboard.getExternalId();
}
@ApiModelProperty(position = 9, value = "JSON object with main configuration of the dashboard: layouts, widgets, aliases, etc. " +
@ -54,31 +62,6 @@ public class Dashboard extends DashboardInfo {
this.configuration = configuration;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((configuration == null) ? 0 : configuration.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
Dashboard other = (Dashboard) obj;
if (configuration == null) {
if (other.configuration != null)
return false;
} else if (!configuration.equals(other.configuration))
return false;
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();

9
common/data/src/main/java/org/thingsboard/server/common/data/Device.java

@ -21,6 +21,8 @@ import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.device.data.DeviceData;
import org.thingsboard.server.common.data.id.CustomerId;
@ -38,7 +40,7 @@ import java.util.Optional;
@ApiModel
@EqualsAndHashCode(callSuper = true)
@Slf4j
public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implements HasName, HasTenantId, HasCustomerId, HasOtaPackage {
public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implements HasName, HasTenantId, HasCustomerId, HasOtaPackage, ExportableEntity<DeviceId> {
private static final long serialVersionUID = 2807343040519543363L;
@ -61,6 +63,9 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
private OtaPackageId firmwareId;
private OtaPackageId softwareId;
@Getter @Setter
private DeviceId externalId;
public Device() {
super();
}
@ -80,6 +85,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
this.setDeviceData(device.getDeviceData());
this.firmwareId = device.getFirmwareId();
this.softwareId = device.getSoftwareId();
this.externalId = device.getExternalId();
}
public Device updateDevice(Device device) {
@ -93,6 +99,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
this.setFirmwareId(device.getFirmwareId());
this.setSoftwareId(device.getSoftwareId());
Optional.ofNullable(device.getAdditionalInfo()).ifPresent(this::setAdditionalInfo);
this.setExternalId(device.getExternalId());
return this;
}

5
common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java

@ -43,7 +43,7 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn
@ToString(exclude = {"image", "profileDataBytes"})
@EqualsAndHashCode(callSuper = true)
@Slf4j
public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements HasName, HasTenantId, HasOtaPackage {
public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements HasName, HasTenantId, HasOtaPackage, ExportableEntity<DeviceProfileId> {
private static final long serialVersionUID = 6998485460273302018L;
@ -90,6 +90,8 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
@ApiModelProperty(position = 10, value = "Reference to the software OTA package. If present, the specified package will be used as default device software. ")
private OtaPackageId softwareId;
private DeviceProfileId externalId;
public DeviceProfile() {
super();
}
@ -112,6 +114,7 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey();
this.firmwareId = deviceProfile.getFirmwareId();
this.softwareId = deviceProfile.getSoftwareId();
this.externalId = deviceProfile.getExternalId();
}
@ApiModelProperty(position = 1, value = "JSON object with the device profile Id. " +

31
common/data/src/main/java/org/thingsboard/server/common/data/ExportableEntity.java

@ -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);
}

10
common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java

@ -19,6 +19,9 @@ import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
@ -33,7 +36,7 @@ import java.util.Optional;
@ApiModel
@EqualsAndHashCode(callSuper = true)
public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasName, HasTenantId, HasCustomerId {
public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasName, HasTenantId, HasCustomerId, ExportableEntity<AssetId> {
private static final long serialVersionUID = 2807343040519543363L;
@ -49,6 +52,9 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
@Length(fieldName = "label")
private String label;
@Getter @Setter
private AssetId externalId;
public Asset() {
super();
}
@ -64,6 +70,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
this.name = asset.getName();
this.type = asset.getType();
this.label = asset.getLabel();
this.externalId = asset.getExternalId();
}
public void update(Asset asset) {
@ -73,6 +80,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
this.type = asset.getType();
this.label = asset.getLabel();
Optional.ofNullable(asset.getAdditionalInfo()).ifPresent(this::setAdditionalInfo);
this.externalId = asset.getExternalId();
}
@ApiModelProperty(position = 1, value = "JSON object with the asset Id. " +

2
common/data/src/main/java/org/thingsboard/server/common/data/id/IdBased.java

@ -16,6 +16,7 @@
package org.thingsboard.server.common.data.id;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonSetter;
import java.io.Serializable;
import java.util.UUID;
@ -33,6 +34,7 @@ public abstract class IdBased<I extends UUIDBased> implements HasId<I> {
this.id = id;
}
@JsonSetter
public void setId(I id) {
this.id = id;
}

6
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java

@ -22,6 +22,7 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
@ -35,7 +36,7 @@ import org.thingsboard.server.common.data.validation.NoXss;
@Data
@EqualsAndHashCode(callSuper = true)
@Slf4j
public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> implements HasName, HasTenantId {
public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> implements HasName, HasTenantId, ExportableEntity<RuleChainId> {
private static final long serialVersionUID = -5656679015121935465L;
@ -56,6 +57,8 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im
@ApiModelProperty(position = 9, value = "Reserved for future usage. The actual list of rule nodes and their relations is stored in the database separately.")
private transient JsonNode configuration;
private RuleChainId externalId;
@JsonIgnore
private byte[] configurationBytes;
@ -75,6 +78,7 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im
this.firstRuleNodeId = ruleChain.getFirstRuleNodeId();
this.root = ruleChain.isRoot();
this.setConfiguration(ruleChain.getConfiguration());
this.setExternalId(ruleChain.getExternalId());
}
@Override

5
dao/src/main/java/org/thingsboard/server/dao/Dao.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.Collection;
@ -36,8 +37,12 @@ public interface Dao<T> {
T save(TenantId tenantId, T t);
T saveAndFlush(TenantId tenantId, T t);
boolean removeById(TenantId tenantId, UUID id);
void removeAllByIds(Collection<UUID> ids);
default EntityType getEntityType() { return null; }
}

20
dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java

@ -32,6 +32,8 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;
public abstract class DaoUtil {
@ -51,7 +53,7 @@ public abstract class DaoUtil {
return toPageable(pageLink, Collections.emptyMap());
}
public static Pageable toPageable(PageLink pageLink, Map<String,String> columnMap) {
public static Pageable toPageable(PageLink pageLink, Map<String, String> columnMap) {
return PageRequest.of(pageLink.getPage(), pageLink.getPageSize(), pageLink.toSort(pageLink.getSortOrder(), columnMap));
}
@ -59,7 +61,7 @@ public abstract class DaoUtil {
return toPageable(pageLink, Collections.emptyMap(), sortOrders);
}
public static Pageable toPageable(PageLink pageLink, Map<String,String> columnMap, List<SortOrder> sortOrders) {
public static Pageable toPageable(PageLink pageLink, Map<String, String> columnMap, List<SortOrder> sortOrders) {
return PageRequest.of(pageLink.getPage(), pageLink.getPageSize(), pageLink.toSort(sortOrders, columnMap));
}
@ -108,4 +110,18 @@ public abstract class DaoUtil {
return ids;
}
public static <T> void processInBatches(Function<PageLink, PageData<T>> finder, int batchSize, Consumer<T> processor) {
PageLink pageLink = new PageLink(batchSize);
PageData<T> batch;
boolean hasNextBatch;
do {
batch = finder.apply(pageLink);
batch.getData().forEach(processor);
hasNextBatch = batch.hasNext();
pageLink = pageLink.nextPageLink();
} while (hasNextBatch);
}
}

32
dao/src/main/java/org/thingsboard/server/dao/ExportableEntityDao.java

@ -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);
}

24
dao/src/main/java/org/thingsboard/server/dao/ExportableEntityRepository.java

@ -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);
}

4
dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java

@ -22,8 +22,8 @@ import org.thingsboard.server.common.data.asset.AssetInfo;
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.page.TimePageLink;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import org.thingsboard.server.dao.TenantEntityDao;
import java.util.List;
@ -34,7 +34,7 @@ import java.util.UUID;
* The Interface AssetDao.
*
*/
public interface AssetDao extends Dao<Asset>, TenantEntityDao {
public interface AssetDao extends Dao<Asset>, TenantEntityDao, ExportableEntityDao<Asset> {
/**
* Find asset info by id.

2
dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java

@ -117,7 +117,7 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
assetValidator.validate(asset, Asset::getTenantId);
Asset savedAsset;
try {
savedAsset = assetDao.save(asset.getTenantId(), asset);
savedAsset = assetDao.saveAndFlush(asset.getTenantId(), asset);
} catch (Exception t) {
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("asset_name_unq_key")) {

5
dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java

@ -20,6 +20,7 @@ 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.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import org.thingsboard.server.dao.TenantEntityDao;
import java.util.Optional;
@ -28,7 +29,7 @@ import java.util.UUID;
/**
* The Interface CustomerDao.
*/
public interface CustomerDao extends Dao<Customer>, TenantEntityDao {
public interface CustomerDao extends Dao<Customer>, TenantEntityDao, ExportableEntityDao<Customer> {
/**
* Save or update customer object
@ -37,7 +38,7 @@ public interface CustomerDao extends Dao<Customer>, TenantEntityDao {
* @return saved customer object
*/
Customer save(TenantId tenantId, Customer customer);
/**
* Find customers by tenant id and page link.
*

9
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java

@ -18,12 +18,16 @@ package org.thingsboard.server.dao.dashboard;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import org.thingsboard.server.dao.TenantEntityDao;
import java.util.List;
import java.util.UUID;
/**
* The Interface DashboardDao.
*/
public interface DashboardDao extends Dao<Dashboard>, TenantEntityDao {
public interface DashboardDao extends Dao<Dashboard>, TenantEntityDao, ExportableEntityDao<Dashboard> {
/**
* Save or update dashboard object
@ -32,4 +36,7 @@ public interface DashboardDao extends Dao<Dashboard>, TenantEntityDao {
* @return saved dashboard object
*/
Dashboard save(TenantId tenantId, Dashboard dashboard);
List<Dashboard> findByTenantIdAndTitle(UUID tenantId, String title);
}

7
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java

@ -40,6 +40,8 @@ import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
import java.util.List;
import static org.thingsboard.server.dao.service.Validator.validateId;
@Service
@ -279,6 +281,11 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
return dashboardInfoDao.findFirstByTenantIdAndName(tenantId.getId(), name);
}
@Override
public List<Dashboard> findTenantDashboardsByTitle(TenantId tenantId, String title) {
return dashboardDao.findByTenantIdAndTitle(tenantId.getId(), title);
}
private PaginatedRemover<TenantId, DashboardInfo> tenantDashboardsRemover =
new PaginatedRemover<TenantId, DashboardInfo>() {

3
dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import org.thingsboard.server.dao.TenantEntityDao;
import java.util.List;
@ -35,7 +36,7 @@ import java.util.UUID;
* The Interface DeviceDao.
*
*/
public interface DeviceDao extends Dao<Device>, TenantEntityDao {
public interface DeviceDao extends Dao<Device>, TenantEntityDao, ExportableEntityDao<Device> {
/**
* Find device info by id.

3
dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java

@ -21,10 +21,11 @@ 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.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import java.util.UUID;
public interface DeviceProfileDao extends Dao<DeviceProfile> {
public interface DeviceProfileDao extends Dao<DeviceProfile>, ExportableEntityDao<DeviceProfile> {
DeviceProfileInfo findDeviceProfileInfoById(TenantId tenantId, UUID deviceProfileId);

2
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -559,6 +559,8 @@ public class ModelConstants {
public static final String EDGE_EVENT_BY_ID_VIEW_NAME = "edge_event_by_id";
public static final String EXTERNAL_ID_PROPERTY = "external_id";
/**
* Cassandra attributes and timeseries constants.
*/

11
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java

@ -38,6 +38,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.ASSET_LABEL_PROPER
import static org.thingsboard.server.dao.model.ModelConstants.ASSET_NAME_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TENANT_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TYPE_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.EXTERNAL_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
@Data
@ -68,6 +69,9 @@ public abstract class AbstractAssetEntity<T extends Asset> extends BaseSqlEntity
@Column(name = ModelConstants.ASSET_ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo;
@Column(name = EXTERNAL_ID_PROPERTY)
private UUID externalId;
public AbstractAssetEntity() {
super();
}
@ -87,6 +91,9 @@ public abstract class AbstractAssetEntity<T extends Asset> extends BaseSqlEntity
this.type = asset.getType();
this.label = asset.getLabel();
this.additionalInfo = asset.getAdditionalInfo();
if (asset.getExternalId() != null) {
this.externalId = asset.getExternalId().getId();
}
}
public AbstractAssetEntity(AssetEntity assetEntity) {
@ -99,6 +106,7 @@ public abstract class AbstractAssetEntity<T extends Asset> extends BaseSqlEntity
this.label = assetEntity.getLabel();
this.searchText = assetEntity.getSearchText();
this.additionalInfo = assetEntity.getAdditionalInfo();
this.externalId = assetEntity.getExternalId();
}
@Override
@ -128,6 +136,9 @@ public abstract class AbstractAssetEntity<T extends Asset> extends BaseSqlEntity
asset.setType(type);
asset.setLabel(label);
asset.setAdditionalInfo(additionalInfo);
if (externalId != null) {
asset.setExternalId(new AssetId(externalId));
}
return asset;
}

10
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java

@ -84,6 +84,9 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
@Column(name = ModelConstants.DEVICE_DEVICE_DATA_PROPERTY, columnDefinition = "jsonb")
private JsonNode deviceData;
@Column(name = ModelConstants.EXTERNAL_ID_PROPERTY, columnDefinition = "uuid")
private UUID externalId;
public AbstractDeviceEntity() {
super();
}
@ -113,6 +116,9 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
this.type = device.getType();
this.label = device.getLabel();
this.additionalInfo = device.getAdditionalInfo();
if (device.getExternalId() != null) {
this.externalId = device.getExternalId().getId();
}
}
public AbstractDeviceEntity(DeviceEntity deviceEntity) {
@ -129,6 +135,7 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
this.additionalInfo = deviceEntity.getAdditionalInfo();
this.firmwareId = deviceEntity.getFirmwareId();
this.softwareId = deviceEntity.getSoftwareId();
this.externalId = deviceEntity.getExternalId();
}
@Override
@ -164,6 +171,9 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
device.setType(type);
device.setLabel(label);
device.setAdditionalInfo(additionalInfo);
if (externalId != null) {
device.setExternalId(new DeviceId(externalId));
}
return device;
}

9
dao/src/main/java/org/thingsboard/server/dao/model/sql/CustomerEntity.java

@ -77,6 +77,9 @@ public final class CustomerEntity extends BaseSqlEntity<Customer> implements Sea
@Column(name = ModelConstants.CUSTOMER_ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo;
@Column(name = ModelConstants.EXTERNAL_ID_PROPERTY)
private UUID externalId;
public CustomerEntity() {
super();
}
@ -97,6 +100,9 @@ public final class CustomerEntity extends BaseSqlEntity<Customer> implements Sea
this.phone = customer.getPhone();
this.email = customer.getEmail();
this.additionalInfo = customer.getAdditionalInfo();
if (customer.getExternalId() != null) {
this.externalId = customer.getExternalId().getId();
}
}
@Override
@ -124,6 +130,9 @@ public final class CustomerEntity extends BaseSqlEntity<Customer> implements Sea
customer.setPhone(phone);
customer.setEmail(email);
customer.setAdditionalInfo(additionalInfo);
if (externalId != null) {
customer.setExternalId(new CustomerId(externalId));
}
return customer;
}

9
dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java

@ -78,6 +78,9 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
@Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY)
private JsonNode configuration;
@Column(name = ModelConstants.EXTERNAL_ID_PROPERTY)
private UUID externalId;
public DashboardEntity() {
super();
}
@ -102,6 +105,9 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
this.mobileHide = dashboard.isMobileHide();
this.mobileOrder = dashboard.getMobileOrder();
this.configuration = dashboard.getConfiguration();
if (dashboard.getExternalId() != null) {
this.externalId = dashboard.getExternalId().getId();
}
}
@Override
@ -133,6 +139,9 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
dashboard.setMobileHide(mobileHide);
dashboard.setMobileOrder(mobileOrder);
dashboard.setConfiguration(configuration);
if (externalId != null) {
dashboard.setExternalId(new DashboardId(externalId));
}
return dashboard;
}
}

9
dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java

@ -103,6 +103,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
@Column(name = ModelConstants.DEVICE_PROFILE_SOFTWARE_ID_PROPERTY)
private UUID softwareId;
@Column(name = ModelConstants.EXTERNAL_ID_PROPERTY)
private UUID externalId;
public DeviceProfileEntity() {
super();
}
@ -137,6 +140,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
if (deviceProfile.getSoftwareId() != null) {
this.softwareId = deviceProfile.getSoftwareId().getId();
}
if (deviceProfile.getExternalId() != null) {
this.externalId = deviceProfile.getExternalId().getId();
}
}
@Override
@ -184,6 +190,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
if (softwareId != null) {
deviceProfile.setSoftwareId(new OtaPackageId(softwareId));
}
if (externalId != null) {
deviceProfile.setExternalId(new DeviceProfileId(externalId));
}
return deviceProfile;
}

9
dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java

@ -75,6 +75,9 @@ public class RuleChainEntity extends BaseSqlEntity<RuleChain> implements SearchT
@Column(name = ModelConstants.ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo;
@Column(name = ModelConstants.EXTERNAL_ID_PROPERTY)
private UUID externalId;
public RuleChainEntity() {
}
@ -94,6 +97,9 @@ public class RuleChainEntity extends BaseSqlEntity<RuleChain> implements SearchT
this.debugMode = ruleChain.isDebugMode();
this.configuration = ruleChain.getConfiguration();
this.additionalInfo = ruleChain.getAdditionalInfo();
if (ruleChain.getExternalId() != null) {
this.externalId = ruleChain.getExternalId().getId();
}
}
@Override
@ -120,6 +126,9 @@ public class RuleChainEntity extends BaseSqlEntity<RuleChain> implements SearchT
ruleChain.setDebugMode(debugMode);
ruleChain.setConfiguration(configuration);
ruleChain.setAdditionalInfo(additionalInfo);
if (externalId != null) {
ruleChain.setExternalId(new RuleChainId(externalId));
}
return ruleChain;
}
}

12
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java

@ -391,6 +391,11 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
return ruleChainDao.findRuleChainsByTenantIdAndType(tenantId.getId(), type, pageLink);
}
@Override
public Collection<RuleChain> findTenantRuleChainsByTypeAndName(TenantId tenantId, RuleChainType type, String name) {
return ruleChainDao.findByTenantIdAndTypeAndName(tenantId, type, name);
}
@Override
@Transactional
public void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId) {
@ -457,7 +462,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
ruleChain.setRoot(false);
if (overwrite) {
Collection<RuleChain> existingRuleChains = ruleChainDao.findByTenantIdAndTypeAndName(tenantId,
Collection<RuleChain> existingRuleChains = findTenantRuleChainsByTypeAndName(tenantId,
Optional.ofNullable(ruleChain.getType()).orElse(RuleChainType.CORE), ruleChain.getName());
Optional<RuleChain> existingRuleChain = existingRuleChains.stream().findFirst();
if (existingRuleChain.isPresent()) {
@ -684,6 +689,11 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
throw t;
}
}
deleteRuleNodes(tenantId, ruleChainId);
}
@Override
public void deleteRuleNodes(TenantId tenantId, RuleChainId ruleChainId) {
List<EntityRelation> nodeRelations = getRuleChainToNodeRelations(tenantId, ruleChainId);
for (EntityRelation relation : nodeRelations) {
deleteRuleNode(tenantId, relation.getTo());

5
dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java

@ -19,19 +19,18 @@ 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.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import org.thingsboard.server.dao.TenantEntityDao;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
/**
* Created by igor on 3/12/18.
*/
public interface RuleChainDao extends Dao<RuleChain>, TenantEntityDao {
public interface RuleChainDao extends Dao<RuleChain>, TenantEntityDao, ExportableEntityDao<RuleChain> {
/**
* Find rule chains by tenantId and page link.

8
dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java

@ -67,6 +67,14 @@ public abstract class JpaAbstractDao<E extends BaseEntity<D>, D>
return DaoUtil.getData(entity);
}
@Override
@Transactional
public D saveAndFlush(TenantId tenantId, D domain) {
D d = save(tenantId, domain);
getRepository().flush();
return d;
}
@Override
public D findById(TenantId tenantId, UUID key) {
log.debug("Get entity by key {}", key);

7
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java

@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
@ -188,4 +189,10 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
log.trace("[{}] Try to delete entity alarm records using [{}]", tenantId, entityId);
entityAlarmRepository.deleteByEntityId(entityId.getId());
}
@Override
public EntityType getEntityType() {
return EntityType.ALARM;
}
}

3
dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java

@ -20,6 +20,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.dao.ExportableEntityRepository;
import org.thingsboard.server.dao.model.sql.AssetEntity;
import org.thingsboard.server.dao.model.sql.AssetInfoEntity;
@ -29,7 +30,7 @@ import java.util.UUID;
/**
* Created by Valerii Sosliuk on 5/21/2017.
*/
public interface AssetRepository extends JpaRepository<AssetEntity, UUID> {
public interface AssetRepository extends JpaRepository<AssetEntity, UUID>, ExportableEntityRepository<AssetEntity> {
@Query("SELECT new org.thingsboard.server.dao.model.sql.AssetInfoEntity(a, c.title, c.additionalInfo) " +
"FROM AssetEntity a " +

21
dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java

@ -208,4 +208,25 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao<AssetEntity, Asset> im
public Long countByTenantId(TenantId tenantId) {
return assetRepository.countByTenantIdAndTypeIsNot(tenantId.getId(), TB_SERVICE_QUEUE);
}
@Override
public Asset findByTenantIdAndExternalId(UUID tenantId, UUID externalId) {
return DaoUtil.getData(assetRepository.findByTenantIdAndExternalId(tenantId, externalId));
}
@Override
public Asset findByTenantIdAndName(UUID tenantId, String name) {
return findAssetsByTenantIdAndName(tenantId, name).orElse(null);
}
@Override
public PageData<Asset> findByTenantId(UUID tenantId, PageLink pageLink) {
return findAssetsByTenantId(tenantId, pageLink);
}
@Override
public EntityType getEntityType() {
return EntityType.ASSET;
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save