Fixes and improvements for images processing
This commit is contained in:
parent
ddfa55ba6e
commit
2d26fdcb95
@ -18,15 +18,11 @@ package org.thingsboard.server.service.install.update;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.thingsboard.server.common.data.Dashboard;
|
import org.thingsboard.server.common.data.HasImage;
|
||||||
import org.thingsboard.server.common.data.DeviceProfile;
|
import org.thingsboard.server.common.data.id.EntityId;
|
||||||
import org.thingsboard.server.common.data.asset.AssetProfile;
|
|
||||||
import org.thingsboard.server.common.data.id.DashboardId;
|
|
||||||
import org.thingsboard.server.common.data.id.TenantId;
|
import org.thingsboard.server.common.data.id.TenantId;
|
||||||
import org.thingsboard.server.common.data.id.WidgetTypeId;
|
|
||||||
import org.thingsboard.server.common.data.page.PageDataIterable;
|
import org.thingsboard.server.common.data.page.PageDataIterable;
|
||||||
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
|
import org.thingsboard.server.dao.Dao;
|
||||||
import org.thingsboard.server.common.data.widget.WidgetsBundle;
|
|
||||||
import org.thingsboard.server.dao.asset.AssetProfileDao;
|
import org.thingsboard.server.dao.asset.AssetProfileDao;
|
||||||
import org.thingsboard.server.dao.dashboard.DashboardDao;
|
import org.thingsboard.server.dao.dashboard.DashboardDao;
|
||||||
import org.thingsboard.server.dao.device.DeviceProfileDao;
|
import org.thingsboard.server.dao.device.DeviceProfileDao;
|
||||||
@ -34,6 +30,9 @@ import org.thingsboard.server.dao.resource.ImageService;
|
|||||||
import org.thingsboard.server.dao.widget.WidgetTypeDao;
|
import org.thingsboard.server.dao.widget.WidgetTypeDao;
|
||||||
import org.thingsboard.server.dao.widget.WidgetsBundleDao;
|
import org.thingsboard.server.dao.widget.WidgetsBundleDao;
|
||||||
|
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ -48,108 +47,78 @@ public class ImagesUpdater {
|
|||||||
public void updateWidgetsBundlesImages() {
|
public void updateWidgetsBundlesImages() {
|
||||||
log.info("Updating widgets bundles images...");
|
log.info("Updating widgets bundles images...");
|
||||||
var widgetsBundles = new PageDataIterable<>(widgetsBundleDao::findAllWidgetsBundles, 128);
|
var widgetsBundles = new PageDataIterable<>(widgetsBundleDao::findAllWidgetsBundles, 128);
|
||||||
int updatedCount = 0;
|
updateImages(widgetsBundles, "bundle", imageService::replaceBase64WithImageUrl, widgetsBundleDao);
|
||||||
int totalCount = 0;
|
|
||||||
for (WidgetsBundle widgetsBundle : widgetsBundles) {
|
|
||||||
totalCount++;
|
|
||||||
try {
|
|
||||||
boolean updated = imageService.replaceBase64WithImageUrl(widgetsBundle, "bundle");
|
|
||||||
if (updated) {
|
|
||||||
widgetsBundleDao.save(widgetsBundle.getTenantId(), widgetsBundle);
|
|
||||||
log.debug("[{}][{}][{}] Updated widgets bundle images", widgetsBundle.getTenantId(), widgetsBundle.getId(), widgetsBundle.getTitle());
|
|
||||||
updatedCount++;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[{}][{}][{}] Failed to update widgets bundle images", widgetsBundle.getTenantId(), widgetsBundle.getId(), widgetsBundle.getTitle(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.info("Updated {} widgets bundles out of {}", updatedCount, totalCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateWidgetTypesImages() {
|
public void updateWidgetTypesImages() {
|
||||||
log.info("Updating widget types images...");
|
log.info("Updating widget types images...");
|
||||||
var widgetTypes = new PageDataIterable<>(widgetTypeDao::findAllWidgetTypesIds, 1024);
|
var widgetTypesIds = new PageDataIterable<>(widgetTypeDao::findAllWidgetTypesIds, 1024);
|
||||||
int updatedCount = 0;
|
updateImages(widgetTypesIds, "widget type", imageService::replaceBase64WithImageUrl, widgetTypeDao);
|
||||||
int totalCount = 0;
|
|
||||||
for (WidgetTypeId widgetTypeId : widgetTypes) {
|
|
||||||
totalCount++;
|
|
||||||
WidgetTypeDetails widgetTypeDetails = widgetTypeDao.findById(TenantId.SYS_TENANT_ID, widgetTypeId.getId());
|
|
||||||
try {
|
|
||||||
boolean updated = imageService.replaceBase64WithImageUrl(widgetTypeDetails);
|
|
||||||
if (updated) {
|
|
||||||
widgetTypeDao.save(widgetTypeDetails.getTenantId(), widgetTypeDetails);
|
|
||||||
log.debug("[{}][{}][{}] Updated widget type images", widgetTypeDetails.getTenantId(), widgetTypeDetails.getId(), widgetTypeDetails.getName());
|
|
||||||
updatedCount++;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[{}][{}][{}] Failed to update widget type images", widgetTypeDetails.getTenantId(), widgetTypeDetails.getId(), widgetTypeDetails.getName(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.info("Updated {} widget types out of {}", updatedCount, totalCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateDashboardsImages() {
|
public void updateDashboardsImages() {
|
||||||
log.info("Updating dashboards images...");
|
log.info("Updating dashboards images...");
|
||||||
var dashboards = new PageDataIterable<>(dashboardDao::findAllIds, 1024);
|
var dashboardsIds = new PageDataIterable<>(dashboardDao::findAllIds, 1024);
|
||||||
int updatedCount = 0;
|
updateImages(dashboardsIds, "dashboard", imageService::replaceBase64WithImageUrl, dashboardDao);
|
||||||
int totalCount = 0;
|
|
||||||
for (DashboardId dashboardId : dashboards) {
|
|
||||||
totalCount++;
|
|
||||||
Dashboard dashboard = dashboardDao.findById(TenantId.SYS_TENANT_ID, dashboardId.getId());
|
|
||||||
try {
|
|
||||||
boolean updated = imageService.replaceBase64WithImageUrl(dashboard);
|
|
||||||
if (updated) {
|
|
||||||
dashboardDao.save(dashboard.getTenantId(), dashboard);
|
|
||||||
log.info("[{}][{}][{}] Updated dashboard images", dashboard.getTenantId(), dashboardId, dashboard.getTitle());
|
|
||||||
updatedCount++;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[{}][{}][{}] Failed to update dashboard images", dashboard.getTenantId(), dashboardId, dashboard.getTitle(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.info("Updated {} dashboards out of {}", updatedCount, totalCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateDeviceProfilesImages() {
|
public void updateDeviceProfilesImages() {
|
||||||
log.info("Updating device profiles images...");
|
log.info("Updating device profiles images...");
|
||||||
var deviceProfiles = new PageDataIterable<>(deviceProfileDao::findAll, 256);
|
var deviceProfiles = new PageDataIterable<>(deviceProfileDao::findAll, 256);
|
||||||
int updatedCount = 0;
|
updateImages(deviceProfiles, "device profile", imageService::replaceBase64WithImageUrl, deviceProfileDao);
|
||||||
int totalCount = 0;
|
|
||||||
for (DeviceProfile deviceProfile : deviceProfiles) {
|
|
||||||
totalCount++;
|
|
||||||
try {
|
|
||||||
boolean updated = imageService.replaceBase64WithImageUrl(deviceProfile, "device profile");
|
|
||||||
if (updated) {
|
|
||||||
deviceProfileDao.save(deviceProfile.getTenantId(), deviceProfile);
|
|
||||||
log.debug("[{}][{}][{}] Updated device profile images", deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile.getName());
|
|
||||||
updatedCount++;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[{}][{}][{}] Failed to update device profile images", deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile.getName(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.info("Updated {} device profiles out of {}", updatedCount, totalCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateAssetProfilesImages() {
|
public void updateAssetProfilesImages() {
|
||||||
log.info("Updating asset profiles images...");
|
log.info("Updating asset profiles images...");
|
||||||
var assetProfiles = new PageDataIterable<>(assetProfileDao::findAll, 256);
|
var assetProfiles = new PageDataIterable<>(assetProfileDao::findAll, 256);
|
||||||
|
updateImages(assetProfiles, "asset profile", imageService::replaceBase64WithImageUrl, assetProfileDao);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <E extends HasImage> void updateImages(Iterable<E> entities, String type,
|
||||||
|
BiFunction<E, String, Boolean> updater, Dao<E> dao) {
|
||||||
int updatedCount = 0;
|
int updatedCount = 0;
|
||||||
int totalCount = 0;
|
int totalCount = 0;
|
||||||
for (AssetProfile assetProfile : assetProfiles) {
|
for (E entity : entities) {
|
||||||
totalCount++;
|
totalCount++;
|
||||||
try {
|
try {
|
||||||
boolean updated = imageService.replaceBase64WithImageUrl(assetProfile, "asset profile");
|
boolean updated = updater.apply(entity, type);
|
||||||
if (updated) {
|
if (updated) {
|
||||||
assetProfileDao.save(assetProfile.getTenantId(), assetProfile);
|
dao.save(entity.getTenantId(), entity);
|
||||||
log.debug("[{}][{}][{}] Updated asset profile images", assetProfile.getTenantId(), assetProfile.getId(), assetProfile.getName());
|
log.debug("[{}][{}] Updated {} images", entity.getTenantId(), entity.getName(), type);
|
||||||
updatedCount++;
|
updatedCount++;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[{}][{}][{}] Failed to update asset profile images", assetProfile.getTenantId(), assetProfile.getId(), assetProfile.getName(), e);
|
log.error("[{}][{}] Failed to update {} images", entity.getTenantId(), entity.getName(), type, e);
|
||||||
|
}
|
||||||
|
if (totalCount % 100 == 0) {
|
||||||
|
log.info("Processed {} {}s so far", totalCount, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info("Updated {} asset profiles out of {}", updatedCount, totalCount);
|
log.info("Updated {} {}s out of {}", updatedCount, type, totalCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <E extends HasImage> void updateImages(Iterable<? extends EntityId> entitiesIds, String type,
|
||||||
|
Function<E, Boolean> updater, Dao<E> dao) {
|
||||||
|
int updatedCount = 0;
|
||||||
|
int totalCount = 0;
|
||||||
|
for (EntityId id : entitiesIds) {
|
||||||
|
totalCount++;
|
||||||
|
E entity = dao.findById(TenantId.SYS_TENANT_ID, id.getId());
|
||||||
|
try {
|
||||||
|
boolean updated = updater.apply(entity);
|
||||||
|
if (updated) {
|
||||||
|
dao.save(entity.getTenantId(), entity);
|
||||||
|
log.debug("[{}][{}] Updated {} images", entity.getTenantId(), entity.getName(), type);
|
||||||
|
updatedCount++;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[{}][{}] Failed to update {} images", entity.getTenantId(), entity.getName(), type, e);
|
||||||
|
}
|
||||||
|
if (totalCount % 100 == 0) {
|
||||||
|
log.info("Processed {} {}s so far", totalCount, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info("Updated {} {}s out of {}", updatedCount, type, totalCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -242,6 +242,10 @@
|
|||||||
<groupId>org.apache.xmlgraphics</groupId>
|
<groupId>org.apache.xmlgraphics</groupId>
|
||||||
<artifactId>batik-codec</artifactId>
|
<artifactId>batik-codec</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.drewnoakes</groupId>
|
||||||
|
<artifactId>metadata-extractor</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
|||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -285,8 +286,10 @@ public class BaseImageService extends BaseResourceService implements ImageServic
|
|||||||
if (entity.getDescriptor().isObject()) {
|
if (entity.getDescriptor().isObject()) {
|
||||||
ObjectNode descriptor = (ObjectNode) entity.getDescriptor();
|
ObjectNode descriptor = (ObjectNode) entity.getDescriptor();
|
||||||
JsonNode defaultConfig = JacksonUtil.toJsonNode(descriptor.get("defaultConfig").asText());
|
JsonNode defaultConfig = JacksonUtil.toJsonNode(descriptor.get("defaultConfig").asText());
|
||||||
updated |= base64ToImageUrlUsingMapping(entity.getTenantId(), WIDGET_TYPE_BASE64_MAPPING, Collections.singletonMap("prefix", prefix), defaultConfig);
|
if (defaultConfig != null && defaultConfig.isObject()) {
|
||||||
descriptor.put("defaultConfig", defaultConfig.toString());
|
updated |= base64ToImageUrlUsingMapping(entity.getTenantId(), WIDGET_TYPE_BASE64_MAPPING, Collections.singletonMap("prefix", prefix), defaultConfig);
|
||||||
|
descriptor.put("defaultConfig", defaultConfig.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updated |= base64ToImageUrlRecursively(entity.getTenantId(), prefix, entity.getDescriptor());
|
updated |= base64ToImageUrlRecursively(entity.getTenantId(), prefix, entity.getDescriptor());
|
||||||
return updated;
|
return updated;
|
||||||
@ -313,6 +316,9 @@ public class BaseImageService extends BaseResourceService implements ImageServic
|
|||||||
JsonPathProcessingTask task = tasks.poll();
|
JsonPathProcessingTask task = tasks.poll();
|
||||||
String token = task.currentToken();
|
String token = task.currentToken();
|
||||||
JsonNode node = task.getNode();
|
JsonNode node = task.getNode();
|
||||||
|
if (node == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (token.equals("*") || token.startsWith("$")) {
|
if (token.equals("*") || token.startsWith("$")) {
|
||||||
String variableName = token.startsWith("$") ? token.substring(1) : null;
|
String variableName = token.startsWith("$") ? token.substring(1) : null;
|
||||||
if (node.isArray()) {
|
if (node.isArray()) {
|
||||||
@ -345,8 +351,8 @@ public class BaseImageService extends BaseResourceService implements ImageServic
|
|||||||
}
|
}
|
||||||
if (task.isLast()) {
|
if (task.isLast()) {
|
||||||
String name = expression;
|
String name = expression;
|
||||||
for (var replacements : task.getVariables().entrySet()) {
|
for (var replacement : task.getVariables().entrySet()) {
|
||||||
name = name.replace("$" + replacements.getKey(), replacements.getValue());
|
name = name.replace("$" + replacement.getKey(), Strings.nullToEmpty(replacement.getValue()));
|
||||||
}
|
}
|
||||||
if (node.isObject() && value.isTextual()) {
|
if (node.isObject() && value.isTextual()) {
|
||||||
var result = base64ToImageUrl(tenantId, name, value.asText());
|
var result = base64ToImageUrl(tenantId, name, value.asText());
|
||||||
@ -362,7 +368,7 @@ public class BaseImageService extends BaseResourceService implements ImageServic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (StringUtils.isNotEmpty(variableName) && StringUtils.isNotEmpty(variableValue)) {
|
if (StringUtils.isNotEmpty(variableName)) {
|
||||||
tasks.add(task.next(value, variableName, variableValue));
|
tasks.add(task.next(value, variableName, variableValue));
|
||||||
} else {
|
} else {
|
||||||
tasks.add(task.next(value));
|
tasks.add(task.next(value));
|
||||||
@ -404,6 +410,9 @@ public class BaseImageService extends BaseResourceService implements ImageServic
|
|||||||
byte[] imageData = Base64.getDecoder().decode(base64Data);
|
byte[] imageData = Base64.getDecoder().decode(base64Data);
|
||||||
String etag = calculateEtag(imageData);
|
String etag = calculateEtag(imageData);
|
||||||
var imageInfo = findImageByTenantIdAndEtag(tenantId, etag);
|
var imageInfo = findImageByTenantIdAndEtag(tenantId, etag);
|
||||||
|
if (imageInfo == null && !tenantId.isSysTenantId()) {
|
||||||
|
imageInfo = findImageByTenantIdAndEtag(TenantId.SYS_TENANT_ID, etag);
|
||||||
|
}
|
||||||
if (imageInfo == null) {
|
if (imageInfo == null) {
|
||||||
TbResource image = new TbResource();
|
TbResource image = new TbResource();
|
||||||
image.setTenantId(tenantId);
|
image.setTenantId(tenantId);
|
||||||
@ -415,9 +424,9 @@ public class BaseImageService extends BaseResourceService implements ImageServic
|
|||||||
|
|
||||||
String fileName;
|
String fileName;
|
||||||
if (StringUtils.isBlank(mdResourceKey)) {
|
if (StringUtils.isBlank(mdResourceKey)) {
|
||||||
fileName = mdResourceName.toLowerCase()
|
fileName = StringUtils.strip(mdResourceName.toLowerCase()
|
||||||
.replace("'", "").replace("\"", "")
|
.replaceAll("['\"]", "")
|
||||||
.replace(" ", "_").replace("/", "_")
|
.replaceAll("[^\\pL\\d]+", "_"), "_") // leaving only letters and numbers
|
||||||
+ "." + extension;
|
+ "." + extension;
|
||||||
} else {
|
} else {
|
||||||
fileName = mdResourceKey;
|
fileName = mdResourceKey;
|
||||||
|
|||||||
@ -43,7 +43,7 @@ public interface DashboardRepository extends JpaRepository<DashboardEntity, UUID
|
|||||||
@Query("SELECT d.id FROM DashboardEntity d WHERE d.tenantId = :tenantId")
|
@Query("SELECT d.id FROM DashboardEntity d WHERE d.tenantId = :tenantId")
|
||||||
Page<UUID> findIdsByTenantId(@Param("tenantId") UUID tenantId, Pageable pageable);
|
Page<UUID> findIdsByTenantId(@Param("tenantId") UUID tenantId, Pageable pageable);
|
||||||
|
|
||||||
@Query("SELECT id FROM DashboardEntity")
|
@Query("SELECT d.id FROM DashboardEntity d")
|
||||||
Page<UUID> findAllIds(Pageable pageable);
|
Page<UUID> findAllIds(Pageable pageable);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -91,7 +91,7 @@ public interface TbResourceRepository extends JpaRepository<TbResourceEntity, UU
|
|||||||
@Query("SELECT externalId FROM TbResourceInfoEntity WHERE id = :id")
|
@Query("SELECT externalId FROM TbResourceInfoEntity WHERE id = :id")
|
||||||
UUID getExternalIdByInternal(@Param("id") UUID internalId);
|
UUID getExternalIdByInternal(@Param("id") UUID internalId);
|
||||||
|
|
||||||
@Query("SELECT id FROM TbResourceInfoEntity WHERE tenantId = :tenantId")
|
@Query("SELECT r.id FROM TbResourceInfoEntity r WHERE r.tenantId = :tenantId")
|
||||||
Page<UUID> findIdsByTenantId(@Param("tenantId") UUID tenantId, Pageable pageable);
|
Page<UUID> findIdsByTenantId(@Param("tenantId") UUID tenantId, Pageable pageable);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,17 +69,17 @@ public interface WidgetTypeRepository extends JpaRepository<WidgetTypeDetailsEnt
|
|||||||
|
|
||||||
@Query(value = "SELECT * FROM widget_type wt " +
|
@Query(value = "SELECT * FROM widget_type wt " +
|
||||||
"WHERE wt.tenant_id = :tenantId AND cast(wt.descriptor as json) ->> 'resources' LIKE LOWER(CONCAT('%', :resourceId, '%'))",
|
"WHERE wt.tenant_id = :tenantId AND cast(wt.descriptor as json) ->> 'resources' LIKE LOWER(CONCAT('%', :resourceId, '%'))",
|
||||||
nativeQuery = true)
|
nativeQuery = true)
|
||||||
List<WidgetTypeDetailsEntity> findWidgetTypesInfosByTenantIdAndResourceId(@Param("tenantId") UUID tenantId,
|
List<WidgetTypeDetailsEntity> findWidgetTypesInfosByTenantIdAndResourceId(@Param("tenantId") UUID tenantId,
|
||||||
@Param("resourceId") UUID resourceId);
|
@Param("resourceId") UUID resourceId);
|
||||||
|
|
||||||
@Query("SELECT externalId FROM WidgetTypeDetailsEntity WHERE id = :id")
|
@Query("SELECT externalId FROM WidgetTypeDetailsEntity WHERE id = :id")
|
||||||
UUID getExternalIdById(@Param("id") UUID id);
|
UUID getExternalIdById(@Param("id") UUID id);
|
||||||
|
|
||||||
@Query("SELECT id FROM WidgetTypeDetailsEntity")
|
@Query("SELECT w.id FROM WidgetTypeDetailsEntity w")
|
||||||
Page<UUID> findAllIds(Pageable pageable);
|
Page<UUID> findAllIds(Pageable pageable);
|
||||||
|
|
||||||
@Query("SELECT id FROM WidgetTypeDetailsEntity WHERE tenantId = :tenantId")
|
@Query("SELECT w.id FROM WidgetTypeDetailsEntity w WHERE w.tenantId = :tenantId")
|
||||||
Page<UUID> findIdsByTenantId(@Param("tenantId") UUID tenantId, Pageable pageable);
|
Page<UUID> findIdsByTenantId(@Param("tenantId") UUID tenantId, Pageable pageable);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,9 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.thingsboard.server.dao.util;
|
package org.thingsboard.server.dao.util;
|
||||||
|
|
||||||
|
import com.drew.imaging.ImageMetadataReader;
|
||||||
|
import com.drew.metadata.Directory;
|
||||||
|
import com.drew.metadata.Metadata;
|
||||||
|
import com.drew.metadata.Tag;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.batik.anim.dom.SAXSVGDocumentFactory;
|
import org.apache.batik.anim.dom.SAXSVGDocumentFactory;
|
||||||
import org.apache.batik.bridge.BridgeContext;
|
import org.apache.batik.bridge.BridgeContext;
|
||||||
import org.apache.batik.bridge.DocumentLoader;
|
import org.apache.batik.bridge.DocumentLoader;
|
||||||
@ -41,11 +46,13 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@Slf4j
|
||||||
public class ImageUtils {
|
public class ImageUtils {
|
||||||
|
|
||||||
private static final Map<String, String> mediaTypeMappings = Map.of(
|
private static final Map<String, String> mediaTypeMappings = Map.of(
|
||||||
"jpeg", "jpg",
|
"jpeg", "jpg",
|
||||||
"svg+xml", "svg"
|
"svg+xml", "svg",
|
||||||
|
"x-icon", "ico"
|
||||||
);
|
);
|
||||||
|
|
||||||
public static String mediaTypeToFileExtension(String mimeType) {
|
public static String mediaTypeToFileExtension(String mimeType) {
|
||||||
@ -64,15 +71,49 @@ public class ImageUtils {
|
|||||||
if (mediaTypeToFileExtension(mediaType).equals("svg")) {
|
if (mediaTypeToFileExtension(mediaType).equals("svg")) {
|
||||||
return processSvgImage(data, mediaType, thumbnailMaxDimension);
|
return processSvgImage(data, mediaType, thumbnailMaxDimension);
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferedImage bufferedImage = ImageIO.read(new ByteArrayInputStream(data));
|
|
||||||
ProcessedImage image = new ProcessedImage();
|
ProcessedImage image = new ProcessedImage();
|
||||||
image.setMediaType(mediaType);
|
image.setMediaType(mediaType);
|
||||||
image.setWidth(bufferedImage.getWidth());
|
|
||||||
image.setHeight(bufferedImage.getHeight());
|
|
||||||
image.setData(data);
|
image.setData(data);
|
||||||
image.setSize(data.length);
|
image.setSize(data.length);
|
||||||
|
|
||||||
|
BufferedImage bufferedImage = null;
|
||||||
|
try {
|
||||||
|
bufferedImage = ImageIO.read(new ByteArrayInputStream(data));
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
if (bufferedImage == null) { // means that media type is not supported by ImageIO; extracting width and height from metadata and leaving preview as original image
|
||||||
|
Metadata metadata = ImageMetadataReader.readMetadata(new ByteArrayInputStream(data));
|
||||||
|
for (Directory dir : metadata.getDirectories()) {
|
||||||
|
Tag widthTag = dir.getTags().stream()
|
||||||
|
.filter(tag -> tag.getTagName().toLowerCase().contains("width"))
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
Tag heightTag = dir.getTags().stream()
|
||||||
|
.filter(tag -> tag.getTagName().toLowerCase().contains("height"))
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
if (widthTag == null || heightTag == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int width = Integer.parseInt(dir.getObject(widthTag.getTagType()).toString());
|
||||||
|
int height = Integer.parseInt(dir.getObject(widthTag.getTagType()).toString());
|
||||||
|
image.setWidth(width);
|
||||||
|
image.setHeight(height);
|
||||||
|
|
||||||
|
ProcessedImage preview = new ProcessedImage();
|
||||||
|
preview.setWidth(image.getWidth());
|
||||||
|
preview.setHeight(image.getHeight());
|
||||||
|
preview.setMediaType(mediaType);
|
||||||
|
preview.setData(null);
|
||||||
|
preview.setSize(data.length);
|
||||||
|
image.setPreview(preview);
|
||||||
|
log.debug("Couldn't parse {} ({}) with ImageIO, got width {} and height {} from metadata", mediaType, dir.getName(), width, height);
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Media type " + mediaType + " not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
image.setWidth(bufferedImage.getWidth());
|
||||||
|
image.setHeight(bufferedImage.getHeight());
|
||||||
|
|
||||||
ProcessedImage preview = new ProcessedImage();
|
ProcessedImage preview = new ProcessedImage();
|
||||||
int[] thumbnailDimensions = getThumbnailDimensions(image.getWidth(), image.getHeight(), thumbnailMaxDimension);
|
int[] thumbnailDimensions = getThumbnailDimensions(image.getWidth(), image.getHeight(), thumbnailMaxDimension);
|
||||||
preview.setWidth(thumbnailDimensions[0]);
|
preview.setWidth(thumbnailDimensions[0]);
|
||||||
@ -101,18 +142,19 @@ public class ImageUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static ProcessedImage processSvgImage(byte[] data, String mediaType, int thumbnailMaxDimension) throws Exception {
|
public static ProcessedImage processSvgImage(byte[] data, String mediaType, int thumbnailMaxDimension) throws Exception {
|
||||||
SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory(
|
SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory(XMLResourceDescriptor.getXMLParserClassName());
|
||||||
XMLResourceDescriptor.getXMLParserClassName());
|
Document document = factory.createDocument(null, new ByteArrayInputStream(data));
|
||||||
Document document = factory.createDocument(
|
|
||||||
null, new ByteArrayInputStream(data));
|
|
||||||
Integer width = null;
|
Integer width = null;
|
||||||
Integer height = null;
|
Integer height = null;
|
||||||
String strWidth = document.getDocumentElement().getAttribute("width");
|
String strWidth = document.getDocumentElement().getAttribute("width");
|
||||||
String strHeight = document.getDocumentElement().getAttribute("height");
|
String strHeight = document.getDocumentElement().getAttribute("height");
|
||||||
if (StringUtils.isNotEmpty(strWidth) && StringUtils.isNotEmpty(strHeight)) {
|
if (StringUtils.isNotEmpty(strWidth) && StringUtils.isNotEmpty(strHeight)) {
|
||||||
width = (int) Double.parseDouble(strWidth);
|
try {
|
||||||
height = (int) Double.parseDouble(strHeight);;
|
width = (int) Double.parseDouble(strWidth);
|
||||||
} else {
|
height = (int) Double.parseDouble(strHeight);
|
||||||
|
} catch (NumberFormatException ignored) {} // in case width and height are in %, mm, etc.
|
||||||
|
}
|
||||||
|
if (width == null || height == null) {
|
||||||
String viewBox = document.getDocumentElement().getAttribute("viewBox");
|
String viewBox = document.getDocumentElement().getAttribute("viewBox");
|
||||||
if (StringUtils.isNotEmpty(viewBox)) {
|
if (StringUtils.isNotEmpty(viewBox)) {
|
||||||
String[] viewBoxValues = viewBox.split(" ");
|
String[] viewBoxValues = viewBox.split(" ");
|
||||||
@ -124,10 +166,10 @@ public class ImageUtils {
|
|||||||
}
|
}
|
||||||
if (width == null) {
|
if (width == null) {
|
||||||
UserAgent agent = new UserAgentAdapter();
|
UserAgent agent = new UserAgentAdapter();
|
||||||
DocumentLoader loader= new DocumentLoader(agent);
|
DocumentLoader loader = new DocumentLoader(agent);
|
||||||
BridgeContext context = new BridgeContext(agent, loader);
|
BridgeContext context = new BridgeContext(agent, loader);
|
||||||
context.setDynamic(true);
|
context.setDynamic(true);
|
||||||
GVTBuilder builder= new GVTBuilder();
|
GVTBuilder builder = new GVTBuilder();
|
||||||
GraphicsNode root = builder.build(context, document);
|
GraphicsNode root = builder.build(context, document);
|
||||||
var bounds = root.getPrimitiveBounds();
|
var bounds = root.getPrimitiveBounds();
|
||||||
if (bounds != null) {
|
if (bounds != null) {
|
||||||
|
|||||||
6
pom.xml
6
pom.xml
@ -154,6 +154,7 @@
|
|||||||
<oshi.version>6.4.2</oshi.version>
|
<oshi.version>6.4.2</oshi.version>
|
||||||
<google-oauth-client.version>1.34.1</google-oauth-client.version>
|
<google-oauth-client.version>1.34.1</google-oauth-client.version>
|
||||||
<apache-xmlgraphics.version>1.17</apache-xmlgraphics.version>
|
<apache-xmlgraphics.version>1.17</apache-xmlgraphics.version>
|
||||||
|
<drewnoakes-metadata-extractor.version>2.19.0</drewnoakes-metadata-extractor.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
@ -2040,6 +2041,11 @@
|
|||||||
<artifactId>batik-codec</artifactId>
|
<artifactId>batik-codec</artifactId>
|
||||||
<version>${apache-xmlgraphics.version}</version>
|
<version>${apache-xmlgraphics.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.drewnoakes</groupId>
|
||||||
|
<artifactId>metadata-extractor</artifactId>
|
||||||
|
<version>${drewnoakes-metadata-extractor.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user