* TenantAwareCacheManager define CacheEvictEvent which could be used to evict entities in general way * JpaTenantConfigurationManagement start using genera cache approach Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.jpa.management;
|
||||
|
||||
import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_DELAY;
|
||||
import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_MAX;
|
||||
import static org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor.afterCommit;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
@@ -38,8 +39,8 @@ import jakarta.validation.constraints.NotNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.Identifiable;
|
||||
import org.eclipse.hawkbit.repository.RepositoryManagement;
|
||||
import org.eclipse.hawkbit.repository.QueryField;
|
||||
import org.eclipse.hawkbit.repository.RepositoryManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
|
||||
import org.eclipse.hawkbit.repository.jpa.Jpa;
|
||||
@@ -48,9 +49,10 @@ import org.eclipse.hawkbit.repository.jpa.JpaRepositoryConfiguration;
|
||||
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.BaseEntityRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.ql.QLSupport;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.BaseEntityRepository;
|
||||
import org.eclipse.hawkbit.utils.ObjectCopyUtil;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@@ -132,6 +134,7 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
|
||||
@Transactional
|
||||
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
|
||||
public T create(final C create) {
|
||||
getCache().ifPresent(cache -> afterCommit(cache::clear));
|
||||
return jpaRepository.save(AccessController.Operation.CREATE, jpaEntity(create));
|
||||
}
|
||||
|
||||
@@ -139,17 +142,47 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
|
||||
@Transactional
|
||||
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
|
||||
public List<T> create(final Collection<C> create) {
|
||||
getCache().ifPresent(cache -> afterCommit(cache::clear));
|
||||
return jpaRepository.saveAll(AccessController.Operation.CREATE, create.stream().map(this::jpaEntity).toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public T get(final long id) {
|
||||
return jpaRepository.getById(id);
|
||||
final Cache cache = getCache().orElse(null);
|
||||
if (cache == null) {
|
||||
return jpaRepository.getById(id);
|
||||
} else {
|
||||
final T cached = (T) cache.get(id);
|
||||
if (cached == null) {
|
||||
final T entity = jpaRepository.getById(id);
|
||||
if (entity != null) { // should not be null - but throw exception - but for sure check
|
||||
cache.put(id, entity);
|
||||
}
|
||||
return entity;
|
||||
} else {
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Optional<T> find(final long id) {
|
||||
return jpaRepository.findById(id);
|
||||
final Cache cache = getCache().orElse(null);
|
||||
if (cache == null) {
|
||||
return jpaRepository.findById(id);
|
||||
} else {
|
||||
final T cached = (T) cache.get(id);
|
||||
if (cached == null) {
|
||||
final Optional<T> entity = jpaRepository.findById(id);
|
||||
// should not be null - but throw exception - but for sure check
|
||||
entity.ifPresent(t -> cache.put(id, t));
|
||||
return entity;
|
||||
} else {
|
||||
return Optional.of(cached);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -193,6 +226,7 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
|
||||
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
|
||||
@SuppressWarnings("java:S1066") // javaS1066 - better readable that way
|
||||
public T update(final U update) {
|
||||
getCache().ifPresent(cache -> afterCommit(() -> cache.evict(update.getId())));
|
||||
final T entity = getValid(update.getId());
|
||||
// update getId has no setter in target JPA entity but shall have getter and the value shall be the same
|
||||
// otherwise the Utils will throw an exception that there is no counterpart setter for getId
|
||||
@@ -230,6 +264,7 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
|
||||
if (toSave.isEmpty()) {
|
||||
return toUpdate;
|
||||
} else {
|
||||
getCache().ifPresent(cache -> afterCommit(() -> toSave.forEach(updated -> cache.evict(updated.getId()))));
|
||||
final List<T> savedEntities = jpaRepository.saveAll(toSave);
|
||||
final Map<Long, T> result = new HashMap<>(toUpdate);
|
||||
for (final T savedEntity : savedEntities) {
|
||||
@@ -243,6 +278,7 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
|
||||
@Transactional
|
||||
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
|
||||
public void delete(final long id) {
|
||||
getCache().ifPresent(cache -> afterCommit(() -> cache.evict(id)));
|
||||
delete0(List.of(id));
|
||||
}
|
||||
|
||||
@@ -250,6 +286,7 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
|
||||
@Transactional
|
||||
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
|
||||
public void delete(final Collection<Long> ids) {
|
||||
getCache().ifPresent(cache -> afterCommit(() -> ids.forEach(cache::evict)));
|
||||
delete0(ids);
|
||||
}
|
||||
|
||||
@@ -314,6 +351,11 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
|
||||
}
|
||||
}
|
||||
|
||||
// if cache is supported for the entities - override this method
|
||||
protected Optional<Cache> getCache() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private List<T> findAllById(final Collection<Long> ids, final boolean throwIfNotFound) {
|
||||
final List<T> foundDs = jpaRepository.findAllById(ids);
|
||||
if (throwIfNotFound && foundDs.size() != ids.size()) {
|
||||
|
||||
@@ -16,10 +16,10 @@ import jakarta.persistence.EntityManager;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.hawkbit.artifact.ArtifactStorage;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager;
|
||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantStatsManagement;
|
||||
import org.eclipse.hawkbit.repository.event.EventPublisherHolder;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.jpa.CurrentTenantCacheKeyGenerator;
|
||||
import org.eclipse.hawkbit.repository.jpa.SystemManagementCacheKeyGenerator;
|
||||
@@ -47,6 +47,7 @@ import org.eclipse.hawkbit.repository.model.report.SystemUsageReport;
|
||||
import org.eclipse.hawkbit.repository.model.report.SystemUsageReportWithTenants;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
|
||||
@@ -160,7 +161,6 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
|
||||
}
|
||||
|
||||
final String tenant = t.toUpperCase();
|
||||
TenantAwareCacheManager.getInstance().evictTenant(tenant);
|
||||
tenantAware.runAsTenant(tenant, () -> DeploymentHelper.runInNewTransaction(txManager, "deleteTenant", status -> {
|
||||
tenantMetaDataRepository.deleteByTenantIgnoreCase(tenant);
|
||||
tenantConfigurationRepository.deleteByTenant(tenant);
|
||||
@@ -177,6 +177,7 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
|
||||
softwareModuleTypeRepository.deleteByTenant(tenant);
|
||||
return null;
|
||||
}));
|
||||
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new CacheEvictEvent.Default(tenant, null, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
package org.eclipse.hawkbit.repository.jpa.management;
|
||||
|
||||
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_GATEWAY_SECURITY_TOKEN;
|
||||
import static org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor.afterCommit;
|
||||
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY;
|
||||
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.BATCH_ASSIGNMENTS_ENABLED;
|
||||
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED;
|
||||
@@ -26,14 +25,13 @@ import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.hawkbit.repository.TargetFields;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
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;
|
||||
@@ -51,14 +49,11 @@ import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.DurationHelper;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.PollingTime;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.PollingTime.PollingInterval;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
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,7 +74,7 @@ import org.springframework.validation.annotation.Validated;
|
||||
@ConditionalOnBooleanProperty(prefix = "hawkbit.jpa", name = { "enabled", "tenant-configuration-management" }, matchIfMissing = true)
|
||||
public class JpaTenantConfigurationManagement implements TenantConfigurationManagement {
|
||||
|
||||
private static final String CACHE_TENANT_CONFIGURATION_NAME = "tenantConfiguration";
|
||||
private static final String CACHE_TENANT_CONFIGURATION_NAME = JpaTenantConfiguration.class.getSimpleName();
|
||||
private static final ConfigurableConversionService CONVERSION_SERVICE = new DefaultConversionService();
|
||||
|
||||
private final TenantConfigurationRepository tenantConfigurationRepository;
|
||||
@@ -96,12 +91,11 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(value = CACHE_TENANT_CONFIGURATION_NAME, key = "#configurationKeyName")
|
||||
@Transactional
|
||||
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
|
||||
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||
public <T extends Serializable> TenantConfigurationValue<T> addOrUpdateConfiguration(final String configurationKeyName, final T value) {
|
||||
return addOrUpdateConfiguration0(Map.of(configurationKeyName, value)).values().iterator().next();
|
||||
public <T extends Serializable> TenantConfigurationValue<T> addOrUpdateConfiguration(final String keyName, final T value) {
|
||||
return addOrUpdateConfiguration0(Map.of(keyName, value)).values().iterator().next();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -109,89 +103,38 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
|
||||
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||
public <T extends Serializable> Map<String, TenantConfigurationValue<T>> addOrUpdateConfiguration(final Map<String, T> configurations) {
|
||||
// Register a callback to be invoked after the transaction is committed - for cache eviction
|
||||
afterCommit(() -> {
|
||||
final Cache cache = TenantAwareCacheManager.getInstance().getCache(CACHE_TENANT_CONFIGURATION_NAME);
|
||||
if (cache != null) {
|
||||
configurations.keySet().forEach(cache::evict);
|
||||
}
|
||||
});
|
||||
|
||||
return addOrUpdateConfiguration0(configurations);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(value = CACHE_TENANT_CONFIGURATION_NAME, key = "#configurationKeyName")
|
||||
public <T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(final String keyName) {
|
||||
return getConfigurationValue0(keyName, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(final String keyName, final Class<T> propertyType) {
|
||||
return getConfigurationValue0(keyName, propertyType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
|
||||
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||
public void deleteConfiguration(final String configurationKeyName) {
|
||||
tenantConfigurationRepository.deleteByKey(configurationKeyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
// TODO - check if cache works
|
||||
// @Cacheable(value = CACHE_TENANT_CONFIGURATION_NAME, key = "#configurationKeyName")
|
||||
public <T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(final String configurationKeyName) {
|
||||
checkAccess(configurationKeyName);
|
||||
|
||||
final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(configurationKeyName);
|
||||
|
||||
return getConfigurationValue(configurationKeyName, (Class<T>) configurationKey.getDataType());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = CACHE_TENANT_CONFIGURATION_NAME, key = "#configurationKeyName")
|
||||
public <T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(
|
||||
final String configurationKeyName, final Class<T> propertyType) {
|
||||
checkAccess(configurationKeyName);
|
||||
|
||||
final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(configurationKeyName);
|
||||
validateTenantConfigurationDataType(configurationKey, propertyType);
|
||||
|
||||
final TenantConfiguration tenantConfiguration = tenantConfigurationRepository.findByKey(configurationKey.getKeyName());
|
||||
return buildTenantConfigurationValueByKey(configurationKey, propertyType, tenantConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getGlobalConfigurationValue(final String configurationKeyName, final Class<T> propertyType) {
|
||||
checkAccess(configurationKeyName);
|
||||
|
||||
final TenantConfigurationKey key = tenantConfigurationProperties.fromKeyName(configurationKeyName);
|
||||
if (!key.getDataType().isAssignableFrom(propertyType)) {
|
||||
throw new TenantConfigurationValidatorException(
|
||||
String.format("Cannot parse the database value of type %s into the type %s.", key.getDataType(), propertyType));
|
||||
}
|
||||
|
||||
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) {
|
||||
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<TenantConfiguration> event) {
|
||||
event.getEntity().ifPresent(tenantConfiguration -> evictCacheEntryByKeyIfPresent(tenantConfiguration.getKey()));
|
||||
public void deleteConfiguration(final String keyName) {
|
||||
tenantConfigurationRepository.deleteByKey(keyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("java:S3776") // java:S3776 - not really too complex
|
||||
public Function<Target, PollStatus> pollStatusResolver() {
|
||||
final PollingTime pollingTime = new PollingTime(
|
||||
getConfigurationValue(TenantConfigurationKey.POLLING_TIME, String.class).getValue());
|
||||
Objects.requireNonNull(getConfigurationValue0(TenantConfigurationKey.POLLING_TIME, String.class),
|
||||
"Polling time shall always be non-null")
|
||||
.getValue());
|
||||
final Duration pollingOverdueTime = DurationHelper.fromString(
|
||||
getConfigurationValue(TenantConfigurationKey.POLLING_OVERDUE_TIME, String.class).getValue());
|
||||
Objects.requireNonNull(getConfigurationValue0(TenantConfigurationKey.POLLING_OVERDUE_TIME, String.class),
|
||||
"Polling overdue time shall always be non-null")
|
||||
.getValue());
|
||||
return target -> {
|
||||
final Long lastTargetQuery = target.getLastTargetQuery();
|
||||
if (lastTargetQuery == null) {
|
||||
@@ -214,35 +157,18 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
};
|
||||
}
|
||||
|
||||
private static PollStatus pollStatus(
|
||||
final long lastTargetQuery,
|
||||
final PollingTime.PollingInterval pollingInterval, final Duration pollingOverdueTime) {
|
||||
final LocalDateTime currentDate = LocalDateTime.now();
|
||||
final LocalDateTime lastPollDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastTargetQuery), ZoneId.systemDefault());
|
||||
LocalDateTime nextPollDate = lastPollDate.plus(pollingInterval.getInterval());
|
||||
if (pollingInterval.getDeviationPercent() > 0) {
|
||||
nextPollDate = nextPollDate.plus(
|
||||
pollingInterval.getInterval().toMillis() * pollingInterval.getDeviationPercent() / 100,
|
||||
ChronoUnit.MILLIS);
|
||||
}
|
||||
final LocalDateTime overdueDate = nextPollDate.plus(pollingOverdueTime);
|
||||
return new PollStatus(lastPollDate, nextPollDate, overdueDate, currentDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the data type of the tenant configuration. If it is possible to cast to the given data type.
|
||||
*/
|
||||
private static <T> void validateTenantConfigurationDataType(final TenantConfigurationKey configurationKey, final Class<T> propertyType) {
|
||||
if (!configurationKey.getDataType().isAssignableFrom(propertyType)) {
|
||||
throw new TenantConfigurationValidatorException(
|
||||
String.format(
|
||||
"Cannot parse the database value of type %s into the type %s.",
|
||||
configurationKey.getDataType(), propertyType));
|
||||
throw new TenantConfigurationValidatorException(String.format(
|
||||
"Cannot parse the database value of type %s into the type %s.", configurationKey.getDataType(), propertyType));
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAccess(final String configurationKeyName) {
|
||||
if (AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY.equalsIgnoreCase(configurationKeyName)) {
|
||||
private void checkAccess(final String keyName) {
|
||||
if (AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY.equalsIgnoreCase(keyName)) {
|
||||
final SystemSecurityContext systemSecurityContext = SystemSecurityContextHolder.getInstance().getSystemSecurityContext();
|
||||
if (!SystemSecurityContext.isCurrentThreadSystemCode() && !systemSecurityContext.hasPermission(READ_GATEWAY_SECURITY_TOKEN)) {
|
||||
throw new InsufficientPermissionException(
|
||||
@@ -251,14 +177,15 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends Serializable> Map<String, TenantConfigurationValue<T>> addOrUpdateConfiguration0(final Map<String, T> configurations) {
|
||||
final List<JpaTenantConfiguration> configurationList = new ArrayList<>();
|
||||
configurations.forEach((configurationKeyName, value) -> {
|
||||
final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(configurationKeyName);
|
||||
configurations.forEach((keyName, value) -> {
|
||||
final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(keyName);
|
||||
if (!configurationKey.getDataType().isAssignableFrom(value.getClass())) {
|
||||
throw new TenantConfigurationValidatorException(String.format(
|
||||
"Cannot parse the value %s of type %s into the type %s defined by the configuration key.", value,
|
||||
value.getClass(), configurationKey.getDataType()));
|
||||
"Cannot parse the value %s of type %s into the type %s defined by the configuration key.",
|
||||
value, value.getClass(), configurationKey.getDataType()));
|
||||
}
|
||||
configurationKey.validate(value, applicationContext);
|
||||
// additional validation for specific configuration keys
|
||||
@@ -279,43 +206,72 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
tenantConfiguration.setValue(value.toString());
|
||||
}
|
||||
|
||||
assertValueChangeIsAllowed(configurationKeyName, tenantConfiguration);
|
||||
assertValueChangeIsAllowed(keyName, tenantConfiguration);
|
||||
configurationList.add(tenantConfiguration);
|
||||
});
|
||||
return tenantConfigurationRepository.saveAll(configurationList).
|
||||
stream().
|
||||
collect(Collectors.toMap(
|
||||
JpaTenantConfiguration::getKey,
|
||||
updatedTenantConfiguration -> TenantConfigurationValue.<T> builder()
|
||||
.global(false)
|
||||
.createdBy(updatedTenantConfiguration.getCreatedBy())
|
||||
.createdAt(updatedTenantConfiguration.getCreatedAt())
|
||||
.lastModifiedAt(updatedTenantConfiguration.getLastModifiedAt())
|
||||
.lastModifiedBy(updatedTenantConfiguration.getLastModifiedBy())
|
||||
.value(CONVERSION_SERVICE.convert(
|
||||
updatedTenantConfiguration.getValue(),
|
||||
(Class<T>) configurations.get(updatedTenantConfiguration.getKey()).getClass()))
|
||||
.build()));
|
||||
}
|
||||
|
||||
final List<JpaTenantConfiguration> jpaTenantConfigurations = tenantConfigurationRepository.saveAll(configurationList);
|
||||
return jpaTenantConfigurations.stream().collect(Collectors.toMap(
|
||||
JpaTenantConfiguration::getKey,
|
||||
updatedTenantConfiguration -> {
|
||||
@SuppressWarnings("unchecked") final Class<T> clazzT = (Class<T>) configurations.get(updatedTenantConfiguration.getKey())
|
||||
.getClass();
|
||||
return TenantConfigurationValue.<T> builder().global(false)
|
||||
.createdBy(updatedTenantConfiguration.getCreatedBy())
|
||||
.createdAt(updatedTenantConfiguration.getCreatedAt())
|
||||
.lastModifiedAt(updatedTenantConfiguration.getLastModifiedAt())
|
||||
.lastModifiedBy(updatedTenantConfiguration.getLastModifiedBy())
|
||||
.value(CONVERSION_SERVICE.convert(updatedTenantConfiguration.getValue(), clazzT))
|
||||
.build();
|
||||
}));
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends Serializable> TenantConfigurationValue<T> getConfigurationValue0(final String keyName, final Class<T> propertyType) {
|
||||
checkAccess(keyName);
|
||||
|
||||
final TenantConfigurationKey key = tenantConfigurationProperties.fromKeyName(keyName);
|
||||
if (propertyType != null) {
|
||||
validateTenantConfigurationDataType(key, propertyType);
|
||||
}
|
||||
|
||||
final TenantConfiguration tenantConfiguration = TenantAwareCacheManager.getInstance().getCache(CACHE_TENANT_CONFIGURATION_NAME)
|
||||
.get(key.getKeyName(), () -> tenantConfigurationRepository.findByKey(key.getKeyName()));
|
||||
return buildTenantConfigurationValueByKey(key, propertyType == null ? (Class<T>) key.getDataType() : propertyType, tenantConfiguration);
|
||||
}
|
||||
|
||||
private <T extends Serializable> TenantConfigurationValue<T> buildTenantConfigurationValueByKey(
|
||||
final TenantConfigurationKey configurationKey, final Class<T> propertyType, final TenantConfiguration tenantConfiguration) {
|
||||
if (tenantConfiguration != null) {
|
||||
return TenantConfigurationValue.<T> builder().global(false).createdBy(tenantConfiguration.getCreatedBy())
|
||||
return TenantConfigurationValue.<T> builder().global(false)
|
||||
.createdBy(tenantConfiguration.getCreatedBy())
|
||||
.createdAt(tenantConfiguration.getCreatedAt())
|
||||
.lastModifiedAt(tenantConfiguration.getLastModifiedAt())
|
||||
.lastModifiedBy(tenantConfiguration.getLastModifiedBy())
|
||||
.value(CONVERSION_SERVICE.convert(tenantConfiguration.getValue(), propertyType)).build();
|
||||
} else if (configurationKey.getDefaultValue() != null) {
|
||||
return TenantConfigurationValue.<T> builder().global(true).createdBy(null).createdAt(null)
|
||||
.lastModifiedAt(null).lastModifiedBy(null)
|
||||
.value(getGlobalConfigurationValue(configurationKey.getKeyName(), propertyType)).build();
|
||||
return TenantConfigurationValue.<T> builder().global(true)
|
||||
.createdBy(null)
|
||||
.createdAt(null)
|
||||
.lastModifiedAt(null)
|
||||
.lastModifiedBy(null)
|
||||
.value(getGlobalConfigurationValue0(configurationKey.getKeyName(), propertyType)).build();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T getGlobalConfigurationValue0(final String keyName, final Class<T> propertyType) {
|
||||
checkAccess(keyName);
|
||||
|
||||
final TenantConfigurationKey key = tenantConfigurationProperties.fromKeyName(keyName);
|
||||
if (!key.getDataType().isAssignableFrom(propertyType)) {
|
||||
throw new TenantConfigurationValidatorException(
|
||||
String.format("Cannot parse the database value of type %s into the type %s.", key.getDataType(), propertyType));
|
||||
}
|
||||
|
||||
return CONVERSION_SERVICE.convert(key.getDefaultValue(), propertyType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the requested configuration value change is allowed. Throws a {@link TenantConfigurationValueChangeNotAllowedException}
|
||||
* otherwise.
|
||||
@@ -326,19 +282,10 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
*/
|
||||
private void assertValueChangeIsAllowed(final String key, final JpaTenantConfiguration valueChange) {
|
||||
assertMultiAssignmentsValueChange(key, valueChange);
|
||||
assertAutoCloseValueChange(key, valueChange);
|
||||
assertAutoCloseValueChange(key);
|
||||
assertBatchAssignmentValueChange(key, valueChange);
|
||||
}
|
||||
|
||||
@SuppressWarnings("squid:S1172")
|
||||
private void assertAutoCloseValueChange(final String key, final JpaTenantConfiguration valueChange) {
|
||||
if (REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED.equals(key)
|
||||
&& Boolean.TRUE.equals(getConfigurationValue(MULTI_ASSIGNMENTS_ENABLED, Boolean.class).getValue())) {
|
||||
log.debug("The property '{}' must not be changed because the Multi-Assignments feature is currently enabled.", key);
|
||||
throw new TenantConfigurationValueChangeNotAllowedException();
|
||||
}
|
||||
}
|
||||
|
||||
private void assertMultiAssignmentsValueChange(final String key, final JpaTenantConfiguration valueChange) {
|
||||
if (MULTI_ASSIGNMENTS_ENABLED.equals(key) && !Boolean.parseBoolean(valueChange.getValue())) {
|
||||
log.debug("The Multi-Assignments '{}' feature cannot be disabled.", key);
|
||||
@@ -355,6 +302,14 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
}
|
||||
}
|
||||
|
||||
private void assertAutoCloseValueChange(final String key) {
|
||||
if (REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED.equals(key)
|
||||
&& Boolean.TRUE.equals(getConfigurationValue0(MULTI_ASSIGNMENTS_ENABLED, Boolean.class).getValue())) {
|
||||
log.debug("The property '{}' must not be changed because the Multi-Assignments feature is currently enabled.", key);
|
||||
throw new TenantConfigurationValueChangeNotAllowedException();
|
||||
}
|
||||
}
|
||||
|
||||
private void assertBatchAssignmentValueChange(final String key, final JpaTenantConfiguration valueChange) {
|
||||
if (BATCH_ASSIGNMENTS_ENABLED.equals(key) && Boolean.parseBoolean(valueChange.getValue())) {
|
||||
JpaTenantConfiguration multiConfig = tenantConfigurationRepository.findByKey(MULTI_ASSIGNMENTS_ENABLED);
|
||||
@@ -367,10 +322,15 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
}
|
||||
}
|
||||
|
||||
private void evictCacheEntryByKeyIfPresent(final String key) {
|
||||
final Cache cache = TenantAwareCacheManager.getInstance().getCache(CACHE_TENANT_CONFIGURATION_NAME);
|
||||
if (cache != null) {
|
||||
cache.evictIfPresent(key);
|
||||
private static PollStatus pollStatus(final long lastTargetQuery, final PollingInterval pollingInterval, final Duration pollingOverdueTime) {
|
||||
final LocalDateTime currentDate = LocalDateTime.now();
|
||||
final LocalDateTime lastPollDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastTargetQuery), ZoneId.systemDefault());
|
||||
LocalDateTime nextPollDate = lastPollDate.plus(pollingInterval.getInterval());
|
||||
if (pollingInterval.getDeviationPercent() > 0) {
|
||||
nextPollDate = nextPollDate.plus(
|
||||
pollingInterval.getInterval().toMillis() * pollingInterval.getDeviationPercent() / 100, ChronoUnit.MILLIS);
|
||||
}
|
||||
final LocalDateTime overdueDate = nextPollDate.plus(pollingOverdueTime);
|
||||
return new PollStatus(lastPollDate, nextPollDate, overdueDate, currentDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user