Batch system config update (#1402)
* Added an endpoint for batch update of system configurations Signed-off-by: Denislav Prinov <denislav.prinov@bosch.com> * batch db save Signed-off-by: Denislav Prinov <denislav.prinov@bosch.com> * Review changes and added tests Signed-off-by: Denislav Prinov <denislav.prinov@bosch.com> * Evict cache only if transaction is commited - such as @CacheEvict Signed-off-by: Denislav Prinov <denislav.prinov@bosch.com> * refactoring Signed-off-by: Denislav Prinov <denislav.prinov@bosch.com> * Using AfterTransactionCommitExecutor for cache eviction Signed-off-by: Denislav Prinov <denislav.prinov@bosch.com> * Change request body Signed-off-by: Denislav Prinov <denislav.prinov@bosch.com> --------- Signed-off-by: Denislav Prinov <denislav.prinov@bosch.com>
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
package org.eclipse.hawkbit.repository;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
||||
import org.eclipse.hawkbit.repository.model.TenantConfiguration;
|
||||
@@ -44,6 +45,22 @@ public interface TenantConfigurationManagement {
|
||||
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
|
||||
<T extends Serializable> TenantConfigurationValue<T> addOrUpdateConfiguration(String configurationKeyName, T value);
|
||||
|
||||
/**
|
||||
* Adds or updates a specific configuration for a specific tenant.
|
||||
*
|
||||
*
|
||||
* @param configurations
|
||||
* map containing the key - value of the configuration
|
||||
* @return map of all configuration values which were written into the database.
|
||||
* @throws TenantConfigurationValidatorException
|
||||
* if the {@code propertyType} and the value in general does not
|
||||
* match the expected type and format defined by the Key
|
||||
* @throws ConversionFailedException
|
||||
* if the property cannot be converted to the given
|
||||
*/
|
||||
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
|
||||
<T extends Serializable> Map<String, TenantConfigurationValue<T>> addOrUpdateConfiguration(Map<String, T> configurations);
|
||||
|
||||
/**
|
||||
* Build the tenant configuration by the given key
|
||||
*
|
||||
|
||||
@@ -13,10 +13,16 @@ import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationPrope
|
||||
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.TenantConfigurationValueChangeNotAllowedException;
|
||||
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
||||
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTenantConfiguration;
|
||||
import org.eclipse.hawkbit.repository.model.TenantConfiguration;
|
||||
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
|
||||
@@ -26,6 +32,8 @@ import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationVa
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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.context.ApplicationContext;
|
||||
@@ -55,6 +63,12 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Autowired
|
||||
private CacheManager cacheManager;
|
||||
|
||||
@Autowired
|
||||
private AfterTransactionCommitExecutor afterCommitExecutor;
|
||||
|
||||
private static final ConfigurableConversionService conversionService = new DefaultConversionService();
|
||||
|
||||
@Override
|
||||
@@ -139,39 +153,68 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
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(Collections.singletonMap(configurationKeyName, value)).values().iterator().next();
|
||||
}
|
||||
|
||||
final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(configurationKeyName);
|
||||
@Override
|
||||
@Transactional
|
||||
@Retryable(include = {
|
||||
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||
public <T extends Serializable> Map<String, TenantConfigurationValue<T>> addOrUpdateConfiguration(Map<String, T> configurations) {
|
||||
// Register a callback to be invoked after the transaction is committed - for cache eviction
|
||||
afterCommitExecutor.afterCommit(() -> {
|
||||
Cache cache = cacheManager.getCache("tenantConfiguration");
|
||||
if (cache != null) {
|
||||
configurations.keySet().forEach(cache::evict);
|
||||
}
|
||||
});
|
||||
|
||||
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()));
|
||||
}
|
||||
return addOrUpdateConfiguration0(configurations);
|
||||
}
|
||||
|
||||
configurationKey.validate(applicationContext, value);
|
||||
private <T extends Serializable> Map<String, TenantConfigurationValue<T>> addOrUpdateConfiguration0(Map<String, T> configurations) {
|
||||
List<JpaTenantConfiguration> configurationList = new ArrayList<>();
|
||||
configurations.forEach((configurationKeyName, value) -> {
|
||||
final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(configurationKeyName);
|
||||
|
||||
JpaTenantConfiguration tenantConfiguration = tenantConfigurationRepository
|
||||
.findByKey(configurationKey.getKeyName());
|
||||
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()));
|
||||
}
|
||||
|
||||
if (tenantConfiguration == null) {
|
||||
tenantConfiguration = new JpaTenantConfiguration(configurationKey.getKeyName(), value.toString());
|
||||
} else {
|
||||
tenantConfiguration.setValue(value.toString());
|
||||
}
|
||||
configurationKey.validate(applicationContext, value);
|
||||
|
||||
assertValueChangeIsAllowed(configurationKeyName, tenantConfiguration);
|
||||
JpaTenantConfiguration tenantConfiguration = tenantConfigurationRepository
|
||||
.findByKey(configurationKey.getKeyName());
|
||||
|
||||
final JpaTenantConfiguration updatedTenantConfiguration = tenantConfigurationRepository
|
||||
.save(tenantConfiguration);
|
||||
if (tenantConfiguration == null) {
|
||||
tenantConfiguration = new JpaTenantConfiguration(configurationKey.getKeyName(), value.toString());
|
||||
} else {
|
||||
tenantConfiguration.setValue(value.toString());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final Class<T> clazzT = (Class<T>) value.getClass();
|
||||
assertValueChangeIsAllowed(configurationKeyName, tenantConfiguration);
|
||||
configurationList.add(tenantConfiguration);
|
||||
});
|
||||
|
||||
return TenantConfigurationValue.<T> builder().global(false).createdBy(updatedTenantConfiguration.getCreatedBy())
|
||||
.createdAt(updatedTenantConfiguration.getCreatedAt())
|
||||
.lastModifiedAt(updatedTenantConfiguration.getLastModifiedAt())
|
||||
.lastModifiedBy(updatedTenantConfiguration.getLastModifiedBy())
|
||||
.value(conversionService.convert(updatedTenantConfiguration.getValue(), clazzT)).build();
|
||||
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(conversionService.convert(updatedTenantConfiguration.getValue(), clazzT))
|
||||
.build();
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -73,7 +73,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
|
||||
|
||||
@Test
|
||||
@Description("Tests that the tenant specific configuration can be updated")
|
||||
public void updateTenantSpecifcConfiguration() {
|
||||
public void updateTenantSpecificConfiguration() {
|
||||
final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY;
|
||||
final String value1 = "firstValue";
|
||||
final String value2 = "secondValue";
|
||||
@@ -89,6 +89,22 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
|
||||
.isEqualTo(value2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests that the tenant specific configuration can be batch updated")
|
||||
public void batchUpdateTenantSpecificConfiguration() {
|
||||
Map<String, Serializable> configuration = new HashMap<>() {{
|
||||
put(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, "token_123");
|
||||
put(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, true);
|
||||
}};
|
||||
|
||||
// add value first
|
||||
tenantConfigurationManagement.addOrUpdateConfiguration(configuration);
|
||||
assertThat(tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class).getValue())
|
||||
.isEqualTo("token_123");
|
||||
assertThat(tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, Boolean.class).getValue())
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests that the configuration value can be converted from String to Integer automatically")
|
||||
public void storeAndUpdateTenantSpecificConfigurationAsBoolean() {
|
||||
@@ -118,6 +134,26 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests that the get configuration throws exception in case the value is the wrong type")
|
||||
public void batchWrongTenantConfigurationValueTypeThrowsException() {
|
||||
Map<String, Serializable> configuration = new HashMap<>() {{
|
||||
put(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, "token_123");
|
||||
put(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, true);
|
||||
put(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED, "wrong");
|
||||
}};
|
||||
|
||||
try {
|
||||
tenantConfigurationManagement.addOrUpdateConfiguration(configuration);
|
||||
fail("should not have worked as type is wrong");
|
||||
} catch (final TenantConfigurationValidatorException e) {
|
||||
assertThat(tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class).getValue())
|
||||
.isNotEqualTo("token_123");
|
||||
assertThat(tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, Boolean.class).getValue())
|
||||
.isNotEqualTo(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests that a deletion of a tenant specific configuration deletes it from the database.")
|
||||
public void deleteConfigurationReturnNullConfiguration() {
|
||||
|
||||
Reference in New Issue
Block a user