665 changed files with 26230 additions and 4518 deletions
@ -0,0 +1,348 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.controller; |
||||
|
|
||||
|
import com.google.common.util.concurrent.Futures; |
||||
|
import com.google.common.util.concurrent.ListenableFuture; |
||||
|
import com.google.common.util.concurrent.MoreExecutors; |
||||
|
import io.swagger.annotations.ApiOperation; |
||||
|
import io.swagger.annotations.ApiParam; |
||||
|
import lombok.Data; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.springframework.security.access.prepost.PreAuthorize; |
||||
|
import org.springframework.web.bind.annotation.GetMapping; |
||||
|
import org.springframework.web.bind.annotation.PathVariable; |
||||
|
import org.springframework.web.bind.annotation.PostMapping; |
||||
|
import org.springframework.web.bind.annotation.RequestBody; |
||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||
|
import org.springframework.web.bind.annotation.RequestParam; |
||||
|
import org.springframework.web.bind.annotation.RestController; |
||||
|
import org.springframework.web.context.request.async.DeferredResult; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.StringUtils; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.EntityIdFactory; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.page.PageData; |
||||
|
import org.thingsboard.server.common.data.page.PageLink; |
||||
|
import org.thingsboard.server.common.data.sync.vc.EntityDataDiff; |
||||
|
import org.thingsboard.server.common.data.sync.vc.EntityDataInfo; |
||||
|
import org.thingsboard.server.common.data.sync.vc.EntityVersion; |
||||
|
import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; |
||||
|
import org.thingsboard.server.common.data.sync.vc.VersionLoadResult; |
||||
|
import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo; |
||||
|
import org.thingsboard.server.common.data.sync.vc.request.create.VersionCreateRequest; |
||||
|
import org.thingsboard.server.common.data.sync.vc.request.load.VersionLoadRequest; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
import org.thingsboard.server.service.security.permission.Operation; |
||||
|
import org.thingsboard.server.service.security.permission.Resource; |
||||
|
import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE; |
||||
|
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; |
||||
|
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; |
||||
|
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION; |
||||
|
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; |
||||
|
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; |
||||
|
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; |
||||
|
import static org.thingsboard.server.controller.ControllerConstants.VC_REQUEST_ID_PARAM_DESCRIPTION; |
||||
|
|
||||
|
@RestController |
||||
|
@TbCoreComponent |
||||
|
@RequestMapping("/api/entities/vc") |
||||
|
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
||||
|
@RequiredArgsConstructor |
||||
|
public class EntitiesVersionControlController extends BaseController { |
||||
|
|
||||
|
private final EntitiesVersionControlService versionControlService; |
||||
|
|
||||
|
|
||||
|
@ApiOperation(value = "", notes = "" + |
||||
|
"SINGLE_ENTITY:" + NEW_LINE + |
||||
|
"```\n{\n" + |
||||
|
" \"type\": \"SINGLE_ENTITY\",\n" + |
||||
|
"\n" + |
||||
|
" \"versionName\": \"Version 1.0\",\n" + |
||||
|
" \"branch\": \"dev\",\n" + |
||||
|
"\n" + |
||||
|
" \"entityId\": {\n" + |
||||
|
" \"entityType\": \"DEVICE\",\n" + |
||||
|
" \"id\": \"b79448e0-d4f4-11ec-847b-0f432358ab48\"\n" + |
||||
|
" },\n" + |
||||
|
" \"config\": {\n" + |
||||
|
" \"saveRelations\": true\n" + |
||||
|
" }\n" + |
||||
|
"}\n```" + NEW_LINE + |
||||
|
"COMPLEX:" + NEW_LINE + |
||||
|
"```\n{\n" + |
||||
|
" \"type\": \"COMPLEX\",\n" + |
||||
|
"\n" + |
||||
|
" \"versionName\": \"Devices and profiles: release 2\",\n" + |
||||
|
" \"branch\": \"master\",\n" + |
||||
|
"\n" + |
||||
|
" \"syncStrategy\": \"OVERWRITE\",\n" + |
||||
|
" \"entityTypes\": {\n" + |
||||
|
" \"DEVICE\": {\n" + |
||||
|
" \"syncStrategy\": null,\n" + |
||||
|
" \"allEntities\": true,\n" + |
||||
|
" \"saveRelations\": true\n" + |
||||
|
" },\n" + |
||||
|
" \"DEVICE_PROFILE\": {\n" + |
||||
|
" \"syncStrategy\": \"MERGE\",\n" + |
||||
|
" \"allEntities\": false,\n" + |
||||
|
" \"entityIds\": [\n" + |
||||
|
" \"b79448e0-d4f4-11ec-847b-0f432358ab48\"\n" + |
||||
|
" ],\n" + |
||||
|
" \"saveRelations\": true\n" + |
||||
|
" }\n" + |
||||
|
" }\n" + |
||||
|
"}\n```") |
||||
|
@PostMapping("/version") |
||||
|
public DeferredResult<UUID> saveEntitiesVersion(@RequestBody VersionCreateRequest request) throws Exception { |
||||
|
SecurityUser user = getCurrentUser(); |
||||
|
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.WRITE); |
||||
|
return wrapFuture(versionControlService.saveEntitiesVersion(user, request)); |
||||
|
} |
||||
|
|
||||
|
@ApiOperation(value = "", notes = "") |
||||
|
@GetMapping(value = "/version/{requestId}/status") |
||||
|
public VersionCreationResult getVersionCreateRequestStatus(@ApiParam(value = VC_REQUEST_ID_PARAM_DESCRIPTION, required = true) |
||||
|
@PathVariable UUID requestId) throws Exception { |
||||
|
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); |
||||
|
return versionControlService.getVersionCreateStatus(getCurrentUser(), requestId); |
||||
|
} |
||||
|
|
||||
|
@ApiOperation(value = "", notes = "" + |
||||
|
"```\n[\n" + |
||||
|
" {\n" + |
||||
|
" \"id\": \"c30c8bcaed3f0813649f0dee51a89d04d0a12b28\",\n" + |
||||
|
" \"name\": \"Device profile 1 version 1.0\"\n" + |
||||
|
" }\n" + |
||||
|
"]\n```") |
||||
|
@GetMapping(value = "/version/{branch}/{entityType}/{externalEntityUuid}", params = {"pageSize", "page"}) |
||||
|
public DeferredResult<PageData<EntityVersion>> listEntityVersions(@PathVariable String branch, |
||||
|
@PathVariable EntityType entityType, |
||||
|
@PathVariable UUID externalEntityUuid, |
||||
|
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) |
||||
|
@RequestParam int pageSize, |
||||
|
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) |
||||
|
@RequestParam int page, |
||||
|
@ApiParam(value = ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION) |
||||
|
@RequestParam(required = false) String textSearch, |
||||
|
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = "timestamp") |
||||
|
@RequestParam(required = false) String sortProperty, |
||||
|
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) |
||||
|
@RequestParam(required = false) String sortOrder) throws Exception { |
||||
|
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); |
||||
|
EntityId externalEntityId = EntityIdFactory.getByTypeAndUuid(entityType, externalEntityUuid); |
||||
|
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
||||
|
return wrapFuture(versionControlService.listEntityVersions(getTenantId(), branch, externalEntityId, pageLink)); |
||||
|
} |
||||
|
|
||||
|
@ApiOperation(value = "", notes = "" + |
||||
|
"```\n[\n" + |
||||
|
" {\n" + |
||||
|
" \"id\": \"c30c8bcaed3f0813649f0dee51a89d04d0a12b28\",\n" + |
||||
|
" \"name\": \"Device profiles from dev\"\n" + |
||||
|
" }\n" + |
||||
|
"]\n```") |
||||
|
@GetMapping(value = "/version/{branch}/{entityType}", params = {"pageSize", "page"}) |
||||
|
public DeferredResult<PageData<EntityVersion>> listEntityTypeVersions(@PathVariable String branch, |
||||
|
@PathVariable EntityType entityType, |
||||
|
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) |
||||
|
@RequestParam int pageSize, |
||||
|
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) |
||||
|
@RequestParam int page, |
||||
|
@ApiParam(value = ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION) |
||||
|
@RequestParam(required = false) String textSearch, |
||||
|
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = "timestamp") |
||||
|
@RequestParam(required = false) String sortProperty, |
||||
|
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) |
||||
|
@RequestParam(required = false) String sortOrder) throws Exception { |
||||
|
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); |
||||
|
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
||||
|
return wrapFuture(versionControlService.listEntityTypeVersions(getTenantId(), branch, entityType, pageLink)); |
||||
|
} |
||||
|
|
||||
|
@ApiOperation(value = "", notes = "" + |
||||
|
"```\n[\n" + |
||||
|
" {\n" + |
||||
|
" \"id\": \"ba9baaca1742b730e7331f31a6a51da5fc7da7f7\",\n" + |
||||
|
" \"name\": \"Device 1 removed\"\n" + |
||||
|
" },\n" + |
||||
|
" {\n" + |
||||
|
" \"id\": \"b3c28d722d328324c7c15b0b30047b0c40011cf7\",\n" + |
||||
|
" \"name\": \"Device profiles added\"\n" + |
||||
|
" },\n" + |
||||
|
" {\n" + |
||||
|
" \"id\": \"c30c8bcaed3f0813649f0dee51a89d04d0a12b28\",\n" + |
||||
|
" \"name\": \"Devices added\"\n" + |
||||
|
" }\n" + |
||||
|
"]\n```") |
||||
|
@GetMapping(value = "/version/{branch}", params = {"pageSize", "page"}) |
||||
|
public DeferredResult<PageData<EntityVersion>> listVersions(@PathVariable String branch, |
||||
|
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) |
||||
|
@RequestParam int pageSize, |
||||
|
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) |
||||
|
@RequestParam int page, |
||||
|
@ApiParam(value = ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION) |
||||
|
@RequestParam(required = false) String textSearch, |
||||
|
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = "timestamp") |
||||
|
@RequestParam(required = false) String sortProperty, |
||||
|
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) |
||||
|
@RequestParam(required = false) String sortOrder) throws Exception { |
||||
|
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); |
||||
|
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
||||
|
return wrapFuture(versionControlService.listVersions(getTenantId(), branch, pageLink)); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@GetMapping("/entity/{branch}/{entityType}/{versionId}") |
||||
|
public DeferredResult<List<VersionedEntityInfo>> listEntitiesAtVersion(@PathVariable String branch, |
||||
|
@PathVariable EntityType entityType, |
||||
|
@PathVariable String versionId) throws Exception { |
||||
|
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); |
||||
|
return wrapFuture(versionControlService.listEntitiesAtVersion(getTenantId(), branch, versionId, entityType)); |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/entity/{branch}/{versionId}") |
||||
|
public DeferredResult<List<VersionedEntityInfo>> listAllEntitiesAtVersion(@PathVariable String branch, |
||||
|
@PathVariable String versionId) throws Exception { |
||||
|
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); |
||||
|
return wrapFuture(versionControlService.listAllEntitiesAtVersion(getTenantId(), branch, versionId)); |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/info/{versionId}/{entityType}/{externalEntityUuid}") |
||||
|
public DeferredResult<EntityDataInfo> getEntityDataInfo(@PathVariable String versionId, |
||||
|
@PathVariable EntityType entityType, |
||||
|
@PathVariable UUID externalEntityUuid) throws Exception { |
||||
|
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); |
||||
|
EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, externalEntityUuid); |
||||
|
return wrapFuture(versionControlService.getEntityDataInfo(getCurrentUser(), entityId, versionId)); |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/diff/{branch}/{entityType}/{internalEntityUuid}") |
||||
|
public DeferredResult<EntityDataDiff> compareEntityDataToVersion(@PathVariable String branch, |
||||
|
@PathVariable EntityType entityType, |
||||
|
@PathVariable UUID internalEntityUuid, |
||||
|
@RequestParam String versionId) throws Exception { |
||||
|
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); |
||||
|
EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, internalEntityUuid); |
||||
|
return wrapFuture(versionControlService.compareEntityDataToVersion(getCurrentUser(), branch, entityId, versionId)); |
||||
|
} |
||||
|
|
||||
|
@ApiOperation(value = "", notes = "" + |
||||
|
"SINGLE_ENTITY:" + NEW_LINE + |
||||
|
"```\n{\n" + |
||||
|
" \"type\": \"SINGLE_ENTITY\",\n" + |
||||
|
" \n" + |
||||
|
" \"branch\": \"dev\",\n" + |
||||
|
" \"versionId\": \"b3c28d722d328324c7c15b0b30047b0c40011cf7\",\n" + |
||||
|
" \n" + |
||||
|
" \"externalEntityId\": {\n" + |
||||
|
" \"entityType\": \"DEVICE\",\n" + |
||||
|
" \"id\": \"b7944123-d4f4-11ec-847b-0f432358ab48\"\n" + |
||||
|
" },\n" + |
||||
|
" \"config\": {\n" + |
||||
|
" \"loadRelations\": false,\n" + |
||||
|
" \"findExistingEntityByName\": false\n" + |
||||
|
" }\n" + |
||||
|
"}\n```" + NEW_LINE + |
||||
|
"ENTITY_TYPE:" + NEW_LINE + |
||||
|
"```\n{\n" + |
||||
|
" \"type\": \"ENTITY_TYPE\",\n" + |
||||
|
"\n" + |
||||
|
" \"branch\": \"dev\",\n" + |
||||
|
" \"versionId\": \"b3c28d722d328324c7c15b0b30047b0c40011cf7\",\n" + |
||||
|
"\n" + |
||||
|
" \"entityTypes\": {\n" + |
||||
|
" \"DEVICE\": {\n" + |
||||
|
" \"loadRelations\": false,\n" + |
||||
|
" \"findExistingEntityByName\": false,\n" + |
||||
|
" \"removeOtherEntities\": true\n" + |
||||
|
" }\n" + |
||||
|
" }\n" + |
||||
|
"}\n```") |
||||
|
@PostMapping("/entity") |
||||
|
public UUID loadEntitiesVersion(@RequestBody VersionLoadRequest request) throws Exception { |
||||
|
SecurityUser user = getCurrentUser(); |
||||
|
accessControlService.checkPermission(user, Resource.VERSION_CONTROL, Operation.WRITE); |
||||
|
return versionControlService.loadEntitiesVersion(user, request); |
||||
|
} |
||||
|
|
||||
|
@ApiOperation(value = "", notes = "") |
||||
|
@GetMapping(value = "/entity/{requestId}/status") |
||||
|
public VersionLoadResult getVersionLoadRequestStatus(@ApiParam(value = VC_REQUEST_ID_PARAM_DESCRIPTION, required = true) |
||||
|
@PathVariable UUID requestId) throws Exception { |
||||
|
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); |
||||
|
return versionControlService.getVersionLoadStatus(getCurrentUser(), requestId); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@ApiOperation(value = "", notes = "" + |
||||
|
"```\n[\n" + |
||||
|
" {\n" + |
||||
|
" \"name\": \"master\",\n" + |
||||
|
" \"default\": true\n" + |
||||
|
" },\n" + |
||||
|
" {\n" + |
||||
|
" \"name\": \"dev\",\n" + |
||||
|
" \"default\": false\n" + |
||||
|
" },\n" + |
||||
|
" {\n" + |
||||
|
" \"name\": \"dev-2\",\n" + |
||||
|
" \"default\": false\n" + |
||||
|
" }\n" + |
||||
|
"]\n\n```") |
||||
|
@GetMapping("/branches") |
||||
|
public DeferredResult<List<BranchInfo>> listBranches() throws ThingsboardException { |
||||
|
try { |
||||
|
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); |
||||
|
final TenantId tenantId = getTenantId(); |
||||
|
ListenableFuture<List<String>> branches = versionControlService.listBranches(tenantId); |
||||
|
return wrapFuture(Futures.transform(branches, remoteBranches -> { |
||||
|
List<BranchInfo> infos = new ArrayList<>(); |
||||
|
|
||||
|
String defaultBranch = versionControlService.getVersionControlSettings(tenantId).getDefaultBranch(); |
||||
|
if (StringUtils.isNotEmpty(defaultBranch)) { |
||||
|
infos.add(new BranchInfo(defaultBranch, true)); |
||||
|
} |
||||
|
|
||||
|
remoteBranches.forEach(branch -> { |
||||
|
if (!branch.equals(defaultBranch)) { |
||||
|
infos.add(new BranchInfo(branch, false)); |
||||
|
} |
||||
|
}); |
||||
|
return infos; |
||||
|
}, MoreExecutors.directExecutor())); |
||||
|
} catch (Exception e) { |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
public static class BranchInfo { |
||||
|
private final String name; |
||||
|
private final boolean isDefault; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.apiusage; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; |
||||
|
import org.thingsboard.server.common.msg.tools.TbRateLimits; |
||||
|
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; |
||||
|
|
||||
|
import java.util.Map; |
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
import java.util.function.Function; |
||||
|
|
||||
|
@Service |
||||
|
@RequiredArgsConstructor |
||||
|
public class DefaultRateLimitService implements RateLimitService { |
||||
|
|
||||
|
private final TbTenantProfileCache tenantProfileCache; |
||||
|
|
||||
|
private final Map<String, Map<TenantId, TbRateLimits>> rateLimits = new ConcurrentHashMap<>(); |
||||
|
|
||||
|
@Override |
||||
|
public boolean checkEntityExportLimit(TenantId tenantId) { |
||||
|
return checkLimit(tenantId, "entityExport", DefaultTenantProfileConfiguration::getTenantEntityExportRateLimit); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean checkEntityImportLimit(TenantId tenantId) { |
||||
|
return checkLimit(tenantId, "entityImport", DefaultTenantProfileConfiguration::getTenantEntityImportRateLimit); |
||||
|
} |
||||
|
|
||||
|
private boolean checkLimit(TenantId tenantId, String rateLimitsKey, Function<DefaultTenantProfileConfiguration, String> rateLimitConfigExtractor) { |
||||
|
String rateLimitConfig = tenantProfileCache.get(tenantId).getProfileConfiguration() |
||||
|
.map(rateLimitConfigExtractor).orElse(null); |
||||
|
|
||||
|
Map<TenantId, TbRateLimits> rateLimits = this.rateLimits.get(rateLimitsKey); |
||||
|
if (StringUtils.isEmpty(rateLimitConfig)) { |
||||
|
if (rateLimits != null) { |
||||
|
rateLimits.remove(tenantId); |
||||
|
if (rateLimits.isEmpty()) { |
||||
|
this.rateLimits.remove(rateLimitsKey); |
||||
|
} |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (rateLimits == null) { |
||||
|
rateLimits = new ConcurrentHashMap<>(); |
||||
|
this.rateLimits.put(rateLimitsKey, rateLimits); |
||||
|
} |
||||
|
TbRateLimits rateLimit = rateLimits.get(tenantId); |
||||
|
if (rateLimit == null || !rateLimit.getConfiguration().equals(rateLimitConfig)) { |
||||
|
rateLimit = new TbRateLimits(rateLimitConfig); |
||||
|
rateLimits.put(tenantId, rateLimit); |
||||
|
} |
||||
|
|
||||
|
return rateLimit.tryConsume(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.apiusage; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
|
||||
|
public interface RateLimitService { |
||||
|
|
||||
|
boolean checkEntityExportLimit(TenantId tenantId); |
||||
|
|
||||
|
boolean checkEntityImportLimit(TenantId tenantId); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,77 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.edge.rpc.constructor; |
||||
|
|
||||
|
import com.google.protobuf.ByteString; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.common.util.JacksonUtil; |
||||
|
import org.thingsboard.server.common.data.OtaPackage; |
||||
|
import org.thingsboard.server.common.data.id.OtaPackageId; |
||||
|
import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg; |
||||
|
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
|
||||
|
@Component |
||||
|
@TbCoreComponent |
||||
|
public class OtaPackageMsgConstructor { |
||||
|
|
||||
|
public OtaPackageUpdateMsg constructOtaPackageUpdatedMsg(UpdateMsgType msgType, OtaPackage otaPackage) { |
||||
|
OtaPackageUpdateMsg.Builder builder = OtaPackageUpdateMsg.newBuilder() |
||||
|
.setMsgType(msgType) |
||||
|
.setIdMSB(otaPackage.getId().getId().getMostSignificantBits()) |
||||
|
.setIdLSB(otaPackage.getId().getId().getLeastSignificantBits()) |
||||
|
.setDeviceProfileIdMSB(otaPackage.getDeviceProfileId().getId().getMostSignificantBits()) |
||||
|
.setDeviceProfileIdLSB(otaPackage.getDeviceProfileId().getId().getLeastSignificantBits()) |
||||
|
.setType(otaPackage.getType().name()) |
||||
|
.setTitle(otaPackage.getTitle()) |
||||
|
.setVersion(otaPackage.getVersion()) |
||||
|
.setTag(otaPackage.getTag()); |
||||
|
|
||||
|
if (otaPackage.getUrl() != null) { |
||||
|
builder.setUrl(otaPackage.getUrl()); |
||||
|
} |
||||
|
if (otaPackage.getAdditionalInfo() != null) { |
||||
|
builder.setAdditionalInfo(JacksonUtil.toString(otaPackage.getAdditionalInfo())); |
||||
|
} |
||||
|
if (otaPackage.getFileName() != null) { |
||||
|
builder.setFileName(otaPackage.getFileName()); |
||||
|
} |
||||
|
if (otaPackage.getContentType() != null) { |
||||
|
builder.setContentType(otaPackage.getContentType()); |
||||
|
} |
||||
|
if (otaPackage.getChecksumAlgorithm() != null) { |
||||
|
builder.setChecksumAlgorithm(otaPackage.getChecksumAlgorithm().name()); |
||||
|
} |
||||
|
if (otaPackage.getChecksum() != null) { |
||||
|
builder.setChecksum(otaPackage.getChecksum()); |
||||
|
} |
||||
|
if (otaPackage.getDataSize() != null) { |
||||
|
builder.setDataSize(otaPackage.getDataSize()); |
||||
|
} |
||||
|
if (otaPackage.getData() != null) { |
||||
|
builder.setData(ByteString.copyFrom(otaPackage.getData().array())); |
||||
|
} |
||||
|
return builder.build(); |
||||
|
} |
||||
|
|
||||
|
public OtaPackageUpdateMsg constructOtaPackageDeleteMsg(OtaPackageId otaPackageId) { |
||||
|
return OtaPackageUpdateMsg.newBuilder() |
||||
|
.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) |
||||
|
.setIdMSB(otaPackageId.getId().getMostSignificantBits()) |
||||
|
.setIdLSB(otaPackageId.getId().getLeastSignificantBits()).build(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.edge.rpc.fetch; |
||||
|
|
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.thingsboard.server.common.data.EdgeUtils; |
||||
|
import org.thingsboard.server.common.data.OtaPackageInfo; |
||||
|
import org.thingsboard.server.common.data.edge.Edge; |
||||
|
import org.thingsboard.server.common.data.edge.EdgeEvent; |
||||
|
import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
||||
|
import org.thingsboard.server.common.data.edge.EdgeEventType; |
||||
|
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.ota.OtaPackageService; |
||||
|
|
||||
|
@AllArgsConstructor |
||||
|
@Slf4j |
||||
|
public class OtaPackagesEdgeEventFetcher extends BasePageableEdgeEventFetcher<OtaPackageInfo> { |
||||
|
|
||||
|
private final OtaPackageService otaPackageService; |
||||
|
|
||||
|
@Override |
||||
|
PageData<OtaPackageInfo> fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) { |
||||
|
return otaPackageService.findTenantOtaPackagesByTenantId(tenantId, pageLink); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, OtaPackageInfo otaPackageInfo) { |
||||
|
return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.OTA_PACKAGE, |
||||
|
EdgeEventActionType.ADDED, otaPackageInfo.getId(), null); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.edge.rpc.processor; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.server.common.data.EdgeUtils; |
||||
|
import org.thingsboard.server.common.data.OtaPackage; |
||||
|
import org.thingsboard.server.common.data.edge.EdgeEvent; |
||||
|
import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
||||
|
import org.thingsboard.server.common.data.id.OtaPackageId; |
||||
|
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; |
||||
|
import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg; |
||||
|
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
|
||||
|
@Component |
||||
|
@Slf4j |
||||
|
@TbCoreComponent |
||||
|
public class OtaPackageEdgeProcessor extends BaseEdgeProcessor { |
||||
|
|
||||
|
public DownlinkMsg processOtaPackageToEdge(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) { |
||||
|
OtaPackageId otaPackageId = new OtaPackageId(edgeEvent.getEntityId()); |
||||
|
DownlinkMsg downlinkMsg = null; |
||||
|
switch (action) { |
||||
|
case ADDED: |
||||
|
case UPDATED: |
||||
|
OtaPackage otaPackage = otaPackageService.findOtaPackageById(edgeEvent.getTenantId(), otaPackageId); |
||||
|
if (otaPackage != null) { |
||||
|
OtaPackageUpdateMsg otaPackageUpdateMsg = |
||||
|
otaPackageMsgConstructor.constructOtaPackageUpdatedMsg(msgType, otaPackage); |
||||
|
downlinkMsg = DownlinkMsg.newBuilder() |
||||
|
.setDownlinkMsgId(EdgeUtils.nextPositiveInt()) |
||||
|
.addOtaPackageUpdateMsg(otaPackageUpdateMsg) |
||||
|
.build(); |
||||
|
} |
||||
|
break; |
||||
|
case DELETED: |
||||
|
OtaPackageUpdateMsg otaPackageUpdateMsg = |
||||
|
otaPackageMsgConstructor.constructOtaPackageDeleteMsg(otaPackageId); |
||||
|
downlinkMsg = DownlinkMsg.newBuilder() |
||||
|
.setDownlinkMsgId(EdgeUtils.nextPositiveInt()) |
||||
|
.addOtaPackageUpdateMsg(otaPackageUpdateMsg) |
||||
|
.build(); |
||||
|
break; |
||||
|
} |
||||
|
return downlinkMsg; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,115 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.install.update; |
||||
|
|
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.server.common.data.TenantProfile; |
||||
|
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.tenant.TenantProfileService; |
||||
|
|
||||
|
@Component |
||||
|
class RateLimitsUpdater extends PaginatedUpdater<String, TenantProfile> { |
||||
|
|
||||
|
@Value("#{ environment.getProperty('TB_SERVER_REST_LIMITS_TENANT_ENABLED') ?: environment.getProperty('server.rest.limits.tenant.enabled') ?: 'false' }") |
||||
|
boolean tenantServerRestLimitsEnabled; |
||||
|
@Value("#{ environment.getProperty('TB_SERVER_REST_LIMITS_TENANT_CONFIGURATION') ?: environment.getProperty('server.rest.limits.tenant.configuration') ?: '100:1,2000:60' }") |
||||
|
String tenantServerRestLimitsConfiguration; |
||||
|
@Value("#{ environment.getProperty('TB_SERVER_REST_LIMITS_CUSTOMER_ENABLED') ?: environment.getProperty('server.rest.limits.customer.enabled') ?: 'false' }") |
||||
|
boolean customerServerRestLimitsEnabled; |
||||
|
@Value("#{ environment.getProperty('TB_SERVER_REST_LIMITS_CUSTOMER_CONFIGURATION') ?: environment.getProperty('server.rest.limits.customer.configuration') ?: '50:1,1000:60' }") |
||||
|
String customerServerRestLimitsConfiguration; |
||||
|
|
||||
|
@Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SESSIONS_PER_TENANT') ?: environment.getProperty('server.ws.limits.max_sessions_per_tenant') ?: '0' }") |
||||
|
private int maxWsSessionsPerTenant; |
||||
|
@Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SESSIONS_PER_CUSTOMER') ?: environment.getProperty('server.ws.limits.max_sessions_per_customer') ?: '0' }") |
||||
|
private int maxWsSessionsPerCustomer; |
||||
|
@Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SESSIONS_PER_REGULAR_USER') ?: environment.getProperty('server.ws.limits.max_sessions_per_regular_user') ?: '0' }") |
||||
|
private int maxWsSessionsPerRegularUser; |
||||
|
@Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SESSIONS_PER_PUBLIC_USER') ?: environment.getProperty('server.ws.limits.max_sessions_per_public_user') ?: '0' }") |
||||
|
private int maxWsSessionsPerPublicUser; |
||||
|
@Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_QUEUE_PER_WS_SESSION') ?: environment.getProperty('server.ws.limits.max_queue_per_ws_session') ?: '500' }") |
||||
|
private int wsMsgQueueLimitPerSession; |
||||
|
@Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SUBSCRIPTIONS_PER_TENANT') ?: environment.getProperty('server.ws.limits.max_subscriptions_per_tenant') ?: '0' }") |
||||
|
private long maxWsSubscriptionsPerTenant; |
||||
|
@Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SUBSCRIPTIONS_PER_CUSTOMER') ?: environment.getProperty('server.ws.limits.max_subscriptions_per_customer') ?: '0' }") |
||||
|
private long maxWsSubscriptionsPerCustomer; |
||||
|
@Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SUBSCRIPTIONS_PER_REGULAR_USER') ?: environment.getProperty('server.ws.limits.max_subscriptions_per_regular_user') ?: '0' }") |
||||
|
private long maxWsSubscriptionsPerRegularUser; |
||||
|
@Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SUBSCRIPTIONS_PER_PUBLIC_USER') ?: environment.getProperty('server.ws.limits.max_subscriptions_per_public_user') ?: '0' }") |
||||
|
private long maxWsSubscriptionsPerPublicUser; |
||||
|
@Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_UPDATES_PER_SESSION') ?: environment.getProperty('server.ws.limits.max_updates_per_session') ?: '300:1,3000:60' }") |
||||
|
private String wsUpdatesPerSessionRateLimit; |
||||
|
|
||||
|
@Value("#{ environment.getProperty('CASSANDRA_QUERY_TENANT_RATE_LIMITS_ENABLED') ?: environment.getProperty('cassandra.query.tenant_rate_limits.enabled') ?: 'false' }") |
||||
|
private boolean cassandraQueryTenantRateLimitsEnabled; |
||||
|
@Value("#{ environment.getProperty('CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION') ?: environment.getProperty('cassandra.query.tenant_rate_limits.configuration') ?: '1000:1,30000:60' }") |
||||
|
private String cassandraQueryTenantRateLimitsConfiguration; |
||||
|
|
||||
|
@Autowired |
||||
|
private TenantProfileService tenantProfileService; |
||||
|
|
||||
|
@Override |
||||
|
protected boolean forceReportTotal() { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected String getName() { |
||||
|
return "Rate limits updater"; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected PageData<TenantProfile> findEntities(String id, PageLink pageLink) { |
||||
|
return tenantProfileService.findTenantProfiles(TenantId.SYS_TENANT_ID, pageLink); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void updateEntity(TenantProfile tenantProfile) { |
||||
|
var profileConfiguration = tenantProfile.getDefaultProfileConfiguration(); |
||||
|
|
||||
|
if (tenantServerRestLimitsEnabled && StringUtils.isNotEmpty(tenantServerRestLimitsConfiguration)) { |
||||
|
profileConfiguration.setTenantServerRestLimitsConfiguration(tenantServerRestLimitsConfiguration); |
||||
|
} |
||||
|
if (customerServerRestLimitsEnabled && StringUtils.isNotEmpty(customerServerRestLimitsConfiguration)) { |
||||
|
profileConfiguration.setCustomerServerRestLimitsConfiguration(customerServerRestLimitsConfiguration); |
||||
|
} |
||||
|
|
||||
|
profileConfiguration.setMaxWsSessionsPerTenant(maxWsSessionsPerTenant); |
||||
|
profileConfiguration.setMaxWsSessionsPerCustomer(maxWsSessionsPerCustomer); |
||||
|
profileConfiguration.setMaxWsSessionsPerPublicUser(maxWsSessionsPerPublicUser); |
||||
|
profileConfiguration.setMaxWsSessionsPerRegularUser(maxWsSessionsPerRegularUser); |
||||
|
profileConfiguration.setMaxWsSubscriptionsPerTenant(maxWsSubscriptionsPerTenant); |
||||
|
profileConfiguration.setMaxWsSubscriptionsPerCustomer(maxWsSubscriptionsPerCustomer); |
||||
|
profileConfiguration.setMaxWsSubscriptionsPerPublicUser(maxWsSubscriptionsPerPublicUser); |
||||
|
profileConfiguration.setMaxWsSubscriptionsPerRegularUser(maxWsSubscriptionsPerRegularUser); |
||||
|
profileConfiguration.setWsMsgQueueLimitPerSession(wsMsgQueueLimitPerSession); |
||||
|
if (StringUtils.isNotEmpty(wsUpdatesPerSessionRateLimit)) { |
||||
|
profileConfiguration.setWsUpdatesPerSessionRateLimit(wsUpdatesPerSessionRateLimit); |
||||
|
} |
||||
|
|
||||
|
if (cassandraQueryTenantRateLimitsEnabled && StringUtils.isNotEmpty(cassandraQueryTenantRateLimitsConfiguration)) { |
||||
|
profileConfiguration.setCassandraQueryTenantRateLimitsConfiguration(cassandraQueryTenantRateLimitsConfiguration); |
||||
|
} |
||||
|
|
||||
|
tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, tenantProfile); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,166 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.ExportableEntity; |
||||
|
import org.thingsboard.server.common.data.audit.ActionType; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.relation.EntityRelation; |
||||
|
import org.thingsboard.server.common.data.sync.ThrowingRunnable; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityImportResult; |
||||
|
import org.thingsboard.server.dao.exception.DataValidationException; |
||||
|
import org.thingsboard.server.dao.relation.RelationService; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.action.EntityActionService; |
||||
|
import org.thingsboard.server.service.apiusage.RateLimitService; |
||||
|
import org.thingsboard.server.service.sync.ie.exporting.EntityExportService; |
||||
|
import org.thingsboard.server.service.sync.ie.exporting.impl.BaseEntityExportService; |
||||
|
import org.thingsboard.server.service.sync.ie.exporting.impl.DefaultEntityExportService; |
||||
|
import org.thingsboard.server.service.sync.ie.importing.EntityImportService; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.Collection; |
||||
|
import java.util.Comparator; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
@RequiredArgsConstructor |
||||
|
@Slf4j |
||||
|
public class DefaultEntitiesExportImportService implements EntitiesExportImportService { |
||||
|
|
||||
|
private final Map<EntityType, EntityExportService<?, ?, ?>> exportServices = new HashMap<>(); |
||||
|
private final Map<EntityType, EntityImportService<?, ?, ?>> importServices = new HashMap<>(); |
||||
|
|
||||
|
private final EntityActionService entityActionService; |
||||
|
private final RelationService relationService; |
||||
|
private final RateLimitService rateLimitService; |
||||
|
|
||||
|
protected static final List<EntityType> SUPPORTED_ENTITY_TYPES = List.of( |
||||
|
EntityType.CUSTOMER, EntityType.ASSET, EntityType.RULE_CHAIN, |
||||
|
EntityType.DASHBOARD, EntityType.DEVICE_PROFILE, EntityType.DEVICE, |
||||
|
EntityType.ENTITY_VIEW, EntityType.WIDGETS_BUNDLE |
||||
|
); |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
public <E extends ExportableEntity<I>, I extends EntityId> EntityExportData<E> exportEntity(EntitiesExportCtx<?> ctx, I entityId) throws ThingsboardException { |
||||
|
if (!rateLimitService.checkEntityExportLimit(ctx.getTenantId())) { |
||||
|
throw new ThingsboardException("Rate limit for entities export is exceeded", ThingsboardErrorCode.TOO_MANY_REQUESTS); |
||||
|
} |
||||
|
|
||||
|
EntityType entityType = entityId.getEntityType(); |
||||
|
EntityExportService<I, E, EntityExportData<E>> exportService = getExportService(entityType); |
||||
|
|
||||
|
return exportService.getExportData(ctx, entityId); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public <E extends ExportableEntity<I>, I extends EntityId> EntityImportResult<E> importEntity(EntitiesImportCtx ctx, EntityExportData<E> exportData) throws ThingsboardException { |
||||
|
if (!rateLimitService.checkEntityImportLimit(ctx.getTenantId())) { |
||||
|
throw new ThingsboardException("Rate limit for entities import is exceeded", ThingsboardErrorCode.TOO_MANY_REQUESTS); |
||||
|
} |
||||
|
if (exportData.getEntity() == null || exportData.getEntity().getId() == null) { |
||||
|
throw new DataValidationException("Invalid entity data"); |
||||
|
} |
||||
|
|
||||
|
EntityType entityType = exportData.getEntityType(); |
||||
|
EntityImportService<I, E, EntityExportData<E>> importService = getImportService(entityType); |
||||
|
|
||||
|
EntityImportResult<E> importResult = importService.importEntity(ctx, exportData); |
||||
|
ctx.putInternalId(exportData.getExternalId(), importResult.getSavedEntity().getId()); |
||||
|
|
||||
|
ctx.addReferenceCallback(importResult.getSaveReferencesCallback()); |
||||
|
ctx.addEventCallback(importResult.getSendEventsCallback()); |
||||
|
return importResult; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void saveReferencesAndRelations(EntitiesImportCtx ctx) throws ThingsboardException { |
||||
|
for (ThrowingRunnable saveReferencesCallback : ctx.getReferenceCallbacks()) { |
||||
|
saveReferencesCallback.run(); |
||||
|
} |
||||
|
|
||||
|
relationService.saveRelations(ctx.getTenantId(), new ArrayList<>(ctx.getRelations())); |
||||
|
|
||||
|
for (EntityRelation relation : ctx.getRelations()) { |
||||
|
entityActionService.logEntityAction(ctx.getUser(), relation.getFrom(), null, null, |
||||
|
ActionType.RELATION_ADD_OR_UPDATE, null, relation); |
||||
|
entityActionService.logEntityAction(ctx.getUser(), relation.getTo(), null, null, |
||||
|
ActionType.RELATION_ADD_OR_UPDATE, null, relation); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
public Comparator<EntityType> getEntityTypeComparatorForImport() { |
||||
|
return Comparator.comparing(SUPPORTED_ENTITY_TYPES::indexOf); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@SuppressWarnings("unchecked") |
||||
|
private <I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> EntityExportService<I, E, D> getExportService(EntityType entityType) { |
||||
|
EntityExportService<?, ?, ?> exportService = exportServices.get(entityType); |
||||
|
if (exportService == null) { |
||||
|
throw new IllegalArgumentException("Export for entity type " + entityType + " is not supported"); |
||||
|
} |
||||
|
return (EntityExportService<I, E, D>) exportService; |
||||
|
} |
||||
|
|
||||
|
@SuppressWarnings("unchecked") |
||||
|
private <I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> EntityImportService<I, E, D> getImportService(EntityType entityType) { |
||||
|
EntityImportService<?, ?, ?> importService = importServices.get(entityType); |
||||
|
if (importService == null) { |
||||
|
throw new IllegalArgumentException("Import for entity type " + entityType + " is not supported"); |
||||
|
} |
||||
|
return (EntityImportService<I, E, D>) importService; |
||||
|
} |
||||
|
|
||||
|
@Autowired |
||||
|
private void setExportServices(DefaultEntityExportService<?, ?, ?> defaultExportService, |
||||
|
Collection<BaseEntityExportService<?, ?, ?>> exportServices) { |
||||
|
exportServices.stream() |
||||
|
.sorted(Comparator.comparing(exportService -> exportService.getSupportedEntityTypes().size(), Comparator.reverseOrder())) |
||||
|
.forEach(exportService -> { |
||||
|
exportService.getSupportedEntityTypes().forEach(entityType -> { |
||||
|
this.exportServices.put(entityType, exportService); |
||||
|
}); |
||||
|
}); |
||||
|
SUPPORTED_ENTITY_TYPES.forEach(entityType -> { |
||||
|
this.exportServices.putIfAbsent(entityType, defaultExportService); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@Autowired |
||||
|
private void setImportServices(Collection<EntityImportService<?, ?, ?>> importServices) { |
||||
|
importServices.forEach(entityImportService -> { |
||||
|
this.importServices.put(entityImportService.getEntityType(), entityImportService); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.ExportableEntity; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityImportResult; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; |
||||
|
|
||||
|
import java.util.Comparator; |
||||
|
|
||||
|
public interface EntitiesExportImportService { |
||||
|
|
||||
|
<E extends ExportableEntity<I>, I extends EntityId> EntityExportData<E> exportEntity(EntitiesExportCtx<?> ctx, I entityId) throws ThingsboardException; |
||||
|
|
||||
|
<E extends ExportableEntity<I>, I extends EntityId> EntityImportResult<E> importEntity(EntitiesImportCtx ctx, EntityExportData<E> exportData) throws ThingsboardException; |
||||
|
|
||||
|
|
||||
|
void saveReferencesAndRelations(EntitiesImportCtx ctx) throws ThingsboardException; |
||||
|
|
||||
|
Comparator<EntityType> getEntityTypeComparatorForImport(); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,210 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.exporting; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.ExportableEntity; |
||||
|
import org.thingsboard.server.common.data.HasTenantId; |
||||
|
import org.thingsboard.server.common.data.id.AssetId; |
||||
|
import org.thingsboard.server.common.data.id.CustomerId; |
||||
|
import org.thingsboard.server.common.data.id.DashboardId; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.id.DeviceProfileId; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.HasId; |
||||
|
import org.thingsboard.server.common.data.id.RuleChainId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.id.WidgetsBundleId; |
||||
|
import org.thingsboard.server.common.data.page.PageData; |
||||
|
import org.thingsboard.server.common.data.page.PageLink; |
||||
|
import org.thingsboard.server.dao.Dao; |
||||
|
import org.thingsboard.server.dao.ExportableEntityDao; |
||||
|
import org.thingsboard.server.dao.asset.AssetService; |
||||
|
import org.thingsboard.server.dao.customer.CustomerService; |
||||
|
import org.thingsboard.server.dao.dashboard.DashboardService; |
||||
|
import org.thingsboard.server.dao.device.DeviceProfileService; |
||||
|
import org.thingsboard.server.dao.device.DeviceService; |
||||
|
import org.thingsboard.server.dao.rule.RuleChainService; |
||||
|
import org.thingsboard.server.dao.widget.WidgetsBundleService; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.security.permission.AccessControlService; |
||||
|
|
||||
|
import java.util.Collection; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
import java.util.function.BiConsumer; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
@RequiredArgsConstructor |
||||
|
@Slf4j |
||||
|
public class DefaultExportableEntitiesService implements ExportableEntitiesService { |
||||
|
|
||||
|
private final Map<EntityType, Dao<?>> daos = new HashMap<>(); |
||||
|
private final Map<EntityType, BiConsumer<TenantId, EntityId>> removers = new HashMap<>(); |
||||
|
|
||||
|
private final AccessControlService accessControlService; |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
public <E extends ExportableEntity<I>, I extends EntityId> E findEntityByTenantIdAndExternalId(TenantId tenantId, I externalId) { |
||||
|
EntityType entityType = externalId.getEntityType(); |
||||
|
Dao<E> dao = getDao(entityType); |
||||
|
|
||||
|
E entity = null; |
||||
|
|
||||
|
if (dao instanceof ExportableEntityDao) { |
||||
|
ExportableEntityDao<I, E> exportableEntityDao = (ExportableEntityDao<I, E>) dao; |
||||
|
entity = exportableEntityDao.findByTenantIdAndExternalId(tenantId.getId(), externalId.getId()); |
||||
|
} |
||||
|
if (entity == null || !belongsToTenant(entity, tenantId)) { |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return entity; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public <E extends HasId<I>, I extends EntityId> E findEntityByTenantIdAndId(TenantId tenantId, I id) { |
||||
|
E entity = findEntityById(id); |
||||
|
|
||||
|
if (entity == null || !belongsToTenant(entity, tenantId)) { |
||||
|
return null; |
||||
|
} |
||||
|
return entity; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public <E extends HasId<I>, I extends EntityId> E findEntityById(I id) { |
||||
|
EntityType entityType = id.getEntityType(); |
||||
|
Dao<E> dao = getDao(entityType); |
||||
|
if (dao == null) { |
||||
|
throw new IllegalArgumentException("Unsupported entity type " + entityType); |
||||
|
} |
||||
|
|
||||
|
return dao.findById(TenantId.SYS_TENANT_ID, id.getId()); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public <E extends ExportableEntity<I>, I extends EntityId> E findEntityByTenantIdAndName(TenantId tenantId, EntityType entityType, String name) { |
||||
|
Dao<E> dao = getDao(entityType); |
||||
|
|
||||
|
E entity = null; |
||||
|
|
||||
|
if (dao instanceof ExportableEntityDao) { |
||||
|
ExportableEntityDao<I, E> exportableEntityDao = (ExportableEntityDao<I, E>) dao; |
||||
|
try { |
||||
|
entity = exportableEntityDao.findByTenantIdAndName(tenantId.getId(), name); |
||||
|
} catch (UnsupportedOperationException ignored) { |
||||
|
} |
||||
|
} |
||||
|
if (entity == null || !belongsToTenant(entity, tenantId)) { |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return entity; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public <E extends ExportableEntity<I>, I extends EntityId> PageData<E> findEntitiesByTenantId(TenantId tenantId, EntityType entityType, PageLink pageLink) { |
||||
|
ExportableEntityDao<I, E> dao = getExportableEntityDao(entityType); |
||||
|
if (dao != null) { |
||||
|
return dao.findByTenantId(tenantId.getId(), pageLink); |
||||
|
} else { |
||||
|
return new PageData<>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public <I extends EntityId> I getExternalIdByInternal(I internalId) { |
||||
|
ExportableEntityDao<I, ?> dao = getExportableEntityDao(internalId.getEntityType()); |
||||
|
if (dao != null) { |
||||
|
return dao.getExternalIdByInternal(internalId); |
||||
|
} else { |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private boolean belongsToTenant(HasId<? extends EntityId> entity, TenantId tenantId) { |
||||
|
return tenantId.equals(((HasTenantId) entity).getTenantId()); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
public <I extends EntityId> void removeById(TenantId tenantId, I id) { |
||||
|
EntityType entityType = id.getEntityType(); |
||||
|
BiConsumer<TenantId, EntityId> entityRemover = removers.get(entityType); |
||||
|
if (entityRemover == null) { |
||||
|
throw new IllegalArgumentException("Unsupported entity type " + entityType); |
||||
|
} |
||||
|
entityRemover.accept(tenantId, id); |
||||
|
} |
||||
|
|
||||
|
private <I extends EntityId, E extends ExportableEntity<I>> ExportableEntityDao<I, E> getExportableEntityDao(EntityType entityType) { |
||||
|
Dao<E> dao = getDao(entityType); |
||||
|
if (dao instanceof ExportableEntityDao) { |
||||
|
return (ExportableEntityDao<I, E>) dao; |
||||
|
} else { |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@SuppressWarnings("unchecked") |
||||
|
private <E> Dao<E> getDao(EntityType entityType) { |
||||
|
return (Dao<E>) daos.get(entityType); |
||||
|
} |
||||
|
|
||||
|
@Autowired |
||||
|
private void setDaos(Collection<Dao<?>> daos) { |
||||
|
daos.forEach(dao -> { |
||||
|
if (dao.getEntityType() != null) { |
||||
|
this.daos.put(dao.getEntityType(), dao); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@Autowired |
||||
|
private void setRemovers(CustomerService customerService, AssetService assetService, RuleChainService ruleChainService, |
||||
|
DashboardService dashboardService, DeviceProfileService deviceProfileService, |
||||
|
DeviceService deviceService, WidgetsBundleService widgetsBundleService) { |
||||
|
removers.put(EntityType.CUSTOMER, (tenantId, entityId) -> { |
||||
|
customerService.deleteCustomer(tenantId, (CustomerId) entityId); |
||||
|
}); |
||||
|
removers.put(EntityType.ASSET, (tenantId, entityId) -> { |
||||
|
assetService.deleteAsset(tenantId, (AssetId) entityId); |
||||
|
}); |
||||
|
removers.put(EntityType.RULE_CHAIN, (tenantId, entityId) -> { |
||||
|
ruleChainService.deleteRuleChainById(tenantId, (RuleChainId) entityId); |
||||
|
}); |
||||
|
removers.put(EntityType.DASHBOARD, (tenantId, entityId) -> { |
||||
|
dashboardService.deleteDashboard(tenantId, (DashboardId) entityId); |
||||
|
}); |
||||
|
removers.put(EntityType.DEVICE_PROFILE, (tenantId, entityId) -> { |
||||
|
deviceProfileService.deleteDeviceProfile(tenantId, (DeviceProfileId) entityId); |
||||
|
}); |
||||
|
removers.put(EntityType.DEVICE, (tenantId, entityId) -> { |
||||
|
deviceService.deleteDevice(tenantId, (DeviceId) entityId); |
||||
|
}); |
||||
|
removers.put(EntityType.WIDGETS_BUNDLE, (tenantId, entityId) -> { |
||||
|
widgetsBundleService.deleteWidgetsBundle(tenantId, (WidgetsBundleId) entityId); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.exporting; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.ExportableEntity; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; |
||||
|
|
||||
|
public interface EntityExportService<I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> { |
||||
|
|
||||
|
D getExportData(EntitiesExportCtx<?> ctx, I entityId) throws ThingsboardException; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.exporting; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.ExportableEntity; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.HasId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.page.PageData; |
||||
|
import org.thingsboard.server.common.data.page.PageLink; |
||||
|
|
||||
|
public interface ExportableEntitiesService { |
||||
|
|
||||
|
<E extends ExportableEntity<I>, I extends EntityId> E findEntityByTenantIdAndExternalId(TenantId tenantId, I externalId); |
||||
|
|
||||
|
<E extends HasId<I>, I extends EntityId> E findEntityByTenantIdAndId(TenantId tenantId, I id); |
||||
|
|
||||
|
<E extends HasId<I>, I extends EntityId> E findEntityById(I id); |
||||
|
|
||||
|
<E extends ExportableEntity<I>, I extends EntityId> E findEntityByTenantIdAndName(TenantId tenantId, EntityType entityType, String name); |
||||
|
|
||||
|
<E extends ExportableEntity<I>, I extends EntityId> PageData<E> findEntitiesByTenantId(TenantId tenantId, EntityType entityType, PageLink pageLink); |
||||
|
|
||||
|
<I extends EntityId> I getExternalIdByInternal(I internalId); |
||||
|
|
||||
|
<I extends EntityId> void removeById(TenantId tenantId, I id); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.exporting.impl; |
||||
|
|
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.asset.Asset; |
||||
|
import org.thingsboard.server.common.data.id.AssetId; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; |
||||
|
|
||||
|
import java.util.Set; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
public class AssetExportService extends BaseEntityExportService<AssetId, Asset, EntityExportData<Asset>> { |
||||
|
|
||||
|
@Override |
||||
|
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, Asset asset, EntityExportData<Asset> exportData) { |
||||
|
asset.setCustomerId(getExternalIdOrElseInternal(ctx, asset.getCustomerId())); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Set<EntityType> getSupportedEntityTypes() { |
||||
|
return Set.of(EntityType.ASSET); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,52 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.exporting.impl; |
||||
|
|
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import org.thingsboard.common.util.JacksonUtil; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.ExportableEntity; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; |
||||
|
|
||||
|
import java.util.Set; |
||||
|
|
||||
|
public abstract class BaseEntityExportService<I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> extends DefaultEntityExportService<I, E, D> { |
||||
|
|
||||
|
@Override |
||||
|
protected void setAdditionalExportData(EntitiesExportCtx<?> ctx, E entity, D exportData) throws ThingsboardException { |
||||
|
setRelatedEntities(ctx, entity, (D) exportData); |
||||
|
super.setAdditionalExportData(ctx, entity, exportData); |
||||
|
} |
||||
|
|
||||
|
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, E mainEntity, D exportData) { |
||||
|
} |
||||
|
|
||||
|
protected D newExportData() { |
||||
|
return (D) new EntityExportData<E>(); |
||||
|
} |
||||
|
|
||||
|
; |
||||
|
|
||||
|
public abstract Set<EntityType> getSupportedEntityTypes(); |
||||
|
|
||||
|
protected void replaceUuidsRecursively(EntitiesExportCtx<?> ctx, JsonNode node, Set<String> skipFieldsSet) { |
||||
|
JacksonUtil.replaceUuidsRecursively(node, skipFieldsSet, uuid -> getExternalIdOrElseInternalByUuid(ctx, uuid)); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.exporting.impl; |
||||
|
|
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import com.fasterxml.jackson.databind.node.ObjectNode; |
||||
|
import com.google.common.collect.Lists; |
||||
|
import org.apache.commons.collections.CollectionUtils; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.common.util.JacksonUtil; |
||||
|
import org.thingsboard.server.common.data.Dashboard; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.id.DashboardId; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; |
||||
|
import org.thingsboard.common.util.RegexUtils; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.Collections; |
||||
|
import java.util.Set; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
public class DashboardExportService extends BaseEntityExportService<DashboardId, Dashboard, EntityExportData<Dashboard>> { |
||||
|
|
||||
|
@Override |
||||
|
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, Dashboard dashboard, EntityExportData<Dashboard> exportData) { |
||||
|
if (CollectionUtils.isNotEmpty(dashboard.getAssignedCustomers())) { |
||||
|
dashboard.getAssignedCustomers().forEach(customerInfo -> { |
||||
|
customerInfo.setCustomerId(getExternalIdOrElseInternal(ctx, customerInfo.getCustomerId())); |
||||
|
}); |
||||
|
} |
||||
|
for (JsonNode entityAlias : dashboard.getEntityAliasesConfig()) { |
||||
|
replaceUuidsRecursively(ctx, entityAlias, Collections.emptySet()); |
||||
|
} |
||||
|
for (JsonNode widgetConfig : dashboard.getWidgetsConfig()) { |
||||
|
replaceUuidsRecursively(ctx, JacksonUtil.getSafely(widgetConfig, "config", "actions"), Collections.singleton("id")); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Set<EntityType> getSupportedEntityTypes() { |
||||
|
return Set.of(EntityType.DASHBOARD); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,184 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.exporting.impl; |
||||
|
|
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.context.annotation.Lazy; |
||||
|
import org.springframework.context.annotation.Primary; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.DataConstants; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.ExportableEntity; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.EntityIdFactory; |
||||
|
import org.thingsboard.server.common.data.relation.EntityRelation; |
||||
|
import org.thingsboard.server.common.data.relation.RelationTypeGroup; |
||||
|
import org.thingsboard.server.common.data.sync.ie.AttributeExportData; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.dao.attributes.AttributesService; |
||||
|
import org.thingsboard.server.dao.relation.RelationService; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.sync.ie.exporting.EntityExportService; |
||||
|
import org.thingsboard.server.service.sync.ie.exporting.ExportableEntitiesService; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.Collections; |
||||
|
import java.util.LinkedHashMap; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
import java.util.Optional; |
||||
|
import java.util.UUID; |
||||
|
import java.util.concurrent.ExecutionException; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
@Primary |
||||
|
public class DefaultEntityExportService<I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> implements EntityExportService<I, E, D> { |
||||
|
|
||||
|
@Autowired |
||||
|
@Lazy |
||||
|
protected ExportableEntitiesService exportableEntitiesService; |
||||
|
@Autowired |
||||
|
private RelationService relationService; |
||||
|
@Autowired |
||||
|
private AttributesService attributesService; |
||||
|
|
||||
|
@Override |
||||
|
public final D getExportData(EntitiesExportCtx<?> ctx, I entityId) throws ThingsboardException { |
||||
|
D exportData = newExportData(); |
||||
|
|
||||
|
E entity = exportableEntitiesService.findEntityByTenantIdAndId(ctx.getTenantId(), entityId); |
||||
|
if (entity == null) { |
||||
|
throw new IllegalArgumentException(entityId.getEntityType() + " [" + entityId.getId() + "] not found"); |
||||
|
} |
||||
|
|
||||
|
exportData.setEntity(entity); |
||||
|
exportData.setEntityType(entityId.getEntityType()); |
||||
|
setAdditionalExportData(ctx, entity, exportData); |
||||
|
|
||||
|
var externalId = entity.getExternalId() != null ? entity.getExternalId() : entity.getId(); |
||||
|
ctx.putExternalId(entityId, externalId); |
||||
|
entity.setId(externalId); |
||||
|
entity.setTenantId(null); |
||||
|
|
||||
|
return exportData; |
||||
|
} |
||||
|
|
||||
|
protected void setAdditionalExportData(EntitiesExportCtx<?> ctx, E entity, D exportData) throws ThingsboardException { |
||||
|
var exportSettings = ctx.getSettings(); |
||||
|
if (exportSettings.isExportRelations()) { |
||||
|
List<EntityRelation> relations = exportRelations(ctx, entity); |
||||
|
relations.forEach(relation -> { |
||||
|
relation.setFrom(getExternalIdOrElseInternal(ctx, relation.getFrom())); |
||||
|
relation.setTo(getExternalIdOrElseInternal(ctx, relation.getTo())); |
||||
|
}); |
||||
|
exportData.setRelations(relations); |
||||
|
} |
||||
|
if (exportSettings.isExportAttributes()) { |
||||
|
Map<String, List<AttributeExportData>> attributes = exportAttributes(ctx, entity); |
||||
|
exportData.setAttributes(attributes); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private List<EntityRelation> exportRelations(EntitiesExportCtx<?> ctx, E entity) throws ThingsboardException { |
||||
|
List<EntityRelation> relations = new ArrayList<>(); |
||||
|
|
||||
|
List<EntityRelation> inboundRelations = relationService.findByTo(ctx.getTenantId(), entity.getId(), RelationTypeGroup.COMMON); |
||||
|
relations.addAll(inboundRelations); |
||||
|
|
||||
|
List<EntityRelation> outboundRelations = relationService.findByFrom(ctx.getTenantId(), entity.getId(), RelationTypeGroup.COMMON); |
||||
|
relations.addAll(outboundRelations); |
||||
|
return relations; |
||||
|
} |
||||
|
|
||||
|
private Map<String, List<AttributeExportData>> exportAttributes(EntitiesExportCtx<?> ctx, E entity) throws ThingsboardException { |
||||
|
List<String> scopes; |
||||
|
if (entity.getId().getEntityType() == EntityType.DEVICE) { |
||||
|
scopes = List.of(DataConstants.SERVER_SCOPE, DataConstants.SHARED_SCOPE); |
||||
|
} else { |
||||
|
scopes = Collections.singletonList(DataConstants.SERVER_SCOPE); |
||||
|
} |
||||
|
Map<String, List<AttributeExportData>> attributes = new LinkedHashMap<>(); |
||||
|
scopes.forEach(scope -> { |
||||
|
try { |
||||
|
attributes.put(scope, attributesService.findAll(ctx.getTenantId(), entity.getId(), scope).get().stream() |
||||
|
.map(attribute -> { |
||||
|
AttributeExportData attributeExportData = new AttributeExportData(); |
||||
|
attributeExportData.setKey(attribute.getKey()); |
||||
|
attributeExportData.setLastUpdateTs(attribute.getLastUpdateTs()); |
||||
|
attributeExportData.setStrValue(attribute.getStrValue().orElse(null)); |
||||
|
attributeExportData.setDoubleValue(attribute.getDoubleValue().orElse(null)); |
||||
|
attributeExportData.setLongValue(attribute.getLongValue().orElse(null)); |
||||
|
attributeExportData.setBooleanValue(attribute.getBooleanValue().orElse(null)); |
||||
|
attributeExportData.setJsonValue(attribute.getJsonValue().orElse(null)); |
||||
|
return attributeExportData; |
||||
|
}) |
||||
|
.collect(Collectors.toList())); |
||||
|
} catch (InterruptedException | ExecutionException e) { |
||||
|
throw new RuntimeException(e); |
||||
|
} |
||||
|
}); |
||||
|
return attributes; |
||||
|
} |
||||
|
|
||||
|
protected <ID extends EntityId> ID getExternalIdOrElseInternal(EntitiesExportCtx<?> ctx, ID internalId) { |
||||
|
if (internalId == null || internalId.isNullUid()) return internalId; |
||||
|
var result = ctx.getExternalId(internalId); |
||||
|
if (result == null) { |
||||
|
result = Optional.ofNullable(exportableEntitiesService.getExternalIdByInternal(internalId)) |
||||
|
.orElse(internalId); |
||||
|
ctx.putExternalId(internalId, result); |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
protected UUID getExternalIdOrElseInternalByUuid(EntitiesExportCtx<?> ctx, UUID internalUuid) { |
||||
|
for (EntityType entityType : EntityType.values()) { |
||||
|
EntityId internalId; |
||||
|
try { |
||||
|
internalId = EntityIdFactory.getByTypeAndUuid(entityType, internalUuid); |
||||
|
} catch (Exception e) { |
||||
|
continue; |
||||
|
} |
||||
|
EntityId externalId = ctx.getExternalId(internalId); |
||||
|
if (externalId != null) { |
||||
|
return externalId.getId(); |
||||
|
} |
||||
|
} |
||||
|
for (EntityType entityType : EntityType.values()) { |
||||
|
EntityId internalId; |
||||
|
try { |
||||
|
internalId = EntityIdFactory.getByTypeAndUuid(entityType, internalUuid); |
||||
|
} catch (Exception e) { |
||||
|
continue; |
||||
|
} |
||||
|
EntityId externalId = exportableEntitiesService.getExternalIdByInternal(internalId); |
||||
|
if (externalId != null) { |
||||
|
ctx.putExternalId(internalId, externalId); |
||||
|
return externalId.getId(); |
||||
|
} |
||||
|
} |
||||
|
return internalUuid; |
||||
|
} |
||||
|
|
||||
|
protected D newExportData() { |
||||
|
return (D) new EntityExportData<E>(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.exporting.impl; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.Device; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.sync.ie.DeviceExportData; |
||||
|
import org.thingsboard.server.dao.device.DeviceCredentialsService; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; |
||||
|
|
||||
|
import java.util.Set; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
@RequiredArgsConstructor |
||||
|
public class DeviceExportService extends BaseEntityExportService<DeviceId, Device, DeviceExportData> { |
||||
|
|
||||
|
private final DeviceCredentialsService deviceCredentialsService; |
||||
|
|
||||
|
@Override |
||||
|
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, Device device, DeviceExportData exportData) { |
||||
|
device.setCustomerId(getExternalIdOrElseInternal(ctx, device.getCustomerId())); |
||||
|
device.setDeviceProfileId(getExternalIdOrElseInternal(ctx, device.getDeviceProfileId())); |
||||
|
if (ctx.getSettings().isExportCredentials()) { |
||||
|
var credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(ctx.getTenantId(), device.getId()); |
||||
|
credentials.setId(null); |
||||
|
credentials.setDeviceId(null); |
||||
|
exportData.setCredentials(credentials); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected DeviceExportData newExportData() { |
||||
|
return new DeviceExportData(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Set<EntityType> getSupportedEntityTypes() { |
||||
|
return Set.of(EntityType.DEVICE); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.exporting.impl; |
||||
|
|
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.DeviceProfile; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.id.DeviceProfileId; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; |
||||
|
|
||||
|
import java.util.Set; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
public class DeviceProfileExportService extends BaseEntityExportService<DeviceProfileId, DeviceProfile, EntityExportData<DeviceProfile>> { |
||||
|
|
||||
|
@Override |
||||
|
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, DeviceProfile deviceProfile, EntityExportData<DeviceProfile> exportData) { |
||||
|
deviceProfile.setDefaultDashboardId(getExternalIdOrElseInternal(ctx, deviceProfile.getDefaultDashboardId())); |
||||
|
deviceProfile.setDefaultRuleChainId(getExternalIdOrElseInternal(ctx, deviceProfile.getDefaultRuleChainId())); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Set<EntityType> getSupportedEntityTypes() { |
||||
|
return Set.of(EntityType.DEVICE_PROFILE); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.exporting.impl; |
||||
|
|
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.EntityView; |
||||
|
import org.thingsboard.server.common.data.id.EntityViewId; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; |
||||
|
|
||||
|
import java.util.Set; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
public class EntityViewExportService extends BaseEntityExportService<EntityViewId, EntityView, EntityExportData<EntityView>> { |
||||
|
|
||||
|
@Override |
||||
|
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, EntityView entityView, EntityExportData<EntityView> exportData) { |
||||
|
entityView.setEntityId(getExternalIdOrElseInternal(ctx, entityView.getEntityId())); |
||||
|
entityView.setCustomerId(getExternalIdOrElseInternal(ctx, entityView.getCustomerId())); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Set<EntityType> getSupportedEntityTypes() { |
||||
|
return Set.of(EntityType.ENTITY_VIEW); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.exporting.impl; |
||||
|
|
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.common.util.JacksonUtil; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.id.RuleChainId; |
||||
|
import org.thingsboard.server.common.data.rule.RuleChain; |
||||
|
import org.thingsboard.server.common.data.rule.RuleChainMetaData; |
||||
|
import org.thingsboard.server.common.data.sync.ie.RuleChainExportData; |
||||
|
import org.thingsboard.server.dao.rule.RuleChainService; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; |
||||
|
import org.thingsboard.common.util.RegexUtils; |
||||
|
|
||||
|
import java.util.Collections; |
||||
|
import java.util.Optional; |
||||
|
import java.util.Set; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
@RequiredArgsConstructor |
||||
|
public class RuleChainExportService extends BaseEntityExportService<RuleChainId, RuleChain, RuleChainExportData> { |
||||
|
|
||||
|
private final RuleChainService ruleChainService; |
||||
|
|
||||
|
@Override |
||||
|
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, RuleChain ruleChain, RuleChainExportData exportData) { |
||||
|
RuleChainMetaData metaData = ruleChainService.loadRuleChainMetaData(ctx.getTenantId(), ruleChain.getId()); |
||||
|
Optional.ofNullable(metaData.getNodes()).orElse(Collections.emptyList()) |
||||
|
.forEach(ruleNode -> { |
||||
|
ruleNode.setRuleChainId(null); |
||||
|
ctx.putExternalId(ruleNode.getId(), ruleNode.getExternalId()); |
||||
|
ruleNode.setId(ctx.getExternalId(ruleNode.getId())); |
||||
|
ruleNode.setCreatedTime(0); |
||||
|
ruleNode.setExternalId(null); |
||||
|
replaceUuidsRecursively(ctx, ruleNode.getConfiguration(), Collections.emptySet()); |
||||
|
}); |
||||
|
Optional.ofNullable(metaData.getRuleChainConnections()).orElse(Collections.emptyList()) |
||||
|
.forEach(ruleChainConnectionInfo -> { |
||||
|
ruleChainConnectionInfo.setTargetRuleChainId(getExternalIdOrElseInternal(ctx, ruleChainConnectionInfo.getTargetRuleChainId())); |
||||
|
}); |
||||
|
exportData.setMetaData(metaData); |
||||
|
if (ruleChain.getFirstRuleNodeId() != null) { |
||||
|
ruleChain.setFirstRuleNodeId(ctx.getExternalId(ruleChain.getFirstRuleNodeId())); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected RuleChainExportData newExportData() { |
||||
|
return new RuleChainExportData(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Set<EntityType> getSupportedEntityTypes() { |
||||
|
return Set.of(EntityType.RULE_CHAIN); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.exporting.impl; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.id.WidgetsBundleId; |
||||
|
import org.thingsboard.server.common.data.sync.ie.WidgetsBundleExportData; |
||||
|
import org.thingsboard.server.common.data.widget.WidgetTypeDetails; |
||||
|
import org.thingsboard.server.common.data.widget.WidgetsBundle; |
||||
|
import org.thingsboard.server.dao.widget.WidgetTypeService; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.Set; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
@RequiredArgsConstructor |
||||
|
public class WidgetsBundleExportService extends BaseEntityExportService<WidgetsBundleId, WidgetsBundle, WidgetsBundleExportData> { |
||||
|
|
||||
|
private final WidgetTypeService widgetTypeService; |
||||
|
|
||||
|
@Override |
||||
|
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, WidgetsBundle widgetsBundle, WidgetsBundleExportData exportData) { |
||||
|
if (widgetsBundle.getTenantId() == null || widgetsBundle.getTenantId().isNullUid()) { |
||||
|
throw new IllegalArgumentException("Export of system Widget Bundles is not allowed"); |
||||
|
} |
||||
|
|
||||
|
List<WidgetTypeDetails> widgets = widgetTypeService.findWidgetTypesDetailsByTenantIdAndBundleAlias(ctx.getTenantId(), widgetsBundle.getAlias()); |
||||
|
exportData.setWidgets(widgets); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected WidgetsBundleExportData newExportData() { |
||||
|
return new WidgetsBundleExportData(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Set<EntityType> getSupportedEntityTypes() { |
||||
|
return Set.of(EntityType.WIDGETS_BUNDLE); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.importing; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.ExportableEntity; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityImportResult; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; |
||||
|
|
||||
|
public interface EntityImportService<I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> { |
||||
|
|
||||
|
EntityImportResult<E> importEntity(EntitiesImportCtx ctx, D exportData) throws ThingsboardException; |
||||
|
|
||||
|
EntityType getEntityType(); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,81 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.importing.impl; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.asset.Asset; |
||||
|
import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.AssetId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.dao.asset.AssetService; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
@RequiredArgsConstructor |
||||
|
public class AssetImportService extends BaseEntityImportService<AssetId, Asset, EntityExportData<Asset>> { |
||||
|
|
||||
|
private final AssetService assetService; |
||||
|
|
||||
|
@Override |
||||
|
protected void setOwner(TenantId tenantId, Asset asset, IdProvider idProvider) { |
||||
|
asset.setTenantId(tenantId); |
||||
|
asset.setCustomerId(idProvider.getInternalId(asset.getCustomerId())); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected Asset prepare(EntitiesImportCtx ctx, Asset asset, Asset old, EntityExportData<Asset> exportData, IdProvider idProvider) { |
||||
|
return asset; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected Asset saveOrUpdate(EntitiesImportCtx ctx, Asset asset, EntityExportData<Asset> exportData, IdProvider idProvider) { |
||||
|
return assetService.saveAsset(asset); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void onEntitySaved(SecurityUser user, Asset savedAsset, Asset oldAsset) throws ThingsboardException { |
||||
|
super.onEntitySaved(user, savedAsset, oldAsset); |
||||
|
if (oldAsset != null) { |
||||
|
entityActionService.sendEntityNotificationMsgToEdge(user.getTenantId(), savedAsset.getId(), EdgeEventActionType.UPDATED); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected Asset deepCopy(Asset asset) { |
||||
|
return new Asset(asset); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void cleanupForComparison(Asset e) { |
||||
|
super.cleanupForComparison(e); |
||||
|
if (e.getCustomerId() != null && e.getCustomerId().isNullUid()) { |
||||
|
e.setCustomerId(null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public EntityType getEntityType() { |
||||
|
return EntityType.ASSET; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,398 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.importing.impl; |
||||
|
|
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import com.google.api.client.util.Objects; |
||||
|
import com.google.common.util.concurrent.FutureCallback; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.checkerframework.checker.nullness.qual.Nullable; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.context.annotation.Lazy; |
||||
|
import org.springframework.transaction.annotation.Transactional; |
||||
|
import org.thingsboard.common.util.JacksonUtil; |
||||
|
import org.thingsboard.server.cluster.TbClusterService; |
||||
|
import org.thingsboard.server.common.data.Device; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.ExportableEntity; |
||||
|
import org.thingsboard.server.common.data.HasCustomerId; |
||||
|
import org.thingsboard.server.common.data.audit.ActionType; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.EntityIdFactory; |
||||
|
import org.thingsboard.server.common.data.id.HasId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
||||
|
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; |
||||
|
import org.thingsboard.server.common.data.kv.BooleanDataEntry; |
||||
|
import org.thingsboard.server.common.data.kv.DoubleDataEntry; |
||||
|
import org.thingsboard.server.common.data.kv.JsonDataEntry; |
||||
|
import org.thingsboard.server.common.data.kv.KvEntry; |
||||
|
import org.thingsboard.server.common.data.kv.LongDataEntry; |
||||
|
import org.thingsboard.server.common.data.kv.StringDataEntry; |
||||
|
import org.thingsboard.server.common.data.relation.EntityRelation; |
||||
|
import org.thingsboard.server.common.data.relation.RelationTypeGroup; |
||||
|
import org.thingsboard.server.common.data.sync.ie.AttributeExportData; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityImportResult; |
||||
|
import org.thingsboard.server.dao.relation.RelationService; |
||||
|
import org.thingsboard.server.service.action.EntityActionService; |
||||
|
import org.thingsboard.server.service.entitiy.TbNotificationEntityService; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
import org.thingsboard.server.service.sync.ie.exporting.ExportableEntitiesService; |
||||
|
import org.thingsboard.server.service.sync.ie.importing.EntityImportService; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; |
||||
|
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.LinkedHashMap; |
||||
|
import java.util.LinkedHashSet; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
import java.util.Optional; |
||||
|
import java.util.Set; |
||||
|
import java.util.UUID; |
||||
|
import java.util.function.Function; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
@Slf4j |
||||
|
public abstract class BaseEntityImportService<I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> implements EntityImportService<I, E, D> { |
||||
|
|
||||
|
@Autowired |
||||
|
@Lazy |
||||
|
private ExportableEntitiesService exportableEntitiesService; |
||||
|
@Autowired |
||||
|
private RelationService relationService; |
||||
|
@Autowired |
||||
|
private TelemetrySubscriptionService tsSubService; |
||||
|
@Autowired |
||||
|
protected EntityActionService entityActionService; |
||||
|
@Autowired |
||||
|
protected TbClusterService clusterService; |
||||
|
@Autowired |
||||
|
protected TbNotificationEntityService entityNotificationService; |
||||
|
|
||||
|
@Transactional(rollbackFor = Exception.class) |
||||
|
@Override |
||||
|
public EntityImportResult<E> importEntity(EntitiesImportCtx ctx, D exportData) throws ThingsboardException { |
||||
|
EntityImportResult<E> importResult = new EntityImportResult<>(); |
||||
|
ctx.setCurrentImportResult(importResult); |
||||
|
importResult.setEntityType(getEntityType()); |
||||
|
IdProvider idProvider = new IdProvider(ctx, importResult); |
||||
|
|
||||
|
E entity = exportData.getEntity(); |
||||
|
entity.setExternalId(entity.getId()); |
||||
|
|
||||
|
E existingEntity = findExistingEntity(ctx, entity, idProvider); |
||||
|
importResult.setOldEntity(existingEntity); |
||||
|
|
||||
|
setOwner(ctx.getTenantId(), entity, idProvider); |
||||
|
if (existingEntity == null) { |
||||
|
entity.setId(null); |
||||
|
} else { |
||||
|
entity.setId(existingEntity.getId()); |
||||
|
entity.setCreatedTime(existingEntity.getCreatedTime()); |
||||
|
} |
||||
|
|
||||
|
E prepared = prepare(ctx, entity, existingEntity, exportData, idProvider); |
||||
|
|
||||
|
boolean saveOrUpdate = existingEntity == null || compare(ctx, exportData, prepared, existingEntity); |
||||
|
|
||||
|
if (saveOrUpdate) { |
||||
|
E savedEntity = saveOrUpdate(ctx, prepared, exportData, idProvider); |
||||
|
boolean created = existingEntity == null; |
||||
|
importResult.setCreated(created); |
||||
|
importResult.setUpdated(!created); |
||||
|
importResult.setSavedEntity(savedEntity); |
||||
|
ctx.putInternalId(exportData.getExternalId(), savedEntity.getId()); |
||||
|
} else { |
||||
|
importResult.setSavedEntity(existingEntity); |
||||
|
ctx.putInternalId(exportData.getExternalId(), existingEntity.getId()); |
||||
|
importResult.setUpdatedRelatedEntities(updateRelatedEntitiesIfUnmodified(ctx, prepared, exportData, idProvider)); |
||||
|
} |
||||
|
|
||||
|
processAfterSaved(ctx, importResult, exportData, idProvider); |
||||
|
|
||||
|
return importResult; |
||||
|
} |
||||
|
|
||||
|
protected boolean updateRelatedEntitiesIfUnmodified(EntitiesImportCtx ctx, E prepared, D exportData, IdProvider idProvider) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public abstract EntityType getEntityType(); |
||||
|
|
||||
|
protected abstract void setOwner(TenantId tenantId, E entity, IdProvider idProvider); |
||||
|
|
||||
|
protected abstract E prepare(EntitiesImportCtx ctx, E entity, E oldEntity, D exportData, IdProvider idProvider); |
||||
|
|
||||
|
protected boolean compare(EntitiesImportCtx ctx, D exportData, E prepared, E existing) { |
||||
|
var newCopy = deepCopy(prepared); |
||||
|
var existingCopy = deepCopy(existing); |
||||
|
cleanupForComparison(newCopy); |
||||
|
cleanupForComparison(existingCopy); |
||||
|
var result = !newCopy.equals(existingCopy); |
||||
|
if (result) { |
||||
|
log.debug("[{}] Found update.", prepared.getId()); |
||||
|
log.debug("[{}] From: {}", prepared.getId(), newCopy); |
||||
|
log.debug("[{}] To: {}", prepared.getId(), existingCopy); |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
protected abstract E deepCopy(E e); |
||||
|
|
||||
|
protected void cleanupForComparison(E e) { |
||||
|
e.setTenantId(null); |
||||
|
e.setCreatedTime(0); |
||||
|
} |
||||
|
|
||||
|
protected abstract E saveOrUpdate(EntitiesImportCtx ctx, E entity, D exportData, IdProvider idProvider); |
||||
|
|
||||
|
|
||||
|
protected void processAfterSaved(EntitiesImportCtx ctx, EntityImportResult<E> importResult, D exportData, IdProvider idProvider) throws ThingsboardException { |
||||
|
E savedEntity = importResult.getSavedEntity(); |
||||
|
E oldEntity = importResult.getOldEntity(); |
||||
|
|
||||
|
if (importResult.isCreated() || importResult.isUpdated()) { |
||||
|
importResult.addSendEventsCallback(() -> onEntitySaved(ctx.getUser(), savedEntity, oldEntity)); |
||||
|
} |
||||
|
|
||||
|
if (ctx.isUpdateRelations() && exportData.getRelations() != null) { |
||||
|
importRelations(ctx, exportData.getRelations(), importResult, idProvider); |
||||
|
} |
||||
|
if (ctx.isSaveAttributes() && exportData.getAttributes() != null) { |
||||
|
if (exportData.getAttributes().values().stream().anyMatch(d -> !d.isEmpty())) { |
||||
|
importResult.setUpdatedRelatedEntities(true); |
||||
|
} |
||||
|
importAttributes(ctx.getUser(), exportData.getAttributes(), importResult); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void importRelations(EntitiesImportCtx ctx, List<EntityRelation> relations, EntityImportResult<E> importResult, IdProvider idProvider) { |
||||
|
var tenantId = ctx.getTenantId(); |
||||
|
E entity = importResult.getSavedEntity(); |
||||
|
importResult.addSaveReferencesCallback(() -> { |
||||
|
for (EntityRelation relation : relations) { |
||||
|
if (!relation.getTo().equals(entity.getId())) { |
||||
|
relation.setTo(idProvider.getInternalId(relation.getTo())); |
||||
|
} |
||||
|
if (!relation.getFrom().equals(entity.getId())) { |
||||
|
relation.setFrom(idProvider.getInternalId(relation.getFrom())); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Map<EntityRelation, EntityRelation> relationsMap = new LinkedHashMap<>(); |
||||
|
relations.forEach(r -> relationsMap.put(r, r)); |
||||
|
|
||||
|
if (importResult.getOldEntity() != null) { |
||||
|
List<EntityRelation> existingRelations = new ArrayList<>(); |
||||
|
existingRelations.addAll(relationService.findByTo(tenantId, entity.getId(), RelationTypeGroup.COMMON)); |
||||
|
existingRelations.addAll(relationService.findByFrom(tenantId, entity.getId(), RelationTypeGroup.COMMON)); |
||||
|
|
||||
|
for (EntityRelation existingRelation : existingRelations) { |
||||
|
EntityRelation relation = relationsMap.get(existingRelation); |
||||
|
if (relation == null) { |
||||
|
importResult.setUpdatedRelatedEntities(true); |
||||
|
relationService.deleteRelation(tenantId, existingRelation); |
||||
|
importResult.addSendEventsCallback(() -> { |
||||
|
entityActionService.logEntityAction(ctx.getUser(), existingRelation.getFrom(), null, null, |
||||
|
ActionType.RELATION_DELETED, null, existingRelation); |
||||
|
entityActionService.logEntityAction(ctx.getUser(), existingRelation.getTo(), null, null, |
||||
|
ActionType.RELATION_DELETED, null, existingRelation); |
||||
|
}); |
||||
|
} else if (Objects.equal(relation.getAdditionalInfo(), existingRelation.getAdditionalInfo())) { |
||||
|
relationsMap.remove(relation); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
if (!relationsMap.isEmpty()) { |
||||
|
importResult.setUpdatedRelatedEntities(true); |
||||
|
ctx.addRelations(relationsMap.values()); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void importAttributes(SecurityUser user, Map<String, List<AttributeExportData>> attributes, EntityImportResult<E> importResult) { |
||||
|
E entity = importResult.getSavedEntity(); |
||||
|
importResult.addSaveReferencesCallback(() -> { |
||||
|
attributes.forEach((scope, attributesExportData) -> { |
||||
|
List<AttributeKvEntry> attributeKvEntries = attributesExportData.stream() |
||||
|
.map(attributeExportData -> { |
||||
|
KvEntry kvEntry; |
||||
|
String key = attributeExportData.getKey(); |
||||
|
if (attributeExportData.getStrValue() != null) { |
||||
|
kvEntry = new StringDataEntry(key, attributeExportData.getStrValue()); |
||||
|
} else if (attributeExportData.getBooleanValue() != null) { |
||||
|
kvEntry = new BooleanDataEntry(key, attributeExportData.getBooleanValue()); |
||||
|
} else if (attributeExportData.getDoubleValue() != null) { |
||||
|
kvEntry = new DoubleDataEntry(key, attributeExportData.getDoubleValue()); |
||||
|
} else if (attributeExportData.getLongValue() != null) { |
||||
|
kvEntry = new LongDataEntry(key, attributeExportData.getLongValue()); |
||||
|
} else if (attributeExportData.getJsonValue() != null) { |
||||
|
kvEntry = new JsonDataEntry(key, attributeExportData.getJsonValue()); |
||||
|
} else { |
||||
|
throw new IllegalArgumentException("Invalid attribute export data"); |
||||
|
} |
||||
|
return new BaseAttributeKvEntry(kvEntry, attributeExportData.getLastUpdateTs()); |
||||
|
}) |
||||
|
.collect(Collectors.toList()); |
||||
|
// fixme: attributes are saved outside the transaction
|
||||
|
tsSubService.saveAndNotify(user.getTenantId(), entity.getId(), scope, attributeKvEntries, new FutureCallback<Void>() { |
||||
|
@Override |
||||
|
public void onSuccess(@Nullable Void unused) { |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onFailure(Throwable thr) { |
||||
|
log.error("Failed to import attributes for {} {}", entity.getId().getEntityType(), entity.getId(), thr); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected void onEntitySaved(SecurityUser user, E savedEntity, E oldEntity) throws ThingsboardException { |
||||
|
entityActionService.logEntityAction(user, savedEntity.getId(), savedEntity, |
||||
|
savedEntity instanceof HasCustomerId ? ((HasCustomerId) savedEntity).getCustomerId() : user.getCustomerId(), |
||||
|
oldEntity == null ? ActionType.ADDED : ActionType.UPDATED, null); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@SuppressWarnings("unchecked") |
||||
|
protected E findExistingEntity(EntitiesImportCtx ctx, E entity, IdProvider idProvider) { |
||||
|
return (E) Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndExternalId(ctx.getTenantId(), entity.getId())) |
||||
|
.or(() -> Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndId(ctx.getTenantId(), entity.getId()))) |
||||
|
.or(() -> { |
||||
|
if (ctx.isFindExistingByName()) { |
||||
|
return Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndName(ctx.getTenantId(), getEntityType(), entity.getName())); |
||||
|
} else { |
||||
|
return Optional.empty(); |
||||
|
} |
||||
|
}) |
||||
|
.orElse(null); |
||||
|
} |
||||
|
|
||||
|
@SuppressWarnings("unchecked") |
||||
|
private <ID extends EntityId> HasId<ID> findInternalEntity(TenantId tenantId, ID externalId) { |
||||
|
return (HasId<ID>) Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndExternalId(tenantId, externalId)) |
||||
|
.or(() -> Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndId(tenantId, externalId))) |
||||
|
.orElseThrow(() -> new MissingEntityException(externalId)); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@SuppressWarnings("unchecked") |
||||
|
@RequiredArgsConstructor |
||||
|
protected class IdProvider { |
||||
|
private final EntitiesImportCtx ctx; |
||||
|
private final EntityImportResult<E> importResult; |
||||
|
|
||||
|
public <ID extends EntityId> ID getInternalId(ID externalId) { |
||||
|
return getInternalId(externalId, true); |
||||
|
} |
||||
|
|
||||
|
public <ID extends EntityId> ID getInternalId(ID externalId, boolean throwExceptionIfNotFound) { |
||||
|
if (externalId == null || externalId.isNullUid()) return null; |
||||
|
|
||||
|
EntityId localId = ctx.getInternalId(externalId); |
||||
|
if (localId != null) { |
||||
|
return (ID) localId; |
||||
|
} |
||||
|
|
||||
|
HasId<ID> entity; |
||||
|
try { |
||||
|
entity = findInternalEntity(ctx.getTenantId(), externalId); |
||||
|
} catch (Exception e) { |
||||
|
if (throwExceptionIfNotFound) { |
||||
|
throw e; |
||||
|
} else { |
||||
|
importResult.setUpdatedAllExternalIds(false); |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
ctx.putInternalId(externalId, entity.getId()); |
||||
|
return entity.getId(); |
||||
|
} |
||||
|
|
||||
|
public Optional<EntityId> getInternalIdByUuid(UUID externalUuid, boolean fetchAllUUIDs, Set<EntityType> hints) { |
||||
|
if (externalUuid.equals(EntityId.NULL_UUID)) return Optional.empty(); |
||||
|
|
||||
|
for (EntityType entityType : EntityType.values()) { |
||||
|
Optional<EntityId> externalIdOpt = buildEntityId(entityType, externalUuid); |
||||
|
if (!externalIdOpt.isPresent()) { |
||||
|
continue; |
||||
|
} |
||||
|
EntityId internalId = ctx.getInternalId(externalIdOpt.get()); |
||||
|
if (internalId != null) { |
||||
|
return Optional.of(internalId); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (fetchAllUUIDs) { |
||||
|
for (EntityType entityType : hints) { |
||||
|
Optional<EntityId> internalId = lookupInDb(externalUuid, entityType); |
||||
|
if (internalId.isPresent()) return internalId; |
||||
|
} |
||||
|
for (EntityType entityType : EntityType.values()) { |
||||
|
if (hints.contains(entityType)) { |
||||
|
continue; |
||||
|
} |
||||
|
Optional<EntityId> internalId = lookupInDb(externalUuid, entityType); |
||||
|
if (internalId.isPresent()) return internalId; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
importResult.setUpdatedAllExternalIds(false); |
||||
|
return Optional.empty(); |
||||
|
} |
||||
|
|
||||
|
private Optional<EntityId> lookupInDb(UUID externalUuid, EntityType entityType) { |
||||
|
Optional<EntityId> externalIdOpt = buildEntityId(entityType, externalUuid); |
||||
|
if (externalIdOpt.isEmpty() || ctx.isNotFound(externalIdOpt.get())) { |
||||
|
return Optional.empty(); |
||||
|
} |
||||
|
EntityId internalId = getInternalId(externalIdOpt.get(), false); |
||||
|
if (internalId != null) { |
||||
|
return Optional.of(internalId); |
||||
|
} else { |
||||
|
ctx.registerNotFound(externalIdOpt.get()); |
||||
|
} |
||||
|
return Optional.empty(); |
||||
|
} |
||||
|
|
||||
|
private Optional<EntityId> buildEntityId(EntityType entityType, UUID externalUuid) { |
||||
|
try { |
||||
|
return Optional.of(EntityIdFactory.getByTypeAndUuid(entityType, externalUuid)); |
||||
|
} catch (Exception e) { |
||||
|
return Optional.empty(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
protected <T extends EntityId, O> T getOldEntityField(O oldEntity, Function<O, T> getter) { |
||||
|
return oldEntity == null ? null : getter.apply(oldEntity); |
||||
|
} |
||||
|
|
||||
|
protected void replaceIdsRecursively(EntitiesImportCtx ctx, IdProvider idProvider, JsonNode entityAlias, Set<String> skipFieldsSet, LinkedHashSet<EntityType> hints) { |
||||
|
JacksonUtil.replaceUuidsRecursively(entityAlias, skipFieldsSet, |
||||
|
uuid -> idProvider.getInternalIdByUuid(uuid, ctx.isFinalImportAttempt(), hints).map(EntityId::getId).orElse(uuid)); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,84 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.importing.impl; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.Customer; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.CustomerId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.dao.customer.CustomerDao; |
||||
|
import org.thingsboard.server.dao.customer.CustomerService; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
@RequiredArgsConstructor |
||||
|
public class CustomerImportService extends BaseEntityImportService<CustomerId, Customer, EntityExportData<Customer>> { |
||||
|
|
||||
|
private final CustomerService customerService; |
||||
|
private final CustomerDao customerDao; |
||||
|
|
||||
|
@Override |
||||
|
protected void setOwner(TenantId tenantId, Customer customer, IdProvider idProvider) { |
||||
|
customer.setTenantId(tenantId); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected Customer prepare(EntitiesImportCtx ctx, Customer customer, Customer old, EntityExportData<Customer> exportData, IdProvider idProvider) { |
||||
|
if (customer.isPublic()) { |
||||
|
Customer publicCustomer = customerService.findOrCreatePublicCustomer(ctx.getTenantId()); |
||||
|
publicCustomer.setExternalId(customer.getExternalId()); |
||||
|
return publicCustomer; |
||||
|
} else { |
||||
|
return customer; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected Customer saveOrUpdate(EntitiesImportCtx ctx, Customer customer, EntityExportData<Customer> exportData, IdProvider idProvider) { |
||||
|
if (!customer.isPublic()) { |
||||
|
return customerService.saveCustomer(customer); |
||||
|
} else { |
||||
|
return customerDao.save(ctx.getTenantId(), customer); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected Customer deepCopy(Customer customer) { |
||||
|
return new Customer(customer); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void onEntitySaved(SecurityUser user, Customer savedCustomer, Customer oldCustomer) throws ThingsboardException { |
||||
|
super.onEntitySaved(user, savedCustomer, oldCustomer); |
||||
|
if (oldCustomer != null) { |
||||
|
entityActionService.sendEntityNotificationMsgToEdge(user.getTenantId(), savedCustomer.getId(), EdgeEventActionType.UPDATED); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public EntityType getEntityType() { |
||||
|
return EntityType.CUSTOMER; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,133 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.importing.impl; |
||||
|
|
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.common.util.JacksonUtil; |
||||
|
import org.thingsboard.server.common.data.Dashboard; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.ShortCustomerInfo; |
||||
|
import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.CustomerId; |
||||
|
import org.thingsboard.server.common.data.id.DashboardId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.dao.dashboard.DashboardService; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; |
||||
|
|
||||
|
import java.util.Arrays; |
||||
|
import java.util.Collections; |
||||
|
import java.util.HashSet; |
||||
|
import java.util.LinkedHashSet; |
||||
|
import java.util.Optional; |
||||
|
import java.util.Set; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
@RequiredArgsConstructor |
||||
|
public class DashboardImportService extends BaseEntityImportService<DashboardId, Dashboard, EntityExportData<Dashboard>> { |
||||
|
|
||||
|
private static final LinkedHashSet<EntityType> HINTS = new LinkedHashSet<>(Arrays.asList(EntityType.DASHBOARD, EntityType.DEVICE, EntityType.ASSET)); |
||||
|
|
||||
|
private final DashboardService dashboardService; |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
protected void setOwner(TenantId tenantId, Dashboard dashboard, IdProvider idProvider) { |
||||
|
dashboard.setTenantId(tenantId); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected Dashboard findExistingEntity(EntitiesImportCtx ctx, Dashboard dashboard, IdProvider idProvider) { |
||||
|
Dashboard existingDashboard = super.findExistingEntity(ctx, dashboard, idProvider); |
||||
|
if (existingDashboard == null && ctx.isFindExistingByName()) { |
||||
|
existingDashboard = dashboardService.findTenantDashboardsByTitle(ctx.getTenantId(), dashboard.getName()).stream().findFirst().orElse(null); |
||||
|
} |
||||
|
return existingDashboard; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected Dashboard prepare(EntitiesImportCtx ctx, Dashboard dashboard, Dashboard old, EntityExportData<Dashboard> exportData, IdProvider idProvider) { |
||||
|
for (JsonNode entityAlias : dashboard.getEntityAliasesConfig()) { |
||||
|
replaceIdsRecursively(ctx, idProvider, entityAlias, Collections.emptySet(), HINTS); |
||||
|
} |
||||
|
for (JsonNode widgetConfig : dashboard.getWidgetsConfig()) { |
||||
|
replaceIdsRecursively(ctx, idProvider, JacksonUtil.getSafely(widgetConfig, "config", "actions"), Collections.singleton("id"), HINTS); |
||||
|
} |
||||
|
return dashboard; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected Dashboard saveOrUpdate(EntitiesImportCtx ctx, Dashboard dashboard, EntityExportData<Dashboard> exportData, IdProvider idProvider) { |
||||
|
var tenantId = ctx.getTenantId(); |
||||
|
|
||||
|
Set<ShortCustomerInfo> assignedCustomers = Optional.ofNullable(dashboard.getAssignedCustomers()).orElse(Collections.emptySet()).stream() |
||||
|
.peek(customerInfo -> customerInfo.setCustomerId(idProvider.getInternalId(customerInfo.getCustomerId()))) |
||||
|
.collect(Collectors.toSet()); |
||||
|
|
||||
|
if (dashboard.getId() == null) { |
||||
|
dashboard.setAssignedCustomers(assignedCustomers); |
||||
|
dashboard = dashboardService.saveDashboard(dashboard); |
||||
|
for (ShortCustomerInfo customerInfo : assignedCustomers) { |
||||
|
dashboard = dashboardService.assignDashboardToCustomer(tenantId, dashboard.getId(), customerInfo.getCustomerId()); |
||||
|
} |
||||
|
} else { |
||||
|
Set<CustomerId> existingAssignedCustomers = Optional.ofNullable(dashboardService.findDashboardById(tenantId, dashboard.getId()).getAssignedCustomers()) |
||||
|
.orElse(Collections.emptySet()).stream().map(ShortCustomerInfo::getCustomerId).collect(Collectors.toSet()); |
||||
|
Set<CustomerId> newAssignedCustomers = assignedCustomers.stream().map(ShortCustomerInfo::getCustomerId).collect(Collectors.toSet()); |
||||
|
|
||||
|
Set<CustomerId> toUnassign = new HashSet<>(existingAssignedCustomers); |
||||
|
toUnassign.removeAll(newAssignedCustomers); |
||||
|
for (CustomerId customerId : toUnassign) { |
||||
|
assignedCustomers = dashboardService.unassignDashboardFromCustomer(tenantId, dashboard.getId(), customerId).getAssignedCustomers(); |
||||
|
} |
||||
|
|
||||
|
Set<CustomerId> toAssign = new HashSet<>(newAssignedCustomers); |
||||
|
toAssign.removeAll(existingAssignedCustomers); |
||||
|
for (CustomerId customerId : toAssign) { |
||||
|
assignedCustomers = dashboardService.assignDashboardToCustomer(tenantId, dashboard.getId(), customerId).getAssignedCustomers(); |
||||
|
} |
||||
|
dashboard.setAssignedCustomers(assignedCustomers); |
||||
|
dashboard = dashboardService.saveDashboard(dashboard); |
||||
|
} |
||||
|
return dashboard; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected Dashboard deepCopy(Dashboard dashboard) { |
||||
|
return new Dashboard(dashboard); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void onEntitySaved(SecurityUser user, Dashboard savedDashboard, Dashboard oldDashboard) throws ThingsboardException { |
||||
|
super.onEntitySaved(user, savedDashboard, oldDashboard); |
||||
|
if (oldDashboard != null) { |
||||
|
entityActionService.sendEntityNotificationMsgToEdge(user.getTenantId(), savedDashboard.getId(), EdgeEventActionType.UPDATED); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public EntityType getEntityType() { |
||||
|
return EntityType.DASHBOARD; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,105 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.importing.impl; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.Device; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.sync.ie.DeviceExportData; |
||||
|
import org.thingsboard.server.dao.device.DeviceCredentialsService; |
||||
|
import org.thingsboard.server.dao.device.DeviceService; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
@RequiredArgsConstructor |
||||
|
public class DeviceImportService extends BaseEntityImportService<DeviceId, Device, DeviceExportData> { |
||||
|
|
||||
|
private final DeviceService deviceService; |
||||
|
private final DeviceCredentialsService credentialsService; |
||||
|
|
||||
|
@Override |
||||
|
protected void setOwner(TenantId tenantId, Device device, IdProvider idProvider) { |
||||
|
device.setTenantId(tenantId); |
||||
|
device.setCustomerId(idProvider.getInternalId(device.getCustomerId())); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected Device prepare(EntitiesImportCtx ctx, Device device, Device old, DeviceExportData exportData, IdProvider idProvider) { |
||||
|
device.setDeviceProfileId(idProvider.getInternalId(device.getDeviceProfileId())); |
||||
|
device.setFirmwareId(getOldEntityField(old, Device::getFirmwareId)); |
||||
|
device.setSoftwareId(getOldEntityField(old, Device::getSoftwareId)); |
||||
|
return device; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected Device deepCopy(Device d) { |
||||
|
return new Device(d); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void cleanupForComparison(Device e) { |
||||
|
super.cleanupForComparison(e); |
||||
|
if (e.getCustomerId() != null && e.getCustomerId().isNullUid()) { |
||||
|
e.setCustomerId(null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected Device saveOrUpdate(EntitiesImportCtx ctx, Device device, DeviceExportData exportData, IdProvider idProvider) { |
||||
|
if (exportData.getCredentials() != null && ctx.isSaveCredentials()) { |
||||
|
exportData.getCredentials().setId(null); |
||||
|
exportData.getCredentials().setDeviceId(null); |
||||
|
return deviceService.saveDeviceWithCredentials(device, exportData.getCredentials()); |
||||
|
} else { |
||||
|
return deviceService.saveDevice(device); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected boolean updateRelatedEntitiesIfUnmodified(EntitiesImportCtx ctx, Device prepared, DeviceExportData exportData, IdProvider idProvider) { |
||||
|
boolean updated = super.updateRelatedEntitiesIfUnmodified(ctx, prepared, exportData, idProvider); |
||||
|
var credentials = exportData.getCredentials(); |
||||
|
if (credentials != null && ctx.isSaveCredentials()) { |
||||
|
var existing = credentialsService.findDeviceCredentialsByDeviceId(ctx.getTenantId(), prepared.getId()); |
||||
|
credentials.setId(existing.getId()); |
||||
|
credentials.setDeviceId(prepared.getId()); |
||||
|
if (!existing.equals(credentials)) { |
||||
|
credentialsService.updateDeviceCredentials(ctx.getTenantId(), credentials); |
||||
|
updated = true; |
||||
|
} |
||||
|
} |
||||
|
return updated; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void onEntitySaved(SecurityUser user, Device savedDevice, Device oldDevice) throws ThingsboardException { |
||||
|
super.onEntitySaved(user, savedDevice, oldDevice); |
||||
|
clusterService.onDeviceUpdated(savedDevice, oldDevice); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public EntityType getEntityType() { |
||||
|
return EntityType.DEVICE; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,93 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.importing.impl; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.DeviceProfile; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.DeviceProfileId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.dao.device.DeviceProfileService; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.ota.OtaPackageStateService; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; |
||||
|
|
||||
|
import java.util.Objects; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
@RequiredArgsConstructor |
||||
|
public class DeviceProfileImportService extends BaseEntityImportService<DeviceProfileId, DeviceProfile, EntityExportData<DeviceProfile>> { |
||||
|
|
||||
|
private final DeviceProfileService deviceProfileService; |
||||
|
private final OtaPackageStateService otaPackageStateService; |
||||
|
|
||||
|
@Override |
||||
|
protected void setOwner(TenantId tenantId, DeviceProfile deviceProfile, IdProvider idProvider) { |
||||
|
deviceProfile.setTenantId(tenantId); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected DeviceProfile prepare(EntitiesImportCtx ctx, DeviceProfile deviceProfile, DeviceProfile old, EntityExportData<DeviceProfile> exportData, IdProvider idProvider) { |
||||
|
deviceProfile.setDefaultRuleChainId(idProvider.getInternalId(deviceProfile.getDefaultRuleChainId())); |
||||
|
deviceProfile.setDefaultDashboardId(idProvider.getInternalId(deviceProfile.getDefaultDashboardId())); |
||||
|
deviceProfile.setFirmwareId(getOldEntityField(old, DeviceProfile::getFirmwareId)); |
||||
|
deviceProfile.setSoftwareId(getOldEntityField(old, DeviceProfile::getSoftwareId)); |
||||
|
return deviceProfile; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected DeviceProfile saveOrUpdate(EntitiesImportCtx ctx, DeviceProfile deviceProfile, EntityExportData<DeviceProfile> exportData, IdProvider idProvider) { |
||||
|
return deviceProfileService.saveDeviceProfile(deviceProfile); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void onEntitySaved(SecurityUser user, DeviceProfile savedDeviceProfile, DeviceProfile oldDeviceProfile) throws ThingsboardException { |
||||
|
super.onEntitySaved(user, savedDeviceProfile, oldDeviceProfile); |
||||
|
clusterService.onDeviceProfileChange(savedDeviceProfile, null); |
||||
|
clusterService.broadcastEntityStateChangeEvent(user.getTenantId(), savedDeviceProfile.getId(), |
||||
|
oldDeviceProfile == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); |
||||
|
entityActionService.sendEntityNotificationMsgToEdge(user.getTenantId(), savedDeviceProfile.getId(), |
||||
|
oldDeviceProfile == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED); |
||||
|
otaPackageStateService.update(savedDeviceProfile, |
||||
|
oldDeviceProfile != null && !Objects.equals(oldDeviceProfile.getFirmwareId(), savedDeviceProfile.getFirmwareId()), |
||||
|
oldDeviceProfile != null && !Objects.equals(oldDeviceProfile.getSoftwareId(), savedDeviceProfile.getSoftwareId())); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected DeviceProfile deepCopy(DeviceProfile deviceProfile) { |
||||
|
return new DeviceProfile(deviceProfile); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void cleanupForComparison(DeviceProfile deviceProfile) { |
||||
|
super.cleanupForComparison(deviceProfile); |
||||
|
deviceProfile.setFirmwareId(null); |
||||
|
deviceProfile.setSoftwareId(null); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public EntityType getEntityType() { |
||||
|
return EntityType.DEVICE_PROFILE; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,90 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.importing.impl; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.context.annotation.Lazy; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.EntityView; |
||||
|
import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.EntityViewId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.dao.entityview.EntityViewService; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.entitiy.entityView.TbEntityViewService; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
@RequiredArgsConstructor |
||||
|
public class EntityViewImportService extends BaseEntityImportService<EntityViewId, EntityView, EntityExportData<EntityView>> { |
||||
|
|
||||
|
private final EntityViewService entityViewService; |
||||
|
|
||||
|
@Lazy |
||||
|
@Autowired |
||||
|
private TbEntityViewService tbEntityViewService; |
||||
|
|
||||
|
@Override |
||||
|
protected void setOwner(TenantId tenantId, EntityView entityView, IdProvider idProvider) { |
||||
|
entityView.setTenantId(tenantId); |
||||
|
entityView.setCustomerId(idProvider.getInternalId(entityView.getCustomerId())); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected EntityView prepare(EntitiesImportCtx ctx, EntityView entityView, EntityView old, EntityExportData<EntityView> exportData, IdProvider idProvider) { |
||||
|
entityView.setEntityId(idProvider.getInternalId(entityView.getEntityId())); |
||||
|
return entityView; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected EntityView saveOrUpdate(EntitiesImportCtx ctx, EntityView entityView, EntityExportData<EntityView> exportData, IdProvider idProvider) { |
||||
|
return entityViewService.saveEntityView(entityView); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void onEntitySaved(SecurityUser user, EntityView savedEntityView, EntityView oldEntityView) throws ThingsboardException { |
||||
|
tbEntityViewService.updateEntityViewAttributes(user, savedEntityView, oldEntityView); |
||||
|
super.onEntitySaved(user, savedEntityView, oldEntityView); |
||||
|
if (oldEntityView != null) { |
||||
|
entityActionService.sendEntityNotificationMsgToEdge(user.getTenantId(), savedEntityView.getId(), EdgeEventActionType.UPDATED); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected EntityView deepCopy(EntityView entityView) { |
||||
|
return new EntityView(entityView); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void cleanupForComparison(EntityView e) { |
||||
|
super.cleanupForComparison(e); |
||||
|
if (e.getCustomerId() != null && e.getCustomerId().isNullUid()) { |
||||
|
e.setCustomerId(null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public EntityType getEntityType() { |
||||
|
return EntityType.ENTITY_VIEW; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.importing.impl; |
||||
|
|
||||
|
public class ImportServiceException extends RuntimeException{ |
||||
|
private static final long serialVersionUID = -4932715239522125041L; |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.importing.impl; |
||||
|
|
||||
|
import lombok.Getter; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
|
||||
|
public class MissingEntityException extends ImportServiceException { |
||||
|
|
||||
|
private static final long serialVersionUID = 3669135386955906022L; |
||||
|
@Getter |
||||
|
private final EntityId entityId; |
||||
|
|
||||
|
public MissingEntityException(EntityId entityId) { |
||||
|
this.entityId = entityId; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,148 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.importing.impl; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.RuleChainId; |
||||
|
import org.thingsboard.server.common.data.id.RuleNodeId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
||||
|
import org.thingsboard.server.common.data.rule.RuleChain; |
||||
|
import org.thingsboard.server.common.data.rule.RuleChainMetaData; |
||||
|
import org.thingsboard.server.common.data.rule.RuleChainType; |
||||
|
import org.thingsboard.server.common.data.rule.RuleNode; |
||||
|
import org.thingsboard.server.common.data.sync.ie.RuleChainExportData; |
||||
|
import org.thingsboard.server.dao.rule.RuleChainService; |
||||
|
import org.thingsboard.server.dao.rule.RuleNodeDao; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; |
||||
|
|
||||
|
import java.util.Arrays; |
||||
|
import java.util.Collections; |
||||
|
import java.util.LinkedHashSet; |
||||
|
import java.util.List; |
||||
|
import java.util.Optional; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
@RequiredArgsConstructor |
||||
|
public class RuleChainImportService extends BaseEntityImportService<RuleChainId, RuleChain, RuleChainExportData> { |
||||
|
|
||||
|
private static final LinkedHashSet<EntityType> HINTS = new LinkedHashSet<>(Arrays.asList(EntityType.RULE_CHAIN, EntityType.DEVICE, EntityType.ASSET)); |
||||
|
|
||||
|
private final RuleChainService ruleChainService; |
||||
|
private final RuleNodeDao ruleNodeDao; |
||||
|
|
||||
|
@Override |
||||
|
protected void setOwner(TenantId tenantId, RuleChain ruleChain, IdProvider idProvider) { |
||||
|
ruleChain.setTenantId(tenantId); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected RuleChain findExistingEntity(EntitiesImportCtx ctx, RuleChain ruleChain, IdProvider idProvider) { |
||||
|
RuleChain existingRuleChain = super.findExistingEntity(ctx, ruleChain, idProvider); |
||||
|
if (existingRuleChain == null && ctx.isFindExistingByName()) { |
||||
|
existingRuleChain = ruleChainService.findTenantRuleChainsByTypeAndName(ctx.getTenantId(), ruleChain.getType(), ruleChain.getName()).stream().findFirst().orElse(null); |
||||
|
} |
||||
|
return existingRuleChain; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected RuleChain prepare(EntitiesImportCtx ctx, RuleChain ruleChain, RuleChain old, RuleChainExportData exportData, IdProvider idProvider) { |
||||
|
RuleChainMetaData metaData = exportData.getMetaData(); |
||||
|
List<RuleNode> ruleNodes = Optional.ofNullable(metaData.getNodes()).orElse(Collections.emptyList()); |
||||
|
if (old != null) { |
||||
|
List<RuleNodeId> nodeIds = ruleNodes.stream().map(RuleNode::getId).collect(Collectors.toList()); |
||||
|
List<RuleNode> existing = ruleNodeDao.findByExternalIds(old.getId(), nodeIds); |
||||
|
existing.forEach(node -> ctx.putInternalId(node.getExternalId(), node.getId())); |
||||
|
ruleNodes.forEach(node -> { |
||||
|
node.setRuleChainId(old.getId()); |
||||
|
node.setExternalId(node.getId()); |
||||
|
node.setId((RuleNodeId) ctx.getInternalId(node.getId())); |
||||
|
}); |
||||
|
} else { |
||||
|
ruleNodes.forEach(node -> { |
||||
|
node.setRuleChainId(null); |
||||
|
node.setExternalId(node.getId()); |
||||
|
node.setId(null); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
ruleNodes.forEach(ruleNode -> replaceIdsRecursively(ctx, idProvider, ruleNode.getConfiguration(), Collections.emptySet(), HINTS)); |
||||
|
Optional.ofNullable(metaData.getRuleChainConnections()).orElse(Collections.emptyList()) |
||||
|
.forEach(ruleChainConnectionInfo -> { |
||||
|
ruleChainConnectionInfo.setTargetRuleChainId(idProvider.getInternalId(ruleChainConnectionInfo.getTargetRuleChainId(), false)); |
||||
|
}); |
||||
|
if (ruleChain.getFirstRuleNodeId() != null) { |
||||
|
ruleChain.setFirstRuleNodeId((RuleNodeId) ctx.getInternalId(ruleChain.getFirstRuleNodeId())); |
||||
|
} |
||||
|
return ruleChain; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected RuleChain saveOrUpdate(EntitiesImportCtx ctx, RuleChain ruleChain, RuleChainExportData exportData, IdProvider idProvider) { |
||||
|
ruleChain = ruleChainService.saveRuleChain(ruleChain); |
||||
|
if (ctx.isFinalImportAttempt() || ctx.getCurrentImportResult().isUpdatedAllExternalIds()) { |
||||
|
exportData.getMetaData().setRuleChainId(ruleChain.getId()); |
||||
|
ruleChainService.saveRuleChainMetaData(ctx.getTenantId(), exportData.getMetaData()); |
||||
|
return ruleChainService.findRuleChainById(ctx.getTenantId(), ruleChain.getId()); |
||||
|
} else { |
||||
|
return ruleChain; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected boolean compare(EntitiesImportCtx ctx, RuleChainExportData exportData, RuleChain prepared, RuleChain existing) { |
||||
|
boolean different = super.compare(ctx, exportData, prepared, existing); |
||||
|
if (!different) { |
||||
|
RuleChainMetaData newMD = exportData.getMetaData(); |
||||
|
RuleChainMetaData existingMD = ruleChainService.loadRuleChainMetaData(ctx.getTenantId(), prepared.getId()); |
||||
|
existingMD.setRuleChainId(null); |
||||
|
different = newMD.equals(existingMD); |
||||
|
} |
||||
|
return different; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void onEntitySaved(SecurityUser user, RuleChain savedRuleChain, RuleChain oldRuleChain) throws ThingsboardException { |
||||
|
super.onEntitySaved(user, savedRuleChain, oldRuleChain); |
||||
|
if (savedRuleChain.getType() == RuleChainType.CORE) { |
||||
|
clusterService.broadcastEntityStateChangeEvent(user.getTenantId(), savedRuleChain.getId(), |
||||
|
oldRuleChain == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); |
||||
|
} else if (savedRuleChain.getType() == RuleChainType.EDGE && oldRuleChain != null) { |
||||
|
entityActionService.sendEntityNotificationMsgToEdge(user.getTenantId(), savedRuleChain.getId(), EdgeEventActionType.UPDATED); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected RuleChain deepCopy(RuleChain ruleChain) { |
||||
|
return new RuleChain(ruleChain); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public EntityType getEntityType() { |
||||
|
return EntityType.RULE_CHAIN; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,106 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sync.ie.importing.impl; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.id.WidgetsBundleId; |
||||
|
import org.thingsboard.server.common.data.sync.ie.WidgetsBundleExportData; |
||||
|
import org.thingsboard.server.common.data.widget.BaseWidgetType; |
||||
|
import org.thingsboard.server.common.data.widget.WidgetTypeDetails; |
||||
|
import org.thingsboard.server.common.data.widget.WidgetTypeInfo; |
||||
|
import org.thingsboard.server.common.data.widget.WidgetsBundle; |
||||
|
import org.thingsboard.server.dao.widget.WidgetTypeService; |
||||
|
import org.thingsboard.server.dao.widget.WidgetsBundleService; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; |
||||
|
|
||||
|
import java.util.Map; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
@RequiredArgsConstructor |
||||
|
public class WidgetsBundleImportService extends BaseEntityImportService<WidgetsBundleId, WidgetsBundle, WidgetsBundleExportData> { |
||||
|
|
||||
|
private final WidgetsBundleService widgetsBundleService; |
||||
|
private final WidgetTypeService widgetTypeService; |
||||
|
|
||||
|
@Override |
||||
|
protected void setOwner(TenantId tenantId, WidgetsBundle widgetsBundle, IdProvider idProvider) { |
||||
|
widgetsBundle.setTenantId(tenantId); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected WidgetsBundle prepare(EntitiesImportCtx ctx, WidgetsBundle widgetsBundle, WidgetsBundle old, WidgetsBundleExportData exportData, IdProvider idProvider) { |
||||
|
return widgetsBundle; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected WidgetsBundle saveOrUpdate(EntitiesImportCtx ctx, WidgetsBundle widgetsBundle, WidgetsBundleExportData exportData, IdProvider idProvider) { |
||||
|
WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle); |
||||
|
if (widgetsBundle.getId() == null) { |
||||
|
for (WidgetTypeDetails widget : exportData.getWidgets()) { |
||||
|
widget.setId(null); |
||||
|
widget.setTenantId(ctx.getTenantId()); |
||||
|
widget.setBundleAlias(savedWidgetsBundle.getAlias()); |
||||
|
widgetTypeService.saveWidgetType(widget); |
||||
|
} |
||||
|
} else { |
||||
|
Map<String, WidgetTypeInfo> existingWidgets = widgetTypeService.findWidgetTypesInfosByTenantIdAndBundleAlias(ctx.getTenantId(), savedWidgetsBundle.getAlias()).stream() |
||||
|
.collect(Collectors.toMap(BaseWidgetType::getAlias, w -> w)); |
||||
|
for (WidgetTypeDetails widget : exportData.getWidgets()) { |
||||
|
WidgetTypeInfo existingWidget; |
||||
|
if ((existingWidget = existingWidgets.remove(widget.getAlias())) != null) { |
||||
|
widget.setId(existingWidget.getId()); |
||||
|
widget.setCreatedTime(existingWidget.getCreatedTime()); |
||||
|
} else { |
||||
|
widget.setId(null); |
||||
|
} |
||||
|
widget.setTenantId(ctx.getTenantId()); |
||||
|
widget.setBundleAlias(savedWidgetsBundle.getAlias()); |
||||
|
widgetTypeService.saveWidgetType(widget); |
||||
|
} |
||||
|
existingWidgets.values().stream() |
||||
|
.map(BaseWidgetType::getId) |
||||
|
.forEach(widgetTypeId -> widgetTypeService.deleteWidgetType(ctx.getTenantId(), widgetTypeId)); |
||||
|
} |
||||
|
return savedWidgetsBundle; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void onEntitySaved(SecurityUser user, WidgetsBundle savedWidgetsBundle, WidgetsBundle oldWidgetsBundle) throws ThingsboardException { |
||||
|
super.onEntitySaved(user, savedWidgetsBundle, oldWidgetsBundle); |
||||
|
entityNotificationService.notifySendMsgToEdgeService(user.getTenantId(), savedWidgetsBundle.getId(), |
||||
|
oldWidgetsBundle == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected WidgetsBundle deepCopy(WidgetsBundle widgetsBundle) { |
||||
|
return new WidgetsBundle(widgetsBundle); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public EntityType getEntityType() { |
||||
|
return EntityType.WIDGETS_BUNDLE; |
||||
|
} |
||||
|
|
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue