Browse Source

Tb Image Metadata in import/export

pull/9542/head
Andrii Shvaika 3 years ago
parent
commit
c3f1ffeb41
  1. 6
      application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java
  2. 6
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  3. 6
      application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
  4. 3
      application/src/main/java/org/thingsboard/server/controller/ImageController.java
  5. 8
      application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java
  6. 6
      application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java
  7. 2
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  8. 103
      application/src/main/java/org/thingsboard/server/service/resource/DefaultTbImageService.java
  9. 7
      application/src/main/java/org/thingsboard/server/service/resource/TbImageService.java
  10. 6
      common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ImageService.java
  11. 4
      common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java
  12. 2
      common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java
  13. 187
      dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java
  14. 2
      dao/src/main/java/org/thingsboard/server/dao/resource/ImageCacheKey.java

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

@ -35,9 +35,9 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.asset.profile.TbAssetProfileService;
import org.thingsboard.server.service.resource.TbImageService;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
@ -67,7 +67,7 @@ import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LI
public class AssetProfileController extends BaseController {
private final TbAssetProfileService tbAssetProfileService;
private final TbImageService tbImageService;
private final ImageService imageService;
@ApiOperation(value = "Get Asset Profile (getAssetProfileById)",
notes = "Fetch the Asset Profile object based on the provided Asset Profile Id. " +
@ -85,7 +85,7 @@ public class AssetProfileController extends BaseController {
AssetProfileId assetProfileId = new AssetProfileId(toUUID(strAssetProfileId));
var result = checkAssetProfileId(assetProfileId, Operation.READ);
if (inlineImages) {
tbImageService.inlineImage(result);
imageService.inlineImage(result);
}
return result;
}

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

@ -51,9 +51,9 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.dashboard.TbDashboardService;
import org.thingsboard.server.service.resource.TbImageService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
@ -97,7 +97,7 @@ import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LI
public class DashboardController extends BaseController {
private final TbDashboardService tbDashboardService;
private final TbImageService tbImageService;
private final ImageService imageService;
public static final String DASHBOARD_ID = "dashboardId";
private static final String HOME_DASHBOARD_ID = "homeDashboardId";
@ -164,7 +164,7 @@ public class DashboardController extends BaseController {
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
var result = checkDashboardId(dashboardId, Operation.READ);
if (inlineImages) {
tbImageService.inlineImages(result);
imageService.inlineImages(result);
}
return result;
}

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

@ -37,10 +37,10 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.device.profile.TbDeviceProfileService;
import org.thingsboard.server.service.resource.TbImageService;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
@ -75,7 +75,7 @@ import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LI
public class DeviceProfileController extends BaseController {
private final TbDeviceProfileService tbDeviceProfileService;
private final TbImageService tbImageService;
private final ImageService imageService;
@Autowired
private TimeseriesService timeseriesService;
@ -96,7 +96,7 @@ public class DeviceProfileController extends BaseController {
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
var result = checkDeviceProfileId(deviceProfileId, Operation.READ);
if (inlineImages) {
tbImageService.inlineImage(result);
imageService.inlineImage(result);
}
return result;
}

3
application/src/main/java/org/thingsboard/server/controller/ImageController.java

@ -52,13 +52,12 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.resource.ImageCacheKey;
import org.thingsboard.server.dao.resource.ImageCacheKey;
import org.thingsboard.server.service.resource.TbImageService;
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 javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;

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

@ -43,9 +43,9 @@ import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.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.resource.TbImageService;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
@ -75,7 +75,7 @@ import static org.thingsboard.server.controller.ControllerConstants.WIDGET_TYPE_
public class WidgetTypeController extends AutoCommitController {
private final TbWidgetTypeService tbWidgetTypeService;
private final TbImageService tbImageService;
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. " +
@ -103,7 +103,7 @@ public class WidgetTypeController extends AutoCommitController {
WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId));
var result = checkWidgetTypeId(widgetTypeId, Operation.READ);
if (inlineImages) {
tbImageService.inlineImages(result);
imageService.inlineImages(result);
}
return result;
}
@ -270,7 +270,7 @@ public class WidgetTypeController extends AutoCommitController {
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId));
var result = checkNotNull(widgetTypeService.findWidgetTypesDetailsByWidgetsBundleId(getTenantId(), widgetsBundleId));
if (inlineImages) {
result.forEach(tbImageService::inlineImages);
result.forEach(imageService::inlineImages);
}
return result;
}

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

@ -36,9 +36,9 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.widgets.bundle.TbWidgetsBundleService;
import org.thingsboard.server.service.resource.TbImageService;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
@ -69,7 +69,7 @@ import static org.thingsboard.server.controller.ControllerConstants.WIDGET_BUNDL
public class WidgetsBundleController extends BaseController {
private final TbWidgetsBundleService tbWidgetsBundleService;
private final TbImageService tbImageService;
private final ImageService imageService;
private static final String WIDGET_BUNDLE_DESCRIPTION = "Widget Bundle represents a group(bundle) of widgets. Widgets are grouped into bundle by type or use case. ";
private static final String FULL_SEARCH_PARAM_DESCRIPTION = "Optional boolean parameter indicating extended search of widget bundles by description and by name / description of related widget types";
@ -89,7 +89,7 @@ public class WidgetsBundleController extends BaseController {
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId));
var result = checkWidgetsBundleId(widgetsBundleId, Operation.READ);
if (inlineImages) {
tbImageService.inlineImage(result);
imageService.inlineImage(result);
}
return result;
}

