Browse Source

Export/import used resources along with dashboard and widget type

pull/11873/head
ViacheslavKlimov 2 years ago
parent
commit
52a6499d1c
  1. 1
      application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
  2. 15
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  3. 44
      application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java
  4. 41
      application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java
  5. 82
      application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/DefaultWidgetTypeService.java
  6. 8
      application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/TbWidgetTypeService.java
  7. 55
      application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java
  8. 8
      application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java
  9. 15
      common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java
  10. 4
      common/data/src/main/java/org/thingsboard/server/common/data/ResourceExportData.java
  11. 16
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsExportData.java
  12. 118
      common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java
  13. 114
      dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java
  14. 136
      dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java
  15. 2
      dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java
  16. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java
  17. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java
  18. 69
      dao/src/main/java/org/thingsboard/server/dao/util/JsonPathProcessingTask.java

1
application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java

@ -34,7 +34,6 @@ public class ControllerConstants {
"See the 'Model' tab of the Response Class for more details. ";
protected static final String INLINE_IMAGES = "inlineImages";
protected static final String INLINE_IMAGES_DESCRIPTION = "Inline images as a data URL (Base64)";
protected static final String DASHBOARD_ID_PARAM_DESCRIPTION = "A string value representing the dashboard id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String RPC_ID_PARAM_DESCRIPTION = "A string value representing the rpc id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String DEVICE_ID_PARAM_DESCRIPTION = "A string value representing the device id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";

15
application/src/main/java/org/thingsboard/server/controller/DashboardController.java

@ -78,8 +78,6 @@ import static org.thingsboard.server.controller.ControllerConstants.EDGE_ID;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.INLINE_IMAGES;
import static org.thingsboard.server.controller.ControllerConstants.INLINE_IMAGES_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
@ -150,18 +148,11 @@ public class DashboardController extends BaseController {
)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@GetMapping(value = "/dashboard/{dashboardId}")
public Dashboard getDashboardById(
@Parameter(description = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DASHBOARD_ID) String strDashboardId,
@Parameter(description = INLINE_IMAGES_DESCRIPTION)
@RequestParam(value = INLINE_IMAGES, required = false) boolean inlineImages) throws ThingsboardException {
public Dashboard getDashboardById(@Parameter(description = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId);
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
var result = checkDashboardId(dashboardId, Operation.READ);
if (inlineImages) {
imageService.inlineImages(result);
}
return result;
return checkDashboardId(dashboardId, Operation.READ);
}
@GetMapping(value = "/dashboard/{dashboardId}/export")

44
application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java

@ -47,9 +47,9 @@ import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetTypeFilter;
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.common.data.widget.WidgetsExportData;
import org.thingsboard.server.config.annotations.ApiOperation;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.widgets.type.TbWidgetTypeService;
import org.thingsboard.server.service.security.permission.Operation;
@ -61,8 +61,6 @@ import java.util.List;
import java.util.UUID;
import static org.thingsboard.server.controller.ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER;
import static org.thingsboard.server.controller.ControllerConstants.INLINE_IMAGES;
import static org.thingsboard.server.controller.ControllerConstants.INLINE_IMAGES_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
@ -80,7 +78,6 @@ import static org.thingsboard.server.controller.ControllerConstants.WIDGET_TYPE_
public class WidgetTypeController extends AutoCommitController {
private final TbWidgetTypeService tbWidgetTypeService;
private final ImageService imageService;
private static final String WIDGET_TYPE_DESCRIPTION = "Widget Type represents the template for widget creation. Widget Type and Widget are similar to class and object in OOP theory.";
private static final String WIDGET_TYPE_DETAILS_DESCRIPTION = "Widget Type Details extend Widget Type and add image and description properties. " +
@ -97,18 +94,11 @@ public class WidgetTypeController extends AutoCommitController {
notes = "Get the Widget Type Details based on the provided Widget Type Id. " + WIDGET_TYPE_DETAILS_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@GetMapping(value = "/widgetType/{widgetTypeId}")
public WidgetTypeDetails getWidgetTypeById(
@Parameter(description = WIDGET_TYPE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable("widgetTypeId") String strWidgetTypeId,
@Parameter(description = INLINE_IMAGES_DESCRIPTION)
@RequestParam(value = INLINE_IMAGES, required = false) boolean inlineImages) throws ThingsboardException {
public WidgetTypeDetails getWidgetTypeById(@Parameter(description = WIDGET_TYPE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable("widgetTypeId") String strWidgetTypeId) throws ThingsboardException {
checkParameter("widgetTypeId", strWidgetTypeId);
WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId));
var result = checkWidgetTypeId(widgetTypeId, Operation.READ);
if (inlineImages) {
imageService.inlineImages(result);
}
return result;
return checkWidgetTypeId(widgetTypeId, Operation.READ);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@ -293,16 +283,26 @@ public class WidgetTypeController extends AutoCommitController {
@ResponseBody
public List<WidgetTypeDetails> getBundleWidgetTypesDetails(
@Parameter(description = "Widget Bundle Id", required = true)
@RequestParam("widgetsBundleId") String strWidgetsBundleId,
@Parameter(description = INLINE_IMAGES_DESCRIPTION)
@RequestParam(value = INLINE_IMAGES, required = false) boolean inlineImages
@RequestParam("widgetsBundleId") String strWidgetsBundleId
) throws ThingsboardException {
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId));
var result = checkNotNull(widgetTypeService.findWidgetTypesDetailsByWidgetsBundleId(getTenantId(), widgetsBundleId));
if (inlineImages) {
result.forEach(imageService::inlineImages);
}
return result;
return checkNotNull(widgetTypeService.findWidgetTypesDetailsByWidgetsBundleId(getTenantId(), widgetsBundleId));
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@GetMapping(value = "/widgetTypes/export", params = {"widgetsBundleId"})
public WidgetsExportData exportBundleWidgetTypes(@Parameter(description = "Widget Bundle Id", required = true)
@RequestParam("widgetsBundleId") String strWidgetsBundleId) throws ThingsboardException {
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId));
return tbWidgetTypeService.exportBundleWidgetTypes(getTenantId(), widgetsBundleId, getCurrentUser());
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@PostMapping(value = "/widgetTypes/import", params = {"widgetsBundleId"})
public List<WidgetTypeDetails> importBundleWidgetTypes(@Parameter(description = "Widget Bundle Id", required = true)
@RequestParam("widgetsBundleId") String strWidgetsBundleId) throws ThingsboardException {
// FIXME: determine how bundle widget types were imported before
throw new UnsupportedOperationException();
}
@ApiOperation(value = "Get all Widget type fqns for specified Bundle (getBundleWidgetTypeFqns)",

41
application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java

@ -32,18 +32,22 @@ 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.EdgeId;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import org.thingsboard.server.service.resource.TbImageService;
import org.thingsboard.server.service.resource.TbResourceService;
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 java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Service
@ -54,6 +58,8 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement
private final DashboardService dashboardService;
private final ImageService imageService;
private final TbImageService tbImageService;
private final ResourceService resourceService;
private final TbResourceService tbResourceService;
@Override
public Dashboard save(Dashboard dashboard, User user) throws Exception {
@ -297,27 +303,48 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement
@Override
public DashboardExportData exportDashboard(TenantId tenantId, Dashboard dashboard, SecurityUser user) throws ThingsboardException {
List<TbResourceInfo> images = imageService.inlineImages(dashboard);
for (TbResourceInfo imageInfo : images) {
accessControlService.checkPermission(user, Resource.TB_RESOURCE, Operation.READ, imageInfo.getId(), imageInfo);
Map<TbResourceId, TbResourceInfo> resources = new HashMap<>();
for (TbResourceInfo imageInfo : imageService.inlineImages(dashboard)) {
resources.putIfAbsent(imageInfo.getId(), imageInfo);
}
for (TbResourceInfo resourceInfo : resourceService.processResourcesForExport(dashboard)) {
resources.putIfAbsent(resourceInfo.getId(), resourceInfo);
}
for (TbResourceInfo resourceInfo : resources.values()) {
accessControlService.checkPermission(user, Resource.TB_RESOURCE, Operation.READ, resourceInfo.getId(), resourceInfo);
}
DashboardExportData exportData = new DashboardExportData();
exportData.setDashboard(dashboard);
exportData.setResources(images.stream()
.map(tbImageService::exportImage)
exportData.setResources(resources.values().stream()
.map(resourceInfo -> {
if (resourceInfo.getResourceType() == ResourceType.IMAGE) {
ResourceExportData imageExportData = tbImageService.exportImage(resourceInfo);
imageExportData.setResourceKey(null); // so that the image is not updated by resource key on import
return imageExportData;
} else {
return tbResourceService.exportResource(resourceInfo);
}
})
.toList());
return exportData;
}
@Override
public Dashboard importDashboard(DashboardExportData exportData, SecurityUser user) throws Exception {
Map<TbResourceId, TbResourceId> importedResources = new HashMap<>();
for (ResourceExportData resourceExportData : exportData.getResources()) {
if (resourceExportData.getType() == ResourceType.IMAGE) {
tbImageService.importImage(resourceExportData, true, user);
} else {
TbResourceInfo importedResource = tbResourceService.importResource(resourceExportData, true, user);
importedResources.put(resourceExportData.getId(), importedResource.getId());
}
}
return save(exportData.getDashboard(), user);
Dashboard dashboard = exportData.getDashboard();
resourceService.processResourcesForImport(dashboard, importedResources);
return save(dashboard, user);
}
}

82
application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/DefaultWidgetTypeService.java

@ -25,20 +25,28 @@ import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetsBundleId;
import org.thingsboard.server.common.data.widget.WidgetExportData;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetsExportData;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import org.thingsboard.server.service.resource.TbImageService;
import org.thingsboard.server.service.resource.TbResourceService;
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 java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@TbCoreComponent
@ -49,6 +57,8 @@ public class DefaultWidgetTypeService extends AbstractTbEntityService implements
private final WidgetTypeService widgetTypeService;
private final ImageService imageService;
private final TbImageService tbImageService;
private final ResourceService resourceService;
private final TbResourceService tbResourceService;
@Override
public WidgetTypeDetails save(WidgetTypeDetails entity, User user) throws Exception {
@ -92,27 +102,81 @@ public class DefaultWidgetTypeService extends AbstractTbEntityService implements
@Override
public WidgetExportData exportWidgetType(TenantId tenantId, WidgetTypeDetails widgetTypeDetails, SecurityUser user) throws ThingsboardException {
List<TbResourceInfo> images = imageService.inlineImages(widgetTypeDetails);
for (TbResourceInfo imageInfo : images) {
accessControlService.checkPermission(user, Resource.TB_RESOURCE, Operation.READ, imageInfo.getId(), imageInfo);
}
List<ResourceExportData> resources = processWidgetTypesForExport(List.of(widgetTypeDetails), user);
WidgetExportData exportData = new WidgetExportData();
exportData.setWidgetTypeDetails(widgetTypeDetails);
exportData.setResources(images.stream()
.map(tbImageService::exportImage)
.toList());
exportData.setResources(resources);
return exportData;
}
@Override
public WidgetsExportData exportBundleWidgetTypes(TenantId tenantId, WidgetsBundleId bundleId, SecurityUser user) throws ThingsboardException {
List<WidgetTypeDetails> widgetTypes = widgetTypeService.findWidgetTypesDetailsByWidgetsBundleId(tenantId, bundleId);
List<ResourceExportData> resources = processWidgetTypesForExport(widgetTypes, user);
WidgetsExportData exportData = new WidgetsExportData();
exportData.setWidgetTypesDetails(widgetTypes);
exportData.setResources(resources);
return exportData;
}
private List<ResourceExportData> processWidgetTypesForExport(List<WidgetTypeDetails> widgetTypes, SecurityUser user) throws ThingsboardException {
Map<TbResourceId, TbResourceInfo> resources = new HashMap<>();
for (WidgetTypeDetails widgetTypeDetails : widgetTypes) {
for (TbResourceInfo imageInfo : imageService.inlineImages(widgetTypeDetails)) {
resources.putIfAbsent(imageInfo.getId(), imageInfo);
}
for (TbResourceInfo resourceInfo : resourceService.processResourcesForExport(widgetTypeDetails)) {
resources.putIfAbsent(resourceInfo.getId(), resourceInfo);
}
}
for (TbResourceInfo resourceInfo : resources.values()) {
accessControlService.checkPermission(user, Resource.TB_RESOURCE, Operation.READ, resourceInfo.getId(), resourceInfo);
}
return resources.values().stream()
.map(resourceInfo -> {
if (resourceInfo.getResourceType() == ResourceType.IMAGE) {
ResourceExportData imageExportData = tbImageService.exportImage(resourceInfo);
imageExportData.setResourceKey(null); // so that the image is not updated by resource key on import
return imageExportData;
} else {
return tbResourceService.exportResource(resourceInfo);
}
})
.toList();
}
@Override
public WidgetTypeDetails importWidgetType(WidgetExportData exportData, SecurityUser user) throws Exception {
for (ResourceExportData resourceExportData : exportData.getResources()) {
return importWidgetTypes(List.of(exportData.getWidgetTypeDetails()), exportData.getResources(), user).get(0);
}
@Override
public List<WidgetTypeDetails> importWidgetTypes(WidgetsExportData exportData, SecurityUser user) throws Exception {
return importWidgetTypes(exportData.getWidgetTypesDetails(), exportData.getResources(), user);
}
private List<WidgetTypeDetails> importWidgetTypes(List<WidgetTypeDetails> widgetTypesDetails, List<ResourceExportData> resources, SecurityUser user) throws Exception {
Map<TbResourceId, TbResourceId> importedResources = new HashMap<>();
for (ResourceExportData resourceExportData : resources) {
if (resourceExportData.getType() == ResourceType.IMAGE) {
tbImageService.importImage(resourceExportData, true, user);
} else {
TbResourceInfo importedResource = tbResourceService.importResource(resourceExportData, true, user);
importedResources.put(resourceExportData.getId(), importedResource.getId());
}
}
return save(exportData.getWidgetTypeDetails(), user);
List<WidgetTypeDetails> result = new ArrayList<>();
for (WidgetTypeDetails widgetTypeDetails : widgetTypesDetails) {
resourceService.processResourcesForImport(widgetTypeDetails, importedResources);
// FIXME: shouldn't just save it, have to be processed somehow
widgetTypeDetails = save(widgetTypeDetails, user);
result.add(widgetTypeDetails);
}
return result;
}
}

8
application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/TbWidgetTypeService.java

@ -18,17 +18,25 @@ package org.thingsboard.server.service.entitiy.widgets.type;
import org.thingsboard.server.common.data.User;
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.widget.WidgetExportData;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetsExportData;
import org.thingsboard.server.service.entitiy.SimpleTbEntityService;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.List;
public interface TbWidgetTypeService extends SimpleTbEntityService<WidgetTypeDetails> {
WidgetTypeDetails save(WidgetTypeDetails widgetTypeDetails, boolean updateExistingByFqn, User user) throws Exception;
WidgetExportData exportWidgetType(TenantId tenantId, WidgetTypeDetails widgetTypeDetails, SecurityUser user) throws ThingsboardException;
WidgetsExportData exportBundleWidgetTypes(TenantId tenantId, WidgetsBundleId bundleId, SecurityUser user) throws ThingsboardException;
WidgetTypeDetails importWidgetType(WidgetExportData exportData, SecurityUser user) throws Exception;
List<WidgetTypeDetails> importWidgetTypes(WidgetsExportData exportData, SecurityUser user) throws Exception;
}

55
application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java

@ -19,8 +19,11 @@ 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.ResourceExportData;
import org.thingsboard.server.common.data.ResourceSubType;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
@ -32,12 +35,17 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
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 java.util.Base64;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.thingsboard.server.common.data.StringUtils.isNotEmpty;
import static org.thingsboard.server.dao.device.DeviceServiceImpl.INCORRECT_TENANT_ID;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.utils.LwM2mObjectModelUtils.toLwM2mObject;
@ -115,6 +123,53 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements
.collect(Collectors.toList());
}
@Override
public ResourceExportData exportResource(TbResourceInfo resourceInfo) {
byte[] data = resourceService.getResourceData(resourceInfo.getTenantId(), resourceInfo.getId());
return ResourceExportData.builder()
.id(resourceInfo.getId())
.mediaType(resourceInfo.getResourceType().getMediaType())
.fileName(resourceInfo.getFileName())
.title(resourceInfo.getTitle())
.type(resourceInfo.getResourceType())
.subType(resourceInfo.getResourceSubType())
.resourceKey(resourceInfo.getResourceKey())
.data(Base64.getEncoder().encodeToString(data))
.build();
}
@Override
public TbResourceInfo importResource(ResourceExportData exportData, boolean checkExisting, SecurityUser user) throws ThingsboardException {
if (exportData.getType() == ResourceType.IMAGE || exportData.getSubType() == ResourceSubType.IMAGE
|| exportData.getSubType() == ResourceSubType.SCADA_SYMBOL) {
throw new IllegalArgumentException("Image import not supported");
}
TbResource resource = new TbResource();
resource.setTenantId(user.getTenantId());
accessControlService.checkPermission(user, Resource.TB_RESOURCE, Operation.CREATE, null, resource);
byte[] data = Base64.getDecoder().decode(exportData.getData());
if (checkExisting) {
String etag = resourceService.calculateEtag(data);
TbResourceInfo existingResource = resourceService.findSystemOrTenantResourceByEtag(user.getTenantId(), exportData.getType(), etag);
if (existingResource != null) {
return existingResource;
}
}
resource.setFileName(exportData.getFileName());
if (isNotEmpty(exportData.getTitle())) {
resource.setTitle(exportData.getTitle());
} else {
resource.setTitle(exportData.getFileName());
}
resource.setResourceSubType(exportData.getSubType());
resource.setResourceType(exportData.getType());
resource.setResourceKey(exportData.getResourceKey());
resource.setData(data);
return save(resource, user);
}
private Comparator<? super LwM2mObject> getComparator(String sortProperty, String sortOrder) {
Comparator<LwM2mObject> comparator;
if ("name".equals(sortProperty)) {

8
application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java

@ -15,11 +15,15 @@
*/
package org.thingsboard.server.service.resource;
import org.thingsboard.server.common.data.ResourceExportData;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.service.entitiy.SimpleTbEntityService;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.List;
@ -35,4 +39,8 @@ public interface TbResourceService extends SimpleTbEntityService<TbResource> {
String sortOrder,
PageLink pageLink);
ResourceExportData exportResource(TbResourceInfo resourceInfo);
TbResourceInfo importResource(ResourceExportData exportData, boolean checkExisting, SecurityUser user) throws ThingsboardException;
}

15
common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.resource;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
@ -24,9 +25,11 @@ import org.thingsboard.server.common.data.id.TbResourceId;
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.widget.WidgetTypeDetails;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
import java.util.Map;
public interface ResourceService extends EntityDaoService {
@ -64,6 +67,18 @@ public interface ResourceService extends EntityDaoService {
long sumDataSizeByTenantId(TenantId tenantId);
String calculateEtag(byte[] data);
TbResourceInfo findSystemOrTenantResourceByEtag(TenantId tenantId, ResourceType resourceType, String etag);
List<TbResourceInfo> processResourcesForExport(Dashboard dashboard);
List<TbResourceInfo> processResourcesForExport(WidgetTypeDetails widgetTypeDetails);
void processResourcesForImport(Dashboard dashboard, Map<TbResourceId, TbResourceId> importedResources);
void processResourcesForImport(WidgetTypeDetails widgetTypeDetails, Map<TbResourceId, TbResourceId> importedResources);
TbResource updateSystemResource(ResourceType resourceType, String resourceKey, String data);
String checkSystemResourcesUsage(String content, ResourceType... usedResourceTypes);

4
common/data/src/main/java/org/thingsboard/server/common/data/ResourceExportData.java

@ -15,12 +15,14 @@
*/
package org.thingsboard.server.common.data;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.TbResourceId;
@Schema
@Slf4j
@ -28,8 +30,10 @@ import lombok.extern.slf4j.Slf4j;
@NoArgsConstructor
@AllArgsConstructor
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResourceExportData {
private TbResourceId id;
private String title;
private ResourceType type;
private ResourceSubType subType;

16
dao/src/main/java/org/thingsboard/server/dao/util/JsonNodeProcessingTask.java → common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsExportData.java

@ -13,18 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.util;
package org.thingsboard.server.common.data.widget;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import org.thingsboard.server.common.data.ResourceExportData;
import java.util.List;
@Data
public class JsonNodeProcessingTask {
private final String path;
private final JsonNode node;
public class WidgetsExportData {
private List<WidgetTypeDetails> widgetTypesDetails;
private List<ResourceExportData> resources;
public JsonNodeProcessingTask(String path, JsonNode node) {
this.path = path;
this.node = node;
}
}

118
common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java

@ -31,6 +31,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@ -467,6 +468,75 @@ public class JacksonUtil {
}
}
public static void replaceAllByMapping(JsonNode jsonNode, Map<String, String> mapping, Map<String, String> templateParams, BiFunction<String, String, String> processor) {
for (var entry : mapping.entrySet()) {
String expression = entry.getValue();
Queue<JsonPathProcessingTask> tasks = new LinkedList<>();
tasks.add(new JsonPathProcessingTask(entry.getKey().split("\\."), templateParams, jsonNode));
while (!tasks.isEmpty()) {
JsonPathProcessingTask task = tasks.poll();
String token = task.currentToken();
JsonNode node = task.getNode();
if (node == null) {
continue;
}
if (token.equals("*") || token.startsWith("$")) {
String variableName = token.startsWith("$") ? token.substring(1) : null;
if (node.isArray()) {
ArrayNode childArray = (ArrayNode) node;
for (JsonNode element : childArray) {
tasks.add(task.next(element));
}
} else if (node.isObject()) {
ObjectNode on = (ObjectNode) node;
for (Iterator<Map.Entry<String, JsonNode>> it = on.fields(); it.hasNext(); ) {
var kv = it.next();
if (variableName != null) {
tasks.add(task.next(kv.getValue(), variableName, kv.getKey()));
} else {
tasks.add(task.next(kv.getValue()));
}
}
}
} else {
String variableName = null;
String variableValue = null;
if (token.contains("[$")) {
variableName = StringUtils.substringBetween(token, "[$", "]");
token = StringUtils.substringBefore(token, "[$");
}
if (node.has(token)) {
JsonNode value = node.get(token);
if (variableName != null && value.has(variableName) && value.get(variableName).isTextual()) {
variableValue = value.get(variableName).asText();
}
if (task.isLast()) {
String name = expression;
for (var replacement : task.getVariables().entrySet()) {
name = name.replace("$" + replacement.getKey(), Strings.nullToEmpty(replacement.getValue()));
}
if (node.isObject() && value.isTextual()) {
((ObjectNode) node).put(token, processor.apply(name, value.asText()));
} else if (value.isArray()) {
ArrayNode array = (ArrayNode) value;
for (int i = 0; i < array.size(); i++) {
String arrayElementName = name.replace("$index", Integer.toString(i));
array.set(i, processor.apply(arrayElementName, array.get(i).asText()));
}
}
} else {
if (StringUtils.isNotEmpty(variableName)) {
tasks.add(task.next(value, variableName, variableValue));
} else {
tasks.add(task.next(value));
}
}
}
}
}
}
}
@Data
public static class JsonNodeProcessingTask {
private final String path;
@ -476,6 +546,54 @@ public class JacksonUtil {
this.path = path;
this.node = node;
}
}
@Data
public static class JsonPathProcessingTask {
private final String[] tokens;
private final Map<String, String> variables;
private final JsonNode node;
public JsonPathProcessingTask(String[] tokens, Map<String, String> variables, JsonNode node) {
this.tokens = tokens;
this.variables = variables;
this.node = node;
}
public boolean isLast() {
return tokens.length == 1;
}
public String currentToken() {
return tokens[0];
}
public JsonPathProcessingTask next(JsonNode next) {
return new JsonPathProcessingTask(
Arrays.copyOfRange(tokens, 1, tokens.length),
variables,
next);
}
public JsonPathProcessingTask next(JsonNode next, String key, String value) {
Map<String, String> variables = new HashMap<>(this.variables);
variables.put(key, value);
return new JsonPathProcessingTask(
Arrays.copyOfRange(tokens, 1, tokens.length),
variables,
next);
}
@Override
public String toString() {
return "JsonPathProcessingTask{" +
"tokens=" + Arrays.toString(tokens) +
", variables=" + variables +
", node=" + node.toString().substring(0, 20) +
'}';
}
}
}

114
dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java

@ -16,9 +16,7 @@
package org.thingsboard.server.dao.resource;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Strings;
import jakarta.annotation.PostConstruct;
import lombok.Data;
import lombok.SneakyThrows;
@ -55,7 +53,6 @@ import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.service.validator.ResourceDataValidator;
import org.thingsboard.server.dao.util.ImageUtils;
import org.thingsboard.server.dao.util.ImageUtils.ProcessedImage;
import org.thingsboard.server.dao.util.JsonPathProcessingTask;
import org.thingsboard.server.dao.widget.WidgetTypeDao;
import org.thingsboard.server.dao.widget.WidgetsBundleDao;
@ -64,12 +61,9 @@ import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction;
@ -143,7 +137,7 @@ public class BaseImageService extends BaseResourceService implements ImageServic
@SneakyThrows
public TbResourceInfo saveImage(TbResource image) {
if (image.getId() == null) {
image.setResourceKey(getUniqueKey(image.getTenantId(), StringUtils.defaultIfEmpty(image.getResourceKey(), image.getFileName())));
image.setResourceKey(getUniqueKey(image.getTenantId(), ResourceType.IMAGE, StringUtils.defaultIfEmpty(image.getResourceKey(), image.getFileName())));
}
if (image.getResourceSubType() == null) {
image.setResourceSubType(ResourceSubType.IMAGE);
@ -185,27 +179,6 @@ public class BaseImageService extends BaseResourceService implements ImageServic
return Pair.of(descriptor, preview.getData());
}
private String getUniqueKey(TenantId tenantId, String filename) {
if (!resourceInfoDao.existsByTenantIdAndResourceTypeAndResourceKey(tenantId, ResourceType.IMAGE, filename)) {
return filename;
}
String basename = StringUtils.substringBeforeLast(filename, ".");
String extension = StringUtils.substringAfterLast(filename, ".");
Set<String> existing = resourceInfoDao.findKeysByTenantIdAndResourceTypeAndResourceKeyPrefix(
tenantId, ResourceType.IMAGE, basename
);
String resourceKey = filename;
int idx = 1;
while (existing.contains(resourceKey)) {
resourceKey = basename + "_(" + idx + ")." + extension;
idx++;
}
log.debug("[{}] Generated unique key {} for image {}", tenantId, resourceKey, filename);
return resourceKey;
}
private String generatePublicResourceKey() {
return RandomStringUtils.randomAlphanumeric(32);
}
@ -296,11 +269,7 @@ public class BaseImageService extends BaseResourceService implements ImageServic
@Override
public TbResourceInfo findSystemOrTenantImageByEtag(TenantId tenantId, String etag) {
if (StringUtils.isEmpty(etag)) {
return null;
}
log.trace("Executing findSystemOrTenantImageByEtag [{}] [{}]", tenantId, etag);
return resourceInfoDao.findSystemOrTenantImageByEtag(tenantId, ResourceType.IMAGE, etag);
return findSystemOrTenantResourceByEtag(tenantId, ResourceType.IMAGE, etag);
}
@Transactional(noRollbackFor = Exception.class) // we don't want transaction to rollback in case of an image processing failure
@ -358,78 +327,15 @@ public class BaseImageService extends BaseResourceService implements ImageServic
}
private boolean base64ToImageUrlUsingMapping(TenantId tenantId, Map<String, String> mapping, Map<String, String> templateParams, JsonNode configuration) {
boolean updated = false;
for (var entry : mapping.entrySet()) {
String expression = entry.getValue();
Queue<JsonPathProcessingTask> tasks = new LinkedList<>();
tasks.add(new JsonPathProcessingTask(entry.getKey().split("\\."), templateParams, configuration));
while (!tasks.isEmpty()) {
JsonPathProcessingTask task = tasks.poll();
String token = task.currentToken();
JsonNode node = task.getNode();
if (node == null) {
continue;
}
if (token.equals("*") || token.startsWith("$")) {
String variableName = token.startsWith("$") ? token.substring(1) : null;
if (node.isArray()) {
ArrayNode childArray = (ArrayNode) node;
for (JsonNode element : childArray) {
tasks.add(task.next(element));
}
} else if (node.isObject()) {
ObjectNode on = (ObjectNode) node;
for (Iterator<Map.Entry<String, JsonNode>> it = on.fields(); it.hasNext(); ) {
var kv = it.next();
if (variableName != null) {
tasks.add(task.next(kv.getValue(), variableName, kv.getKey()));
} else {
tasks.add(task.next(kv.getValue()));
}
}
}
} else {
String variableName = null;
String variableValue = null;
if (token.contains("[$")) {
variableName = StringUtils.substringBetween(token, "[$", "]");
token = StringUtils.substringBefore(token, "[$");
}
if (node.has(token)) {
JsonNode value = node.get(token);
if (variableName != null && value.has(variableName) && value.get(variableName).isTextual()) {
variableValue = value.get(variableName).asText();
}
if (task.isLast()) {
String name = expression;
for (var replacement : task.getVariables().entrySet()) {
name = name.replace("$" + replacement.getKey(), Strings.nullToEmpty(replacement.getValue()));
}
if (node.isObject() && value.isTextual()) {
var result = base64ToImageUrl(tenantId, name, value.asText());
((ObjectNode) node).put(token, result.getValue());
updated |= result.isUpdated();
} else if (value.isArray()) {
ArrayNode array = (ArrayNode) value;
for (int i = 0; i < array.size(); i++) {
String arrayElementName = name.replace("$index", Integer.toString(i));
UpdateResult result = base64ToImageUrl(tenantId, arrayElementName, array.get(i).asText());
array.set(i, result.getValue());
updated |= result.isUpdated();
}
}
} else {
if (StringUtils.isNotEmpty(variableName)) {
tasks.add(task.next(value, variableName, variableValue));
} else {
tasks.add(task.next(value));
}
}
}
}
AtomicBoolean updated = new AtomicBoolean(false);
JacksonUtil.replaceAllByMapping(configuration, mapping, templateParams, (name, value) -> {
UpdateResult result = base64ToImageUrl(tenantId, name, value);
if (result.isUpdated()) {
updated.set(true);
}
}
return updated;
return result.getValue();
});
return updated.get();
}
private UpdateResult base64ToImageUrl(TenantId tenantId, String name, String data) {

136
dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java

@ -15,17 +15,21 @@
*/
package org.thingsboard.server.dao.resource;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.hash.Hashing;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.RegexUtils;
import org.thingsboard.server.cache.resourceInfo.ResourceInfoCacheKey;
import org.thingsboard.server.cache.resourceInfo.ResourceInfoEvictEvent;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
@ -37,6 +41,7 @@ import org.thingsboard.server.common.data.id.TbResourceId;
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.widget.WidgetTypeDetails;
import org.thingsboard.server.dao.entity.AbstractCachedEntityService;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
@ -46,8 +51,13 @@ import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.service.validator.ResourceDataValidator;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import static org.thingsboard.server.dao.device.DeviceServiceImpl.INCORRECT_TENANT_ID;
import static org.thingsboard.server.dao.service.Validator.validateId;
@ -63,9 +73,20 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
protected final TbResourceInfoDao resourceInfoDao;
protected final ResourceDataValidator resourceValidator;
private static final Map<String, String> DASHBOARD_RESOURCES_MAPPING = Map.of(
"widgets.*.config.actions.*.*.customResources.*.url.id", ""
);
private static final Map<String, String> WIDGET_RESOURCES_MAPPING = Map.of(
"descriptor.resources.*.url.id", ""
);
@Override
public TbResource saveResource(TbResource resource, boolean doValidate) {
log.trace("Executing saveResource [{}]", resource);
if (resource.getId() == null) {
resource.setResourceKey(getUniqueKey(resource.getTenantId(), resource.getResourceType(), StringUtils.defaultIfEmpty(resource.getResourceKey(), resource.getFileName())));
}
if (doValidate) {
resourceValidator.validate(resource, TbResourceInfo::getTenantId);
}
@ -109,6 +130,27 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
return resourceInfoDao.save(resource.getTenantId(), new TbResourceInfo(resource));
}
protected String getUniqueKey(TenantId tenantId, ResourceType resourceType, String filename) {
if (!resourceInfoDao.existsByTenantIdAndResourceTypeAndResourceKey(tenantId, resourceType, filename)) {
return filename;
}
String basename = StringUtils.substringBeforeLast(filename, ".");
String extension = StringUtils.substringAfterLast(filename, ".");
Set<String> existing = resourceInfoDao.findKeysByTenantIdAndResourceTypeAndResourceKeyPrefix(
tenantId, resourceType, basename
);
String resourceKey = filename;
int idx = 1;
while (existing.contains(resourceKey)) {
resourceKey = basename + "_(" + idx + ")." + extension;
idx++;
}
log.debug("[{}] Generated unique key {} for {} {}", tenantId, resourceKey, resourceType, filename);
return resourceKey;
}
@Override
public TbResource findResourceByTenantIdAndKey(TenantId tenantId, ResourceType resourceType, String resourceKey) {
log.trace("Executing findResourceByTenantIdAndKey [{}] [{}] [{}]", tenantId, resourceType, resourceKey);
@ -239,6 +281,67 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
return resourceDao.sumDataSizeByTenantId(tenantId);
}
@Override
public List<TbResourceInfo> processResourcesForExport(Dashboard dashboard) {
return processResourcesForExport(dashboard.getTenantId(), dashboard.getConfiguration(), DASHBOARD_RESOURCES_MAPPING);
}
@Override
public List<TbResourceInfo> processResourcesForExport(WidgetTypeDetails widgetTypeDetails) {
return processResourcesForExport(widgetTypeDetails.getTenantId(), widgetTypeDetails.getDescriptor(), WIDGET_RESOURCES_MAPPING);
}
private List<TbResourceInfo> processResourcesForExport(TenantId tenantId, JsonNode jsonNode, Map<String, String> mapping) {
List<TbResourceInfo> resources = new ArrayList<>();
JacksonUtil.replaceAllByMapping(jsonNode, mapping, Collections.emptyMap(), (name, value) -> {
if (StringUtils.isBlank(value)) {
return value;
}
TbResourceId resourceId;
try {
resourceId = new TbResourceId(UUID.fromString(value));
} catch (IllegalArgumentException e) {
return value;
}
TbResourceInfo resourceInfo = findResourceInfoById(tenantId, resourceId);
resources.add(resourceInfo);
return value;
});
return resources;
}
@Override
public void processResourcesForImport(Dashboard dashboard, Map<TbResourceId, TbResourceId> importedResources) {
processResourcesForImport(dashboard.getConfiguration(), DASHBOARD_RESOURCES_MAPPING, importedResources);
}
@Override
public void processResourcesForImport(WidgetTypeDetails widgetTypeDetails, Map<TbResourceId, TbResourceId> importedResources) {
processResourcesForImport(widgetTypeDetails.getDescriptor(), WIDGET_RESOURCES_MAPPING, importedResources);
}
private void processResourcesForImport(JsonNode jsonNode, Map<String, String> mapping, Map<TbResourceId, TbResourceId> importedResources) {
JacksonUtil.replaceAllByMapping(jsonNode, mapping, Collections.emptyMap(), (name, value) -> {
if (StringUtils.isBlank(value)) {
return value;
}
TbResourceId oldResourceId;
try {
oldResourceId = new TbResourceId(UUID.fromString(value));
} catch (IllegalArgumentException e) {
return value;
}
TbResourceId importedResourceId = importedResources.get(oldResourceId);
if (importedResourceId != null) {
return importedResourceId.toString();
} else {
return value;
}
});
}
@Override
public TbResource updateSystemResource(ResourceType resourceType, String resourceKey, String data) {
if (resourceType == ResourceType.DASHBOARD) {
@ -274,23 +377,32 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
});
}
protected String calculateEtag(byte[] data) {
@Override
public String calculateEtag(byte[] data) {
return Hashing.sha256().hashBytes(data).toString();
}
private final PaginatedRemover<TenantId, TbResource> tenantResourcesRemover =
new PaginatedRemover<>() {
@Override
public TbResourceInfo findSystemOrTenantResourceByEtag(TenantId tenantId, ResourceType resourceType, String etag) {
if (StringUtils.isEmpty(etag)) {
return null;
}
log.trace("Executing findSystemOrTenantResourceByEtag [{}] [{}] [{}]", tenantId, resourceType, etag);
return resourceInfoDao.findSystemOrTenantResourceByEtag(tenantId, resourceType, etag);
}
private final PaginatedRemover<TenantId, TbResourceId> tenantResourcesRemover = new PaginatedRemover<>() {
@Override
protected PageData<TbResource> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return resourceDao.findAllByTenantId(id, pageLink);
}
@Override
protected PageData<TbResourceId> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return resourceDao.findIdsByTenantId(id.getId(), pageLink);
}
@Override
protected void removeEntity(TenantId tenantId, TbResource entity) {
deleteResource(tenantId, entity.getId());
}
};
@Override
protected void removeEntity(TenantId tenantId, TbResourceId resourceId) {
deleteResource(tenantId, resourceId);
}
};
@TransactionalEventListener(classes = ResourceInfoEvictEvent.class)
@Override

2
dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java

@ -40,7 +40,7 @@ public interface TbResourceInfoDao extends Dao<TbResourceInfo> {
List<TbResourceInfo> findByTenantIdAndEtagAndKeyStartingWith(TenantId tenantId, String etag, String query);
TbResourceInfo findSystemOrTenantImageByEtag(TenantId tenantId, ResourceType resourceType, String etag);
TbResourceInfo findSystemOrTenantResourceByEtag(TenantId tenantId, ResourceType resourceType, String etag);
boolean existsByPublicResourceKey(ResourceType resourceType, String publicResourceKey);

4
dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java

@ -113,8 +113,8 @@ public class JpaTbResourceInfoDao extends JpaAbstractDao<TbResourceInfoEntity, T
}
@Override
public TbResourceInfo findSystemOrTenantImageByEtag(TenantId tenantId, ResourceType resourceType, String etag) {
return DaoUtil.getData(resourceInfoRepository.findSystemOrTenantImageByEtag(tenantId.getId(), resourceType.name(), etag));
public TbResourceInfo findSystemOrTenantResourceByEtag(TenantId tenantId, ResourceType resourceType, String etag) {
return DaoUtil.getData(resourceInfoRepository.findSystemOrTenantResourceByEtag(tenantId.getId(), resourceType.name(), etag));
}
@Override

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

@ -71,9 +71,9 @@ public interface TbResourceInfoRepository extends JpaRepository<TbResourceInfoEn
@Query(value = "SELECT * FROM resource r WHERE (r.tenant_id = '13814000-1dd2-11b2-8080-808080808080' OR r.tenant_id = :tenantId) " +
"AND r.resource_type = :resourceType AND r.etag = :etag ORDER BY created_time, id LIMIT 1", nativeQuery = true)
TbResourceInfoEntity findSystemOrTenantImageByEtag(@Param("tenantId") UUID tenantId,
@Param("resourceType") String resourceType,
@Param("etag") String etag);
TbResourceInfoEntity findSystemOrTenantResourceByEtag(@Param("tenantId") UUID tenantId,
@Param("resourceType") String resourceType,
@Param("etag") String etag);
boolean existsByResourceTypeAndPublicResourceKey(String resourceType, String publicResourceKey);

69
dao/src/main/java/org/thingsboard/server/dao/util/JsonPathProcessingTask.java

@ -1,69 +0,0 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.util;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@Data
public class JsonPathProcessingTask {
private final String[] tokens;
private final Map<String, String> variables;
private final JsonNode node;
public JsonPathProcessingTask(String[] tokens, Map<String, String> variables, JsonNode node) {
this.tokens = tokens;
this.variables = variables;
this.node = node;
}
public boolean isLast() {
return tokens.length == 1;
}
public String currentToken() {
return tokens[0];
}
public JsonPathProcessingTask next(JsonNode next) {
return new JsonPathProcessingTask(
Arrays.copyOfRange(tokens, 1, tokens.length),
variables,
next);
}
public JsonPathProcessingTask next(JsonNode next, String key, String value) {
Map<String, String> variables = new HashMap<>(this.variables);
variables.put(key, value);
return new JsonPathProcessingTask(
Arrays.copyOfRange(tokens, 1, tokens.length),
variables,
next);
}
@Override
public String toString() {
return "JsonPathProcessingTask{" +
"tokens=" + Arrays.toString(tokens) +
", variables=" + variables +
", node=" + node.toString().substring(0, 20) +
'}';
}
}
Loading…
Cancel
Save