Add annotation configuration support to configure custom session expiration policies.

Resolves gh-5.
This commit is contained in:
John Blum
2018-10-22 19:09:30 -07:00
parent 6714ed9421
commit 7ea48fdc6c
3 changed files with 176 additions and 15 deletions

View File

@@ -32,6 +32,9 @@ import org.springframework.session.config.annotation.web.http.SpringHttpSessionC
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AbstractGemFireHttpSessionConfiguration} class is an abstract base class containing configuration logic
* common to Apache Geode and Pivotal GemFire in order to manage {@link javax.servlet.http.HttpSession} state.
@@ -59,6 +62,8 @@ public abstract class AbstractGemFireHttpSessionConfiguration extends SpringHttp
private Environment environment;
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* Sets a reference the Spring {@link ApplicationContext}.
*
@@ -68,7 +73,9 @@ public abstract class AbstractGemFireHttpSessionConfiguration extends SpringHttp
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
super.setApplicationContext(applicationContext);
this.applicationContext = applicationContext;
}
@@ -144,6 +151,16 @@ public abstract class AbstractGemFireHttpSessionConfiguration extends SpringHttp
return this.environment;
}
/**
* Return a reference to the configured SLF4J {@link Logger}.
*
* @return a reference to the configured SLF4J {@link Logger}.
* @see org.slf4j.Logger
*/
protected Logger getLogger() {
return this.logger;
}
protected String clientRegionShortcutPropertyName() {
return propertyName("cache.client.region.shortcut");
}
@@ -168,6 +185,10 @@ public abstract class AbstractGemFireHttpSessionConfiguration extends SpringHttp
return propertyName(String.format("session.%s", propertyNameSuffix));
}
protected String sessionExpirationPolicyBeanNamePropertyName() {
return sessionPropertyName("expiration.bean-name");
}
protected String sessionRegionNamePropertyName() {
return sessionPropertyName("region.name");
}

View File

