Unique image resource key based on index
This commit is contained in:
parent
2b9eab0150
commit
e7aabe80a4
@ -125,7 +125,8 @@ public class ImageController extends BaseController {
|
||||
TenantId tenantId = getTenantId();
|
||||
TbResourceInfo imageInfo = checkImageInfo(type, key, Operation.READ);
|
||||
ImageDescriptor descriptor = imageInfo.getDescriptor(ImageDescriptor.class);
|
||||
return downloadIfChanged(etag, imageInfo, descriptor, () -> imageService.getImageData(tenantId, imageInfo.getId()));
|
||||
return downloadIfChanged(etag, descriptor, imageInfo.getFileName(),
|
||||
() -> imageService.getImageData(tenantId, imageInfo.getId()));
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
|
||||
@ -136,7 +137,8 @@ public class ImageController extends BaseController {
|
||||
TenantId tenantId = getTenantId();
|
||||
TbResourceInfo imageInfo = checkImageInfo(type, key, Operation.READ);
|
||||
ImageDescriptor descriptor = imageInfo.getDescriptor(ImageDescriptor.class);
|
||||
return downloadIfChanged(etag, imageInfo, descriptor.getPreviewDescriptor(), () -> imageService.getImagePreview(tenantId, imageInfo.getId()));
|
||||
return downloadIfChanged(etag, descriptor.getPreviewDescriptor(), imageInfo.getFileName(),
|
||||
() -> imageService.getImagePreview(tenantId, imageInfo.getId()));
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
|
||||
@ -176,24 +178,22 @@ public class ImageController extends BaseController {
|
||||
tbImageService.delete(imageInfo, getCurrentUser());
|
||||
}
|
||||
|
||||
private ResponseEntity<ByteArrayResource> downloadIfChanged(String etag, TbResourceInfo imageInfo, ImageDescriptor imageDescriptor,
|
||||
Supplier<byte[]> dataSupplier) {
|
||||
if (etag != null) {
|
||||
if (etag.equals(imageInfo.getEtag())) {
|
||||
return ResponseEntity.status(HttpStatus.NOT_MODIFIED)
|
||||
.eTag(etag)
|
||||
.build();
|
||||
}
|
||||
private ResponseEntity<ByteArrayResource> downloadIfChanged(String actualEtag, ImageDescriptor imageDescriptor,
|
||||
String fileName, Supplier<byte[]> dataSupplier) {
|
||||
if (imageDescriptor.getEtag().equals(actualEtag)) {
|
||||
return ResponseEntity.status(HttpStatus.NOT_MODIFIED)
|
||||
.eTag(actualEtag)
|
||||
.build();
|
||||
}
|
||||
|
||||
byte[] data = dataSupplier.get();
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + imageInfo.getFileName())
|
||||
.header("x-filename", imageInfo.getFileName())
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileName)
|
||||
.header("x-filename", fileName)
|
||||
.contentLength(data.length)
|
||||
.header("Content-Type", imageDescriptor.getMediaType())
|
||||
.cacheControl(CacheControl.noCache())
|
||||
.eTag(imageInfo.getEtag())
|
||||
.eTag(imageDescriptor.getEtag())
|
||||
.body(new ByteArrayResource(data));
|
||||
}
|
||||
|
||||
|
||||
@ -39,7 +39,6 @@ public class DefaultTbImageService extends AbstractTbEntityService implements Tb
|
||||
public TbResourceInfo save(TbResource image, User user) throws Exception {
|
||||
ActionType actionType = image.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
|
||||
TenantId tenantId = image.getTenantId();
|
||||
image.setResourceKey(image.getFileName()); // TODO: generate unique resource key file_name+idx
|
||||
try {
|
||||
TbResourceInfo savedImage = imageService.saveImage(image);
|
||||
notificationEntityService.logEntityAction(tenantId, savedImage.getId(), savedImage, actionType, user);
|
||||
|
||||
@ -25,5 +25,6 @@ public class ImageDescriptor {
|
||||
private int width;
|
||||
private int height;
|
||||
private long size;
|
||||
private String etag;
|
||||
private ImageDescriptor previewDescriptor;
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@ -37,4 +38,14 @@ public class RegexUtils {
|
||||
return pattern.matcher(input).matches();
|
||||
}
|
||||
|
||||
public static String getMatch(String input, Pattern pattern, int group) {
|
||||
Matcher matcher = pattern.matcher(input);
|
||||
if (matcher.find()) {
|
||||
try {
|
||||
return matcher.group(group);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -16,9 +16,12 @@
|
||||
package org.thingsboard.server.dao.resource;
|
||||
|
||||
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.ImageDescriptor;
|
||||
import org.thingsboard.server.common.data.ResourceType;
|
||||
import org.thingsboard.server.common.data.TbResource;
|
||||
@ -32,7 +35,9 @@ import org.thingsboard.server.dao.service.validator.ResourceDataValidator;
|
||||
import org.thingsboard.server.dao.util.ImageUtils;
|
||||
import org.thingsboard.server.dao.util.ImageUtils.ProcessedImage;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@ -42,17 +47,22 @@ public class BaseImageService extends BaseResourceService implements ImageServic
|
||||
super(resourceDao, resourceInfoDao, resourceValidator);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public TbResourceInfo saveImage(TbResource image) throws Exception {
|
||||
if (image.getId() == null) {
|
||||
image.setResourceKey(getUniqueKey(image.getTenantId(), image.getFileName()));
|
||||
}
|
||||
resourceValidator.validate(image, TbResourceInfo::getTenantId);
|
||||
|
||||
ImageDescriptor descriptor = image.getDescriptor(ImageDescriptor.class);
|
||||
Pair<ImageDescriptor, byte[]> result = processImage(image.getData(), descriptor);
|
||||
image.setDescriptor(JacksonUtil.valueToTree(result.getLeft()));
|
||||
descriptor = result.getLeft();
|
||||
image.setEtag(descriptor.getEtag());
|
||||
image.setDescriptor(JacksonUtil.valueToTree(descriptor));
|
||||
image.setPreview(result.getRight());
|
||||
|
||||
image = saveResource(image, false);
|
||||
return new TbResourceInfo(image);
|
||||
return new TbResourceInfo(doSaveResource(image));
|
||||
}
|
||||
|
||||
private Pair<ImageDescriptor, byte[]> processImage(byte[] data, ImageDescriptor descriptor) throws Exception {
|
||||
@ -62,17 +72,42 @@ public class BaseImageService extends BaseResourceService implements ImageServic
|
||||
descriptor.setWidth(image.getWidth());
|
||||
descriptor.setHeight(image.getHeight());
|
||||
descriptor.setSize(image.getSize());
|
||||
descriptor.setEtag(calculateEtag(data));
|
||||
|
||||
ImageDescriptor previewDescriptor = new ImageDescriptor();
|
||||
previewDescriptor.setWidth(preview.getWidth());
|
||||
previewDescriptor.setHeight(preview.getHeight());
|
||||
previewDescriptor.setMediaType(preview.getMediaType());
|
||||
previewDescriptor.setSize(preview.getSize());
|
||||
previewDescriptor.setEtag(preview.getData() != null ? calculateEtag(preview.getData()) : descriptor.getEtag());
|
||||
descriptor.setPreviewDescriptor(previewDescriptor);
|
||||
|
||||
return Pair.of(descriptor, preview.getData());
|
||||
}
|
||||
|
||||
private String getUniqueKey(TenantId tenantId, String filename) {
|
||||
if (!resourceInfoDao.existsByTenantIdAndResourceTypeAndResourceKey(tenantId, ResourceType.IMAGE, filename)) {
|
||||
return filename;
|
||||
}
|
||||
|
||||
String basename = StringUtils.substringBeforeLast(filename, ".");
|
||||
String extension = StringUtils.substringAfterLast(filename, ".");
|
||||
|
||||
Pattern similarImagesPattern = Pattern.compile(
|
||||
Pattern.quote(basename) + "_(\\d+)\\.?" + Pattern.quote(extension)
|
||||
);
|
||||
int maxImageIdx = resourceInfoDao.findKeysByTenantIdAndResourceTypeAndResourceKeyStartingWith(
|
||||
tenantId, ResourceType.IMAGE, basename + "_").stream()
|
||||
.map(key -> RegexUtils.getMatch(key, similarImagesPattern, 1))
|
||||
.filter(Objects::nonNull).mapToInt(Integer::parseInt)
|
||||
.max().orElse(0);
|
||||
String uniqueKey = basename + "_" + (maxImageIdx + 1);
|
||||
if (!extension.isEmpty()) {
|
||||
uniqueKey += "." + extension;
|
||||
}
|
||||
return uniqueKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TbResourceInfo saveImageInfo(TbResourceInfo imageInfo) {
|
||||
return saveResource(new TbResource(imageInfo));
|
||||
|
||||
@ -67,11 +67,19 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
|
||||
if (doValidate) {
|
||||
resourceValidator.validate(resource, TbResourceInfo::getTenantId);
|
||||
}
|
||||
TenantId tenantId = resource.getTenantId();
|
||||
TbResourceId resourceId = resource.getId();
|
||||
if (resource.getData() != null) {
|
||||
resource.setEtag(calculateEtag(resource.getData()));
|
||||
}
|
||||
return doSaveResource(resource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TbResource saveResource(TbResource resource) {
|
||||
return saveResource(resource, true);
|
||||
}
|
||||
|
||||
protected TbResource doSaveResource(TbResource resource) {
|
||||
TenantId tenantId = resource.getTenantId();
|
||||
try {
|
||||
TbResource saved;
|
||||
if (resource.getData() != null) {
|
||||
@ -80,12 +88,12 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
|
||||
TbResourceInfo resourceInfo = saveResourceInfo(resource);
|
||||
saved = new TbResource(resourceInfo);
|
||||
}
|
||||
publishEvictEvent(new ResourceInfoEvictEvent(tenantId, resourceId));
|
||||
publishEvictEvent(new ResourceInfoEvictEvent(tenantId, resource.getId()));
|
||||
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(saved.getTenantId())
|
||||
.entityId(saved.getId()).added(resourceId == null).build());
|
||||
.entityId(saved.getId()).added(resource.getId() == null).build());
|
||||
return saved;
|
||||
} catch (Exception t) {
|
||||
publishEvictEvent(new ResourceInfoEvictEvent(tenantId, resourceId));
|
||||
publishEvictEvent(new ResourceInfoEvictEvent(tenantId, resource.getId()));
|
||||
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
|
||||
if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("resource_unq_key")) {
|
||||
String field = ResourceType.LWM2M_MODEL.equals(resource.getResourceType()) ? "resourceKey" : "fileName";
|
||||
@ -96,11 +104,6 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TbResource saveResource(TbResource resource) {
|
||||
return saveResource(resource, true);
|
||||
}
|
||||
|
||||
private TbResourceInfo saveResourceInfo(TbResource resource) {
|
||||
return resourceInfoDao.save(resource.getTenantId(), new TbResourceInfo(resource));
|
||||
}
|
||||
|
||||
@ -33,6 +33,10 @@ public interface TbResourceInfoDao extends Dao<TbResourceInfo> {
|
||||
|
||||
TbResourceInfo findByTenantIdAndKey(TenantId tenantId, ResourceType resourceType, String resourceKey);
|
||||
|
||||
boolean existsByTenantIdAndResourceTypeAndResourceKey(TenantId tenantId, ResourceType resourceType, String resourceKey);
|
||||
|
||||
List<String> findKeysByTenantIdAndResourceTypeAndResourceKeyStartingWith(TenantId tenantId, ResourceType resourceType, String resourceKeyQuery);
|
||||
|
||||
List<TbResourceInfo> findByTenantIdAndEtagAndKeyStartingWith(TenantId tenantId, String etag, String query);
|
||||
|
||||
}
|
||||
|
||||
@ -15,10 +15,10 @@
|
||||
*/
|
||||
package org.thingsboard.server.dao.service.validator;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.thingsboard.server.common.data.StringUtils;
|
||||
import org.thingsboard.server.common.data.TbResource;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
@ -95,6 +95,9 @@ public class ResourceDataValidator extends DataValidator<TbResource> {
|
||||
if (StringUtils.isEmpty(resource.getFileName())) {
|
||||
throw new DataValidationException("Resource file name should be specified!");
|
||||
}
|
||||
if (StringUtils.containsAny(resource.getFileName(), "/", "\\")) {
|
||||
throw new DataValidationException("File name contains forbidden symbols");
|
||||
}
|
||||
if (StringUtils.isEmpty(resource.getResourceKey())) {
|
||||
throw new DataValidationException("Resource key should be specified!");
|
||||
}
|
||||
|
||||
@ -82,6 +82,16 @@ public class JpaTbResourceInfoDao extends JpaAbstractDao<TbResourceInfoEntity, T
|
||||
return DaoUtil.getData(resourceInfoRepository.findByTenantIdAndResourceTypeAndResourceKey(tenantId.getId(), resourceType.name(), resourceKey));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsByTenantIdAndResourceTypeAndResourceKey(TenantId tenantId, ResourceType resourceType, String resourceKey) {
|
||||
return resourceInfoRepository.existsByTenantIdAndResourceTypeAndResourceKey(tenantId.getId(), resourceType.name(), resourceKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> findKeysByTenantIdAndResourceTypeAndResourceKeyStartingWith(TenantId tenantId, ResourceType resourceType, String resourceKeyQuery) {
|
||||
return resourceInfoRepository.findKeysByTenantIdAndResourceTypeAndResourceKeyStartingWith(tenantId.getId(), resourceType.name(), resourceKeyQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TbResourceInfo> findByTenantIdAndEtagAndKeyStartingWith(TenantId tenantId, String etag, String query) {
|
||||
return DaoUtil.convertDataList(resourceInfoRepository.findByTenantIdAndHashCodeAndResourceKeyStartingWith(tenantId.getId(), etag, query));
|
||||
|
||||
@ -22,7 +22,6 @@ import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.thingsboard.server.dao.model.sql.TbResourceInfoEntity;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@ -55,6 +54,14 @@ public interface TbResourceInfoRepository extends JpaRepository<TbResourceInfoEn
|
||||
|
||||
TbResourceInfoEntity findByTenantIdAndResourceTypeAndResourceKey(UUID tenantId, String resourceType, String resourceKey);
|
||||
|
||||
boolean existsByTenantIdAndResourceTypeAndResourceKey(UUID tenantId, String resourceType, String resourceKey);
|
||||
|
||||
@Query(value = "SELECT r.resource_key FROM resource r WHERE r.tenant_id = :tenantId AND r.resource_type = :resourceType " +
|
||||
"AND starts_with(r.resource_key, :resourceKeyStartsWith)", nativeQuery = true)
|
||||
List<String> findKeysByTenantIdAndResourceTypeAndResourceKeyStartingWith(@Param("tenantId") UUID tenantId,
|
||||
@Param("resourceType") String resourceType,
|
||||
@Param("resourceKeyStartsWith") String resourceKeyStartsWith);
|
||||
|
||||
List<TbResourceInfoEntity> findByTenantIdAndHashCodeAndResourceKeyStartingWith(UUID tenantId, String hashCode, String query);
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user