2
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -82,7 +82,7 @@ import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
import org.thingsboard.server.service.queue.processing.IdMsgPair;
import org.thingsboard.server.service.resource.ImageCacheKey;
import org.thingsboard.server.dao.resource.ImageCacheKey;
import org.thingsboard.server.service.resource.TbImageService;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequestActorMsg;

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

@ -16,20 +16,13 @@
package org.thingsboard.server.service.resource;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
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;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.TbImageDeleteResult;
@ -39,17 +32,13 @@ import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
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.ImageCacheKey;
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;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
@Service
@ -163,94 +152,4 @@ public class DefaultTbImageService extends AbstractTbEntityService implements Tb
throw e;
}
}
public void inlineImage(HasImage entity) {
entity.setImage(inlineImage(entity.getTenantId(), "image", entity.getImage()));
}
@Override
public void inlineImages(Dashboard dashboard) {
inlineImage(dashboard);
inlineIntoJson(dashboard.getTenantId(), dashboard.getConfiguration());
}
@Override
public void inlineImages(WidgetTypeDetails widgetTypeDetails) {
inlineImage(widgetTypeDetails);
inlineIntoJson(widgetTypeDetails.getTenantId(), widgetTypeDetails.getDescriptor());
}
private void inlineIntoJson(TenantId tenantId, JsonNode root) {
Queue<JsonNodeProcessingTask> tasks = new LinkedList<>();
tasks.add(new JsonNodeProcessingTask("", root));
while (!tasks.isEmpty()) {
JsonNodeProcessingTask task = tasks.poll();
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(); ) {
String childName = it.next();
JsonNode childValue = on.get(childName);
if (childValue.isTextual()) {
on.put(childName, inlineImage(tenantId, currentPath + childName, childValue.asText()));
} 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++;
}
}
}
}
private String inlineImage(TenantId tenantId, String path, String url) {
try {
ImageCacheKey key = getKeyFromUrl(tenantId, url);
if (key != null) {
var imageInfo = imageService.getImageInfoByTenantIdAndKey(key.getTenantId(), key.getKey());
if (imageInfo != null) {
byte[] data = key.isPreview() ? imageService.getImagePreview(tenantId, imageInfo.getId()) : imageService.getImageData(tenantId, imageInfo.getId());
ImageDescriptor descriptor = getImageDescriptor(imageInfo, key.isPreview());
return DataConstants.TB_IMAGE_PREFIX + "data:" + descriptor.getMediaType() + ";base64," + Base64Utils.encodeToString(data);
}
}
} catch (Exception e) {
log.warn("[{}][{}][{}] Failed to inline image.", tenantId, path, url, e);
}
return url;
}
private ImageDescriptor getImageDescriptor(TbResourceInfo imageInfo, boolean preview) throws JsonProcessingException {
ImageDescriptor descriptor = imageInfo.getDescriptor(ImageDescriptor.class);
return preview ? descriptor.getPreviewDescriptor() : descriptor;
}
private ImageCacheKey getKeyFromUrl(TenantId tenantId, String url) {
if (StringUtils.isBlank(url)) {
return null;
}
TenantId imageTenantId = null;
if (url.startsWith(DataConstants.TB_IMAGE_PREFIX + "/api/images/tenant/")) {
imageTenantId = tenantId;
} else if (url.startsWith(DataConstants.TB_IMAGE_PREFIX + "/api/images/system/")) {
imageTenantId = TenantId.SYS_TENANT_ID;
}
if (imageTenantId != null) {
var parts = url.split("/");
if (parts.length == 5) {
return new ImageCacheKey(imageTenantId, parts[4], false);
} else if (parts.length == 6 && "preview".equals(parts[5])) {
return new ImageCacheKey(imageTenantId, parts[4], true);
}
}
return null;
}
}

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

