11 changed files with 48 additions and 256 deletions
@ -1,224 +0,0 @@ |
|||||
/** |
|
||||
* 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.Data; |
|
||||
import lombok.RequiredArgsConstructor; |
|
||||
import lombok.SneakyThrows; |
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.springframework.stereotype.Service; |
|
||||
import org.thingsboard.server.common.data.AttributeScope; |
|
||||
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.edqs.AttributeKv; |
|
||||
import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
|
||||
import org.thingsboard.server.common.data.edqs.LatestTsKv; |
|
||||
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.TimeseriesLatestDao; |
|
||||
|
|
||||
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.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; |
|
||||
|
|
||||
@Service |
|
||||
@RequiredArgsConstructor |
|
||||
@Slf4j |
|
||||
public class TenantExportService { |
|
||||
|
|
||||
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 SqlPartitioningRepository partitioningRepository; |
|
||||
|
|
||||
private Map<ObjectType, BiConsumer<TenantId, BiConsumer<ObjectType, Object>>> customExporters; |
|
||||
private Map<ObjectType, Exporter> relatedEntitiesExporters; |
|
||||
|
|
||||
private static final Set<ObjectType> RELATED = EnumSet.of(EVENT, RELATION, ATTRIBUTE_KV, LATEST_TS_KV); |
|
||||
|
|
||||
@PostConstruct |
|
||||
private void init() { |
|
||||
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( |
|
||||
AUDIT_LOG, this::exportAuditLogs |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
public void exportTenant(TenantId tenantId, ExportConfig config, BiConsumer<ObjectType, Object> processor) { |
|
||||
log.info("[{}] Exporting tenant", tenantId); |
|
||||
Tenant tenant = tenantDao.findById(TenantId.SYS_TENANT_ID, tenantId.getId()); |
|
||||
if (tenant == null) { |
|
||||
throw new IllegalArgumentException("Tenant with id " + tenantId + " not found"); |
|
||||
} |
|
||||
|
|
||||
Set<ObjectType> objectTypes = config.getIncludedObjectTypes(); |
|
||||
if (objectTypes.contains(TENANT)) { |
|
||||
exportEntity(tenantId, TENANT, tenant, config, processor); |
|
||||
} |
|
||||
|
|
||||
for (ObjectType type : objectTypes) { |
|
||||
if (RELATED.contains(type) || type == TENANT) { |
|
||||
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) { |
|
||||
exportEntity(tenantId, type, entity, config, processor); |
|
||||
} |
|
||||
} else { |
|
||||
customExporters.get(type).accept(tenantId, processor); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void exportEntity(TenantId tenantId, ObjectType type, Object entity, ExportConfig config, BiConsumer<ObjectType, Object> processor) { |
|
||||
processor.accept(type, entity); |
|
||||
if (entity instanceof HasId<?> hasId && hasId.getId() instanceof EntityId entityId) { |
|
||||
relatedEntitiesExporters.forEach((relatedEntityType, exporter) -> { |
|
||||
if (config.getIncludedObjectTypes().contains(relatedEntityType)) { |
|
||||
exporter.export(tenantId, entityId, processor); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
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; |
|
||||
} |
|
||||
|
|
||||
private void exportAuditLogs(TenantId tenantId, BiConsumer<ObjectType, Object> processor) { |
|
||||
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) { |
|
||||
processor.accept(AUDIT_LOG, auditLog); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
private void exportAttributes(TenantId tenantId, EntityId entityId, BiConsumer<ObjectType, Object> processor) { |
|
||||
for (AttributeScope attributeScope : AttributeScope.values()) { |
|
||||
List<AttributeKvEntry> attributes = attributesDao.findAll(tenantId, entityId, attributeScope); |
|
||||
for (AttributeKvEntry entry : attributes) { |
|
||||
AttributeKv attributeKv = new AttributeKv(entityId, attributeScope, entry, entry.getVersion()); |
|
||||
processor.accept(ATTRIBUTE_KV, attributeKv); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void exportRelations(TenantId tenantId, EntityId entityId, BiConsumer<ObjectType, Object> processor) { |
|
||||
List<EntityRelation> relations = relationDao.findAllByFrom(tenantId, entityId); |
|
||||
for (EntityRelation relation : relations) { |
|
||||
processor.accept(RELATION, relation); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@SneakyThrows |
|
||||
private void exportLatestTelemetry(TenantId tenantId, EntityId entityId, BiConsumer<ObjectType, Object> processor) { |
|
||||
List<TsKvEntry> latestTelemetry = timeseriesLatestDao.findAllLatest(tenantId, entityId).get(30, TimeUnit.SECONDS); |
|
||||
for (TsKvEntry tsKvEntry : latestTelemetry) { |
|
||||
LatestTsKv latestTsKv = new LatestTsKv(entityId, tsKvEntry, tsKvEntry.getVersion()); |
|
||||
processor.accept(LATEST_TS_KV, latestTsKv); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void exportEvents(TenantId tenantId, EntityId entityId, BiConsumer<ObjectType, Object> processor) { |
|
||||
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) { |
|
||||
processor.accept(EVENT, event); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private interface Exporter { |
|
||||
|
|
||||
void export(TenantId tenantId, EntityId entityId, BiConsumer<ObjectType, Object> processor); |
|
||||
|
|
||||
} |
|
||||
|
|
||||
@Data |
|
||||
public static class ExportConfig { |
|
||||
|
|
||||
private Set<ObjectType> includedObjectTypes; |
|
||||
|
|
||||
} |
|
||||
|
|
||||
} |
|
||||
Loading…
Reference in new issue