Browse Source

Automatic image import-export from dashboards and widget types

pull/9542/head
Andrii Shvaika 3 years ago
parent
commit
d6bfc40dc6
  1. 4
      application/src/main/data/upgrade/3.6.1/schema_update.sql
  2. 2
      application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java
  3. 2
      application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
  4. 2
      application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java
  5. 1
      application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java
  6. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/DefaultWidgetTypeService.java
  7. 29
      application/src/main/java/org/thingsboard/server/service/resource/DefaultTbImageService.java
  8. 2
      application/src/main/java/org/thingsboard/server/service/resource/TbImageService.java
  9. 12
      common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ImageService.java
  10. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java
  11. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
  12. 1
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  13. 2
      common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java
  14. 3
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java
  15. 5
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java
  16. 7
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  17. 5
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
  18. 6
      dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceInfoEntity.java
  19. 224
      dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java
  20. 9
      dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java
  21. 2
      dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceDao.java
  22. 1
      dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java
  23. 7
      dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceDao.java
  24. 7
      dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java
  25. 11
      dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java
  26. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceRepository.java
  27. 8
      dao/src/main/java/org/thingsboard/server/dao/util/ImageUtils.java
  28. 30
      dao/src/main/java/org/thingsboard/server/dao/util/JsonNodeProcessingTask.java
  29. 70
      dao/src/main/java/org/thingsboard/server/dao/util/JsonPathProcessingTask.java
  30. 5
      dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java
  31. 5
      dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java
  32. 2
      dao/src/main/resources/sql/schema-entities-idx.sql

4
application/src/main/data/upgrade/3.6.1/schema_update.sql

@ -30,6 +30,8 @@ $$;
ALTER TABLE resource ADD COLUMN IF NOT EXISTS descriptor varchar;
ALTER TABLE resource ADD COLUMN IF NOT EXISTS preview bytea;
ALTER TABLE resource ADD COLUMN IF NOT EXISTS external_id uuid
ALTER TABLE resource ADD COLUMN IF NOT EXISTS external_id uuid;
CREATE INDEX IF NOT EXISTS idx_resource_etag ON resource(tenant_id, etag);
-- RESOURCES UPDATE END

2
application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java

@ -85,7 +85,7 @@ public class AssetProfileController extends BaseController {
AssetProfileId assetProfileId = new AssetProfileId(toUUID(strAssetProfileId));
var result = checkAssetProfileId(assetProfileId, Operation.READ);
if (inlineImages) {
tbImageService.inlineImages(result);
tbImageService.inlineImage(result);
}
return result;
}

2
application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java

@ -96,7 +96,7 @@ public class DeviceProfileController extends BaseController {
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
var result = checkDeviceProfileId(deviceProfileId, Operation.READ);
if (inlineImages) {
tbImageService.inlineImages(result);
tbImageService.inlineImage(result);
}
return result;
}

2
application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java

@ -89,7 +89,7 @@ public class WidgetsBundleController extends BaseController {
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId));
var result = checkWidgetsBundleId(widgetsBundleId, Operation.READ);
if (inlineImages) {
tbImageService.inlineImages(result);
tbImageService.inlineImage(result);
}
return result;
}

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

@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.EdgeId;
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.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;

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