@ -22,6 +22,7 @@ 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.widget.WidgetTypeDetails;
import org.thingsboard.server.dao.resource.ImageCacheKey;
public interface TbImageService {
@ -37,10 +38,4 @@ public interface TbImageService {
void evictETag(ImageCacheKey imageCacheKey);
void inlineImage(HasImage entity);
void inlineImages(Dashboard dashboard);
void inlineImages(WidgetTypeDetails widgetTypeDetails);
}

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

@ -55,4 +55,10 @@ public interface ImageService {
boolean replaceBase64WithImageUrl(HasImage hasImage, String title, String type);
boolean replaceBase64WithImageUrl(Dashboard dashboard);
boolean replaceBase64WithImageUrl(WidgetTypeDetails widgetType);
void inlineImage(HasImage entity);
void inlineImages(Dashboard dashboard);
void inlineImages(WidgetTypeDetails widgetTypeDetails);
}

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

@ -173,6 +173,10 @@ public class StringUtils {
return false;
}
public static String substringBeforeLast(String str, String separator) {
return org.apache.commons.lang3.StringUtils.substringBeforeLast(str, separator);
}
public static String substringAfterLast(String str, String sep) {
return org.apache.commons.lang3.StringUtils.substringAfterLast(str, sep);
}

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 DataConstants.TB_IMAGE_PREFIX + "/api/images/" + ((tenantId == null || !tenantId.isSysTenantId()) ? "tenant" : "system") + "/" + resourceKey; // tenantId is null in case of export to git
return "/api/images/" + ((tenantId == null || !tenantId.isSysTenantId()) ? "tenant" : "system") + "/" + resourceKey; // tenantId is null in case of export to git
}
return null;
}

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

