Browse Source

Feature/tenant export api new (#13634)

* Implement findAllByTenantId for most entities

* Implement export and import of whole tenant

* Major updates for tenant export-import; more tests

* Ts kv export/import; refactoring

* Export-import results in Redis

* Export cancellation, refactoring

* Fixes and improvements

* Fix ota package circular foreign key constraint

* Minor refactoring

* Mobile settings export import; refactoring

* Minor refactoring

* Fix CassandraBaseTimeseriesDao
feature/tenant-export
Viacheslav Klimov 1 year ago
committed by GitHub
parent
commit
d30a326fc6
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 14
      application/pom.xml
  2. 69
      application/src/main/java/org/thingsboard/server/controller/TenantController.java
  3. 341
      application/src/main/java/org/thingsboard/server/service/sync/tenant/TenantExportService.java
  4. 144
      application/src/main/java/org/thingsboard/server/service/sync/tenant/TenantImportService.java
  5. 58
      application/src/main/java/org/thingsboard/server/service/sync/tenant/util/DataWrapper.java
  6. 43
      application/src/main/java/org/thingsboard/server/service/sync/tenant/util/Result.java
  7. 103
      application/src/main/java/org/thingsboard/server/service/sync/tenant/util/ResultStore.java
  8. 193
      application/src/main/java/org/thingsboard/server/service/sync/tenant/util/Storage.java
  9. 37
      application/src/main/java/org/thingsboard/server/service/sync/tenant/util/TenantExportConfig.java
  10. 23
      application/src/main/java/org/thingsboard/server/service/sync/tenant/util/TenantImportConfig.java
  11. 2
      application/src/main/resources/thingsboard.yml
  12. 383
      application/src/test/java/org/thingsboard/server/service/tenant/TenantExportImportTest.java
  13. 75
      application/src/test/java/org/thingsboard/server/utils/TestUtils.java
  14. 8
      application/src/test/resources/logback-test.xml
  15. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/ota/OtaPackageService.java
  16. 3
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  17. 15
      common/data/src/main/java/org/thingsboard/server/common/data/ObjectType.java
  18. 21
      common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java
  19. 27
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmType.java
  20. 12
      common/data/src/main/java/org/thingsboard/server/common/data/event/ErrorEvent.java
  21. 8
      common/data/src/main/java/org/thingsboard/server/common/data/event/Event.java
  22. 13
      common/data/src/main/java/org/thingsboard/server/common/data/event/LifecycleEvent.java
  23. 12
      common/data/src/main/java/org/thingsboard/server/common/data/event/RuleChainDebugEvent.java
  24. 21
      common/data/src/main/java/org/thingsboard/server/common/data/event/RuleNodeDebugEvent.java
  25. 12
      common/data/src/main/java/org/thingsboard/server/common/data/event/StatisticsEvent.java
  26. 35
      common/data/src/main/java/org/thingsboard/server/common/data/kv/AttributeKv.java
  27. 6
      common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicKvEntry.java
  28. 6
      common/data/src/main/java/org/thingsboard/server/common/data/kv/BooleanDataEntry.java
  29. 8
      common/data/src/main/java/org/thingsboard/server/common/data/kv/DoubleDataEntry.java
  30. 6
      common/data/src/main/java/org/thingsboard/server/common/data/kv/JsonDataEntry.java
  31. 3
      common/data/src/main/java/org/thingsboard/server/common/data/kv/KvEntry.java
  32. 33
      common/data/src/main/java/org/thingsboard/server/common/data/kv/LatestTsKv.java
  33. 8
      common/data/src/main/java/org/thingsboard/server/common/data/kv/LongDataEntry.java
  34. 6
      common/data/src/main/java/org/thingsboard/server/common/data/kv/StringDataEntry.java
  35. 35
      common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKv.java
  36. 2
      dao/src/main/java/org/thingsboard/server/dao/Dao.java
  37. 29
      dao/src/main/java/org/thingsboard/server/dao/ObjectDao.java
  38. 2
      dao/src/main/java/org/thingsboard/server/dao/TenantEntityDao.java
  39. 4
      dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java
  40. 6
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
  41. 38
      dao/src/main/java/org/thingsboard/server/dao/entity/EntityDaoRegistry.java
  42. 3
      dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java
  43. 2
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  44. 33
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmTypeCompositeKey.java
  45. 65
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmTypeEntity.java
  46. 39
      dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java
  47. 2
      dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageInfoDao.java
  48. 3
      dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java
  49. 4
      dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java
  50. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmCommentRepository.java
  51. 37
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmTypeRepository.java
  52. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDao.java
  53. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  54. 52
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmTypeDao.java
  55. 12
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaEntityAlarmDao.java
  56. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
  57. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetProfileDao.java
  58. 26
      dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java
  59. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java
  60. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java
  61. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java
  62. 1
      dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceCredentialsRepository.java
  63. 7
      dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java
  64. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
  65. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java
  66. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeEventRepository.java
  67. 16
      dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java
  68. 9
      dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java
  69. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java
  70. 1
      dao/src/main/java/org/thingsboard/server/dao/sql/event/ErrorEventRepository.java
  71. 21
      dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java
  72. 1
      dao/src/main/java/org/thingsboard/server/dao/sql/event/LifecycleEventRepository.java
  73. 1
      dao/src/main/java/org/thingsboard/server/dao/sql/event/RuleChainDebugEventRepository.java
  74. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/event/RuleNodeDebugEventRepository.java
  75. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/event/StatisticsEventRepository.java
  76. 20
      dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaQrCodeSettingsDao.java
  77. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/mobile/QrCodeSettingsRepository.java
  78. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java
  79. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java
  80. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java
  81. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageDao.java
  82. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageInfoDao.java
  83. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageInfoRepository.java
  84. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/queue/JpaQueueDao.java
  85. 13
      dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java
  86. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceDao.java
  87. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java
  88. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java
  89. 11
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java
  90. 16
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeStateDao.java
  91. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java
  92. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeStateRepository.java
  93. 9
      dao/src/main/java/org/thingsboard/server/dao/sql/settings/JpaAdminSettingsDao.java
  94. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDao.java
  95. 22
      dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserAuthSettingsDao.java
  96. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserCredentialsDao.java
  97. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java
  98. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java
  99. 1
      dao/src/main/java/org/thingsboard/server/dao/sql/user/UserAuthSettingsRepository.java
  100. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/user/UserCredentialsRepository.java

14
application/pom.xml

@ -28,8 +28,7 @@
<name>ThingsBoard Server Application</name>
<url>https://thingsboard.io</url>
<description>Open-source IoT Platform - Device management, data collection, processing and visualization
</description>
<description>Open-source IoT Platform - Device management, data collection, processing and visualization</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@ -117,7 +116,7 @@
<artifactId>remote-js-client</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<groupId>org.thingsboard.common</groupId>
<artifactId>stats</artifactId>
</dependency>
<dependency>
@ -197,6 +196,10 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
@ -344,6 +347,11 @@
<artifactId>jdbc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-random-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.javadelight</groupId>
<artifactId>delight-nashorn-sandbox</artifactId>

69
application/src/main/java/org/thingsboard/server/controller/TenantController.java

@ -19,16 +19,25 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
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.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.exception.ThingsboardException;
@ -41,6 +50,14 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.tenant.TbTenantService;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import org.thingsboard.server.service.sync.tenant.TenantExportService;
import org.thingsboard.server.service.sync.tenant.TenantImportService;
import org.thingsboard.server.service.sync.tenant.util.Result;
import org.thingsboard.server.service.sync.tenant.util.TenantExportConfig;
import org.thingsboard.server.service.sync.tenant.util.TenantImportConfig;
import java.io.IOException;
import java.util.UUID;
import static org.thingsboard.server.controller.ControllerConstants.HOME_DASHBOARD;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
@ -66,6 +83,8 @@ public class TenantController extends BaseController {
private final TenantService tenantService;
private final TbTenantService tbTenantService;
private final TenantExportService tenantExportService;
private final TenantImportService tenantImportService;
@ApiOperation(value = "Get Tenant (getTenantById)",
notes = "Fetch the Tenant object based on the provided Tenant Id. " + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@ -166,4 +185,54 @@ public class TenantController extends BaseController {
return checkNotNull(tenantService.findTenantInfos(pageLink));
}
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@PostMapping("/tenant/export")
public UUID exportTenant(@RequestBody TenantExportConfig exportConfig) {
return tenantExportService.exportTenant(exportConfig);
}
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@GetMapping("/tenant/export/result/{tenantId}")
public Result<ObjectType> getTenantExportResult(@PathVariable UUID tenantId) {
Result<ObjectType> result = tenantExportService.getResult(tenantId);
if (result == null) {
result = new Result<>();
}
return result;
}
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@DeleteMapping("/tenant/export/{tenantId}")
public void cancelTenantExport(@PathVariable UUID tenantId) {
tenantExportService.cancelExport(tenantId);
}
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@GetMapping("/tenant/export/result/{tenantId}/download")
public ResponseEntity<InputStreamResource> downloadTenantExportData(@PathVariable UUID tenantId) {
return tenantExportService.downloadResult(tenantId);
}
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@PostMapping(value = "/tenant/import", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public UUID importTenant(@RequestPart MultipartFile dataFile,
@RequestPart(required = false) TenantImportConfig importConfig) throws IOException {
if (importConfig == null) {
importConfig = new TenantImportConfig();
}
return tenantImportService.importTenant(dataFile.getInputStream(), importConfig);
}
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@GetMapping("/tenant/import/result/{tenantId}")
public Result<ObjectType> getTenantImportResult(@PathVariable UUID tenantId) {
Result<ObjectType> result = tenantImportService.getResult(tenantId);
if (result == null) {
result = new Result<>();
}
return result;
}
}

341
application/src/main/java/org/thingsboard/server/service/sync/tenant/TenantExportService.java

@ -0,0 +1,341 @@
/**
* Copyright © 2016-2024 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.tenant;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.event.Event;
import org.thingsboard.server.common.data.event.EventType;
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.kv.AttributeKv;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.LatestTsKv;
import org.thingsboard.server.common.data.kv.TsKv;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.dao.TenantEntityDao;
import org.thingsboard.server.dao.attributes.AttributesDao;
import org.thingsboard.server.dao.audit.AuditLogDao;
import org.thingsboard.server.dao.entity.EntityDaoRegistry;
import org.thingsboard.server.dao.event.EventDao;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.timeseries.TimeseriesDao;
import org.thingsboard.server.dao.timeseries.TimeseriesLatestDao;
import org.thingsboard.server.service.sync.tenant.util.DataWrapper;
import org.thingsboard.server.service.sync.tenant.util.Result;
import org.thingsboard.server.service.sync.tenant.util.ResultStore;
import org.thingsboard.server.service.sync.tenant.util.Storage;
import org.thingsboard.server.service.sync.tenant.util.TenantExportConfig;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import static org.thingsboard.server.common.data.ObjectType.ATTRIBUTE_KV;
import static org.thingsboard.server.common.data.ObjectType.AUDIT_LOG;
import static org.thingsboard.server.common.data.ObjectType.EVENT;
import static org.thingsboard.server.common.data.ObjectType.LATEST_TS_KV;
import static org.thingsboard.server.common.data.ObjectType.RELATION;
import static org.thingsboard.server.common.data.ObjectType.TENANT;
import static org.thingsboard.server.common.data.ObjectType.TS_KV;
import static org.thingsboard.server.common.data.ObjectType.values;
@Service
@RequiredArgsConstructor
@Slf4j
public class TenantExportService {
private final Storage storage;
private final EntityDaoRegistry entityDaoRegistry;
private final TenantDao tenantDao;
private final EventDao eventDao;
private final AuditLogDao auditLogDao;
private final AttributesDao attributesDao;
private final RelationDao relationDao;
private final TimeseriesLatestDao timeseriesLatestDao;
private final TimeseriesDao timeseriesDao;
private final SqlPartitioningRepository partitioningRepository;
private final CacheManager cacheManager;
@Value("${cache.specs.tenantExportResults.timeToLiveInMinutes:1440}")
private int resultsTtl;
private ResultStore<ObjectType> resultStore;
private final ExecutorService executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("tenant-export"));
private Map<ObjectType, BiConsumer<TenantId, TenantExportConfig>> customExporters;
private Map<ObjectType, BiConsumer<TenantId, EntityId>> relatedEntitiesExporters;
private static final Set<ObjectType> RELATED = EnumSet.of(EVENT, RELATION, ATTRIBUTE_KV, LATEST_TS_KV);
private static final Set<ObjectType> IGNORED = EnumSet.of(TENANT);
private static final List<ObjectType> EXPORTABLE = Arrays.stream(values())
.filter(type -> !RELATED.contains(type) && !IGNORED.contains(type))
.sorted().toList();
@PostConstruct
private void init() {
resultStore = ResultStore.<ObjectType>builder()
.name("Tenant export")
.ttlInMinutes(resultsTtl)
.persistFrequency(100)
.removalListener((tenantId, result) -> {
if (result.isDone() && result.isSuccess()) {
storage.cleanUpExportData(tenantId);
}
})
.cacheName(CacheConstants.TENANT_EXPORT_RESULT_CACHE)
.cacheManager(cacheManager)
.build();
relatedEntitiesExporters = Map.of(
RELATION, this::exportRelations,
EVENT, this::exportEvents, // todo: query by tenant
ATTRIBUTE_KV, this::exportAttributes,
LATEST_TS_KV, this::exportLatestTelemetry
);
customExporters = Map.of(
TS_KV, this::exportTelemetry,
AUDIT_LOG, this::exportAuditLogs
);
}
public UUID exportTenant(TenantExportConfig exportConfig) {
UUID tenantId = exportConfig.getTenantId();
log.info("[{}] Exporting tenant", tenantId);
Tenant tenant = tenantDao.findById(TenantId.SYS_TENANT_ID, tenantId);
if (tenant == null) {
throw new IllegalArgumentException("Tenant with id " + tenantId + " not found");
}
executor.submit(() -> {
try {
exportTenant(tenant, exportConfig);
resultStore.update(tenantId, result -> {
result.setSuccess(true);
result.setDone(true);
});
} catch (Throwable t) {
log.error("Failed to export tenant {}", tenant, t);
try {
resultStore.update(tenantId, result -> {
result.setError(ExceptionUtils.getStackTrace(t));
result.getStats().clear();
result.setDone(true);
});
storage.cleanUpExportData(tenantId);
} catch (Exception e) {
log.error("Failed to handle export error", e);
}
}
});
return tenantId;
}
private void exportTenant(Tenant tenant, TenantExportConfig exportConfig) {
TenantId tenantId = tenant.getId();
storage.init(tenantId.getId());
export(tenantId, TENANT, tenant, exportConfig);
for (ObjectType type : EXPORTABLE) {
if (exportConfig.getSkipped().contains(type)) {
continue;
}
log.debug("[{}] Exporting {} entities", tenantId, type);
if (!customExporters.containsKey(type)) {
TenantEntityDao<?> dao = entityDaoRegistry.getTenantEntityDao(type);
var entities = new PageDataIterable<>(pageLink -> dao.findAllByTenantId(tenantId, pageLink), 100);
for (Object entity : entities) {
export(tenantId, type, entity, exportConfig);
}
} else {
customExporters.get(type).accept(tenantId, exportConfig);
}
resultStore.flush(tenantId.getId(), type);
}
resultStore.flush(tenantId.getId(), RELATED.toArray(ObjectType[]::new));
storage.archiveExportData(tenantId.getId());
}
private void export(TenantId tenantId, ObjectType type, Object entity, TenantExportConfig exportConfig) {
save(tenantId, type, entity);
if (entity instanceof HasId<?> hasId && hasId.getId() instanceof EntityId entityId) {
relatedEntitiesExporters.forEach((relatedEntityType, exporter) -> {
if (!exportConfig.getSkipped().contains(relatedEntityType)) {
exporter.accept(tenantId, entityId);
}
});
}
}
private void save(TenantId tenantId, ObjectType type, Object entity) {
DataWrapper dataWrapper = DataWrapper.of(entity);
storage.save(tenantId.getId(), type, dataWrapper);
resultStore.report(tenantId.getId(), type);
log.trace("[{}][{}] Saved entity {}", tenantId, type, entity);
}
private Map<Long, Long> getPartitions(String table) {
List<Long> partitionsStartTime = partitioningRepository.fetchPartitions(table).stream().sorted().toList();
if (partitionsStartTime.isEmpty()) {
return Collections.emptyMap();
}
Map<Long, Long> partitions = new HashMap<>();
for (int i = 0; i < partitionsStartTime.size(); i++) {
Long startTime = partitionsStartTime.get(i);
Long endTime;
if (partitionsStartTime.size() - 1 == i) {
endTime = System.currentTimeMillis();
} else {
endTime = partitionsStartTime.get(i + 1) - 1;
}
partitions.put(startTime, endTime);
}
return partitions;
}
public Result<ObjectType> getResult(UUID tenantId) {
return resultStore.getStoredResult(tenantId);
}
public void cancelExport(UUID tenantId) {
boolean removed = storage.cleanUpExportData(tenantId);
if (!removed) {
throw new IllegalArgumentException("Not found");
}
}
@SneakyThrows
public ResponseEntity<InputStreamResource> downloadResult(UUID tenantId) {
var result = resultStore.getStoredResult(tenantId);
if (result == null || !result.isDone()) {
throw new IllegalStateException("Not ready yet");
} else if (!result.isSuccess()) {
throw new IllegalStateException("Tenant export failed: " + result.getError());
}
String fileName = "tenant_export_data_" + tenantId + ".tar";
return ResponseEntity.ok()
.header("Content-Type", MediaType.APPLICATION_OCTET_STREAM_VALUE)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileName)
.header("x-filename", fileName)
.body(new InputStreamResource(storage.downloadExportData(tenantId)));
}
private void exportTelemetry(TenantId tenantId, TenantExportConfig exportConfig) {
storage.readAndProcess(LATEST_TS_KV, tenantId.getId(), dataWrapper -> {
LatestTsKv latestTsKv = (LatestTsKv) dataWrapper.getEntity();
EntityId entityId = latestTsKv.getEntityId();
String key = latestTsKv.getEntry().getKey();
// todo: submit for 10 latest kv, wait
try {
timeseriesDao.findAllAsync(tenantId, entityId, key, tsKvEntry -> {
TsKv tsKv = new TsKv(entityId, tsKvEntry, exportConfig.getTsKvTtl());
save(tenantId, TS_KV, tsKv);
}).get(60, TimeUnit.MINUTES);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
private void exportAuditLogs(TenantId tenantId, TenantExportConfig exportConfig) {
Map<Long, Long> partitions = getPartitions(ModelConstants.AUDIT_LOG_TABLE_NAME);
partitions.forEach((startTime, endTime) -> {
PageDataIterable<AuditLog> auditLogs = new PageDataIterable<>(pageLink -> {
return auditLogDao.findAuditLogsByTenantId(tenantId.getId(), null, new TimePageLink(pageLink, startTime, endTime));
}, 512);
for (AuditLog auditLog : auditLogs) {
save(tenantId, AUDIT_LOG, auditLog);
}
});
}
private void exportAttributes(TenantId tenantId, EntityId entityId) {
for (AttributeScope attributeScope : AttributeScope.values()) {
List<AttributeKvEntry> attributes = attributesDao.findAll(tenantId, entityId, attributeScope);
for (AttributeKvEntry entry : attributes) {
AttributeKv attributeKv = new AttributeKv(entityId, attributeScope, entry);
save(tenantId, ATTRIBUTE_KV, attributeKv);
}
}
}
private void exportRelations(TenantId tenantId, EntityId entityId) {
List<EntityRelation> relations = relationDao.findAllByFrom(tenantId, entityId);
for (EntityRelation relation : relations) {
save(tenantId, RELATION, relation);
}
}
@SneakyThrows
private void exportLatestTelemetry(TenantId tenantId, EntityId entityId) {
List<TsKvEntry> latestTelemetry = timeseriesLatestDao.findAllLatest(tenantId, entityId).get(30, TimeUnit.SECONDS);
for (TsKvEntry tsKvEntry : latestTelemetry) {
LatestTsKv latestTsKv = new LatestTsKv(entityId, tsKvEntry);
save(tenantId, LATEST_TS_KV, latestTsKv);
}
}
private void exportEvents(TenantId tenantId, EntityId entityId) {
for (EventType eventType : EventType.values()) {
Map<Long, Long> partitions = getPartitions(eventType.getTable());
partitions.forEach((startTime, endTime) -> {
PageDataIterable<? extends Event> events = new PageDataIterable<>(pageLink -> {
return eventDao.findEvents(tenantId.getId(), entityId.getId(), eventType, new TimePageLink(pageLink, startTime, endTime));
}, 512);
for (Event event : events) {
save(tenantId, EVENT, event);
}
});
}
}
}

144
application/src/main/java/org/thingsboard/server/service/sync/tenant/TenantImportService.java

@ -0,0 +1,144 @@
/**
* Copyright © 2016-2024 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.tenant;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.entity.EntityDaoRegistry;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.service.sync.tenant.util.DataWrapper;
import org.thingsboard.server.service.sync.tenant.util.Result;
import org.thingsboard.server.service.sync.tenant.util.ResultStore;
import org.thingsboard.server.service.sync.tenant.util.Storage;
import org.thingsboard.server.service.sync.tenant.util.TenantImportConfig;
import java.io.InputStream;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import static org.thingsboard.server.common.data.ObjectType.TENANT;
@Service
@RequiredArgsConstructor
@Slf4j
public class TenantImportService {
private final Storage storage;
private final EntityDaoRegistry entityDaoRegistry;
private final TenantProfileService tenantProfileService;
private final TransactionTemplate transactionTemplate;
private final CacheManager cacheManager;
private ResultStore<ObjectType> resultStore;
@Value("${cache.specs.tenantImportResults.timeToLiveInMinutes:1440}")
private int resultsTtl;
private final ExecutorService executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("tenant-import"));
@PostConstruct
private void init() {
resultStore = ResultStore.<ObjectType>builder()
.name("Tenant import")
.ttlInMinutes(resultsTtl)
.persistFrequency(100)
.cacheName(CacheConstants.TENANT_IMPORT_RESULT_CACHE)
.cacheManager(cacheManager)
.build();
}
public UUID importTenant(InputStream dataStream, TenantImportConfig config) {
storage.unwrapImportData(dataStream);
AtomicReference<Tenant> tenant = new AtomicReference<>();
storage.readAndProcess(TENANT, dataWrapper -> {
tenant.set((Tenant) dataWrapper.getEntity());
});
if (tenant.get() == null) {
throw new IllegalStateException("No tenant data found");
}
UUID tenantId = tenant.get().getUuidId();
executor.submit(() -> {
try {
transactionTemplate.executeWithoutResult(status -> {
importTenant(tenant.get(), config);
});
resultStore.update(tenantId, result -> {
result.setSuccess(true);
result.setDone(true);
});
} catch (Throwable t) {
log.error("Failed to import tenant", t);
try {
resultStore.update(tenantId, result -> {
result.setError(ExceptionUtils.getStackTrace(t));
result.setDone(true);
});
} catch (Exception e) {
log.error("Failed to handle import error", e);
}
}
storage.cleanUpImportData();
});
return tenantId;
}
private void importTenant(Tenant tenant, TenantImportConfig config) {
TenantId tenantId = tenant.getId();
for (ObjectType type : ObjectType.values()) { // TODO: in parallel for related entities + ts kv
log.debug("[{}] Importing {} entities", tenantId, type);
storage.readAndProcess(type, dataWrapper -> {
save(tenantId, type, dataWrapper);
});
resultStore.flush(tenantId.getId(), type);
}
clearCaches();
}
private void save(TenantId tenantId, ObjectType type, DataWrapper dataWrapper) {
Object entity = dataWrapper.getEntity();
if (entity instanceof Tenant tenant) {
tenant.setTenantProfileId(tenantProfileService.findDefaultTenantProfile(TenantId.SYS_TENANT_ID).getId());
}
entityDaoRegistry.getDao(type).save(tenantId, entity);
resultStore.report(tenantId.getId(), type);
log.trace("[{}][{}] Imported entity {}", tenantId, type, entity);
}
private void clearCaches() {
cacheManager.getCacheNames().forEach(cacheName -> cacheManager.getCache(cacheName).clear());
}
public Result<ObjectType> getResult(UUID tenantId) {
return resultStore.getStoredResult(tenantId);
}
}

58
application/src/main/java/org/thingsboard/server/service/sync/tenant/util/DataWrapper.java

@ -0,0 +1,58 @@
/**
* Copyright © 2016-2024 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.tenant.util;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.HashMap;
import java.util.Map;
@Data
@NoArgsConstructor
public class DataWrapper {
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
private Object entity;
private Map<String, String> additionalInfo;
private DataWrapper(Object entity) {
this.entity = entity;
}
public static DataWrapper of(Object entity) {
return new DataWrapper(entity);
}
public void putAdditionalInfo(String key, String value) {
if (additionalInfo == null) {
additionalInfo = new HashMap<>();
}
additionalInfo.put(key, value);
}
public String getAdditionalInfo(String key) {
if (additionalInfo == null) {
return null;
}
return additionalInfo.get(key);
}
}

43
application/src/main/java/org/thingsboard/server/service/sync/tenant/util/Result.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2024 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.tenant.util;
import lombok.Data;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
@Data
public class Result<K> implements Serializable {
private boolean done;
private boolean success;
private String error;
private final Map<K, AtomicInteger> stats = new LinkedHashMap<>();
public int report(K key) {
return stats.computeIfAbsent(key, k -> new AtomicInteger()).incrementAndGet();
}
public int getCount(K key) {
return Optional.ofNullable(stats.get(key)).map(AtomicInteger::get).orElse(0);
}
}

103
application/src/main/java/org/thingsboard/server/service/sync/tenant/util/ResultStore.java

@ -0,0 +1,103 @@
/**
* Copyright © 2016-2024 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.tenant.util;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.Builder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.CacheManager;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@Slf4j
public class ResultStore<K> {
private final String name;
private final int persistFrequency;
private final Cache<UUID, Result<K>> results;
private final org.springframework.cache.Cache resultsCache;
@Builder
public ResultStore(String name, int ttlInMinutes, int persistFrequency, BiConsumer<UUID, Result<K>> removalListener,
String cacheName, CacheManager cacheManager) {
this.name = name;
this.persistFrequency = persistFrequency;
this.resultsCache = Optional.ofNullable(cacheManager.getCache(cacheName))
.orElseThrow(() -> new IllegalArgumentException(cacheName + " cache is missing"));
this.results = Caffeine.newBuilder()
.expireAfterAccess(ttlInMinutes, TimeUnit.MINUTES)
.<UUID, Result<K>>removalListener((key, result, removalCause) -> {
if (key != null) {
if (removalListener != null) {
removalListener.accept(key, result);
}
resultsCache.evict(key);
}
})
.build();
}
public void report(UUID id, K key) {
Result<K> result = getOrCreateResult(id);
int count = result.report(key);
if (count % persistFrequency == 0) {
resultsCache.put(id, result);
print(id, key, count);
}
}
public int get(UUID id, K key) {
Result<K> result = getOrCreateResult(id);
return result.getCount(key);
}
public void update(UUID id, Consumer<Result<K>> updater) {
Result<K> result = getOrCreateResult(id);
updater.accept(result);
flush(id);
}
public void flush(UUID id, K... keys) {
Result<K> result = getOrCreateResult(id);
resultsCache.put(id, result);
for (K key : keys) {
int count = result.getCount(key);
if (count % persistFrequency != 0) { // if we didn't print it before
print(id, key, count);
}
}
}
private void print(UUID id, K key, int count) {
log.info("[{}][{}][{}] {} processed", name, id, key, count);
}
private Result<K> getOrCreateResult(UUID id) {
return results.get(id, k -> new Result<K>());
}
public Result<K> getStoredResult(UUID id) {
return resultsCache.get(id, Result.class);
}
}

193
application/src/main/java/org/thingsboard/server/service/sync/tenant/util/Storage.java

@ -0,0 +1,193 @@
/**
* Copyright © 2016-2024 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.tenant.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ObjectType;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
@Service
@Slf4j
public class Storage {
@Value("${tenant.export.working_directory:/tmp/tenants-export-import}")
private String workingDirectory;
private static final ObjectMapper jsonMapper = new ObjectMapper();
private final Map<UUID, Map<Path, Writer>> files = new ConcurrentHashMap<>();
@SneakyThrows
public void init(UUID tenantId) {
Path workingDirectory = Path.of(this.workingDirectory, tenantId.toString());
Files.createDirectories(workingDirectory);
files.put(tenantId, new HashMap<>());
}
@SneakyThrows
public void save(UUID tenantId, ObjectType type, DataWrapper dataWrapper) {
Path file = getExportDataPath(tenantId, type);
Map<Path, Writer> dataFiles = files.get(tenantId);
if (dataFiles == null) {
throw new IllegalStateException("Export cancelled");
}
Writer writer = dataFiles.computeIfAbsent(file, path -> {
try {
Files.deleteIfExists(file);
Files.createFile(file);
FileOutputStream fileOutputStream = new FileOutputStream(file.toFile());
return new OutputStreamWriter(new GZIPOutputStream(fileOutputStream), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
String data = toJson(dataWrapper);
writer.write(data + System.lineSeparator());
log.trace("[{}] Saved entity to {}: {}", tenantId, file, dataWrapper.getEntity());
}
@SneakyThrows
public void archiveExportData(UUID tenantId) {
for (Writer writer : files.get(tenantId).values()) {
writer.close();
}
Map<Path, Writer> files = this.files.remove(tenantId);
TarArchiveOutputStream tarArchive = new TarArchiveOutputStream(new FileOutputStream(Path.of(workingDirectory, tenantId.toString(), "data.tar").toFile()));
for (Path file : files.keySet()) {
TarArchiveEntry archiveEntry = new TarArchiveEntry(file, file.getFileName().toString());
tarArchive.putArchiveEntry(archiveEntry);
Files.copy(file, tarArchive);
tarArchive.closeArchiveEntry();
Files.delete(file);
}
tarArchive.close();
}
@SneakyThrows
public boolean cleanUpExportData(UUID tenantId) {
Path workingDirectory = Path.of(this.workingDirectory, tenantId.toString());
deleteDirectory(workingDirectory);
return files.remove(tenantId) != null;
}
@SneakyThrows
public InputStream downloadExportData(UUID tenantId) {
return Files.newInputStream(Path.of(workingDirectory, tenantId.toString(), "data.tar"));
}
@SneakyThrows
public void unwrapImportData(InputStream dataStream) {
Path workingDirectory = Path.of(this.workingDirectory, "importing");
deleteDirectory(workingDirectory);
Files.createDirectories(workingDirectory);
TarArchiveInputStream tarArchive = new TarArchiveInputStream(dataStream);
TarArchiveEntry archiveEntry;
while ((archiveEntry = tarArchive.getNextEntry()) != null) {
Path file = workingDirectory.resolve(archiveEntry.getName());
Files.createFile(file);
IOUtils.copy(tarArchive, Files.newOutputStream(file));
}
}
@SneakyThrows
public void cleanUpImportData() {
Path workingDirectory = Path.of(this.workingDirectory, "importing");
deleteDirectory(workingDirectory);
}
@SneakyThrows
public void readAndProcess(ObjectType type, UUID tenantId, Consumer<DataWrapper> processor) {
Path file = getExportDataPath(tenantId, type);
Writer writer = files.get(tenantId).get(file);
if (writer != null) {
writer.close();
}
readAndProcess(type, file, processor);
}
public void readAndProcess(ObjectType type, Consumer<DataWrapper> processor) {
Path file = Path.of(this.workingDirectory, "importing", type.name().toLowerCase() + ".gz");
readAndProcess(type, file, processor);
}
@SneakyThrows
private void readAndProcess(ObjectType type, Path file, Consumer<DataWrapper> processor) {
if (!Files.exists(file)) {
log.debug("No data for {}", type);
return;
}
FileInputStream fileInputStream = new FileInputStream(file.toFile());
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(fileInputStream)))) {
reader.lines().forEach(line -> {
DataWrapper dataWrapper;
try {
dataWrapper = jsonMapper.readValue(line, DataWrapper.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
processor.accept(dataWrapper);
});
}
}
@SneakyThrows
private String toJson(Object o) {
return jsonMapper.writeValueAsString(o);
}
private Path getExportDataPath(UUID tenantId, ObjectType type) {
return Path.of(workingDirectory, tenantId.toString(), type.name().toLowerCase() + ".gz");
}
private void deleteDirectory(Path directory) throws IOException {
FileUtils.deleteDirectory(directory.toFile());
}
}

37
application/src/main/java/org/thingsboard/server/service/sync/tenant/util/TenantExportConfig.java

@ -0,0 +1,37 @@
/**
* Copyright © 2016-2024 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.tenant.util;
import lombok.Data;
import org.thingsboard.server.common.data.ObjectType;
import java.util.Collections;
import java.util.Set;
import java.util.UUID;
@Data
public class TenantExportConfig {
private UUID tenantId;
private Set<ObjectType> skipped;
private long tsKvTtl;
public Set<ObjectType> getSkipped() {
return skipped != null ? skipped : Collections.emptySet();
}
}

23
application/src/main/java/org/thingsboard/server/service/sync/tenant/util/TenantImportConfig.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2024 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.tenant.util;
import lombok.Data;
@Data
public class TenantImportConfig {
}

2
application/src/main/resources/thingsboard.yml

@ -1772,7 +1772,7 @@ queue:
api:
# Whether to forward entity data query requests to EDQS (otherwise use PostgreSQL implementation)
supported: "${TB_EDQS_API_SUPPORTED:false}"
# Whether to auto-enable EDQS API (if queue.edqs.api.supported is true) when sync of data to Kafka is finished
# Whether to auto-enable EDQS API (if queue.edqs.api.supported is true) when sync of data to Kafka is finished
auto_enable: "${TB_EDQS_API_AUTO_ENABLE:true}"
# Interval in milliseconds to check for ready EDQS servers
readiness_check_interval: "${TB_EDQS_READINESS_CHECK_INTERVAL_MS:60000}"

383
application/src/test/java/org/thingsboard/server/service/tenant/TenantExportImportTest.java

@ -0,0 +1,383 @@
/**
* Copyright © 2016-2024 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.tenant;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.SneakyThrows;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.event.EventType;
import org.thingsboard.server.common.data.event.LifecycleEvent;
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.id.UUIDBased;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
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.common.data.queue.Queue;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.rpc.Rpc;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.TenantEntityDao;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.audit.AuditLogDao;
import org.thingsboard.server.dao.entity.EntityDaoRegistry;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.housekeeper.CleanUpService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.service.sync.tenant.util.Result;
import org.thingsboard.server.service.sync.tenant.util.TenantExportConfig;
import org.thingsboard.server.utils.TestUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.awaitility.Awaitility.await;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.common.data.ObjectType.ADMIN_SETTINGS;
import static org.thingsboard.server.common.data.ObjectType.ATTRIBUTE_KV;
import static org.thingsboard.server.common.data.ObjectType.AUDIT_LOG;
import static org.thingsboard.server.common.data.ObjectType.CUSTOMER;
import static org.thingsboard.server.common.data.ObjectType.DASHBOARD;
import static org.thingsboard.server.common.data.ObjectType.DEVICE_PROFILE;
import static org.thingsboard.server.common.data.ObjectType.EVENT;
import static org.thingsboard.server.common.data.ObjectType.LATEST_TS_KV;
import static org.thingsboard.server.common.data.ObjectType.OTA_PACKAGE;
import static org.thingsboard.server.common.data.ObjectType.QUEUE;
import static org.thingsboard.server.common.data.ObjectType.RPC;
import static org.thingsboard.server.common.data.ObjectType.RULE_CHAIN;
import static org.thingsboard.server.common.data.ObjectType.RULE_NODE;
import static org.thingsboard.server.common.data.ObjectType.TENANT;
import static org.thingsboard.server.common.data.ObjectType.TS_KV;
import static org.thingsboard.server.utils.TestUtils.newRandomizedEntity;
@DaoSqlTest
@SuppressWarnings("unchecked")
public class TenantExportImportTest extends AbstractControllerTest {
@Autowired
private EventService eventService;
@Autowired
private AuditLogDao auditLogDao;
@Autowired
private TimeseriesService timeseriesService;
@Autowired
private AttributesService attributesService;
@Autowired
private RelationService relationService;
@Autowired
private EntityDaoRegistry entityDaoRegistry;
@Autowired
private TenantDao tenantDao;
@Autowired
private CleanUpService cleanUpService;
@Autowired
private JdbcTemplate jdbcTemplate;
private TenantId tenantId;
private Map<ObjectType, List<HasId>> createdEntities;
private Map<ObjectType, Map<EntityId, Object>> createdRelatedEntities;
@Before
public void before() throws Exception {
createdEntities = new HashMap<>();
createdRelatedEntities = new HashMap<>();
loginSysAdmin();
}
@After
public void after() {
}
@Test
public void testExportImport() throws Exception {
Tenant tenant = newRandomizedEntity(Tenant.class);
tenant.setTenantProfileId(tenantProfileId);
this.tenantId = tenant.getId();
saveEntity(tenant, TENANT);
fillTenant();
Result<ObjectType> exportResult = exportTenant(tenantId);
createdEntities.forEach((type, entities) -> {
int exportedCount = exportResult.getCount(type);
if (exportedCount != entities.size()) {
fail("expected " + entities.size() + " exported entities of type " + type + " but was " + exportedCount + ". " +
"expected: " + entities + ", actual: " + findAllOfType(type));
return;
}
assertAllPresent(type, entities);
});
byte[] exportData = doGet("/api/tenant/export/result/" + tenantId + "/download").andExpect(status().isOk()).andReturn().getResponse().getContentAsByteArray();
loginSysAdmin();
createdEntities.values().forEach(entities -> {
for (HasId entity : entities) {
if (entity.getId() instanceof EntityId entityId) {
cleanUpService.cleanUpRelatedData(tenantId, entityId);
}
}
});
jdbcTemplate.execute("TRUNCATE TABLE audit_log");
deleteTenant(tenantId);
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
createdEntities.forEach((type, entities) -> {
assertThat(findAllOfType(type)).isEmpty();
entities.forEach(entity -> {
if (entity.getId() instanceof EntityId entityId) {
assertThat(findAuditLogs(entityId)).isEmpty();
assertThat(findEvents(entityId)).isEmpty();
assertThat(findAttributes(entityId)).isEmpty();
assertThat(findLatestTs(entityId)).isEmpty();
TsKvEntry latestKv = (TsKvEntry) createdRelatedEntities.get(LATEST_TS_KV).get(entityId);
assertThat(findTsHistory(entityId, latestKv.getKey())).isEmpty();
}
});
});
});
Result<ObjectType> importResult = importTenant(exportData);
createdEntities.forEach((type, entities) -> {
int importedCount = importResult.getCount(type);
if (importedCount != entities.size()) {
fail("expected " + entities.size() + " imported entities of type " + type + " but was " + importedCount + ". " +
"expected: " + entities + ", actual: " + findAllOfType(type));
return;
}
assertAllPresent(type, entities);
});
}
private void assertAllPresent(ObjectType type, List<HasId> expectedEntities) {
assertThat(findAllOfType(type)).containsAll(expectedEntities);
expectedEntities.forEach(entity -> {
if (entity.getId() instanceof EntityId entityId) {
assertThat((List) findAttributes(entityId)).containsExactly(createdRelatedEntities.get(ATTRIBUTE_KV).get(entityId));
List<TsKvEntry> latestTs = findLatestTs(entityId);
assertThat((List) latestTs).containsExactly(createdRelatedEntities.get(LATEST_TS_KV).get(entityId));
assertThat((List) findTsHistory(entityId, latestTs.get(0).getKey())).containsExactly(createdRelatedEntities.get(TS_KV).get(entityId));
assertThat((List) findAuditLogs(entityId)).containsExactly(createdRelatedEntities.get(AUDIT_LOG).get(entityId));
assertThat((List) findEvents(entityId)).containsExactly(createdRelatedEntities.get(EVENT).get(entityId));
}
});
}
private Result<ObjectType> exportTenant(TenantId tenantId) {
TenantExportConfig exportConfig = new TenantExportConfig();
exportConfig.setTenantId(tenantId.getId());
doPost("/api/tenant/export", exportConfig, UUID.class);
Result<ObjectType> result = await().atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> doGetTyped("/api/tenant/export/result/" + tenantId.getId(), new TypeReference<>() {}), Result::isDone);
System.err.println(result);
if (!result.isSuccess()) {
throw new RuntimeException(result.getError());
}
return result;
}
private Result<ObjectType> importTenant(byte[] data) throws Exception {
MockMultipartFile dataFile = new MockMultipartFile("dataFile", "data.tar", "application/octet-stream", data);
var request = MockMvcRequestBuilders.multipart(HttpMethod.POST, "/api/tenant/import").file(dataFile);
setJwtToken(request);
UUID tenantId = readResponse(mockMvc.perform(request).andExpect(status().isOk()), UUID.class);
Result<ObjectType> result = await().atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> doGetTyped("/api/tenant/import/result/" + tenantId, new TypeReference<>() {}), Result::isDone);
System.err.println(result);
if (!result.isSuccess()) {
throw new RuntimeException(result.getError());
}
return result;
}
private void fillTenant() {
Customer customer = newRandomizedEntity(Customer.class);
customer.setTenantId(tenantId);
saveEntity(customer, CUSTOMER);
AdminSettings adminSettings = newRandomizedEntity(AdminSettings.class);
adminSettings.setTenantId(tenantId);
saveEntity(adminSettings, ADMIN_SETTINGS);
Queue queue = newRandomizedEntity(Queue.class);
queue.setTenantId(tenantId);
saveEntity(queue, QUEUE);
Rpc rpc = newRandomizedEntity(Rpc.class);
rpc.setTenantId(tenantId);
saveEntity(rpc, RPC);
RuleChain ruleChain = newRandomizedEntity(RuleChain.class);
ruleChain.setTenantId(tenantId);
saveEntity(ruleChain, RULE_CHAIN);
for (int i = 0; i < 10; i++) {
RuleNode ruleNode = newRandomizedEntity(RuleNode.class);
ruleNode.setRuleChainId(ruleChain.getId());
saveEntity(ruleNode, RULE_NODE);
createRelation(ruleNode.getId(), ruleChain.getId());
createRelation(ruleChain.getId(), ruleNode.getId());
}
Dashboard dashboard = newRandomizedEntity(Dashboard.class);
dashboard.setTenantId(tenantId);
saveEntity(dashboard, DASHBOARD);
DeviceProfile deviceProfile = newRandomizedEntity(DeviceProfile.class);
OtaPackage otaPackage = newRandomizedEntity(OtaPackage.class);
otaPackage.setTenantId(tenantId);
otaPackage.setDeviceProfileId(deviceProfile.getId());
saveEntity(otaPackage, OTA_PACKAGE);
deviceProfile.setTenantId(tenantId);
deviceProfile.setFirmwareId(otaPackage.getId());
deviceProfile.setSoftwareId(otaPackage.getId());
deviceProfile.setDefaultEdgeRuleChainId(null);
deviceProfile.setDefaultRuleChainId(ruleChain.getId());
deviceProfile.setDefaultDashboardId(dashboard.getId());
saveEntity(deviceProfile, DEVICE_PROFILE);
}
private <T extends HasId<? extends UUIDBased>> T saveEntity(T entity, ObjectType type) {
entity = (T) entityDaoRegistry.getDao(type).save(tenantId, entity);
if (entity.getId() instanceof EntityId entityId) {
createAttribute(entityId);
createTelemetry(entityId);
createAuditLog(entityId);
createEvent(entityId);
}
createdEntities.computeIfAbsent(type, k -> new ArrayList<>()).add(entity);
return entity;
}
private List<Object> findAllOfType(ObjectType type) {
if (type == TENANT) {
Tenant tenant = tenantDao.findById(TenantId.SYS_TENANT_ID, tenantId.getId());
if (tenant == null) {
return Collections.emptyList();
}
return List.of(tenant);
}
TenantEntityDao<?> dao = entityDaoRegistry.getTenantEntityDao(type);
PageData stored = dao.findAllByTenantId(tenantId, new PageLink(1000));
return stored.getData();
}
private void createRelation(EntityId to, EntityId from) {
EntityRelation relation = TestUtils.newRandomizedEntity(EntityRelation.class);
relation.setTo(to);
relation.setFrom(from);
relationService.saveRelation(tenantId, relation);
}
@SneakyThrows
private void createAttribute(EntityId entityId) {
BaseAttributeKvEntry attributeKv = newRandomizedEntity(BaseAttributeKvEntry.class);
attributesService.save(tenantId, entityId, AttributeScope.SHARED_SCOPE, attributeKv).get();
createdRelatedEntities.computeIfAbsent(ATTRIBUTE_KV, k -> new HashMap<>()).put(entityId, attributeKv);
}
@SneakyThrows
private List<AttributeKvEntry> findAttributes(EntityId entityId) {
return attributesService.findAll(tenantId, entityId, AttributeScope.SHARED_SCOPE).get();
}
@SneakyThrows
private void createTelemetry(EntityId entityId) {
BasicTsKvEntry tsKvEntry = newRandomizedEntity(BasicTsKvEntry.class);
timeseriesService.save(tenantId, entityId, tsKvEntry).get();
createdRelatedEntities.computeIfAbsent(TS_KV, k -> new HashMap<>()).put(entityId, tsKvEntry);
createdRelatedEntities.computeIfAbsent(LATEST_TS_KV, k -> new HashMap<>()).put(entityId, tsKvEntry);
}
@SneakyThrows
private List<TsKvEntry> findLatestTs(EntityId entityId) {
return timeseriesService.findAllLatest(tenantId, entityId).get();
}
@SneakyThrows
private List<TsKvEntry> findTsHistory(EntityId entityId, String key) {
return timeseriesService.findAll(tenantId, entityId, List.of(new BaseReadTsKvQuery(key, 0, System.currentTimeMillis(), 100, "ASC"))).get();
}
@SneakyThrows
private void createEvent(EntityId entityId) {
LifecycleEvent event = LifecycleEvent.builder()
.tenantId(tenantId)
.entityId(entityId.getId())
.serviceId("test")
.lcEventType("test")
.success(true)
.build();
eventService.saveAsync(event).get();
createdRelatedEntities.computeIfAbsent(EVENT, k -> new HashMap<>()).put(entityId, findEvents(entityId).get(0));
}
private List<EventInfo> findEvents(EntityId entityId) {
return eventService.findEvents(tenantId, entityId, EventType.LC_EVENT, new TimePageLink(100, 0)).getData();
}
@SneakyThrows
private void createAuditLog(EntityId entityId) {
AuditLog auditLog = newRandomizedEntity(AuditLog.class);
auditLog.setTenantId(tenantId);
auditLog.setEntityId(entityId);
auditLog = auditLogDao.save(tenantId, auditLog);
createdRelatedEntities.computeIfAbsent(AUDIT_LOG, k -> new HashMap<>()).put(entityId, auditLog);
}
private List<AuditLog> findAuditLogs(EntityId entityId) {
return auditLogDao.findAuditLogsByTenantIdAndEntityId(tenantId.getId(), entityId, Arrays.stream(ActionType.values()).toList(), new TimePageLink(100, 0)).getData();
}
}

75
application/src/test/java/org/thingsboard/server/utils/TestUtils.java

@ -0,0 +1,75 @@
/**
* Copyright © 2016-2024 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.databind.JsonNode;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
import org.jeasy.random.EasyRandom;
import org.jeasy.random.EasyRandomParameters;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration;
import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.DeviceConfiguration;
import org.thingsboard.server.common.data.device.data.DeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration;
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.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.DeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import java.nio.ByteBuffer;
import java.util.UUID;
public class TestUtils {
static EasyRandom easyRandom;
static {
EasyRandomParameters parameters = new EasyRandomParameters()
.randomize(DeviceConfiguration.class, () -> easyRandom.nextObject(DefaultDeviceConfiguration.class))
.randomize(DeviceTransportConfiguration.class, () -> easyRandom.nextObject(DefaultDeviceTransportConfiguration.class))
.randomize(DeviceProfileConfiguration.class, () -> easyRandom.nextObject(DefaultDeviceProfileConfiguration.class))
.randomize(DeviceProfileTransportConfiguration.class, () -> easyRandom.nextObject(DefaultDeviceProfileTransportConfiguration.class))
.randomize(DeviceProfileProvisionConfiguration.class, () -> easyRandom.nextObject(AllowCreateNewDevicesDeviceProfileProvisionConfiguration.class))
.randomize(DeviceProfileAlarm.class, DeviceProfileAlarm::new)
.randomize(JsonNode.class, () -> JacksonUtil.newObjectNode()
.put(RandomStringUtils.randomAlphanumeric(10), RandomStringUtils.randomAlphanumeric(10))
.put(RandomStringUtils.randomAlphanumeric(10), easyRandom.nextDouble()))
.randomize(EntityId.class, () -> new DeviceId(UUID.randomUUID()))
.randomize(KvEntry.class, () -> easyRandom.nextBoolean() ? easyRandom.nextObject(StringDataEntry.class) : easyRandom.nextObject(DoubleDataEntry.class))
.randomize(Long.class, () -> RandomUtils.nextLong(1, Integer.MAX_VALUE))
.randomize(ByteBuffer.class, () -> {
byte[] bytes = new byte[50];
easyRandom.nextBytes(bytes);
return ByteBuffer.wrap(bytes);
});
easyRandom = new EasyRandom(parameters);
}
public static <T> T newRandomizedEntity(Class<T> type) {
return easyRandom.nextObject(type);
}
}

8
application/src/test/resources/logback-test.xml

@ -13,7 +13,7 @@
<logger name="org.springframework" level="WARN"/>
<logger name="org.springframework.boot.test" level="WARN"/>
<logger name="org.apache.cassandra" level="WARN"/>
<logger name="org.testcontainers" level="INFO" />
<logger name="org.testcontainers" level="INFO"/>
<logger name="org.eclipse.leshan" level="INFO"/>
<logger name="org.thingsboard.server.controller.AbstractWebTest" level="INFO"/>
<logger name="org.thingsboard.server.service.script" level="INFO"/>
@ -29,9 +29,9 @@
<logger name="org.eclipse.californium.core" level="INFO"/>
<!-- To reduce logs -->
<logger name="org.apache.catalina.loader.WebappClassLoaderBase" level="ERROR" />
<logger name="org.thingsboard.server.service.queue.DefaultTbClusterService" level="ERROR" />
<logger name="org.thingsboard.server.service.security.auth.jwt.settings.DefaultJwtSettingsService" level="ERROR" />
<logger name="org.apache.catalina.loader.WebappClassLoaderBase" level="ERROR"/>
<logger name="org.thingsboard.server.service.queue.DefaultTbClusterService" level="ERROR"/>
<logger name="org.thingsboard.server.service.security.auth.jwt.settings.DefaultJwtSettingsService" level="ERROR"/>
<!-- Coap client context debug for the test scope -->
<!-- <logger name="org.thingsboard.server.transport.coap.client.DefaultCoapClientContext" level="TRACE" />-->

3
common/dao-api/src/main/java/org/thingsboard/server/dao/ota/OtaPackageService.java

@ -51,5 +51,8 @@ public interface OtaPackageService extends EntityDaoService {
void deleteOtaPackagesByTenantId(TenantId tenantId);
void deleteOtaPackagesByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId);
long sumDataSizeByTenantId(TenantId tenantId);
}

3
common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java

@ -54,4 +54,7 @@ public class CacheConstants {
public static final String ALARM_TYPES_CACHE = "alarmTypes";
public static final String QR_CODE_SETTINGS_CACHE = "qrCodeSettings";
public static final String MOBILE_SECRET_KEY_CACHE = "mobileSecretKey";
public static final String TENANT_EXPORT_RESULT_CACHE = "tenantExportResults";
public static final String TENANT_IMPORT_RESULT_CACHE = "tenantImportResults";
}

15
common/data/src/main/java/org/thingsboard/server/common/data/ObjectType.java

@ -23,17 +23,25 @@ public enum ObjectType {
TENANT,
TENANT_PROFILE,
CUSTOMER,
ADMIN_SETTINGS,
QUEUE,
RPC,
RULE_CHAIN,
RULE_NODE,
RULE_NODE_STATE,
OTA_PACKAGE,
RESOURCE,
EVENT,
RULE_NODE,
USER,
USER_CREDENTIALS,
USER_SETTINGS,
USER_AUTH_SETTINGS,
EDGE,
EDGE_EVENT,
WIDGETS_BUNDLE,
WIDGET_TYPE,
WIDGETS_BUNDLE_WIDGET,
DASHBOARD,
DEVICE_PROFILE,
DEVICE,
@ -53,11 +61,14 @@ public enum ObjectType {
ALARM_COMMENT,
API_USAGE_STATE,
QUEUE_STATS,
ALARM_TYPE,
MOBILE_APP_SETTINGS,
EVENT,
AUDIT_LOG,
RELATION,
ATTRIBUTE_KV,
LATEST_TS_KV;
LATEST_TS_KV,
TS_KV;
public static final Set<ObjectType> edqsTenantTypes = EnumSet.of(
TENANT, CUSTOMER, DEVICE_PROFILE, DEVICE, ASSET_PROFILE, ASSET, EDGE, ENTITY_VIEW, USER, DASHBOARD,

21
common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java

@ -15,12 +15,16 @@
*/
package org.thingsboard.server.common.data;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonSetter;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.id.OtaPackageId;
import java.nio.ByteBuffer;
import java.util.Base64;
import java.util.Optional;
@Schema
@Data
@ -44,4 +48,21 @@ public class OtaPackage extends OtaPackageInfo {
super(otaPackage);
this.data = otaPackage.getData();
}
@JsonGetter("data")
public String getEncodedData() {
return Optional.ofNullable(data)
.map(ByteBuffer::array)
.map(Base64.getEncoder()::encodeToString)
.orElse(null);
}
@JsonSetter("data")
public void setEncodedData(String data) {
this.data = Optional.ofNullable(data)
.map(Base64.getDecoder()::decode)
.map(ByteBuffer::wrap)
.orElse(null);
}
}

27
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmType.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2024 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.alarm;
import lombok.Data;
import org.thingsboard.server.common.data.id.TenantId;
@Data
public class AlarmType {
private TenantId tenantId;
private String type;
}

12
common/data/src/main/java/org/thingsboard/server/common/data/event/ErrorEvent.java

@ -15,6 +15,8 @@
*/
package org.thingsboard.server.common.data.event;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Builder;
import lombok.EqualsAndHashCode;
@ -23,6 +25,7 @@ import lombok.Setter;
import lombok.ToString;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.id.EventId;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.UUID;
@ -40,6 +43,15 @@ public class ErrorEvent extends Event {
this.error = error;
}
@JsonCreator
private ErrorEvent(@JsonProperty("tenantId") TenantId tenantId, @JsonProperty("entityId") UUID entityId,
@JsonProperty("serviceId") String serviceId, @JsonProperty("id") EventId id, @JsonProperty("ts") long ts,
@JsonProperty("method") String method, @JsonProperty("error") String error) {
super(tenantId, entityId, serviceId, id, ts);
this.method = method;
this.error = error;
}
@Getter
@Setter
private String method;

8
common/data/src/main/java/org/thingsboard/server/common/data/event/Event.java

@ -49,6 +49,14 @@ public abstract class Event extends BaseData<EventId> {
this.createdTime = ts;
}
public Event(TenantId tenantId, UUID entityId, String serviceId, EventId id, long ts) {
super(id);
this.tenantId = tenantId != null ? tenantId : TenantId.SYS_TENANT_ID;
this.entityId = entityId;
this.serviceId = serviceId;
this.createdTime = ts;
}
public abstract EventType getType();
public EventInfo toInfo(EntityType entityType) {

13
common/data/src/main/java/org/thingsboard/server/common/data/event/LifecycleEvent.java

@ -15,6 +15,8 @@
*/
package org.thingsboard.server.common.data.event;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Builder;
import lombok.EqualsAndHashCode;
@ -23,6 +25,7 @@ import lombok.Setter;
import lombok.ToString;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.id.EventId;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.UUID;
@ -43,6 +46,16 @@ public class LifecycleEvent extends Event {
this.error = error;
}
@JsonCreator
private LifecycleEvent(@JsonProperty("tenantId") TenantId tenantId, @JsonProperty("entityId") UUID entityId,
@JsonProperty("serviceId") String serviceId, @JsonProperty("id") EventId id, @JsonProperty("ts") long ts,
@JsonProperty("lcEventType") String lcEventType, @JsonProperty("success") boolean success, @JsonProperty("error") String error) {
super(tenantId, entityId, serviceId, id, ts);
this.lcEventType = lcEventType;
this.success = success;
this.error = error;
}
@Getter
private final String lcEventType;
@Getter

12
common/data/src/main/java/org/thingsboard/server/common/data/event/RuleChainDebugEvent.java

@ -15,6 +15,8 @@
*/
package org.thingsboard.server.common.data.event;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Builder;
import lombok.EqualsAndHashCode;
@ -23,6 +25,7 @@ import lombok.Setter;
import lombok.ToString;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.id.EventId;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.UUID;
@ -40,6 +43,15 @@ public class RuleChainDebugEvent extends Event {
this.error = error;
}
@JsonCreator
private RuleChainDebugEvent(@JsonProperty("tenantId") TenantId tenantId, @JsonProperty("entityId") UUID entityId,
@JsonProperty("serviceId") String serviceId, @JsonProperty("id") EventId id, @JsonProperty("ts") long ts,
@JsonProperty("message") String message, @JsonProperty("error") String error) {
super(tenantId, entityId, serviceId, id, ts);
this.message = message;
this.error = error;
}
@Getter
@Setter
private String message;

21
common/data/src/main/java/org/thingsboard/server/common/data/event/RuleNodeDebugEvent.java

@ -15,6 +15,8 @@
*/
package org.thingsboard.server.common.data.event;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Builder;
import lombok.EqualsAndHashCode;
@ -24,6 +26,7 @@ import lombok.ToString;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EventId;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.UUID;
@ -51,6 +54,24 @@ public class RuleNodeDebugEvent extends Event {
this.error = error;
}
@JsonCreator
private RuleNodeDebugEvent(@JsonProperty("tenantId") TenantId tenantId, @JsonProperty("entityId") UUID entityId,
@JsonProperty("serviceId") String serviceId, @JsonProperty("id") EventId id, @JsonProperty("ts") long ts,
@JsonProperty("eventType") String eventType, @JsonProperty("eventEntity") EntityId eventEntity, @JsonProperty("msgId") UUID msgId,
@JsonProperty("msgType") String msgType, @JsonProperty("dataType") String dataType, @JsonProperty("relationType") String relationType,
@JsonProperty("data") String data, @JsonProperty("metadata") String metadata, @JsonProperty("error") String error) {
super(tenantId, entityId, serviceId, id, ts);
this.eventType = eventType;
this.eventEntity = eventEntity;
this.msgId = msgId;
this.msgType = msgType;
this.dataType = dataType;
this.relationType = relationType;
this.data = data;
this.metadata = metadata;
this.error = error;
}
@Getter
private final String eventType;
@Getter

12
common/data/src/main/java/org/thingsboard/server/common/data/event/StatisticsEvent.java

@ -15,6 +15,8 @@
*/
package org.thingsboard.server.common.data.event;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Builder;
import lombok.EqualsAndHashCode;
@ -22,6 +24,7 @@ import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.id.EventId;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.UUID;
@ -39,6 +42,15 @@ public class StatisticsEvent extends Event {
this.errorsOccurred = errorsOccurred;
}
@JsonCreator
private StatisticsEvent(@JsonProperty("tenantId") TenantId tenantId, @JsonProperty("entityId") UUID entityId,
@JsonProperty("serviceId") String serviceId, @JsonProperty("id") EventId id, @JsonProperty("ts") long ts,
@JsonProperty("messagesProcessed") long messagesProcessed, @JsonProperty("errorsOccurred") long errorsOccurred) {
super(tenantId, entityId, serviceId, id, ts);
this.messagesProcessed = messagesProcessed;
this.errorsOccurred = errorsOccurred;
}
@Getter
private final long messagesProcessed;
@Getter

35
common/data/src/main/java/org/thingsboard/server/common/data/kv/AttributeKv.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2024 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.kv;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.id.EntityId;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AttributeKv {
private EntityId entityId;
private AttributeScope scope;
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonIgnoreProperties(ignoreUnknown = true)
private AttributeKvEntry entry;
}

6
common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicKvEntry.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.common.data.kv;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
@ -36,26 +37,31 @@ public abstract class BasicKvEntry implements KvEntry {
return key;
}
@JsonIgnore
@Override
public Optional<String> getStrValue() {
return Optional.ofNullable(null);
}
@JsonIgnore
@Override
public Optional<Long> getLongValue() {
return Optional.ofNullable(null);
}
@JsonIgnore
@Override
public Optional<Boolean> getBooleanValue() {
return Optional.ofNullable(null);
}
@JsonIgnore
@Override
public Optional<Double> getDoubleValue() {
return Optional.ofNullable(null);
}
@JsonIgnore
@Override
public Optional<String> getJsonValue() {
return Optional.ofNullable(null);

6
common/data/src/main/java/org/thingsboard/server/common/data/kv/BooleanDataEntry.java

@ -15,13 +15,17 @@
*/
package org.thingsboard.server.common.data.kv;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Objects;
import java.util.Optional;
public class BooleanDataEntry extends BasicKvEntry {
private final Boolean value;
public BooleanDataEntry(String key, Boolean value) {
@JsonCreator
public BooleanDataEntry(@JsonProperty("key") String key, @JsonProperty("value") Boolean value) {
super(key);
this.value = value;
}

8
common/data/src/main/java/org/thingsboard/server/common/data/kv/DoubleDataEntry.java

@ -15,6 +15,9 @@
*/
package org.thingsboard.server.common.data.kv;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Objects;
import java.util.Optional;
@ -22,7 +25,8 @@ public class DoubleDataEntry extends BasicKvEntry {
private final Double value;
public DoubleDataEntry(String key, Double value) {
@JsonCreator
public DoubleDataEntry(@JsonProperty("key") String key, @JsonProperty("value") Double value) {
super(key);
this.value = value;
}
@ -62,7 +66,7 @@ public class DoubleDataEntry extends BasicKvEntry {
"value=" + value +
"} " + super.toString();
}
@Override
public String getValueAsString() {
return Double.toString(value);

6
common/data/src/main/java/org/thingsboard/server/common/data/kv/JsonDataEntry.java

@ -15,6 +15,9 @@
*/
package org.thingsboard.server.common.data.kv;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Objects;
import java.util.Optional;
@ -22,7 +25,8 @@ public class JsonDataEntry extends BasicKvEntry {
private final String value;
public JsonDataEntry(String key, String value) {
@JsonCreator
public JsonDataEntry(@JsonProperty("key") String key, @JsonProperty("value") String value) {
super(key);
this.value = value;
}

3
common/data/src/main/java/org/thingsboard/server/common/data/kv/KvEntry.java

@ -15,6 +15,8 @@
*/
package org.thingsboard.server.common.data.kv;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.io.Serializable;
import java.util.Optional;
@ -39,6 +41,7 @@ public interface KvEntry extends Serializable {
Optional<String> getJsonValue();
@JsonIgnore
String getValueAsString();
Object getValue();

33
common/data/src/main/java/org/thingsboard/server/common/data/kv/LatestTsKv.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2024 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.kv;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.id.EntityId;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LatestTsKv {
private EntityId entityId;
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonIgnoreProperties(ignoreUnknown = true)
private TsKvEntry entry;
}

8
common/data/src/main/java/org/thingsboard/server/common/data/kv/LongDataEntry.java

@ -15,6 +15,9 @@
*/
package org.thingsboard.server.common.data.kv;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Objects;
import java.util.Optional;
@ -22,7 +25,8 @@ public class LongDataEntry extends BasicKvEntry {
private final Long value;
public LongDataEntry(String key, Long value) {
@JsonCreator
public LongDataEntry(@JsonProperty("key") String key, @JsonProperty("value") Long value) {
super(key);
this.value = value;
}
@ -62,7 +66,7 @@ public class LongDataEntry extends BasicKvEntry {
"value=" + value +
"} " + super.toString();
}
@Override
public String getValueAsString() {
return Long.toString(value);

6
common/data/src/main/java/org/thingsboard/server/common/data/kv/StringDataEntry.java

@ -15,6 +15,9 @@
*/
package org.thingsboard.server.common.data.kv;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Objects;
import java.util.Optional;
@ -24,7 +27,8 @@ public class StringDataEntry extends BasicKvEntry {
private final String value;
public StringDataEntry(String key, String value) {
@JsonCreator
public StringDataEntry(@JsonProperty("key") String key, @JsonProperty("value") String value) {
super(key);
this.value = value;
}

35
common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKv.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2024 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.kv;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.id.EntityId;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TsKv {
private EntityId entityId;
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonIgnoreProperties(ignoreUnknown = true)
private TsKvEntry entry;
private long ttl;
}

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

@ -24,7 +24,7 @@ import java.util.Collection;
import java.util.List;
import java.util.UUID;
public interface Dao<T> {
public interface Dao<T> extends ObjectDao<T> {
List<T> find(TenantId tenantId);

29
dao/src/main/java/org/thingsboard/server/dao/ObjectDao.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2024 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.ObjectType;
import org.thingsboard.server.common.data.id.TenantId;
public interface ObjectDao<T> { // TODO: choose a better name
T save(TenantId tenantId, T entity);
default ObjectType getType() {
return null;
}
}

2
dao/src/main/java/org/thingsboard/server/dao/TenantEntityDao.java

@ -19,7 +19,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;
public interface TenantEntityDao<T> {
public interface TenantEntityDao<T> extends ObjectDao<T> {
default Long countByTenantId(TenantId tenantId) {
throw new UnsupportedOperationException();

4
dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java

@ -21,7 +21,9 @@ import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKv;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.dao.ObjectDao;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.dao.model.sql.AttributeKvEntity;
@ -33,7 +35,7 @@ import java.util.UUID;
/**
* @author Andrew Shvayka
*/
public interface AttributesDao {
public interface AttributesDao extends ObjectDao<AttributeKv> {
Optional<AttributeKvEntry> find(TenantId tenantId, EntityId entityId, AttributeScope attributeScope, String attributeKey);

6
dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java

@ -46,6 +46,7 @@ import org.thingsboard.server.dao.entity.CachedVersionedEntityService;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
@ -87,6 +88,9 @@ public class DeviceProfileServiceImpl extends CachedVersionedEntityService<Devic
@Autowired
private DeviceService deviceService;
@Autowired
private OtaPackageService otaPackageService;
@Autowired
private DeviceProfileDataValidator deviceProfileValidator;
@ -239,6 +243,8 @@ public class DeviceProfileServiceImpl extends CachedVersionedEntityService<Devic
DeviceProfileId deviceProfileId = deviceProfile.getId();
try {
deviceProfileDao.removeById(tenantId, deviceProfileId.getId());
otaPackageService.deleteOtaPackagesByDeviceProfileId(tenantId, deviceProfileId);
publishEvictEvent(new DeviceProfileEvictEvent(deviceProfile.getTenantId(), deviceProfile.getName(),
null, deviceProfile.getId(), deviceProfile.isDefault(),
deviceProfile.getProvisionDeviceKey()));

38
dao/src/main/java/org/thingsboard/server/dao/entity/EntityDaoRegistry.java

@ -18,7 +18,10 @@ package org.thingsboard.server.dao.entity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ObjectDao;
import org.thingsboard.server.dao.TenantEntityDao;
import java.util.EnumMap;
import java.util.List;
@ -29,23 +32,46 @@ import java.util.Map;
@SuppressWarnings({"unchecked"})
public class EntityDaoRegistry {
private final Map<EntityType, Dao<?>> daos = new EnumMap<>(EntityType.class);
private final Map<ObjectType, ObjectDao<?>> objectDaos = new EnumMap<>(ObjectType.class);
private final Map<EntityType, Dao<?>> entityDaos = new EnumMap<>(EntityType.class);
private EntityDaoRegistry(List<Dao<?>> daos) {
private EntityDaoRegistry(List<ObjectDao<?>> daos) {
daos.forEach(dao -> {
EntityType entityType = dao.getEntityType();
if (entityType != null) {
this.daos.put(entityType, dao);
if (dao instanceof Dao<?> entityDao) {
EntityType entityType = entityDao.getEntityType();
if (entityType != null) {
entityDaos.put(entityType, entityDao);
}
}
ObjectType objectType = dao.getType();
if (objectType != null) {
objectDaos.put(objectType, dao);
}
});
}
public <T> Dao<T> getDao(EntityType entityType) {
Dao<T> dao = (Dao<T>) daos.get(entityType);
Dao<T> dao = (Dao<T>) entityDaos.get(entityType);
if (dao == null) {
throw new IllegalArgumentException("Missing dao for entity type " + entityType);
}
return dao;
}
public <T> ObjectDao<T> getDao(ObjectType objectType) {
ObjectDao<T> dao = (ObjectDao<T>) objectDaos.get(objectType);
if (dao == null) {
throw new IllegalArgumentException("Missing dao for object type " + objectType);
}
return dao;
}
public <T> TenantEntityDao<T> getTenantEntityDao(ObjectType objectType) {
ObjectDao<?> dao = objectDaos.get(objectType);
if (!(dao instanceof TenantEntityDao<?>)) {
throw new IllegalArgumentException("Missing tenant entity dao for entity type " + objectType);
}
return (TenantEntityDao<T>) dao;
}
}

3
dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java

@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.event.EventFilter;
import org.thingsboard.server.common.data.event.EventType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.ObjectDao;
import java.util.List;
import java.util.UUID;
@ -28,7 +29,7 @@ import java.util.UUID;
/**
* The Interface EventDao.
*/
public interface EventDao {
public interface EventDao extends ObjectDao<Event> {
/**
* Save or update event object async

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

@ -281,6 +281,8 @@ public class ModelConstants {
public static final String ALARM_COMMENT_TYPE = "type";
public static final String ALARM_COMMENT_COMMENT = "comment";
public static final String ALARM_TYPES_TABLE_NAME = "alarm_types";
/**
* Entity relation constants.
*/

33
dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmTypeCompositeKey.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2024 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.model.sql;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.UUID;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AlarmTypeCompositeKey implements Serializable {
private UUID tenantId;
private String type;
}

65
dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmTypeEntity.java

@ -0,0 +1,65 @@
/**
* Copyright © 2016-2024 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.model.sql;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import jakarta.persistence.Table;
import lombok.Data;
import org.thingsboard.server.common.data.alarm.AlarmType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.ToData;
import java.util.UUID;
@Data
@Entity
@Table(name = ModelConstants.ALARM_TYPES_TABLE_NAME)
@IdClass(AlarmTypeCompositeKey.class)
public class AlarmTypeEntity implements ToData<AlarmType> {
@Id
@Column(name = ModelConstants.TENANT_ID_PROPERTY, nullable = false)
private UUID tenantId;
@Id
@Column(name = ModelConstants.ALARM_TYPE_PROPERTY, nullable = false)
private String type;
public AlarmTypeEntity() {}
public AlarmTypeEntity(AlarmType alarmType) {
setTenantId(alarmType.getTenantId().getId());
setType(alarmType.getType());
}
public AlarmTypeEntity(UUID tenantId, String type) {
this.tenantId = tenantId;
this.type = type;
}
@Override
public AlarmType toData() {
AlarmType alarmType = new AlarmType();
alarmType.setTenantId(TenantId.fromUUID(tenantId));
alarmType.setType(type);
return alarmType;
}
}

39
dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java

@ -235,24 +235,41 @@ public class BaseOtaPackageService extends AbstractCachedEntityService<OtaPackag
tenantOtaPackageRemover.removeEntities(tenantId, tenantId);
}
@Override
public void deleteOtaPackagesByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId) {
deviceProfileOtaPackageRemover.removeEntities(tenantId, deviceProfileId);
}
@Override
public void deleteByTenantId(TenantId tenantId) {
deleteOtaPackagesByTenantId(tenantId);
}
private PaginatedRemover<TenantId, OtaPackageInfo> tenantOtaPackageRemover =
new PaginatedRemover<>() {
private final PaginatedRemover<TenantId, OtaPackageInfo> tenantOtaPackageRemover = new PaginatedRemover<>() {
@Override
protected PageData<OtaPackageInfo> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return otaPackageInfoDao.findOtaPackageInfoByTenantId(id, pageLink);
}
@Override
protected void removeEntity(TenantId tenantId, OtaPackageInfo entity) {
deleteOtaPackage(tenantId, entity.getId());
}
};
@Override
protected PageData<OtaPackageInfo> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return otaPackageInfoDao.findOtaPackageInfoByTenantId(id, pageLink);
}
private final PaginatedRemover<DeviceProfileId, OtaPackageId> deviceProfileOtaPackageRemover = new PaginatedRemover<>() {
@Override
protected void removeEntity(TenantId tenantId, OtaPackageInfo entity) {
deleteOtaPackage(tenantId, entity.getId());
}
};
@Override
protected PageData<OtaPackageId> findEntities(TenantId tenantId, DeviceProfileId deviceProfileId, PageLink pageLink) {
return otaPackageInfoDao.findOtaPackageIdsByDeviceProfileId(tenantId, deviceProfileId, pageLink);
}
@Override
protected void removeEntity(TenantId tenantId, OtaPackageId id) {
deleteOtaPackage(tenantId, id);
}
};
@Override
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {

2
dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageInfoDao.java

@ -30,6 +30,8 @@ public interface OtaPackageInfoDao extends Dao<OtaPackageInfo> {
PageData<OtaPackageInfo> findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, PageLink pageLink);
PageData<OtaPackageId> findOtaPackageIdsByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId, PageLink pageLink);
boolean isOtaPackageUsed(OtaPackageId otaPackageId, OtaPackageType otaPackageType, DeviceProfileId deviceProfileId);
}

3
dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java

@ -21,13 +21,14 @@ 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.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.ObjectDao;
import java.util.List;
/**
* Created by ashvayka on 25.04.17.
*/
public interface RelationDao {
public interface RelationDao extends ObjectDao<EntityRelation> {
List<EntityRelation> findAllByFrom(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup);

4
dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java

@ -19,7 +19,6 @@ import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.dao.device.DeviceProfileDao;
@ -52,8 +51,7 @@ public abstract class BaseOtaPackageDataValidator<D extends BaseData<?>> extends
}
if (otaPackageInfo.getDeviceProfileId() != null) {
DeviceProfile deviceProfile = getDeviceProfileDao().findById(otaPackageInfo.getTenantId(), otaPackageInfo.getDeviceProfileId().getId());
if (deviceProfile == null) {
if (!deviceProfileDao.existsById(otaPackageInfo.getTenantId(), otaPackageInfo.getDeviceProfileId().getId())) {
throw new DataValidationException("OtaPackage is referencing to non-existent device profile!");
}
}

10
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmCommentRepository.java

@ -28,12 +28,12 @@ import java.util.UUID;
public interface AlarmCommentRepository extends JpaRepository<AlarmCommentEntity, UUID> {
@Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmCommentInfoEntity(a, u.firstName, u.lastName, u.email) FROM AlarmCommentEntity a " +
"LEFT JOIN UserEntity u on u.id = a.userId " +
"WHERE a.alarmId = :alarmId ",
"LEFT JOIN UserEntity u on u.id = a.userId " +
"WHERE a.alarmId = :alarmId ",
countQuery = "" +
"SELECT count(a) " +
"FROM AlarmCommentEntity a " +
"WHERE a.alarmId = :alarmId ")
"SELECT count(a) " +
"FROM AlarmCommentEntity a " +
"WHERE a.alarmId = :alarmId ")
Page<AlarmCommentInfoEntity> findAllByAlarmId(@Param("alarmId") UUID alarmId,
Pageable pageable);

37
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmTypeRepository.java

@ -0,0 +1,37 @@
/**
* Copyright © 2016-2024 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.sql.alarm;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.model.sql.AlarmTypeCompositeKey;
import org.thingsboard.server.dao.model.sql.AlarmTypeEntity;
import org.thingsboard.server.dao.model.sql.EntityAlarmCompositeKey;
import org.thingsboard.server.dao.model.sql.EntityAlarmEntity;
import java.util.List;
import java.util.UUID;
public interface AlarmTypeRepository extends JpaRepository<AlarmTypeEntity, AlarmTypeCompositeKey> {
Page<AlarmTypeEntity> findByTenantId(UUID tenantId, Pageable pageable);
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDao.java

@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentInfo;
import org.thingsboard.server.common.data.id.AlarmId;
@ -92,4 +93,9 @@ public class JpaAlarmCommentDao extends JpaPartitionedAbstractDao<AlarmCommentEn
return alarmCommentRepository;
}
@Override
public ObjectType getType() {
return ObjectType.ALARM_COMMENT;
}
}

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

@ -29,6 +29,7 @@ import org.springframework.util.CollectionUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
@ -562,4 +563,9 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
return EntityType.ALARM;
}
@Override
public ObjectType getType() {
return ObjectType.ALARM;
}
}

52
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmTypeDao.java

@ -0,0 +1,52 @@
/**
* Copyright © 2016-2024 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.sql.alarm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.alarm.AlarmType;
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.DaoUtil;
import org.thingsboard.server.dao.TenantEntityDao;
import org.thingsboard.server.dao.model.sql.AlarmTypeEntity;
import org.thingsboard.server.dao.util.SqlDao;
@Component
@SqlDao
public class JpaAlarmTypeDao implements TenantEntityDao<AlarmType> {
@Autowired
private AlarmTypeRepository alarmTypeRepository;
@Override
public PageData<AlarmType> findAllByTenantId(TenantId tenantId, PageLink pageLink) {
return DaoUtil.toPageData(alarmTypeRepository.findByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink, "tenantId", "type")));
}
@Override
public AlarmType save(TenantId tenantId, AlarmType alarmType) {
return alarmTypeRepository.save(new AlarmTypeEntity(alarmType)).toData();
}
@Override
public ObjectType getType() {
return ObjectType.ALARM_TYPE;
}
}

12
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaEntityAlarmDao.java

@ -17,12 +17,14 @@ package org.thingsboard.server.dao.sql.alarm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.alarm.EntityAlarm;
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.DaoUtil;
import org.thingsboard.server.dao.TenantEntityDao;
import org.thingsboard.server.dao.model.sql.EntityAlarmEntity;
import org.thingsboard.server.dao.util.SqlDao;
@Component
@ -37,4 +39,14 @@ public class JpaEntityAlarmDao implements TenantEntityDao<EntityAlarm> {
return DaoUtil.toPageData(entityAlarmRepository.findByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink, "entityId", "alarmId")));
}
@Override
public EntityAlarm save(TenantId tenantId, EntityAlarm entityAlarm) {
return entityAlarmRepository.save(new EntityAlarmEntity(entityAlarm)).toData();
}
@Override
public ObjectType getType() {
return ObjectType.ENTITY_ALARM;
}
}

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

@ -23,6 +23,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.ProfileEntityIdInfo;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo;
@ -311,4 +312,9 @@ public class JpaAssetDao extends JpaAbstractDao<AssetEntity, Asset> implements A
return EntityType.ASSET;
}
@Override
public ObjectType getType() {
return ObjectType.ASSET;
}
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetProfileDao.java

@ -22,6 +22,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.asset.AssetProfileInfo;
import org.thingsboard.server.common.data.edqs.fields.AssetProfileFields;
@ -156,4 +157,9 @@ public class JpaAssetProfileDao extends JpaAbstractDao<AssetProfileEntity, Asset
return EntityType.ASSET_PROFILE;
}
@Override
public ObjectType getType() {
return ObjectType.ASSET_PROFILE;
}
}

26
dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java

@ -26,9 +26,11 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKv;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.common.stats.StatsFactory;
@ -242,4 +244,28 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl
attributeType,
attributeKey);
}
@Override
public AttributeKv save(TenantId tenantId, AttributeKv attributeKv) {
attributeKvInsertRepository.saveOrUpdate(List.of(toEntity(attributeKv.getEntityId(), attributeKv.getScope(), attributeKv.getEntry())));
return attributeKv;
}
private AttributeKvEntity toEntity(EntityId entityId, AttributeScope scope, AttributeKvEntry entry) {
AttributeKvEntity entity = new AttributeKvEntity();
entity.setId(new AttributeKvCompositeKey(entityId.getId(), scope.getId(), keyDictionaryDao.getOrSaveKeyId(entry.getKey())));
entity.setLastUpdateTs(entry.getLastUpdateTs());
entity.setStrValue(entry.getStrValue().orElse(null));
entity.setDoubleValue(entry.getDoubleValue().orElse(null));
entity.setLongValue(entry.getLongValue().orElse(null));
entity.setBooleanValue(entry.getBooleanValue().orElse(null));
entity.setJsonValue(entry.getJsonValue().orElse(null));
return entity;
}
@Override
public ObjectType getType() {
return ObjectType.ATTRIBUTE_KV;
}
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java

@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.id.CustomerId;
@ -129,4 +130,9 @@ public class JpaAuditLogDao extends JpaPartitionedAbstractDao<AuditLogEntity, Au
return auditLogRepository;
}
@Override
public ObjectType getType() {
return ObjectType.AUDIT_LOG;
}
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java

@ -21,6 +21,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.edqs.fields.CustomerFields;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@ -122,4 +123,9 @@ public class JpaCustomerDao extends JpaAbstractDao<CustomerEntity, Customer> imp
return EntityType.CUSTOMER;
}
@Override
public ObjectType getType() {
return ObjectType.CUSTOMER;
}
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java

@ -21,6 +21,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.edqs.fields.DashboardFields;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
@ -107,4 +108,9 @@ public class JpaDashboardDao extends JpaAbstractDao<DashboardEntity, Dashboard>
return EntityType.DASHBOARD;
}
@Override
public ObjectType getType() {
return ObjectType.DASHBOARD;
}
}

1
dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceCredentialsRepository.java

@ -22,6 +22,7 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.model.sql.DeviceCredentialsEntity;
import org.thingsboard.server.dao.model.sql.UserCredentialsEntity;
import java.util.UUID;

7
dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java

@ -19,6 +19,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
@ -75,4 +77,9 @@ public class JpaDeviceCredentialsDao extends JpaAbstractDao<DeviceCredentialsEnt
return DaoUtil.toPageData(deviceCredentialsRepository.findByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink)));
}
@Override
public ObjectType getType() {
return ObjectType.DEVICE_CREDENTIALS;
}
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java

@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ProfileEntityIdInfo;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edqs.fields.DeviceFields;
import org.thingsboard.server.common.data.id.DeviceId;
@ -323,4 +324,9 @@ public class JpaDeviceDao extends JpaAbstractDao<DeviceEntity, Device> implement
return EntityType.DEVICE;
}
@Override
public ObjectType getType() {
return ObjectType.DEVICE;
}
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.DeviceProfileInfo;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edqs.fields.DeviceProfileFields;
import org.thingsboard.server.common.data.id.DeviceProfileId;
@ -174,4 +175,9 @@ public class JpaDeviceProfileDao extends JpaAbstractDao<DeviceProfileEntity, Dev
return EntityType.DEVICE_PROFILE;
}
@Override
public ObjectType getType() {
return ObjectType.DEVICE_PROFILE;
}
}

3
dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeEventRepository.java

@ -44,4 +44,7 @@ public interface EdgeEventRepository extends JpaRepository<EdgeEventEntity, UUID
@Param("seqIdStart") Long seqIdStart,
@Param("seqIdEnd") Long seqIdEnd,
Pageable pageable);
Page<EdgeEventEntity> findByTenantId(UUID tenantId, Pageable pageable);
}

16
dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java

@ -25,15 +25,19 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.EdgeEventId;
import org.thingsboard.server.common.data.id.EdgeId;
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.SortOrder;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.TenantEntityDao;
import org.thingsboard.server.dao.edge.EdgeEventDao;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.sql.EdgeEventEntity;
@ -57,7 +61,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@SqlDao
@RequiredArgsConstructor
@Slf4j
public class JpaBaseEdgeEventDao extends JpaPartitionedAbstractDao<EdgeEventEntity, EdgeEvent> implements EdgeEventDao {
public class JpaBaseEdgeEventDao extends JpaPartitionedAbstractDao<EdgeEventEntity, EdgeEvent> implements EdgeEventDao, TenantEntityDao<EdgeEvent> {
private static final List<SortOrder> SORT_ORDERS = Collections.singletonList(new SortOrder("seqId"));
private final UUID systemTenantId = NULL_UUID;
@ -199,4 +203,14 @@ public class JpaBaseEdgeEventDao extends JpaPartitionedAbstractDao<EdgeEventEnti
partitioningRepository.createPartitionIfNotExists(TABLE_NAME, entity.getCreatedTime(), TimeUnit.HOURS.toMillis(partitionSizeInHours));
}
@Override
public PageData<EdgeEvent> findAllByTenantId(TenantId tenantId, PageLink pageLink) {
return DaoUtil.toPageData(edgeEventRepository.findByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink)));
}
@Override
public ObjectType getType() {
return ObjectType.EDGE_EVENT;
}
}

9
dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java

@ -23,6 +23,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeInfo;
import org.thingsboard.server.common.data.edqs.fields.EdgeFields;
@ -31,6 +32,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.DaoUtil;
import org.thingsboard.server.dao.TenantEntityDao;
import org.thingsboard.server.dao.edge.EdgeDao;
import org.thingsboard.server.dao.model.sql.EdgeEntity;
import org.thingsboard.server.dao.model.sql.EdgeInfoEntity;
@ -46,7 +48,7 @@ import static org.thingsboard.server.dao.DaoUtil.convertTenantEntityTypesToDto;
@Component
@Slf4j
@SqlDao
public class JpaEdgeDao extends JpaAbstractDao<EdgeEntity, Edge> implements EdgeDao {
public class JpaEdgeDao extends JpaAbstractDao<EdgeEntity, Edge> implements EdgeDao, TenantEntityDao<Edge> {
@Autowired
private EdgeRepository edgeRepository;
@ -241,4 +243,9 @@ public class JpaEdgeDao extends JpaAbstractDao<EdgeEntity, Edge> implements Edge
return EntityType.EDGE;
}
@Override
public ObjectType getType() {
return ObjectType.EDGE;
}
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.EntityViewInfo;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.edqs.fields.EntityViewFields;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
@ -235,4 +236,9 @@ public class JpaEntityViewDao extends JpaAbstractDao<EntityViewEntity, EntityVie
return EntityType.ENTITY_VIEW;
}
@Override
public ObjectType getType() {
return ObjectType.ENTITY_VIEW;
}
}

1
dao/src/main/java/org/thingsboard/server/dao/sql/event/ErrorEventRepository.java

@ -108,4 +108,5 @@ public interface ErrorEventRepository extends EventRepository<ErrorEventEntity,
@Param("serviceId") String server,
@Param("method") String method,
@Param("error") String error);
}

21
dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java

@ -19,10 +19,12 @@ import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.google.common.util.concurrent.ListenableFuture;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.SneakyThrows;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.event.CalculatedFieldDebugEventFilter;
import org.thingsboard.server.common.data.event.ErrorEventFilter;
@ -34,6 +36,7 @@ import org.thingsboard.server.common.data.event.RuleChainDebugEventFilter;
import org.thingsboard.server.common.data.event.RuleNodeDebugEventFilter;
import org.thingsboard.server.common.data.event.StatisticsEventFilter;
import org.thingsboard.server.common.data.id.EventId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbNodeConnectionType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink;
@ -125,6 +128,11 @@ public class JpaBaseEventDao implements EventDao {
@Override
public ListenableFuture<Void> saveAsync(Event event) {
log.debug("Save event [{}] ", event);
prepare(event);
return queue.add(event);
}
private void prepare(Event event) {
if (event.getId() == null) {
UUID timeBased = Uuids.timeBased();
event.setId(new EventId(timeBased));
@ -139,7 +147,6 @@ public class JpaBaseEventDao implements EventDao {
}
partitioningRepository.createPartitionIfNotExists(event.getType().getTable(), event.getCreatedTime(),
partitionConfiguration.getPartitionSizeInMs(event.getType()));
return queue.add(event);
}
@Override
@ -478,5 +485,17 @@ public class JpaBaseEventDao implements EventDao {
return repository;
}
@SneakyThrows
@Override
public Event save(TenantId tenantId, Event event) {
prepare(event);
eventInsertRepository.save(List.of(event));
return event;
}
@Override
public ObjectType getType() {
return ObjectType.EVENT;
}
}

1
dao/src/main/java/org/thingsboard/server/dao/sql/event/LifecycleEventRepository.java

@ -114,4 +114,5 @@ public interface LifecycleEventRepository extends EventRepository<LifecycleEvent
@Param("statusFilterEnabled") boolean statusFilterEnabled,
@Param("statusFilter") boolean statusFilter,
@Param("error") String error);
}

1
dao/src/main/java/org/thingsboard/server/dao/sql/event/RuleChainDebugEventRepository.java

@ -112,4 +112,5 @@ public interface RuleChainDebugEventRepository extends EventRepository<RuleChain
@Param("message") String message,
@Param("isError") boolean isError,
@Param("error") String error);
}

5
dao/src/main/java/org/thingsboard/server/dao/sql/event/RuleNodeDebugEventRepository.java

@ -33,10 +33,10 @@ import java.util.UUID;
public interface RuleNodeDebugEventRepository extends EventRepository<RuleNodeDebugEventEntity, RuleNodeDebugEvent>, JpaRepository<RuleNodeDebugEventEntity, UUID> {
@Override
@Query(nativeQuery = true, value = "SELECT * FROM rule_node_debug_event e WHERE e.tenant_id = :tenantId AND e.entity_id = :entityId ORDER BY e.ts DESC LIMIT :limit")
@Query(nativeQuery = true, value = "SELECT * FROM rule_node_debug_event e WHERE e.tenant_id = :tenantId AND e.entity_id = :entityId ORDER BY e.ts DESC LIMIT :limit")
List<RuleNodeDebugEventEntity> findLatestEvents(@Param("tenantId") UUID tenantId, @Param("entityId") UUID entityId, @Param("limit") int limit);
@Query(nativeQuery = true, value = "SELECT * FROM rule_node_debug_event e WHERE e.tenant_id = :tenantId AND e.entity_id = :entityId AND e.e_type = 'IN' ORDER BY e.ts DESC LIMIT 1")
@Query(nativeQuery = true, value = "SELECT * FROM rule_node_debug_event e WHERE e.tenant_id = :tenantId AND e.entity_id = :entityId AND e.e_type = 'IN' ORDER BY e.ts DESC LIMIT 1")
Optional<RuleNodeDebugEventEntity> findLatestDebugRuleNodeInEvent(@Param("tenantId") UUID tenantId, @Param("entityId") UUID entityId);
@Override
@ -151,4 +151,5 @@ public interface RuleNodeDebugEventRepository extends EventRepository<RuleNodeDe
@Param("metadata") String metadata,
@Param("isError") boolean isError,
@Param("error") String error);
}

3
dao/src/main/java/org/thingsboard/server/dao/sql/event/StatisticsEventRepository.java

@ -31,7 +31,7 @@ import java.util.UUID;
public interface StatisticsEventRepository extends EventRepository<StatisticsEventEntity, StatisticsEvent>, JpaRepository<StatisticsEventEntity, UUID> {
@Override
@Query(nativeQuery = true, value = "SELECT * FROM stats_event e WHERE e.tenant_id = :tenantId AND e.entity_id = :entityId ORDER BY e.ts DESC LIMIT :limit")
@Query(nativeQuery = true, value = "SELECT * FROM stats_event e WHERE e.tenant_id = :tenantId AND e.entity_id = :entityId ORDER BY e.ts DESC LIMIT :limit")
List<StatisticsEventEntity> findLatestEvents(@Param("tenantId") UUID tenantId, @Param("entityId") UUID entityId, @Param("limit") int limit);
@Query("SELECT e FROM StatisticsEventEntity e WHERE " +
@ -117,4 +117,5 @@ public interface StatisticsEventRepository extends EventRepository<StatisticsEve
@Param("maxMessagesProcessed") Integer maxMessagesProcessed,
@Param("minErrorsOccurred") Integer minErrorsOccurred,
@Param("maxErrorsOccurred") Integer maxErrorsOccurred);
}

20
dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaQrCodeSettingsDao.java

@ -19,9 +19,16 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileAppSettings;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.mobile.qrCodeSettings.QrCodeSettings;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.TenantEntityDao;
import org.thingsboard.server.dao.mobile.MobileAppSettingsDao;
import org.thingsboard.server.dao.model.sql.MobileAppSettingsEntity;
import org.thingsboard.server.dao.mobile.QrCodeSettingsDao;
import org.thingsboard.server.dao.model.sql.QrCodeSettingsEntity;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
@ -33,7 +40,7 @@ import java.util.UUID;
@Component
@Slf4j
@SqlDao
public class JpaQrCodeSettingsDao extends JpaAbstractDao<QrCodeSettingsEntity, QrCodeSettings> implements QrCodeSettingsDao {
public class JpaQrCodeSettingsDao extends JpaAbstractDao<QrCodeSettingsEntity, QrCodeSettings> implements QrCodeSettingsDao, TenantEntityDao<QrCodeSettings> {
@Autowired
private QrCodeSettingsRepository qrCodeSettingsRepository;
@ -49,6 +56,11 @@ public class JpaQrCodeSettingsDao extends JpaAbstractDao<QrCodeSettingsEntity, Q
qrCodeSettingsRepository.deleteByTenantId(tenantId.getId());
}
@Override
public PageData<MobileAppSettings> findAllByTenantId(TenantId tenantId, PageLink pageLink) {
return DaoUtil.toPageData(mobileAppSettingsRepository.findAllByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink)));
}
@Override
protected Class<QrCodeSettingsEntity> getEntityClass() {
return QrCodeSettingsEntity.class;
@ -58,4 +70,10 @@ public class JpaQrCodeSettingsDao extends JpaAbstractDao<QrCodeSettingsEntity, Q
protected JpaRepository<QrCodeSettingsEntity, UUID> getRepository() {
return qrCodeSettingsRepository;
}
@Override
public ObjectType getType() {
return ObjectType.MOBILE_APP_SETTINGS;
}
}

5
dao/src/main/java/org/thingsboard/server/dao/sql/mobile/QrCodeSettingsRepository.java

@ -15,6 +15,8 @@
*/
package org.thingsboard.server.dao.sql.mobile;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
@ -33,4 +35,7 @@ public interface QrCodeSettingsRepository extends JpaRepository<QrCodeSettingsEn
@Modifying
@Query("DELETE FROM QrCodeSettingsEntity r WHERE r.tenantId = :tenantId")
void deleteByTenantId(@Param("tenantId") UUID tenantId);
Page<MobileAppSettingsEntity> findAllByTenantId(UUID tenantId, Pageable pageable);
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java

@ -19,6 +19,7 @@ import lombok.RequiredArgsConstructor;
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.ObjectType;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId;
@ -122,4 +123,9 @@ public class JpaNotificationRuleDao extends JpaAbstractDao<NotificationRuleEntit
return EntityType.NOTIFICATION_RULE;
}
@Override
public ObjectType getType() {
return ObjectType.NOTIFICATION_RULE;
}
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java

@ -19,6 +19,7 @@ import lombok.RequiredArgsConstructor;
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.ObjectType;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationType;
@ -121,4 +122,9 @@ public class JpaNotificationTargetDao extends JpaAbstractDao<NotificationTargetE
return EntityType.NOTIFICATION_TARGET;
}
@Override
public ObjectType getType() {
return ObjectType.NOTIFICATION_TARGET;
}
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java

@ -19,6 +19,7 @@ import lombok.RequiredArgsConstructor;
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.ObjectType;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationType;
@ -99,4 +100,9 @@ public class JpaNotificationTemplateDao extends JpaAbstractDao<NotificationTempl
return EntityType.NOTIFICATION_TEMPLATE;
}
@Override
public ObjectType getType() {
return ObjectType.NOTIFICATION_TEMPLATE;
}
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageDao.java

@ -21,6 +21,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
@ -68,4 +69,9 @@ public class JpaOtaPackageDao extends JpaAbstractDao<OtaPackageEntity, OtaPackag
return EntityType.OTA_PACKAGE;
}
@Override
public ObjectType getType() {
return ObjectType.OTA_PACKAGE;
}
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageInfoDao.java

@ -89,6 +89,12 @@ public class JpaOtaPackageInfoDao extends JpaAbstractDao<OtaPackageInfoEntity, O
DaoUtil.toPageable(pageLink)));
}
@Override
public PageData<OtaPackageId> findOtaPackageIdsByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId, PageLink pageLink) {
return DaoUtil.pageToPageData(otaPackageInfoRepository.findIdsByDeviceProfileId(deviceProfileId.getId(), DaoUtil.toPageable(pageLink)))
.mapData(OtaPackageId::new);
}
@Override
public boolean isOtaPackageUsed(OtaPackageId otaPackageId, OtaPackageType otaPackageType, DeviceProfileId deviceProfileId) {
return otaPackageInfoRepository.isOtaPackageUsed(otaPackageId.getId(), deviceProfileId.getId(), otaPackageType.name());

4
dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageInfoRepository.java

@ -45,6 +45,10 @@ public interface OtaPackageInfoRepository extends JpaRepository<OtaPackageInfoEn
@Param("searchText") String searchText,
Pageable pageable);
@Query("SELECT f.id FROM OtaPackageEntity f WHERE f.deviceProfileId = :deviceProfileId")
Page<UUID> findIdsByDeviceProfileId(@Param("deviceProfileId") UUID deviceProfileId,
Pageable pageable);
@Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.tag, f.url, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, CASE WHEN (f.data IS NOT NULL OR f.url IS NOT NULL) THEN true ELSE false END) FROM OtaPackageEntity f WHERE f.id = :id")
OtaPackageInfoEntity findOtaPackageInfoById(@Param("id") UUID id);

6
dao/src/main/java/org/thingsboard/server/dao/sql/queue/JpaQueueDao.java

@ -22,6 +22,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -98,4 +99,9 @@ public class JpaQueueDao extends JpaAbstractDao<QueueEntity, Queue> implements Q
return EntityType.QUEUE;
}
@Override
public ObjectType getType() {
return ObjectType.QUEUE;
}
}

13
dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java

@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
@ -293,4 +294,16 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple
public List<EntityRelation> findRuleNodeToRuleChainRelations(RuleChainType ruleChainType, int limit) {
return DaoUtil.convertDataList(relationRepository.findRuleNodeToRuleChainRelations(ruleChainType, PageRequest.of(0, limit)));
}
@Override
public EntityRelation save(TenantId tenantId, EntityRelation relation) {
saveRelation(tenantId, relation);
return relation;
}
@Override
public ObjectType getType() {
return ObjectType.RELATION;
}
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceDao.java

@ -20,6 +20,7 @@ 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.ResourceSubType;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.id.TbResourceId;
@ -146,4 +147,9 @@ public class JpaTbResourceDao extends JpaAbstractDao<TbResourceEntity, TbResourc
return EntityType.TB_RESOURCE;
}
@Override
public ObjectType getType() {
return ObjectType.RESOURCE;
}
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java

@ -21,6 +21,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
@ -85,4 +86,9 @@ public class JpaRpcDao extends JpaAbstractDao<RpcEntity, Rpc> implements RpcDao,
return EntityType.RPC;
}
@Override
public ObjectType getType() {
return ObjectType.RPC;
}
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java

@ -22,6 +22,7 @@ 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.edqs.fields.RuleChainFields;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
@ -151,4 +152,9 @@ public class JpaRuleChainDao extends JpaAbstractDao<RuleChainEntity, RuleChain>
return EntityType.RULE_CHAIN;
}
@Override
public ObjectType getType() {
return ObjectType.RULE_CHAIN;
}
}

11
dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java

@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired;
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.ObjectType;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
@ -107,9 +108,19 @@ public class JpaRuleNodeDao extends JpaAbstractDao<RuleNodeEntity, RuleNode> imp
ruleNodeRepository.deleteAllById(ruleNodeIds.stream().map(RuleNodeId::getId).collect(Collectors.toList()));
}
@Override
public PageData<RuleNode> findAllByTenantId(TenantId tenantId, PageLink pageLink) {
return DaoUtil.toPageData(ruleNodeRepository.findByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink)));
}
@Override
public EntityType getEntityType() {
return EntityType.RULE_NODE;
}
@Override
public ObjectType getType() {
return ObjectType.RULE_NODE;
}
}

16
dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeStateDao.java

@ -20,10 +20,13 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.ObjectType;
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.RuleNodeState;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.TenantEntityDao;
import org.thingsboard.server.dao.model.sql.RuleNodeStateEntity;
import org.thingsboard.server.dao.rule.RuleNodeStateDao;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
@ -34,7 +37,7 @@ import java.util.UUID;
@Slf4j
@Component
@SqlDao
public class JpaRuleNodeStateDao extends JpaAbstractDao<RuleNodeStateEntity, RuleNodeState> implements RuleNodeStateDao {
public class JpaRuleNodeStateDao extends JpaAbstractDao<RuleNodeStateEntity, RuleNodeState> implements RuleNodeStateDao, TenantEntityDao<RuleNodeState> {
@Autowired
private RuleNodeStateRepository ruleNodeStateRepository;
@ -70,4 +73,15 @@ public class JpaRuleNodeStateDao extends JpaAbstractDao<RuleNodeStateEntity, Rul
public void removeByRuleNodeIdAndEntityId(UUID ruleNodeId, UUID entityId) {
ruleNodeStateRepository.removeByRuleNodeIdAndEntityId(ruleNodeId, entityId);
}
@Override
public PageData<RuleNodeState> findAllByTenantId(TenantId tenantId, PageLink pageLink) {
return DaoUtil.toPageData(ruleNodeStateRepository.findByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink)));
}
@Override
public ObjectType getType() {
return ObjectType.RULE_NODE_STATE;
}
}

3
dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java

@ -63,4 +63,7 @@ public interface RuleNodeRepository extends JpaRepository<RuleNodeEntity, UUID>
@Query("DELETE FROM RuleNodeEntity e where e.id in :ids")
void deleteByIdIn(@Param("ids") List<UUID> ids);
@Query("SELECT n FROM RuleNodeEntity n WHERE n.ruleChainId IN (SELECT rc.id FROM RuleChainEntity rc WHERE rc.tenantId = :tenantId)")
Page<RuleNodeEntity> findByTenantId(@Param("tenantId") UUID tenantId, Pageable pageable);
}

5
dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeStateRepository.java

@ -35,4 +35,9 @@ public interface RuleNodeStateRepository extends JpaRepository<RuleNodeStateEnti
void removeByRuleNodeId(@Param("ruleNodeId") UUID ruleNodeId);
void removeByRuleNodeIdAndEntityId(@Param("ruleNodeId") UUID ruleNodeId, @Param("entityId") UUID entityId);
@Query("SELECT s FROM RuleNodeStateEntity s WHERE s.ruleNodeId IN (SELECT n.id FROM RuleNodeEntity n " +
"WHERE n.ruleChainId IN (SELECT c.id FROM RuleChainEntity c WHERE c.tenantId = :tenantId))")
Page<RuleNodeStateEntity> findByTenantId(@Param("tenantId") UUID tenantId, Pageable pageable);
}

9
dao/src/main/java/org/thingsboard/server/dao/sql/settings/JpaAdminSettingsDao.java

@ -21,6 +21,10 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.ObjectType;
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.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -77,4 +81,9 @@ public class JpaAdminSettingsDao extends JpaAbstractDao<AdminSettingsEntity, Adm
return DaoUtil.toPageData(adminSettingsRepository.findByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink)));
}
@Override
public ObjectType getType() {
return ObjectType.ADMIN_SETTINGS;
}
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDao.java

@ -20,6 +20,7 @@ import org.springframework.data.domain.Limit;
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.ObjectType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.edqs.fields.TenantFields;
@ -103,4 +104,9 @@ public class JpaTenantDao extends JpaAbstractDao<TenantEntity, Tenant> implement
return tenantRepository.findNextBatch(id, Limit.of(batchSize));
}
@Override
public ObjectType getType() {
return ObjectType.TENANT;
}
}

22
dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserAuthSettingsDao.java

@ -18,8 +18,13 @@ package org.thingsboard.server.dao.sql.user;
import lombok.RequiredArgsConstructor;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.UserAuthSettings;
import org.thingsboard.server.common.data.security.model.mfa.account.AccountTwoFaSettings;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.TenantEntityDao;
import org.thingsboard.server.dao.model.sql.UserAuthSettingsEntity;
@ -46,6 +51,18 @@ public class JpaUserAuthSettingsDao extends JpaAbstractDao<UserAuthSettingsEntit
repository.deleteByUserId(userId.getId());
}
@Override
public PageData<UserAuthSettings> findAllByTenantId(TenantId tenantId, PageLink pageLink) {
PageData<UserAuthSettings> data = DaoUtil.toPageData(repository.findByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink)));
data.getData().forEach(settings -> {
AccountTwoFaSettings twoFaSettings = settings.getTwoFaSettings();
if (twoFaSettings != null && twoFaSettings.getConfigs() != null) {
twoFaSettings.getConfigs().values().forEach(config -> config.setSerializeHiddenFields(true));
}
});
return data;
}
@Override
protected Class<UserAuthSettingsEntity> getEntityClass() {
return UserAuthSettingsEntity.class;
@ -56,4 +73,9 @@ public class JpaUserAuthSettingsDao extends JpaAbstractDao<UserAuthSettingsEntit
return repository;
}
@Override
public ObjectType getType() {
return ObjectType.USER_AUTH_SETTINGS;
}
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserCredentialsDao.java

