From f249544f5d20461f893559551c8c386c0d743588 Mon Sep 17 00:00:00 2001 From: Birk Blechschmidt Date: Fri, 11 Jul 2025 08:16:37 +0200 Subject: [PATCH] Support cache eviction in microservice mode (#2503) When running in microservice mode, updating configuration properties via the management component does not automatically update caches in other services. This change introduces support for distributed cache eviction using Spring Cloud Bus. It listens for remote tenant configuration events and evicts the relevant cache key upon receipt. Signed-off-by: Birk Blechschmidt --- .../JpaTenantConfigurationManagement.java | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTenantConfigurationManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTenantConfigurationManagement.java index a712d298c..1741c0ef3 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTenantConfigurationManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTenantConfigurationManagement.java @@ -30,6 +30,9 @@ import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.repository.event.remote.RemoteTenantAwareEvent; +import org.eclipse.hawkbit.repository.event.remote.TenantConfigurationDeletedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.RemoteEntityEvent; import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; import org.eclipse.hawkbit.repository.exception.TenantConfigurationValidatorException; import org.eclipse.hawkbit.repository.exception.TenantConfigurationValueChangeNotAllowedException; @@ -49,11 +52,14 @@ import org.eclipse.hawkbit.tenancy.configuration.DurationHelper; import org.eclipse.hawkbit.tenancy.configuration.PollingTime; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; +import org.springframework.cloud.bus.ServiceMatcher; import org.springframework.context.ApplicationContext; +import org.springframework.context.event.EventListener; import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.dao.ConcurrencyFailureException; @@ -79,6 +85,8 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana private final CacheManager cacheManager; private final AfterTransactionCommitExecutor afterCommitExecutor; + private ServiceMatcher serviceMatcher; + public JpaTenantConfigurationManagement( final TenantConfigurationRepository tenantConfigurationRepository, final TenantConfigurationProperties tenantConfigurationProperties, @@ -91,6 +99,11 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana this.applicationContext = applicationContext; } + @Autowired(required = false) + public void setServiceMatcher(final ServiceMatcher serviceMatcher) { + this.serviceMatcher = serviceMatcher; + } + @Override @CacheEvict(value = "tenantConfiguration", key = "#configurationKeyName") @Transactional @@ -162,6 +175,36 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana return CONVERSION_SERVICE.convert(key.getDefaultValue(), propertyType); } + /** + * Ensures that cache eviction takes place in microservice mode in case of deletions. + * + * @param event The event indicating that a configuration value has been deleted. + */ + @EventListener + public void onTenantConfigurationDeletedEvent(final TenantConfigurationDeletedEvent event) { + if (!shouldProcessRemoteTenantAwareEvent(event)) { + return; + } + + evictCacheEntryByKeyIfPresent(event.getConfigKey()); + } + + /** + * Ensures that cache eviction takes place in microservice mode in case of creation or update events. + * + * @param event The event indicating that a configuration value has been created or updated. + */ + @EventListener + public void onTenantConfigurationRemoteEntityEvent(final RemoteEntityEvent event) { + if (!shouldProcessRemoteTenantAwareEvent(event)) { + return; + } + + event.getEntity().ifPresent(tenantConfiguration -> { + evictCacheEntryByKeyIfPresent(tenantConfiguration.getKey()); + }); + } + @Override public Function pollStatusResolver() { final PollingTime pollingTime = new PollingTime( @@ -339,4 +382,15 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana } } } -} \ No newline at end of file + + private void evictCacheEntryByKeyIfPresent(final String key) { + final Cache cache = cacheManager.getCache("tenantConfiguration"); + if (cache != null) { + cache.evictIfPresent(key); + } + } + + private boolean shouldProcessRemoteTenantAwareEvent(final RemoteTenantAwareEvent event) { + return serviceMatcher == null || !serviceMatcher.isFromSelf(event) && serviceMatcher.isForSelf(event); + } +}