fix initial creation of tenant when no current tenant and caching fix

Signed-off-by: Michael Hirsch <michael.hirsch@bosch-si.com>
This commit is contained in:
Michael Hirsch
2016-07-18 12:48:50 +02:00
parent 015f88a54f
commit d2cd13996a
5 changed files with 85 additions and 29 deletions

View File

@@ -25,6 +25,7 @@ import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.SimpleCacheResolver; import org.springframework.cache.interceptor.SimpleCacheResolver;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/** /**
* A configuration for configuring the spring {@link CacheManager} for specific * A configuration for configuring the spring {@link CacheManager} for specific
@@ -33,9 +34,6 @@ import org.springframework.context.annotation.Configuration;
* *
* This is done by providing a special {@link TenantCacheResolver} which * This is done by providing a special {@link TenantCacheResolver} which
* generates a cache name included the current tenant. * generates a cache name included the current tenant.
*
*
*
*/ */
@Configuration @Configuration
@EnableCaching @EnableCaching
@@ -51,18 +49,27 @@ public class CacheAutoConfiguration extends CachingConfigurerSupport {
@Override @Override
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@Primary
public TenancyCacheManager cacheManager() { public TenancyCacheManager cacheManager() {
return new TenantAwareCacheManager(new GuavaCacheManager(), tenantAware); return new TenantAwareCacheManager(directCacheManager(), tenantAware);
}
/**
* @return the direct cache manager to access without tenant aware check,
* cause in sometimes it's necessary to access the cache directly
* without having the current tenant, e.g. initial creation of
* tenant
*/
@Bean(name = "directCacheManager")
@ConditionalOnMissingBean(name = "directCacheManager")
public CacheManager directCacheManager() {
return new GuavaCacheManager();
} }
/** /**
* A {@link SimpleCacheResolver} implementation which includes the * A {@link SimpleCacheResolver} implementation which includes the
* {@link TenantAware#getCurrentTenant()} into the cache name before * {@link TenantAware#getCurrentTenant()} into the cache name before
* resolving it. * resolving it.
*
*
*
*
*/ */
public class TenantCacheResolver extends SimpleCacheResolver { public class TenantCacheResolver extends SimpleCacheResolver {

View File

@@ -15,6 +15,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
@@ -50,8 +51,14 @@ public class RedisConfiguration {
* @return the spring redis cache manager. * @return the spring redis cache manager.
*/ */
@Bean @Bean
@Primary
public CacheManager cacheManager() { public CacheManager cacheManager() {
return new TenantAwareCacheManager(new RedisCacheManager(redisTemplate()), tenantAware); return new TenantAwareCacheManager(directCacheManager(), tenantAware);
}
@Bean(name = "directCacheManager")
public CacheManager directCacheManager() {
return new RedisCacheManager(redisTemplate());
} }
/** /**

View File

@@ -19,6 +19,7 @@ import org.eclipse.hawkbit.repository.Constants;
import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TenantStatsManagement; import org.eclipse.hawkbit.repository.TenantStatsManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.jpa.configuration.MultiTenantJpaTransactionManager;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.jpa.model.JpaTenantMetaData; import org.eclipse.hawkbit.repository.jpa.model.JpaTenantMetaData;
@@ -26,6 +27,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.TenantMetaData; import org.eclipse.hawkbit.repository.model.TenantMetaData;
import org.eclipse.hawkbit.repository.report.model.SystemUsageReport; import org.eclipse.hawkbit.repository.report.model.SystemUsageReport;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.persistence.config.PersistenceUnitProperties; import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -33,11 +35,14 @@ import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.ApplicationContext;
import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Modifying;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
/** /**
@@ -108,7 +113,10 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
private SystemManagementCacheKeyGenerator currentTenantCacheKeyGenerator; private SystemManagementCacheKeyGenerator currentTenantCacheKeyGenerator;
@Autowired @Autowired
private ApplicationContext applicationContext; private SystemSecurityContext systemSecurityContext;
@Autowired
private PlatformTransactionManager txManager;
@Override @Override
public SystemUsageReport getSystemUsageStatistics() { public SystemUsageReport getSystemUsageStatistics() {
@@ -159,27 +167,48 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
} }
@Override @Override
@Cacheable(value = "tenantMetadata", key = "#tenant.toUpperCase()") @Cacheable(value = "tenantMetadata", key = "#tenant.toUpperCase()", cacheManager = "directCacheManager")
@Transactional(isolation = Isolation.READ_UNCOMMITTED) @Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying @Modifying
public TenantMetaData getTenantMetadata(final String tenant) { public TenantMetaData getTenantMetadata(final String tenant) {
final TenantMetaData result = tenantMetaDataRepository.findByTenantIgnoreCase(tenant); final TenantMetaData result = tenantMetaDataRepository.findByTenantIgnoreCase(tenant);
// Create if it does not exist // Create if it does not exist
if (result == null) { if (result == null) {
try { try {
currentTenantCacheKeyGenerator.getCreateInitialTenant().set(tenant); currentTenantCacheKeyGenerator.getCreateInitialTenant().set(tenant);
cacheManager.getCache("currentTenant").evict(currentTenantKeyGenerator().generate(null, null)); return createInitialTenantMetaData(tenant);
applicationContext.getBean("currentTenantKeyGenerator");
return tenantMetaDataRepository.save(new JpaTenantMetaData(createStandardSoftwareDataSetup(), tenant));
} finally { } finally {
currentTenantCacheKeyGenerator.getCreateInitialTenant().remove(); currentTenantCacheKeyGenerator.getCreateInitialTenant().remove();
} }
} }
return result; return result;
} }
/**
* Creating the initial tenant meta-data in a new transaction. Due the
* {@link MultiTenantJpaTransactionManager} is using the current tenant to
* set the necessary tenant discriminator to the query. This is not working
* if we don't have a current tenant set. Due the
* {@link #getTenantMetadata(String)} is maybe called without having a
* current tenant we need to re-open a new transaction so the
* {@link MultiTenantJpaTransactionManager} is called again and set the
* tenant for this transaction.
*
* @param tenant
* the tenant to be created
* @return the initial created {@link TenantMetaData}
*/
private TenantMetaData createInitialTenantMetaData(final String tenant) {
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("initial-tenant-creation");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return systemSecurityContext.runAsSystemAsTenant(
() -> new TransactionTemplate(txManager, def).execute(status -> tenantMetaDataRepository
.save(new JpaTenantMetaData(createStandardSoftwareDataSetup(), tenant))),
tenant);
}
@Override @Override
public List<String> findTenants() { public List<String> findTenants() {
return tenantMetaDataRepository.findAll().stream().map(md -> md.getTenant()).collect(Collectors.toList()); return tenantMetaDataRepository.findAll().stream().map(md -> md.getTenant()).collect(Collectors.toList());
@@ -226,7 +255,7 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
} }
@Override @Override
@Cacheable(value = "currentTenant", keyGenerator = "currentTenantKeyGenerator") @Cacheable(value = "currentTenant", keyGenerator = "currentTenantKeyGenerator", cacheManager = "directCacheManager")
// set transaction to not supported, due we call this in // set transaction to not supported, due we call this in
// BaseEntity#prePersist methods // BaseEntity#prePersist methods
// and it seems that JPA committing the transaction when executing this // and it seems that JPA committing the transaction when executing this

View File

@@ -9,6 +9,7 @@
package org.eclipse.hawkbit.security; package org.eclipse.hawkbit.security;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.TenantAware;
@@ -80,32 +81,37 @@ public class SecurityContextTenantAware implements TenantAware {
@Override @Override
public boolean equals(final Object another) { public boolean equals(final Object another) {
return delegate.equals(another); if (delegate != null) {
return delegate.equals(another);
} else if (another == null) {
return true;
}
return false;
} }
@Override @Override
public String toString() { public String toString() {
return delegate.toString(); return (delegate != null) ? delegate.toString() : null;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return delegate.hashCode(); return (delegate != null) ? delegate.hashCode() : null;
} }
@Override @Override
public String getName() { public String getName() {
return delegate.getName(); return (delegate != null) ? delegate.getName() : null;
} }
@Override @Override
public Collection<? extends GrantedAuthority> getAuthorities() { public Collection<? extends GrantedAuthority> getAuthorities() {
return delegate.getAuthorities(); return (delegate != null) ? delegate.getAuthorities() : Collections.emptyList();
} }
@Override @Override
public Object getCredentials() { public Object getCredentials() {
return delegate.getCredentials(); return (delegate != null) ? delegate.getCredentials() : null;
} }
@Override @Override
@@ -115,16 +121,19 @@ public class SecurityContextTenantAware implements TenantAware {
@Override @Override
public Object getPrincipal() { public Object getPrincipal() {
return delegate.getPrincipal(); return (delegate != null) ? delegate.getPrincipal() : null;
} }
@Override @Override
public boolean isAuthenticated() { public boolean isAuthenticated() {
return delegate.isAuthenticated(); return (delegate != null) ? delegate.isAuthenticated() : null;
} }
@Override @Override
public void setAuthenticated(final boolean isAuthenticated) throws IllegalArgumentException { public void setAuthenticated(final boolean isAuthenticated) {
if (delegate == null) {
return;
}
delegate.setAuthenticated(isAuthenticated); delegate.setAuthenticated(isAuthenticated);
} }
} }

View File

@@ -67,12 +67,16 @@ public class SystemSecurityContext {
// Exception squid:S2221 - Callable declares Exception // Exception squid:S2221 - Callable declares Exception
@SuppressWarnings("squid:S2221") @SuppressWarnings("squid:S2221")
public <T> T runAsSystem(final Callable<T> callable) { public <T> T runAsSystem(final Callable<T> callable) {
return runAsSystemAsTenant(callable, tenantAware.getCurrentTenant());
}
public <T> T runAsSystemAsTenant(final Callable<T> callable, final String tenant) {
final SecurityContext oldContext = SecurityContextHolder.getContext(); final SecurityContext oldContext = SecurityContextHolder.getContext();
try { try {
logger.debug("entering system code execution"); logger.debug("entering system code execution");
return tenantAware.runAsTenant(tenantAware.getCurrentTenant(), () -> { return tenantAware.runAsTenant(tenant, () -> {
try { try {
setSystemContext(oldContext); setSystemContext(SecurityContextHolder.getContext());
return callable.call(); return callable.call();
} catch (final Exception e) { } catch (final Exception e) {
throw Throwables.propagate(e); throw Throwables.propagate(e);