@ -16,6 +16,7 @@
package org.thingsboard.server.service.entitiy.widgets.type;
import lombok.AllArgsConstructor;
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.StringUtils;
@ -24,6 +25,7 @@ import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
@ -33,6 +35,7 @@ import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
@AllArgsConstructor
public class DefaultWidgetTypeService extends AbstractTbEntityService implements TbWidgetTypeService {
private final WidgetTypeService widgetTypeService;
@Override

29
application/src/main/java/org/thingsboard/server/service/resource/DefaultTbImageService.java

@ -27,6 +27,7 @@ import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasImage;
import org.thingsboard.server.common.data.ImageDescriptor;
@ -40,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.widget.WidgetTypeDetails;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.util.JsonNodeProcessingTask;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
@ -162,20 +164,19 @@ public class DefaultTbImageService extends AbstractTbEntityService implements Tb
}
}
@Override
public void inlineImages(HasImage entity) {
public void inlineImage(HasImage entity) {
entity.setImage(inlineImage(entity.getTenantId(), "image", entity.getImage()));
}
@Override
public void inlineImages(Dashboard dashboard) {
inlineImages((HasImage) dashboard);
inlineImage(dashboard);
inlineIntoJson(dashboard.getTenantId(), dashboard.getConfiguration());
}
@Override
public void inlineImages(WidgetTypeDetails widgetTypeDetails) {
inlineImages((HasImage) widgetTypeDetails);
inlineImage(widgetTypeDetails);
inlineIntoJson(widgetTypeDetails.getTenantId(), widgetTypeDetails.getDescriptor());
}
@ -184,8 +185,8 @@ public class DefaultTbImageService extends AbstractTbEntityService implements Tb
tasks.add(new JsonNodeProcessingTask("", root));
while (!tasks.isEmpty()) {
JsonNodeProcessingTask task = tasks.poll();
JsonNode node = task.node;
String currentPath = StringUtils.isBlank(task.path) ? "" : (task.path + ".");
JsonNode node = task.getNode();
String currentPath = StringUtils.isBlank(task.getPath()) ? "" : (task.getPath() + ".");
if (node.isObject()) {
ObjectNode on = (ObjectNode) node;
for (Iterator<String> it = on.fieldNames(); it.hasNext(); ) {
@ -210,16 +211,6 @@ public class DefaultTbImageService extends AbstractTbEntityService implements Tb
}
}
private static class JsonNodeProcessingTask {
private final String path;
private final JsonNode node;
public JsonNodeProcessingTask(String path, JsonNode node) {
this.path = path;
this.node = node;
}
}
private String inlineImage(TenantId tenantId, String path, String url) {
try {
ImageCacheKey key = getKeyFromUrl(tenantId, url);
@ -228,7 +219,7 @@ public class DefaultTbImageService extends AbstractTbEntityService implements Tb
if (imageInfo != null) {
byte[] data = key.isPreview() ? imageService.getImagePreview(tenantId, imageInfo.getId()) : imageService.getImageData(tenantId, imageInfo.getId());
ImageDescriptor descriptor = getImageDescriptor(imageInfo, key.isPreview());
return "data:" + descriptor.getMediaType() + ";base64," + Base64Utils.encodeToString(data);
return DataConstants.TB_IMAGE_PREFIX + "data:" + descriptor.getMediaType() + ";base64," + Base64Utils.encodeToString(data);
}
}
} catch (Exception e) {
@ -247,9 +238,9 @@ public class DefaultTbImageService extends AbstractTbEntityService implements Tb
return null;
}
TenantId imageTenantId = null;
if (url.startsWith("/api/images/tenant/")) {
if (url.startsWith(DataConstants.TB_IMAGE_PREFIX + "/api/images/tenant/")) {
imageTenantId = tenantId;
} else if (url.startsWith("/api/images/system/")) {
} else if (url.startsWith(DataConstants.TB_IMAGE_PREFIX + "/api/images/system/")) {
imageTenantId = TenantId.SYS_TENANT_ID;
}
if (imageTenantId != null) {

2
application/src/main/java/org/thingsboard/server/service/resource/TbImageService.java

@ -37,7 +37,7 @@ public interface TbImageService {
void evictETag(ImageCacheKey imageCacheKey);
void inlineImages(HasImage entity);
void inlineImage(HasImage entity);
void inlineImages(Dashboard dashboard);

12
common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ImageService.java

@ -15,6 +15,9 @@
*/
package org.thingsboard.server.dao.resource;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasImage;
import org.thingsboard.server.common.data.TbImageDeleteResult;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
@ -22,12 +25,14 @@ 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.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import java.util.List;
public interface ImageService {
TbResourceInfo saveImage(TbResource image) throws Exception;
TbResourceInfo saveImage(TbResource image);
TbResourceInfo saveImageInfo(TbResourceInfo imageInfo);
@ -45,4 +50,9 @@ public interface ImageService {
List<TbResourceInfo> findSimilarImagesByTenantIdAndKeyStartingWith(TenantId tenantId, byte[] data, String imageKeyStartingWith);
TbResourceInfo findImageByTenantIdAndEtag(TenantId tenantId, String etag);
boolean replaceBase64WithImageUrl(HasImage hasImage, String title, String type);
boolean replaceBase64WithImageUrl(Dashboard dashboard);
boolean replaceBase64WithImageUrl(WidgetTypeDetails widgetType);
}

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

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
import java.util.Set;
public interface ResourceService extends EntityDaoService {
@ -54,6 +55,8 @@ public interface ResourceService extends EntityDaoService {
PageData<TbResource> findTenantResourcesByResourceTypeAndPageLink(TenantId tenantId, ResourceType lwm2mModel, PageLink pageLink);
Set<String> findResourceKeysByTenantIdResourceTypeAndKeyPrefix(TenantId tenantId, ResourceType resourceType, String key);
void deleteResource(TenantId tenantId, TbResourceId resourceId);
void deleteResource(TenantId tenantId, TbResourceId resourceId, boolean force);

2
common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java

@ -30,7 +30,7 @@ import java.util.Objects;
import java.util.Set;
@ApiModel
public class DashboardInfo extends BaseData<DashboardId> implements HasName, HasTenantId, HasTitle {
public class DashboardInfo extends BaseData<DashboardId> implements HasName, HasTenantId, HasTitle, HasImage {
private TenantId tenantId;
@NoXss

1
common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java

@ -50,6 +50,7 @@ public class DataConstants {
public static final String HTTP_TRANSPORT_NAME = "HTTP";
public static final String SNMP_TRANSPORT_NAME = "SNMP";
public static final String MAXIMUM_NUMBER_OF_DEVICES_REACHED = "Maximum number of devices reached!";
public static final String TB_IMAGE_PREFIX = "tb-image;";
public static final String[] allScopes() {

2
common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java

@ -109,7 +109,7 @@ public class TbResourceInfo extends BaseData<TbResourceId> implements HasName, H
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getLink() {
if (resourceType == ResourceType.IMAGE) {
return "/api/images/" + ((tenantId == null || !tenantId.isSysTenantId()) ? "tenant" : "system") + "/" + resourceKey; // tenantId is null in case of export to git
return DataConstants.TB_IMAGE_PREFIX + "/api/images/" + ((tenantId == null || !tenantId.isSysTenantId()) ? "tenant" : "system") + "/" + resourceKey; // tenantId is null in case of export to git
}
return null;
}

3
common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java

@ -21,6 +21,7 @@ import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasImage;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.WidgetTypeId;
@ -29,7 +30,7 @@ import org.thingsboard.server.common.data.validation.NoXss;
@Data
@JsonPropertyOrder({ "fqn", "name", "deprecated", "image", "description", "descriptor", "externalId" })
public class WidgetTypeDetails extends WidgetType implements HasName, HasTenantId, ExportableEntity<WidgetTypeId> {
public class WidgetTypeDetails extends WidgetType implements HasName, HasTenantId, HasImage, ExportableEntity<WidgetTypeId> {
@Length(fieldName = "image", max = 1000000)
@ApiModelProperty(position = 9, value = "Relative or external image URL. Replaced with image data URL (Base64) in case of relative URL and 'inlineImages' option enabled.")

5
dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java

@ -36,6 +36,7 @@ import org.thingsboard.server.dao.entity.AbstractCachedEntityService;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
@ -71,6 +72,9 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService<AssetPr
@Autowired
private DataValidator<AssetProfile> assetProfileValidator;
@Autowired
private ImageService imageService;
@TransactionalEventListener(classes = AssetProfileEvictEvent.class)
@Override
public void handleEvictEvent(AssetProfileEvictEvent event) {
@ -141,6 +145,7 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService<AssetPr
}
AssetProfile savedAssetProfile;
try {
imageService.replaceBase64WithImageUrl(assetProfile, assetProfile.getName(), "asset profile");
savedAssetProfile = assetProfileDao.saveAndFlush(assetProfile.getTenantId(), assetProfile);
publishEvictEvent(new AssetProfileEvictEvent(savedAssetProfile.getTenantId(), savedAssetProfile.getName(),
oldAssetProfile != null ? oldAssetProfile.getName() : null, savedAssetProfile.getId(), savedAssetProfile.isDefault()));

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

@ -50,6 +50,7 @@ import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
@ -78,6 +79,9 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
@Autowired
private EdgeDao edgeDao;
@Autowired
private ImageService imageService;
@Autowired
private DataValidator<Dashboard> dashboardValidator;
@ -153,6 +157,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
dashboardValidator.validate(dashboard, DashboardInfo::getTenantId);
}
try {
imageService.replaceBase64WithImageUrl(dashboard);
var saved = dashboardDao.save(dashboard.getTenantId(), dashboard);
publishEvictEvent(new DashboardTitleEvictEvent(saved.getId()));
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(saved.getTenantId())
@ -170,6 +175,8 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
}
}
@Override
public Dashboard assignDashboardToCustomer(TenantId tenantId, DashboardId dashboardId, CustomerId customerId) {
Dashboard dashboard = findDashboardById(tenantId, dashboardId);

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

@ -47,6 +47,7 @@ import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
@ -91,6 +92,9 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
@Autowired
private QueueService queueService;
@Autowired
private ImageService imageService;
@TransactionalEventListener(classes = DeviceProfileEvictEvent.class)
@Override
public void handleEvictEvent(DeviceProfileEvictEvent event) {
@ -178,6 +182,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
}
DeviceProfile savedDeviceProfile;
try {
imageService.replaceBase64WithImageUrl(deviceProfile, deviceProfile.getName(), "device profile");
savedDeviceProfile = deviceProfileDao.saveAndFlush(deviceProfile.getTenantId(), deviceProfile);
publishEvictEvent(new DeviceProfileEvictEvent(savedDeviceProfile.getTenantId(), savedDeviceProfile.getName(),
oldDeviceProfile != null ? oldDeviceProfile.getName() : null, savedDeviceProfile.getId(), savedDeviceProfile.isDefault(),

6
dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceInfoEntity.java

@ -67,7 +67,7 @@ public class TbResourceInfoEntity extends BaseSqlEntity<TbResourceInfo> implemen
private String searchText;
@Column(name = RESOURCE_ETAG_COLUMN)
private String hashCode;
private String etag;
@Column(name = RESOURCE_FILE_NAME_COLUMN)
private String fileName;
@ -92,7 +92,7 @@ public class TbResourceInfoEntity extends BaseSqlEntity<TbResourceInfo> implemen
this.resourceType = resource.getResourceType().name();
this.resourceKey = resource.getResourceKey();
this.searchText = resource.getSearchText();
this.hashCode = resource.getEtag();
this.etag = resource.getEtag();
this.fileName = resource.getFileName();
this.descriptor = resource.getDescriptor();
this.externalId = getUuid(resource.getExternalId());
@ -107,7 +107,7 @@ public class TbResourceInfoEntity extends BaseSqlEntity<TbResourceInfo> implemen
resource.setResourceType(ResourceType.valueOf(resourceType));
resource.setResourceKey(resourceKey);
resource.setSearchText(searchText);
resource.setEtag(hashCode);
resource.setEtag(etag);
resource.setFileName(fileName);
resource.setDescriptor(descriptor);
resource.setExternalId(getEntityId(externalId, TbResourceId::new));

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

@ -15,13 +15,21 @@
*/
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.hash.Hashing;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.RegexUtils;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasImage;
import org.thingsboard.server.common.data.ImageDescriptor;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbImageDeleteResult;
@ -33,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.ImageContainerDao;
import org.thingsboard.server.dao.asset.AssetProfileDao;
import org.thingsboard.server.dao.dashboard.DashboardInfoDao;
@ -41,14 +50,21 @@ 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.JsonNodeProcessingTask;
import org.thingsboard.server.dao.util.JsonPathProcessingTask;
import org.thingsboard.server.dao.widget.WidgetTypeDao;
import org.thingsboard.server.dao.widget.WidgetsBundleDao;
import javax.annotation.PostConstruct;
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.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.regex.Pattern;
@ -57,12 +73,38 @@ import java.util.regex.Pattern;
public class BaseImageService extends BaseResourceService implements ImageService {
private static final int MAX_ENTITIES_TO_FIND = 10;
public static Map<String, String> DASHBOARD_BASE64_MAPPING = new HashMap<>();
public static Map<String, String> WIDGET_TYPE_BASE64_MAPPING = new HashMap<>();
static {
DASHBOARD_BASE64_MAPPING.put("settings.dashboardLogoUrl", "$prefix logo");
DASHBOARD_BASE64_MAPPING.put("states.default.layouts.main.gridSettings.backgroundImageUrl", "$prefix background");
DASHBOARD_BASE64_MAPPING.put("states.default.layouts.right.gridSettings.backgroundImageUrl", "$prefix right background");
DASHBOARD_BASE64_MAPPING.put("states.$stateId.layouts.main.gridSettings.backgroundImageUrl", "$prefix $stateId background");
DASHBOARD_BASE64_MAPPING.put("states.$stateId.layouts.right.gridSettings.backgroundImageUrl", "$prefix $stateId right background");
DASHBOARD_BASE64_MAPPING.put("widgets.*.config[$title].settings.backgroundImageUrl", "$prefix widget \"$title\" background");
DASHBOARD_BASE64_MAPPING.put("widgets.*.config[$title].settings.mapImageUrl", "$prefix widget \"$title\" map image");
DASHBOARD_BASE64_MAPPING.put("widgets.*.config[$title].settings.markerImage", "$prefix widget \"$title\" marker image");
DASHBOARD_BASE64_MAPPING.put("widgets.*.config[$title].settings.markerImages", "$prefix widget \"$title\" marker image $index");
DASHBOARD_BASE64_MAPPING.put("widgets.*.config[$title].settings.background.imageUrl", "$prefix widget \"$title\" background");
DASHBOARD_BASE64_MAPPING.put("widgets.*.config[$title].settings.background.imageBase64", "$prefix widget \"$title\" background");
DASHBOARD_BASE64_MAPPING.put("widgets.*.config[$title].datasources.*.dataKeys.*.settings.customIcon", "$prefix widget \"$title\" custom icon");
WIDGET_TYPE_BASE64_MAPPING.put("settings.backgroundImageUrl", "$prefix background");
WIDGET_TYPE_BASE64_MAPPING.put("settings.mapImageUrl", "$prefix map image");
WIDGET_TYPE_BASE64_MAPPING.put("settings.markerImage", "Map marker image");
WIDGET_TYPE_BASE64_MAPPING.put("settings.markerImages", "Map marker image $index");
WIDGET_TYPE_BASE64_MAPPING.put("settings.background.imageUrl", "$prefix background");
WIDGET_TYPE_BASE64_MAPPING.put("settings.background.imageBase64", "$prefix background");
WIDGET_TYPE_BASE64_MAPPING.put("datasources.*.dataKeys.*.settings.customIcon", "$prefix custom icon");
}
private final AssetProfileDao assetProfileDao;
private final DeviceProfileDao deviceProfileDao;
private final WidgetsBundleDao widgetsBundleDao;
private final WidgetTypeDao widgetTypeDao;
private final DashboardInfoDao dashboardInfoDao;
private final Map<EntityType, ImageContainerDao<?>> imageContainerDaoMap = new HashMap<>();
public BaseImageService(TbResourceDao resourceDao, TbResourceInfoDao resourceInfoDao, ResourceDataValidator resourceValidator,
@ -86,9 +128,9 @@ public class BaseImageService extends BaseResourceService implements ImageServic
}
@Transactional
@Override
public TbResourceInfo saveImage(TbResource image) throws Exception {
@SneakyThrows
public TbResourceInfo saveImage(TbResource image) {
if (image.getId() == null && StringUtils.isEmpty(image.getResourceKey())) {
image.setResourceKey(getUniqueKey(image.getTenantId(), image.getFileName()));
}
@ -220,4 +262,178 @@ public class BaseImageService extends BaseResourceService implements ImageServic
return resourceInfoDao.findByTenantIdAndEtagAndKeyStartingWith(tenantId, etag, imageKeyStartingWith);
}
@Override
public TbResourceInfo findImageByTenantIdAndEtag(TenantId tenantId, String etag) {
return resourceInfoDao.findByTenantIdAndEtag(tenantId, ResourceType.IMAGE, etag);
}
@Override
public boolean replaceBase64WithImageUrl(HasImage entity, String title, String type) {
entity.setImage(base64ToImageUrl(entity.getTenantId(), "\"" + title + "\" " + type + " image", entity.getImage()));
return true; //TODO: should return true only if something is changed.
}
@Override
public boolean replaceBase64WithImageUrl(WidgetTypeDetails entity) {
String prefix = "\"" + entity.getName() + "\" widget"; // TODO: "system widget" if SYS TENANT ID;
entity.setImage(base64ToImageUrl(entity.getTenantId(), prefix + " image", entity.getImage()));
if (entity.getDescriptor().isObject()) {
ObjectNode descriptor = (ObjectNode) entity.getDescriptor();
JsonNode defaultConfig = JacksonUtil.toJsonNode(descriptor.get("defaultConfig").asText());
base64ToImageUrlUsingMapping(entity.getTenantId(), WIDGET_TYPE_BASE64_MAPPING, Collections.singletonMap("prefix", prefix), defaultConfig);
descriptor.put("defaultConfig", defaultConfig.toString());
}
base64ToImageUrlRecursively(entity.getTenantId(), prefix, entity.getDescriptor());
return true; //TODO: should return true only if something is changed.
}
@Override
public boolean replaceBase64WithImageUrl(Dashboard entity) {
String prefix = "\"" + entity.getTitle() + "\" dashboard";
entity.setImage(base64ToImageUrl(entity.getTenantId(), prefix + " image", entity.getImage()));
base64ToImageUrlUsingMapping(entity.getTenantId(), DASHBOARD_BASE64_MAPPING, Collections.singletonMap("prefix", prefix), entity.getConfiguration());
base64ToImageUrlRecursively(entity.getTenantId(), prefix, entity.getConfiguration());
return true; //TODO: should return true only if something is changed.
}
private void base64ToImageUrlUsingMapping(TenantId tenantId, Map<String, String> mapping, Map<String, String> templateParams, JsonNode configuration) {
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 (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 = org.apache.commons.lang3.StringUtils.substringBetween(token, "[$", "]");
token = org.apache.commons.lang3.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 replacements : task.getVariables().entrySet()) {
name = name.replace("$" + replacements.getKey(), replacements.getValue());
}
if (node.isObject() && value.isTextual()) {
((ObjectNode) node).put(token, base64ToImageUrl(tenantId, 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, base64ToImageUrl(tenantId, arrayElementName, array.get(i).asText()));
}
}
} else {
if (org.thingsboard.server.common.data.StringUtils.isNotEmpty(variableName) && org.thingsboard.server.common.data.StringUtils.isNotEmpty(variableValue)) {
tasks.add(task.next(value, variableName, variableValue));
} else {
tasks.add(task.next(value));
}
}
}
}
}
}
}
private String base64ToImageUrl(TenantId tenantId, String name, String data) {
return base64ToImageUrl(tenantId, name, data, false);
}
private String base64ToImageUrl(TenantId tenantId, String name, String data, boolean strict) {
if (org.thingsboard.server.common.data.StringUtils.isNotBlank(data) &&
(data.startsWith(DataConstants.TB_IMAGE_PREFIX + "data:image/")
|| (!strict && data.startsWith("data:image/")))) {
String base64Data = org.apache.commons.lang3.StringUtils.substringAfter(data, "base64,");
String mediaType = org.apache.commons.lang3.StringUtils.substringBetween(data, "data:", ";base64");
String extension = ImageUtils.mediaTypeToFileExtension(mediaType);
byte[] imageData = Base64.getDecoder().decode(base64Data);
String etag = Hashing.sha256().hashBytes(imageData).toString();
var imageInfo = findImageByTenantIdAndEtag(tenantId, etag);
if (imageInfo == null) {
TbResource image = new TbResource();
image.setTenantId(tenantId);
image.setResourceType(ResourceType.IMAGE);
String fileName = name.toLowerCase()
//TODO: improve to list all the special characters via regexp or similar
.replace("'", "_")
.replace("\"", "_")
.replace(" ", "_")
.replace("/", "_");
String key = fileName + "." + extension;
Set<String> existingKeys = findResourceKeysByTenantIdResourceTypeAndKeyPrefix(tenantId, ResourceType.IMAGE, key);
int idx = 1;
while (existingKeys.contains(key)) {
key = fileName + " (" + idx + ")." + extension;
idx++;
}
image.setResourceKey(key);
image.setTitle(name);
image.setFileName(key);
image.setDescriptor(JacksonUtil.newObjectNode().put("mediaType", mediaType));
image.setData(imageData);
imageInfo = saveImage(image);
}
return imageInfo.getLink();
} else {
return data;
}
}
private void base64ToImageUrlRecursively(TenantId tenantId, String title, JsonNode root) {
Queue<JsonNodeProcessingTask> tasks = new LinkedList<>();
tasks.add(new JsonNodeProcessingTask(title, root));
while (!tasks.isEmpty()) {
JsonNodeProcessingTask task = tasks.poll();
JsonNode node = task.getNode();
String currentPath = org.thingsboard.server.common.data.StringUtils.isBlank(task.getPath()) ? "" : (task.getPath() + " ");
if (node.isObject()) {
ObjectNode on = (ObjectNode) node;
for (Iterator<String> it = on.fieldNames(); it.hasNext(); ) {
String childName = it.next();
JsonNode childValue = on.get(childName);
if (childValue.isTextual()) {
on.put(childName, base64ToImageUrl(tenantId, currentPath + childName, childValue.asText(), true));
} else if (childValue.isObject() || childValue.isArray()) {
tasks.add(new JsonNodeProcessingTask(currentPath + childName, childValue));
}
}
} else if (node.isArray()) {
ArrayNode childArray = (ArrayNode) node;
int i = 0;
for (JsonNode element : childArray) {
if (element.isObject()) {
tasks.add(new JsonNodeProcessingTask(currentPath + " " + i, element));
}
i++;
}
}
}
}
}

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

@ -44,8 +44,10 @@ import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.service.validator.ResourceDataValidator;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import static org.thingsboard.server.dao.device.DeviceServiceImpl.INCORRECT_TENANT_ID;
import static org.thingsboard.server.dao.service.Validator.validateId;
@ -197,6 +199,13 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
return resourceDao.findResourcesByTenantIdAndResourceType(tenantId, resourceType, pageLink);
}
@Override
public Set<String> findResourceKeysByTenantIdResourceTypeAndKeyPrefix(TenantId tenantId, ResourceType resourceType, String key) {
log.trace("Executing findResourceKeysByTenantIdAndPrefix [{}][{}]", tenantId, key);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
return resourceDao.findResourceKeysByTenantIdResourceTypeAndKeyPrefix(tenantId, resourceType, key);
}
@Override
public void deleteResourcesByTenantId(TenantId tenantId) {
log.trace("Executing deleteResourcesByTenantId, tenantId [{}]", tenantId);

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

@ -26,6 +26,7 @@ import org.thingsboard.server.dao.ExportableEntityDao;
import org.thingsboard.server.dao.TenantEntityWithDataDao;
import java.util.List;
import java.util.Set;
public interface TbResourceDao extends Dao<TbResource>, TenantEntityWithDataDao, ExportableEntityDao<TbResourceId, TbResource> {
@ -46,4 +47,5 @@ public interface TbResourceDao extends Dao<TbResource>, TenantEntityWithDataDao,
byte[] getResourcePreview(TenantId tenantId, TbResourceId resourceId);
Set<String> findResourceKeysByTenantIdResourceTypeAndKeyPrefix(TenantId tenantId, ResourceType resourceType, String keyPrefix);
}

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

@ -39,4 +39,5 @@ public interface TbResourceInfoDao extends Dao<TbResourceInfo> {
List<TbResourceInfo> findByTenantIdAndEtagAndKeyStartingWith(TenantId tenantId, String etag, String query);
TbResourceInfo findByTenantIdAndEtag(TenantId tenantId, ResourceType resourceType, String etag);
}

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

@ -31,7 +31,9 @@ import org.thingsboard.server.dao.resource.TbResourceDao;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
@Slf4j
@ -125,6 +127,11 @@ public class JpaTbResourceDao extends JpaAbstractDao<TbResourceEntity, TbResourc
.map(TbResourceId::new));
}
@Override
public Set<String> findResourceKeysByTenantIdResourceTypeAndKeyPrefix(TenantId tenantId, ResourceType resourceType, String keyPrefix) {
return new HashSet<>(resourceRepository.findResourceKeys(tenantId.getId(), resourceType.name(), keyPrefix + "%"));
}
@Override
public TbResourceId getExternalIdByInternal(TbResourceId internalId) {
return DaoUtil.toEntityId(resourceRepository.getExternalIdByInternal(internalId.getId()), TbResourceId::new);

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

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.sql.resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.ResourceType;
@ -102,7 +103,11 @@ public class JpaTbResourceInfoDao extends JpaAbstractDao<TbResourceInfoEntity, T
@Override
public List<TbResourceInfo> findByTenantIdAndEtagAndKeyStartingWith(TenantId tenantId, String etag, String query) {
return DaoUtil.convertDataList(resourceInfoRepository.findByTenantIdAndHashCodeAndResourceKeyStartingWith(tenantId.getId(), etag, query));
return DaoUtil.convertDataList(resourceInfoRepository.findByTenantIdAndEtagAndResourceKeyStartingWith(tenantId.getId(), etag, query));
}
@Override
public TbResourceInfo findByTenantIdAndEtag(TenantId tenantId, ResourceType resourceType, String etag) {
return DaoUtil.getData(resourceInfoRepository.findByTenantIdAndEtag(TenantId.SYS_TENANT_ID.getId(), tenantId.getId(), resourceType.name(), etag));
}
}

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

@ -30,7 +30,7 @@ public interface TbResourceInfoRepository extends JpaRepository<TbResourceInfoEn
@Query("SELECT tr FROM TbResourceInfoEntity tr WHERE " +
"(:searchText IS NULL OR ilike(tr.title, CONCAT('%', :searchText, '%')) = true) " +
"AND (tr.tenantId = :tenantId " +
"OR (tr.tenantId = :systemAdminId " +
"OR (tr.tenantId = :systemTenantId " +
"AND NOT EXISTS " +
"(SELECT sr FROM TbResourceEntity sr " +
"WHERE sr.tenantId = :tenantId " +
@ -38,7 +38,7 @@ public interface TbResourceInfoRepository extends JpaRepository<TbResourceInfoEn
"AND tr.resourceKey = sr.resourceKey)))" +
"AND tr.resourceType IN :resourceTypes")
Page<TbResourceInfoEntity> findAllTenantResourcesByTenantId(@Param("tenantId") UUID tenantId,
@Param("systemAdminId") UUID sysadminId,
@Param("systemTenantId") UUID systemTenantId,
@Param("resourceTypes") List<String> resourceTypes,
@Param("searchText") String searchText,
Pageable pageable);
@ -62,6 +62,11 @@ public interface TbResourceInfoRepository extends JpaRepository<TbResourceInfoEn
@Param("resourceType") String resourceType,
@Param("resourceKeyStartsWith") String resourceKeyStartsWith);
List<TbResourceInfoEntity> findByTenantIdAndHashCodeAndResourceKeyStartingWith(UUID tenantId, String hashCode, String query);
List<TbResourceInfoEntity> findByTenantIdAndEtagAndResourceKeyStartingWith(UUID tenantId, String etag, String query);
@Query(value = "SELECT * FROM resource r WHERE (r.tenant_id = :systemTenantId OR r.tenant_id = :tenantId) AND r.resource_type = :resourceType AND r.etag = :etag LIMIT 1", nativeQuery = true)
TbResourceInfoEntity findByTenantIdAndEtag(@Param("systemTenantId") UUID sysTenantId,
@Param("tenantId") UUID tenantId,
@Param("resourceType") String resourceType,
@Param("etag") String etag);
}

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

@ -94,4 +94,8 @@ public interface TbResourceRepository extends JpaRepository<TbResourceEntity, UU
@Query("SELECT id FROM TbResourceInfoEntity WHERE tenantId = :tenantId")
Page<UUID> findIdsByTenantId(@Param("tenantId") UUID tenantId, Pageable pageable);
@Query("SELECT resourceKey FROM TbResourceInfoEntity WHERE tenantId = :tenantId AND resourceType = :resourceType AND resourceKey LIKE :resourceKeyPrefix ")
List<String> findResourceKeys(@Param("tenantId")UUID tenantId,
@Param("resourceType") String resourceType,
@Param("resourceKeyPrefix") String resourceKeyPrefix);
}

8
dao/src/main/java/org/thingsboard/server/dao/util/ImageUtils.java

@ -110,15 +110,15 @@ public class ImageUtils {
String strWidth = document.getDocumentElement().getAttribute("width");
String strHeight = document.getDocumentElement().getAttribute("height");
if (StringUtils.isNotEmpty(strWidth) && StringUtils.isNotEmpty(strHeight)) {
width = Integer.parseInt(strWidth);
height = Integer.parseInt(strHeight);
width = (int) Double.parseDouble(strWidth);
height = (int) Double.parseDouble(strHeight);;
} else {
String viewBox = document.getDocumentElement().getAttribute("viewBox");
if (StringUtils.isNotEmpty(viewBox)) {
String[] viewBoxValues = viewBox.split(" ");
if (viewBoxValues.length > 3) {
width = Integer.parseInt(viewBoxValues[2]);
height = Integer.parseInt(viewBoxValues[3]);
width = (int) Double.parseDouble(viewBoxValues[2]);
height = (int) Double.parseDouble(viewBoxValues[3]);
}
}
}

30
dao/src/main/java/org/thingsboard/server/dao/util/JsonNodeProcessingTask.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2023 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;
@Data
public class JsonNodeProcessingTask {
private final String path;
private final JsonNode node;
public JsonNodeProcessingTask(String path, JsonNode node) {
this.path = path;
this.node = node;
}
}

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

@ -0,0 +1,70 @@
/**
* Copyright © 2016-2023 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 org.thingsboard.server.dao.dashboard.DashboardServiceImpl;
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) +
'}';
}
}

5
dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java

@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.widget.WidgetsBundleWidget;
import org.thingsboard.server.dao.entity.AbstractCachedEntityService;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
@ -64,6 +65,9 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
@Autowired
protected ApplicationEventPublisher eventPublisher;
@Autowired
protected ImageService imageService;
@Override
public WidgetType findWidgetTypeById(TenantId tenantId, WidgetTypeId widgetTypeId) {
log.trace("Executing findWidgetTypeById [{}]", widgetTypeId);
@ -90,6 +94,7 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
log.trace("Executing saveWidgetType [{}]", widgetTypeDetails);
widgetTypeValidator.validate(widgetTypeDetails, WidgetType::getTenantId);
try {
imageService.replaceBase64WithImageUrl(widgetTypeDetails);
WidgetTypeDetails result = widgetTypeDao.save(widgetTypeDetails.getTenantId(), widgetTypeDetails);
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId())
.entityId(result.getId()).added(widgetTypeDetails.getId() == null).build());

5
dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java

@ -31,6 +31,7 @@ import org.thingsboard.server.dao.entity.AbstractCachedEntityService;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
@ -59,6 +60,9 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService {
@Autowired
protected ApplicationEventPublisher eventPublisher;
@Autowired
private ImageService imageService;
@Override
public WidgetsBundle findWidgetsBundleById(TenantId tenantId, WidgetsBundleId widgetsBundleId) {
log.trace("Executing findWidgetsBundleById [{}]", widgetsBundleId);
@ -71,6 +75,7 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService {
log.trace("Executing saveWidgetsBundle [{}]", widgetsBundle);
widgetsBundleValidator.validate(widgetsBundle, WidgetsBundle::getTenantId);
try {
imageService.replaceBase64WithImageUrl(widgetsBundle, widgetsBundle.getName(), "bundle");
WidgetsBundle result = widgetsBundleDao.save(widgetsBundle.getTenantId(), widgetsBundle);
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId())
.entityId(result.getId()).added(widgetsBundle.getId() == null).build());

2
dao/src/main/resources/sql/schema-entities-idx.sql

@ -119,3 +119,5 @@ CREATE INDEX IF NOT EXISTS idx_notification_notification_request_id ON notificat
CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_created_time ON notification(recipient_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_unread ON notification(recipient_id) WHERE status <> 'READ';
CREATE INDEX IF NOT EXISTS idx_resource_etag ON resource(tenant_id, etag);

Loading…
Cancel
Save