@@ -34,6 +34,7 @@ import org.apache.geode.cache.client.Pool;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.session.Session;
import org.springframework.session.data.gemfire.expiration.SessionExpirationPolicy;
import org.springframework.session.data.gemfire.serialization.SessionSerializer;
import org.springframework.session.web.http.SessionRepositoryFilter;
@@ -178,11 +179,27 @@ public @interface EnableGemFireHttpSession {
*/
RegionShortcut serverRegionShortcut() default RegionShortcut.PARTITION;
/**
* Defines the name of the bean referring to the {@link SessionExpirationPolicy} used to configure
* the {@link Session} expiration logic and strategy.
*
* The {@link Object bean} referred to by its {@link String name} must be of type {@link SessionExpirationPolicy}.
*
* Defaults to unset.
* Use the {@literal spring.session.data.gemfire.session.expiration.bean-name} in Spring Boot
* {@literal application.properties}.
*
* @return a {@link String} containing the bean name of the configured {@link SessionExpirationPolicy}.
* @see org.springframework.session.data.gemfire.expiration.SessionExpirationPolicy
*/
String sessionExpirationPolicyBeanName() default GemFireHttpSessionConfiguration.DEFAULT_SESSION_EXPIRATION_POLICY_BEAN_NAME;
/**
* Defines the bean name of the {@link SessionSerializer} used to serialize {@link Session} state
* between client and server or to disk when persisting or overflowing {@link Session} state.
*
* The bean referred to by its name must be of type {@link SessionSerializer}.
* The {@link Object bean referred to by its {@link String name} must be of type {@link SessionSerializer}.
*
* Defaults to {@literal SessionPdxSerializer}.
*

View File

@@ -16,6 +16,7 @@
package org.springframework.session.data.gemfire.config.annotation.web.http;
import java.time.Duration;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@@ -49,6 +50,7 @@ import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.gemfire.CacheFactoryBean;
import org.springframework.data.gemfire.GemfireOperations;
import org.springframework.data.gemfire.GemfireTemplate;
import org.springframework.data.gemfire.GemfireUtils;
import org.springframework.data.gemfire.IndexFactoryBean;
import org.springframework.data.gemfire.IndexType;
import org.springframework.data.gemfire.RegionAttributesFactoryBean;
@@ -61,6 +63,9 @@ import org.springframework.session.data.gemfire.GemFireOperationsSessionReposito
import org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean;
import org.springframework.session.data.gemfire.config.annotation.web.http.support.SessionAttributesIndexFactoryBean;
import org.springframework.session.data.gemfire.config.annotation.web.http.support.SpringSessionGemFireConfigurer;
import org.springframework.session.data.gemfire.expiration.SessionExpirationPolicy;
import org.springframework.session.data.gemfire.expiration.config.SessionExpirationTimeoutAwareBeanPostProcessor;
import org.springframework.session.data.gemfire.expiration.support.SessionExpirationPolicyCustomExpiryAdapter;
import org.springframework.session.data.gemfire.serialization.SessionSerializer;
import org.springframework.session.data.gemfire.serialization.data.provider.DataSerializableSessionSerializer;
import org.springframework.session.data.gemfire.serialization.data.support.DataSerializerSessionSerializerAdapter;
@@ -87,20 +92,26 @@ import org.springframework.util.StringUtils;
* @see org.springframework.context.annotation.Bean
* @see org.springframework.context.annotation.Configuration
* @see org.springframework.context.annotation.DependsOn
* @see org.springframework.context.annotation.Import
* @see org.springframework.context.annotation.ImportAware
* @see org.springframework.core.annotation.AnnotationAttributes
* @see org.springframework.core.type.AnnotationMetadata
* @see org.springframework.data.gemfire.CacheFactoryBean
* @see org.springframework.data.gemfire.GemfireOperations
* @see org.springframework.data.gemfire.GemfireTemplate
* @see org.springframework.data.gemfire.IndexFactoryBean
* @see org.springframework.data.gemfire.RegionAttributesFactoryBean
* @see org.springframework.session.Session
* @see org.springframework.session.SessionRepository
* @see org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
* @see org.springframework.session.data.gemfire.config.annotation.web.http.AbstractGemFireHttpSessionConfiguration
* @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession
* @see org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean
* @see org.springframework.session.data.gemfire.config.annotation.web.http.support.SessionAttributesIndexFactoryBean
* @see org.springframework.session.data.gemfire.expiration.SessionExpirationPolicy
* @see org.springframework.session.data.gemfire.expiration.config.SessionExpirationTimeoutAwareBeanPostProcessor
* @see org.springframework.session.data.gemfire.serialization.SessionSerializer
* @since 1.1.0
*/
@Configuration
@@ -136,6 +147,11 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC
*/
public static final String DEFAULT_POOL_NAME = GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME;
/**
* Default name for the {@link SessionExpirationPolicy} bean.
*/
public static final String DEFAULT_SESSION_EXPIRATION_POLICY_BEAN_NAME = "";
/**
* Default name of (Client)Cache {@link Region} used to store {@link Session} state.
*/
@@ -165,6 +181,8 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC
private String sessionRegionName = DEFAULT_SESSION_REGION_NAME;
private String sessionExpirationPolicyBeanName = DEFAULT_SESSION_EXPIRATION_POLICY_BEAN_NAME;
private String sessionSerializerBeanName = DEFAULT_SESSION_SERIALIZER_BEAN_NAME;
private String[] indexableSessionAttributes = DEFAULT_INDEXABLE_SESSION_ATTRIBUTES;
@@ -195,7 +213,9 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC
* @see org.apache.geode.cache.client.ClientRegionShortcut
*/
protected ClientRegionShortcut getClientRegionShortcut() {
return Optional.ofNullable(this.clientRegionShortcut).orElse(DEFAULT_CLIENT_REGION_SHORTCUT);
return Optional.ofNullable(this.clientRegionShortcut)
.orElse(DEFAULT_CLIENT_REGION_SHORTCUT);
}
/**
@@ -216,7 +236,9 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC
* for which an Index will be created. Defaults to an empty array if unspecified.
*/
protected String[] getIndexableSessionAttributes() {
return Optional.ofNullable(this.indexableSessionAttributes).orElse(DEFAULT_INDEXABLE_SESSION_ATTRIBUTES);
return Optional.ofNullable(this.indexableSessionAttributes)
.orElse(DEFAULT_INDEXABLE_SESSION_ATTRIBUTES);
}
/**
@@ -259,7 +281,10 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC
* @see org.apache.geode.cache.client.Pool#getName()
*/
protected String getPoolName() {
return Optional.ofNullable(this.poolName).filter(StringUtils::hasText).orElse(DEFAULT_POOL_NAME);
return Optional.ofNullable(this.poolName)
.filter(StringUtils::hasText)
.orElse(DEFAULT_POOL_NAME);
}
/**
@@ -288,7 +313,19 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC
* @see org.apache.geode.cache.RegionShortcut
*/
protected RegionShortcut getServerRegionShortcut() {
return Optional.ofNullable(this.serverRegionShortcut).orElse(DEFAULT_SERVER_REGION_SHORTCUT);
return Optional.ofNullable(this.serverRegionShortcut)
.orElse(DEFAULT_SERVER_REGION_SHORTCUT);
}
public void setSessionExpirationPolicyBeanName(String sessionExpirationPolicyBeanName) {
this.sessionExpirationPolicyBeanName = sessionExpirationPolicyBeanName;
}
protected Optional<String> getSessionExpirationPolicyBeanName() {
return Optional.ofNullable(this.sessionExpirationPolicyBeanName)
.filter(StringUtils::hasText);
}
/**
@@ -310,7 +347,9 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC
* @see org.apache.geode.cache.Region#getName()
*/
protected String getSessionRegionName() {
return Optional.ofNullable(this.sessionRegionName).filter(StringUtils::hasText)
return Optional.ofNullable(this.sessionRegionName)
.filter(StringUtils::hasText)
.orElse(DEFAULT_SESSION_REGION_NAME);
}
@@ -346,7 +385,9 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC
* @see org.springframework.session.data.gemfire.serialization.SessionSerializer
*/
protected String getSessionSerializerBeanName() {
return Optional.ofNullable(this.sessionSerializerBeanName).filter(StringUtils::hasText)
return Optional.ofNullable(this.sessionSerializerBeanName)
.filter(StringUtils::hasText)
.orElse(DEFAULT_SESSION_SERIALIZER_BEAN_NAME);
}
@@ -376,45 +417,83 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC
AnnotationAttributes.fromMap(importMetadata.getAnnotationAttributes(
EnableGemFireHttpSession.class.getName()));
configureClientRegionShortcut(enableGemFireHttpSessionAttributes);
configureIndexableSessionAttributes(enableGemFireHttpSessionAttributes);
configureMaxInactiveIntervalInSeconds(enableGemFireHttpSessionAttributes);
configurePoolName(enableGemFireHttpSessionAttributes);
configureServerRegionShortcut(enableGemFireHttpSessionAttributes);
configureSessionExpirationPolicyBeanName(enableGemFireHttpSessionAttributes);
configureSessionRegionName(enableGemFireHttpSessionAttributes);
configureSessionSerializerBeanName(enableGemFireHttpSessionAttributes);
applySpringSessionGemFireConfigurer();
}
private void configureClientRegionShortcut(AnnotationAttributes enableGemFireHttpSessionAttributes) {
ClientRegionShortcut defaultClientRegionShortcut =
enableGemFireHttpSessionAttributes.getEnum("clientRegionShortcut");
setClientRegionShortcut(resolveProperty(clientRegionShortcutPropertyName(),
ClientRegionShortcut.class, defaultClientRegionShortcut));
}
private void configureIndexableSessionAttributes(AnnotationAttributes enableGemFireHttpSessionAttributes) {
String[] defaultIndexableSessionAttributes =
enableGemFireHttpSessionAttributes.getStringArray("indexableSessionAttributes");
setIndexableSessionAttributes(resolveProperty(indexableSessionAttributesPropertyName(),
defaultIndexableSessionAttributes));
}
private void configureMaxInactiveIntervalInSeconds(AnnotationAttributes enableGemFireHttpSessionAttributes) {
Integer defaultMaxInactiveIntervalInSeconds =
enableGemFireHttpSessionAttributes.getNumber("maxInactiveIntervalInSeconds").intValue();
setMaxInactiveIntervalInSeconds(resolveProperty(maxInactiveIntervalInSecondsPropertyName(),
defaultMaxInactiveIntervalInSeconds));
}
private void configurePoolName(AnnotationAttributes enableGemFireHttpSessionAttributes) {
String defaultPoolName = enableGemFireHttpSessionAttributes.getString("poolName");
setPoolName(resolveProperty(poolNamePropertyName(), defaultPoolName));
}
String defaultSessionRegionName = enableGemFireHttpSessionAttributes.getString("regionName");
setSessionRegionName(resolveProperty(sessionRegionNamePropertyName(), defaultSessionRegionName));
private void configureServerRegionShortcut(AnnotationAttributes enableGemFireHttpSessionAttributes) {
RegionShortcut defaultServerRegionShortcut =
enableGemFireHttpSessionAttributes.getEnum("serverRegionShortcut");
setServerRegionShortcut(resolveProperty(serverRegionShortcutPropertyName(),
RegionShortcut.class, defaultServerRegionShortcut));
}
private void configureSessionExpirationPolicyBeanName(AnnotationAttributes enableGemFireHttpSessionAttributes) {
String defaultSessionExpirationPolicyBeanName =
enableGemFireHttpSessionAttributes.getString("sessionExpirationPolicyBeanName");
setSessionExpirationPolicyBeanName(resolveProperty(sessionExpirationPolicyBeanNamePropertyName(),
defaultSessionExpirationPolicyBeanName));
}
private void configureSessionRegionName(AnnotationAttributes enableGemFireHttpSessionAttributes) {
String defaultSessionRegionName = enableGemFireHttpSessionAttributes.getString("regionName");
setSessionRegionName(resolveProperty(sessionRegionNamePropertyName(), defaultSessionRegionName));
}
private void configureSessionSerializerBeanName(AnnotationAttributes enableGemFireHttpSessionAttributes) {
String defaultSessionSerializerBeanName =
enableGemFireHttpSessionAttributes.getString("sessionSerializerBeanName");
setSessionSerializerBeanName(resolveProperty(sessionSerializerBeanNamePropertyName(),
defaultSessionSerializerBeanName));
applySpringSessionGemFireConfigurer();
}
private Optional<SpringSessionGemFireConfigurer> resolveSpringSessionGemFireConfigurer() {
@@ -454,6 +533,36 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC
getBeanFactory().registerAlias(getSessionSerializerBeanName(), SESSION_SERIALIZER_BEAN_ALIAS);
}
@Bean
BeanPostProcessor sessionExpirationTimeoutAwareBeanPostProcessor() {
Duration expirationTimeout = Duration.ofSeconds(getMaxInactiveIntervalInSeconds());
return new SessionExpirationTimeoutAwareBeanPostProcessor(expirationTimeout);
}
private Optional<SessionExpirationPolicy> resolveSessionExpirationPolicy() {
Optional<String> sessionExpirationPolicyBeanName = getSessionExpirationPolicyBeanName();
if (sessionExpirationPolicyBeanName.isPresent()) {
if (getApplicationContext().containsBean(sessionExpirationPolicyBeanName.get())) {
return Optional.of(getApplicationContext()
.getBean(sessionExpirationPolicyBeanName.get(), SessionExpirationPolicy.class));
}
else {
String logMessage = "No Bean with name [{}] and type [{}] was configured;"
+ " Defaulting to Expiration policy configured for Region [{}]";
getLogger().warn(logMessage, sessionExpirationPolicyBeanName.get(),
SessionExpirationPolicy.class.getName(), getSessionRegionName());
}
}
return Optional.empty();
}
@Bean
BeanPostProcessor sessionSerializerConfigurationBeanPostProcessor() {
@@ -576,9 +685,23 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC
regionAttributes.setValueConstraint(SESSION_REGION_VALUE_CONSTRAINT);
if (isExpirationAllowed(gemfireCache)) {
regionAttributes.setStatisticsEnabled(true);
regionAttributes.setEntryIdleTimeout(
new ExpirationAttributes(Math.max(getMaxInactiveIntervalInSeconds(), 0), ExpirationAction.INVALIDATE));
Optional<SessionExpirationPolicy> sessionExpirationPolicy = resolveSessionExpirationPolicy();
if (sessionExpirationPolicy.isPresent()) {
regionAttributes.setCustomEntryIdleTimeout(new SessionExpirationPolicyCustomExpiryAdapter(
sessionExpirationPolicy.get()));
}
else {
regionAttributes.setEntryIdleTimeout(new ExpirationAttributes(
Math.max(getMaxInactiveIntervalInSeconds(), 0), ExpirationAction.INVALIDATE));
}
}
else {
getLogger().info("Expiration is not allowed on Regions with a data management policy of {}",
GemfireUtils.isClient(gemfireCache) ? getClientRegionShortcut() : getServerRegionShortcut());
}
return regionAttributes;
@@ -634,7 +757,7 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC
*/
@Bean
public GemFireOperationsSessionRepository sessionRepository(
@Qualifier("sessionRegionTemplate") GemfireOperations gemfireOperations) {
@Qualifier("sessionRegionTemplate") GemfireOperations gemfireOperations) {
GemFireOperationsSessionRepository sessionRepository =
new GemFireOperationsSessionRepository(gemfireOperations);