@ -18,6 +18,7 @@ package org.thingsboard.server.dao.sql.user;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
@ -92,4 +93,9 @@ public class JpaUserCredentialsDao extends JpaAbstractDao<UserCredentialsEntity,
return DaoUtil.toPageData(userCredentialsRepository.findByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink)));
}
@Override
public ObjectType getType() {
return ObjectType.USER_CREDENTIALS;
}
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java

@ -20,6 +20,7 @@ import org.springframework.data.domain.Limit;
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.ObjectType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.edqs.fields.UserFields;
import org.thingsboard.server.common.data.id.CustomerId;
@ -156,4 +157,9 @@ public class JpaUserDao extends JpaAbstractDao<UserEntity, User> implements User
return EntityType.USER;
}
@Override
public ObjectType getType() {
return ObjectType.USER;
}
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java

@ -18,6 +18,7 @@ package org.thingsboard.server.dao.sql.user;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
@ -73,4 +74,9 @@ public class JpaUserSettingsDao implements UserSettingsDao, TenantEntityDao<User
return DaoUtil.toPageData(userSettingsRepository.findByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink)));
}
@Override
public ObjectType getType() {
return ObjectType.USER_SETTINGS;
}
}

1
dao/src/main/java/org/thingsboard/server/dao/sql/user/UserAuthSettingsRepository.java

@ -24,6 +24,7 @@ import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.model.sql.UserAuthSettingsEntity;
import org.thingsboard.server.dao.model.sql.UserCredentialsEntity;
import java.util.UUID;

2
dao/src/main/java/org/thingsboard/server/dao/sql/user/UserCredentialsRepository.java

@ -18,6 +18,8 @@ package org.thingsboard.server.dao.sql.user;
import org.springframework.data.domain.Page;
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.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

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

Loading…
Cancel
Save