@ -15,15 +15,16 @@
*/
package org.thingsboard.server.dao.resource;
import com.fasterxml.jackson.core.JsonProcessingException;
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.util.Base64Utils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.RegexUtils;
import org.thingsboard.server.common.data.Dashboard;
@ -32,6 +33,7 @@ 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.StringUtils;
import org.thingsboard.server.common.data.TbImageDeleteResult;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
@ -56,6 +58,7 @@ import org.thingsboard.server.dao.widget.WidgetTypeDao;
import org.thingsboard.server.dao.widget.WidgetsBundleDao;
import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
@ -236,7 +239,7 @@ public class BaseImageService extends BaseResourceService implements ImageServic
TbImageDeleteResult.TbImageDeleteResultBuilder result = TbImageDeleteResult.builder();
boolean success = true;
if (!force) {
var link = imageInfo.getLink();
var link = DataConstants.TB_IMAGE_PREFIX + imageInfo.getLink();
Map<String, List<? extends HasId<?>>> affectedEntities = new HashMap<>();
imageContainerDaoMap.forEach((entityType, imageContainerDao) -> {
var entities = tenantId.isSysTenantId() ? imageContainerDao.findByImageLink(link, MAX_ENTITIES_TO_FIND) :
@ -366,44 +369,67 @@ public class BaseImageService extends BaseResourceService implements ImageServic
return base64ToImageUrl(tenantId, name, data, false);
}
private static final Pattern TB_IMAGE_METADATA_PATTERN = Pattern.compile("^tb-image:(.*):(.*);data:(.*);.*");
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()
if (StringUtils.isBlank(data)) {
return data;
}
var matcher = TB_IMAGE_METADATA_PATTERN.matcher(data);
boolean matches = matcher.matches();
String mdResourceKey = null;
String mdResourceName = null;
String mdMediaType;
if (matches) {
mdResourceKey = new String(Base64Utils.decodeFromString(matcher.group(1)), StandardCharsets.UTF_8);
mdResourceName = new String(Base64Utils.decodeFromString(matcher.group(2)), StandardCharsets.UTF_8);
mdMediaType = matcher.group(3);
} else if (data.startsWith(DataConstants.TB_IMAGE_PREFIX + "data:image/") || (!strict && data.startsWith("data:image/"))) {
mdMediaType = org.apache.commons.lang3.StringUtils.substringBetween(data, "data:", ";base64");
} else {
return data;
}
String base64Data = org.apache.commons.lang3.StringUtils.substringAfter(data, "base64,");
String extension = ImageUtils.mediaTypeToFileExtension(mdMediaType);
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);
if (StringUtils.isBlank(mdResourceName)) {
mdResourceName = name;
}
String fileName;
if (StringUtils.isBlank(mdResourceKey)) {
fileName = mdResourceName.toLowerCase()
//TODO: improve to list all the special characters via regexp or similar
.replace("'", "_")
.replace("\"", "_")
.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);
mdResourceKey = fileName + "." + extension;
} else {
fileName = mdResourceKey.split("\\.")[0];
}
return imageInfo.getLink();
} else {
return data;
Set<String> existingKeys = findResourceKeysByTenantIdResourceTypeAndKeyPrefix(tenantId, ResourceType.IMAGE, mdResourceKey);
int idx = 1;
while (existingKeys.contains(mdResourceKey)) {
mdResourceKey = fileName + "_(" + idx + ")." + extension;
idx++;
}
image.setResourceKey(mdResourceKey);
image.setTitle(mdResourceName);
image.setFileName(mdResourceKey);
image.setDescriptor(JacksonUtil.newObjectNode().put("mediaType", mdMediaType));
image.setData(imageData);
imageInfo = saveImage(image);
}
return DataConstants.TB_IMAGE_PREFIX + imageInfo.getLink();
}
private void base64ToImageUrlRecursively(TenantId tenantId, String title, JsonNode root) {
@ -436,4 +462,97 @@ public class BaseImageService extends BaseResourceService implements ImageServic
}
}
}
@Override
public void inlineImage(HasImage entity) {
entity.setImage(inlineImage(entity.getTenantId(), "image", entity.getImage()));
}
@Override
public void inlineImages(Dashboard dashboard) {
inlineImage(dashboard);
inlineIntoJson(dashboard.getTenantId(), dashboard.getConfiguration());
}
@Override
public void inlineImages(WidgetTypeDetails widgetTypeDetails) {
inlineImage(widgetTypeDetails);
inlineIntoJson(widgetTypeDetails.getTenantId(), widgetTypeDetails.getDescriptor());
}
private void inlineIntoJson(TenantId tenantId, JsonNode root) {
Queue<JsonNodeProcessingTask> tasks = new LinkedList<>();
tasks.add(new JsonNodeProcessingTask("", 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, inlineImage(tenantId, currentPath + childName, childValue.asText()));
} 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++;
}
}
}
}
private String inlineImage(TenantId tenantId, String path, String url) {
try {
ImageCacheKey key = getKeyFromUrl(tenantId, url);
if (key != null) {
var imageInfo = getImageInfoByTenantIdAndKey(key.getTenantId(), key.getKey());
if (imageInfo != null) {
byte[] data = key.isPreview() ? getImagePreview(tenantId, imageInfo.getId()) : getImageData(tenantId, imageInfo.getId());
ImageDescriptor descriptor = getImageDescriptor(imageInfo, key.isPreview());
String tbImagePrefix = "tb-image:" + Base64Utils.encodeToString(imageInfo.getResourceKey().getBytes(StandardCharsets.UTF_8)) + ":"
+ Base64Utils.encodeToString(imageInfo.getName().getBytes(StandardCharsets.UTF_8)) + ";";
return tbImagePrefix + "data:" + descriptor.getMediaType() + ";base64," + Base64Utils.encodeToString(data);
}
}
} catch (Exception e) {
log.warn("[{}][{}][{}] Failed to inline image.", tenantId, path, url, e);
}
return url;
}
private ImageDescriptor getImageDescriptor(TbResourceInfo imageInfo, boolean preview) throws JsonProcessingException {
ImageDescriptor descriptor = imageInfo.getDescriptor(ImageDescriptor.class);
return preview ? descriptor.getPreviewDescriptor() : descriptor;
}
private ImageCacheKey getKeyFromUrl(TenantId tenantId, String url) {
if (org.thingsboard.server.common.data.StringUtils.isBlank(url)) {
return null;
}
TenantId imageTenantId = null;
if (url.startsWith(DataConstants.TB_IMAGE_PREFIX + "/api/images/tenant/")) {
imageTenantId = tenantId;
} else if (url.startsWith(DataConstants.TB_IMAGE_PREFIX + "/api/images/system/")) {
imageTenantId = TenantId.SYS_TENANT_ID;
}
if (imageTenantId != null) {
var parts = url.split("/");
if (parts.length == 5) {
return new ImageCacheKey(imageTenantId, parts[4], false);
} else if (parts.length == 6 && "preview".equals(parts[5])) {
return new ImageCacheKey(imageTenantId, parts[4], true);
}
}
return null;
}
}

2
application/src/main/java/org/thingsboard/server/service/resource/ImageCacheKey.java → dao/src/main/java/org/thingsboard/server/dao/resource/ImageCacheKey.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.resource;
package org.thingsboard.server.dao.resource;
import lombok.Data;
import org.thingsboard.server.common.data.id.TenantId;
Loading…
Cancel
Save