diff --git a/application/src/main/data/upgrade/3.6.1/schema_update.sql b/application/src/main/data/upgrade/3.6.1/schema_update.sql index 27c7adf523..19362bb38e 100644 --- a/application/src/main/data/upgrade/3.6.1/schema_update.sql +++ b/application/src/main/data/upgrade/3.6.1/schema_update.sql @@ -27,9 +27,9 @@ $$ END IF; END; $$; -ALTER TABLE resource - ADD COLUMN IF NOT EXISTS descriptor varchar, - ADD COLUMN IF NOT EXISTS preview bytea; + +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 -- RESOURCES UPDATE END diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 754537ef11..ac074f983b 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -82,6 +82,8 @@ 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.service.resource.TbImageService; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequestActorMsg; import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; @@ -140,6 +142,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> usageStatsConsumer; private final TbQueueConsumer> firmwareStatesConsumer; + private final TbImageService imageService; protected volatile ExecutorService consumersExecutor; protected volatile ExecutorService usageStatsExecutor; @@ -165,7 +168,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService jwtSettingsService, NotificationSchedulerService notificationSchedulerService, - NotificationRuleProcessor notificationRuleProcessor) { + NotificationRuleProcessor notificationRuleProcessor, + TbImageService imageService) { super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService); this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer(); this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer(); @@ -181,6 +185,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService cache; - public DefaultTbImageService(ImageService imageService, - @Value("${cache.image.etag.timeToLiveInMinutes:120}") int cacheTtl, - @Value("${cache.image.etag.maxSize:200000}") int cacheMaxSize) { + public DefaultTbImageService(TbClusterService clusterService, ImageService imageService, + @Value("${cache.image.etag.timeToLiveInMinutes:44640}") int cacheTtl, + @Value("${cache.image.etag.maxSize:10000}") int cacheMaxSize) { + this.clusterService = clusterService; this.imageService = imageService; this.cache = Caffeine.newBuilder() .expireAfterAccess(cacheTtl, TimeUnit.MINUTES) @@ -60,13 +69,33 @@ public class DefaultTbImageService extends AbstractTbEntityService implements Tb cache.put(imageCacheKey, etag); } + @Override + public void evictETag(ImageCacheKey imageCacheKey) { + cache.invalidate(imageCacheKey); + } + @Override public TbResourceInfo save(TbResource image, User user) throws Exception { ActionType actionType = image.getId() == null ? ActionType.ADDED : ActionType.UPDATED; TenantId tenantId = image.getTenantId(); try { + var oldEtag = getEtag(image); TbResourceInfo savedImage = imageService.saveImage(image); notificationEntityService.logEntityAction(tenantId, savedImage.getId(), savedImage, actionType, user); + if (oldEtag.isPresent()) { + var newEtag = getEtag(savedImage); + if (newEtag.isPresent() && !oldEtag.get().equals(newEtag.get())) { + evictETag(new ImageCacheKey(image.getTenantId(), image.getResourceKey(), false)); + evictETag(new ImageCacheKey(image.getTenantId(), image.getResourceKey(), true)); + clusterService.broadcastToCore(TransportProtos.ToCoreNotificationMsg.newBuilder() + .setResourceCacheInvalidateMsg(TransportProtos.ResourceCacheInvalidateMsg.newBuilder() + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setResourceKey(image.getResourceKey()) + .build()) + .build()); + } + } return savedImage; } catch (Exception e) { image.setData(null); @@ -75,6 +104,17 @@ public class DefaultTbImageService extends AbstractTbEntityService implements Tb } } + private Optional getEtag(TbResourceInfo image) throws JsonProcessingException { + var descriptor = image.getDescriptor(ImageDescriptor.class); + return Optional.ofNullable(descriptor != null ? descriptor.getEtag() : null); + } + + private Optional getPreviewEtag(TbResourceInfo image) throws JsonProcessingException { + var descriptor = image.getDescriptor(ImageDescriptor.class); + descriptor = descriptor != null ? descriptor.getPreviewDescriptor() : null; + return Optional.ofNullable(descriptor != null ? descriptor.getEtag() : null); + } + @Override public TbResourceInfo save(TbResourceInfo imageInfo, User user) { TenantId tenantId = imageInfo.getTenantId(); diff --git a/application/src/main/java/org/thingsboard/server/service/resource/TbImageService.java b/application/src/main/java/org/thingsboard/server/service/resource/TbImageService.java index 2e2f95e4c7..adcf3736f1 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/TbImageService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/TbImageService.java @@ -19,6 +19,7 @@ import org.thingsboard.server.common.data.TbImageDeleteResult; 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.id.TenantId; public interface TbImageService { @@ -31,4 +32,6 @@ public interface TbImageService { String getETag(ImageCacheKey imageCacheKey); void putETag(ImageCacheKey imageCacheKey, String etag); + + void evictETag(ImageCacheKey imageCacheKey); } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 6dde5de957..faa799b7e4 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -589,7 +589,7 @@ cache: image: etag: timeToLiveInMinutes: "${CACHE_SPECS_IMAGE_ETAGS_TTL:44640}" # Image ETags cache TTL - maxSize: "${CACHE_SPECS_IMAGE_ETAGS_MAX_SIZE:1000000}" # 0 means the cache is disabled + maxSize: "${CACHE_SPECS_IMAGE_ETAGS_MAX_SIZE:10000}" # 0 means the cache is disabled systemImagesBrowserTtlInMinutes: "${CACHE_SPECS_IMAGE_SYSTEM_BROWSER_TTL:0}" # Browser cache TTL for system images in minutes. 0 means the cache is disabled tenantImagesBrowserTtlInMinutes: "${CACHE_SPECS_IMAGE_TENANT_BROWSER_TTL:0}" # Browser cache TTL for tenant images in minutes. 0 means the cache is disabled diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 0cb91faee0..57922e3177 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -306,6 +306,12 @@ message CoreStartupMsg { int64 ts = 3; } +message ResourceCacheInvalidateMsg { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + string resourceKey = 3; +} + message LwM2MRegistrationRequestMsg { string tenantId = 1; string endpoint = 2; @@ -1273,6 +1279,7 @@ message ToCoreNotificationMsg { EdgeEventUpdateMsgProto edgeEventUpdate = 14; ToEdgeSyncRequestMsgProto toEdgeSyncRequest = 15; FromEdgeSyncResponseMsgProto fromEdgeSyncResponse = 16; + ResourceCacheInvalidateMsg resourceCacheInvalidateMsg = 17; } /* Messages that are handled by ThingsBoard RuleEngine Service */ diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java index a92da55c03..35c765dccf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java @@ -78,12 +78,12 @@ public interface DashboardInfoRepository extends JpaRepository findByTenantAndImageLink(@Param("tenantId") UUID tenantId, @Param("imageLink") String imageLink, @Param("lmt") int lmt); @Query(nativeQuery = true, - value = "SELECT * FROM dashboard d WHERE d.image = :imageLink or d.configuration ILIKE CONCAT('%', :imageLink, '%') limit :lmt" + value = "SELECT * FROM dashboard d WHERE d.image = :imageLink or d.configuration ILIKE CONCAT('%\"', :imageLink, '\"%') limit :lmt" ) List findByImageLink(@Param("imageLink") String imageLink, @Param("lmt") int lmt); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeInfoRepository.java index e9dcca7914..83797d7baa 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeInfoRepository.java @@ -198,13 +198,13 @@ public interface WidgetTypeInfoRepository extends JpaRepository findByTenantAndImageUrl(@Param("tenantId") UUID tenantId, @Param("imageLink") String imageLink, @Param("lmt") int lmt); @Query(nativeQuery = true, value = "SELECT * FROM widget_type_info_view wti WHERE wti.id IN " + - "(select id from widget_type where image = :imageLink or descriptor ILIKE CONCAT('%', :imageLink, '%') limit :lmt)" + "(select id from widget_type where image = :imageLink or descriptor ILIKE CONCAT('%\"', :imageLink, '\"%') limit :lmt)" ) List findByImageUrl(@Param("imageLink") String imageLink, @Param("lmt") int lmt); }