From 5335c8278101229c558e847fa24739dc9f38fb11 Mon Sep 17 00:00:00 2001 From: John Blum Date: Wed, 29 Aug 2018 10:23:55 -0700 Subject: [PATCH] Add support to apply the 'spring.session.timeout' property or the 'server.servlet.session.timeout' property when the 'spring.session.timeout' property is not set. Neither 'spring.session.timeout' nor 'server.servlet.session.timeout' are applied when the 'spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds' property is set. --- .../SpringSessionAutoConfiguration.java | 57 ++++++- .../main/resources/META-INF/spring.factories | 1 + ...figuredSessionCachingIntegrationTests.java | 159 ++++++++++++------ 3 files changed, 167 insertions(+), 50 deletions(-) diff --git a/spring-geode-autoconfigure/src/main/java/org/springframework/geode/boot/autoconfigure/SpringSessionAutoConfiguration.java b/spring-geode-autoconfigure/src/main/java/org/springframework/geode/boot/autoconfigure/SpringSessionAutoConfiguration.java index 08dfb9fa..3356e4f7 100644 --- a/spring-geode-autoconfigure/src/main/java/org/springframework/geode/boot/autoconfigure/SpringSessionAutoConfiguration.java +++ b/spring-geode-autoconfigure/src/main/java/org/springframework/geode/boot/autoconfigure/SpringSessionAutoConfiguration.java @@ -19,18 +19,24 @@ package org.springframework.geode.boot.autoconfigure; import static org.springframework.data.gemfire.util.CollectionUtils.asSet; import java.util.Optional; +import java.util.Properties; import java.util.Set; import org.apache.geode.cache.GemFireCache; +import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertySource; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.session.Session; import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession; @@ -62,11 +68,56 @@ import org.springframework.util.StringUtils; @SuppressWarnings("unused") public class SpringSessionAutoConfiguration { - static final Set SPRING_SESSION_STORE_TYPES = asSet("gemfire", "geode"); + protected static final Set SPRING_SESSION_STORE_TYPES = asSet("gemfire", "geode"); - static final String SPRING_SESSION_STORE_TYPE_PROPERTY = "spring.session.store-type"; + protected static final String SERVER_SERVLET_SESSION_TIMEOUT_PROPERTY = "server.servlet.session.timeout"; + protected static final String SPRING_SESSION_DATA_GEMFIRE_SESSION_EXPIRATION_TIMEOUT = + "spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds"; + protected static final String SPRING_SESSION_PROPERTY_SOURCE_NAME = "SpringSessionProperties"; + protected static final String SPRING_SESSION_STORE_TYPE_PROPERTY = "spring.session.store-type"; + protected static final String SPRING_SESSION_TIMEOUT_PROPERTY = "spring.session.timeout"; - static class SpringSessionStoreTypeCondition implements Condition { + public static class SpringSessionPropertiesEnvironmentPostProcessor implements EnvironmentPostProcessor { + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + + if (isNotSet(environment, SPRING_SESSION_DATA_GEMFIRE_SESSION_EXPIRATION_TIMEOUT)) { + + Properties springSessionProperties = new Properties(); + + if (isSet(environment, SPRING_SESSION_TIMEOUT_PROPERTY)) { + springSessionProperties.setProperty(SPRING_SESSION_DATA_GEMFIRE_SESSION_EXPIRATION_TIMEOUT, + environment.getProperty(SPRING_SESSION_TIMEOUT_PROPERTY)); + } + else if (isSet(environment, SERVER_SERVLET_SESSION_TIMEOUT_PROPERTY)) { + springSessionProperties.setProperty(SPRING_SESSION_DATA_GEMFIRE_SESSION_EXPIRATION_TIMEOUT, + environment.getProperty(SERVER_SERVLET_SESSION_TIMEOUT_PROPERTY)); + } + + if (!springSessionProperties.isEmpty()) { + environment.getPropertySources() + .addFirst(newPropertySource(SPRING_SESSION_PROPERTY_SOURCE_NAME, springSessionProperties)); + } + } + } + + private PropertySource newPropertySource(String name, Properties properties) { + return new PropertiesPropertySource(name, properties); + } + } + + protected static boolean isNotSet(ConfigurableEnvironment environment, String propertyName) { + return !isSet(environment, propertyName); + } + + protected static boolean isSet(ConfigurableEnvironment environment, String propertyName) { + + return environment.containsProperty(propertyName) + && StringUtils.hasText(environment.getProperty(propertyName)); + } + + protected static class SpringSessionStoreTypeCondition implements Condition { @Override @SuppressWarnings("all") public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { diff --git a/spring-geode-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-geode-autoconfigure/src/main/resources/META-INF/spring.factories index 48fea698..cc9510eb 100644 --- a/spring-geode-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-geode-autoconfigure/src/main/resources/META-INF/spring.factories @@ -14,4 +14,5 @@ org.springframework.geode.boot.autoconfigure.SslAutoConfiguration # Environment Post Processing org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.geode.boot.autoconfigure.ClientSecurityAutoConfiguration.AutoConfiguredCloudSecurityEnvironmentPostProcessor,\ +org.springframework.geode.boot.autoconfigure.SpringSessionAutoConfiguration.SpringSessionPropertiesEnvironmentPostProcessor,\ org.springframework.geode.boot.autoconfigure.SslAutoConfiguration.SslEnvironmentPostProcessor diff --git a/spring-geode-autoconfigure/src/test/java/org/springframework/geode/boot/autoconfigure/session/CustomConfiguredSessionCachingIntegrationTests.java b/spring-geode-autoconfigure/src/test/java/org/springframework/geode/boot/autoconfigure/session/CustomConfiguredSessionCachingIntegrationTests.java index ab91ae68..76e3ebec 100644 --- a/spring-geode-autoconfigure/src/test/java/org/springframework/geode/boot/autoconfigure/session/CustomConfiguredSessionCachingIntegrationTests.java +++ b/spring-geode-autoconfigure/src/test/java/org/springframework/geode/boot/autoconfigure/session/CustomConfiguredSessionCachingIntegrationTests.java @@ -18,32 +18,32 @@ package org.springframework.geode.boot.autoconfigure.session; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; -import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; -import java.lang.reflect.Method; -import java.util.Optional; import java.util.Properties; import java.util.concurrent.TimeUnit; +import java.util.function.Function; import org.apache.geode.cache.RegionShortcut; import org.apache.geode.cache.client.ClientRegionShortcut; +import org.junit.Before; import org.junit.Test; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.PropertySource; -import org.springframework.data.gemfire.tests.integration.SpringApplicationContextIntegrationTestsSupport; +import org.springframework.data.gemfire.tests.integration.SpringBootApplicationIntegrationTestsSupport; +import org.springframework.data.gemfire.tests.mock.GemFireMockObjectsSupport; import org.springframework.data.gemfire.tests.mock.annotation.EnableGemFireMockObjects; import org.springframework.geode.boot.autoconfigure.ContinuousQueryAutoConfiguration; +import org.springframework.geode.core.util.ObjectUtils; import org.springframework.mock.env.MockPropertySource; import org.springframework.session.Session; import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration; import org.springframework.session.data.gemfire.config.annotation.web.http.support.SpringSessionGemFireConfigurer; import org.springframework.session.data.gemfire.serialization.SessionSerializer; -import org.springframework.util.ObjectUtils; -import org.springframework.util.ReflectionUtils; /** * Integration Test for the auto-configuration of Spring Session using either Apache Geode or Pivotal GemFire @@ -62,119 +62,184 @@ import org.springframework.util.ReflectionUtils; * @see org.springframework.context.annotation.Bean * @see org.springframework.context.annotation.Configuration * @see org.springframework.core.env.PropertySource - * @see org.springframework.data.gemfire.tests.integration.SpringApplicationContextIntegrationTestsSupport * @see org.springframework.data.gemfire.tests.mock.annotation.EnableGemFireMockObjects + * @see org.springframework.data.gemfire.tests.integration.SpringBootApplicationIntegrationTestsSupport * @see org.springframework.session.Session * @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration * @see org.springframework.session.data.gemfire.config.annotation.web.http.support.SpringSessionGemFireConfigurer * @since 1.0.0 */ @SuppressWarnings("unused") -public class CustomConfiguredSessionCachingIntegrationTests extends SpringApplicationContextIntegrationTestsSupport { +public class CustomConfiguredSessionCachingIntegrationTests extends SpringBootApplicationIntegrationTestsSupport { private static final String SPRING_SESSION_DATA_GEMFIRE_PROPERTY = "spring.session.data.gemfire"; - @SuppressWarnings("unchecked") - private T invoke(Object obj, String methodName) { + private volatile Function applicationContextFunction = + Function.identity(); - return (T) Optional.ofNullable(obj) - .map(Object::getClass) - .map(type -> ReflectionUtils.findMethod(type, methodName)) - .map(this::makeAccessible) - .map(method -> ReflectionUtils.invokeMethod(method, obj)) - .orElseThrow(() -> newIllegalArgumentException("Method [%s] not found on Object of type [%s]", - methodName, ObjectUtils.nullSafeClassName(obj))); + private volatile Function springApplicationBuilderFunction = + Function.identity(); + + private static Properties singletonProperties(String propertyName, String propertyValue) { + + Properties properties = new Properties(); + + properties.setProperty(propertyName, propertyValue); + + return properties; } - private Method makeAccessible(Method method) { - ReflectionUtils.makeAccessible(method); - return method; + private Function newSpringBootSessionPropertiesConfigurationFunction() { + + return springApplicationBuilder -> + springApplicationBuilder.properties(singletonProperties("spring.session.timeout", "300")); } - private PropertySource newSpringSessionGemFireProperties() { + private Function newSpringSessionGemFirePropertiesConfigurationFunction() { - return new MockPropertySource("TestSpringSessionGemFireProperties") - .withProperty(springSessionPropertyName("cache.client.region.shortcut"), "LOCAL") - .withProperty(springSessionPropertyName("session.attributes.indexable"), "one, two") - .withProperty(springSessionPropertyName("session.expiration.max-inactive-interval-seconds"), "600") - .withProperty(springSessionPropertyName("cache.client.pool.name"), "MockPool") - .withProperty(springSessionPropertyName("session.region.name"), "MockRegion") - .withProperty(springSessionPropertyName("cache.server.region.shortcut"), "REPLICATE") - .withProperty(springSessionPropertyName("session.serializer.bean-name"), "MockSessionSerializer"); + return applicationContext -> { + + PropertySource springSessionGemFireProperties = new MockPropertySource("TestSpringSessionGemFireProperties") + .withProperty(springSessionPropertyName("cache.client.region.shortcut"), "LOCAL") + .withProperty(springSessionPropertyName("session.attributes.indexable"), "one, two") + .withProperty(springSessionPropertyName("session.expiration.max-inactive-interval-seconds"), "600") + .withProperty(springSessionPropertyName("cache.client.pool.name"), "MockPool") + .withProperty(springSessionPropertyName("session.region.name"), "MockRegion") + .withProperty(springSessionPropertyName("cache.server.region.shortcut"), "REPLICATE") + .withProperty(springSessionPropertyName("session.serializer.bean-name"), "MockSessionSerializer"); + + applicationContext.getEnvironment().getPropertySources().addFirst(springSessionGemFireProperties); + + return applicationContext; + }; + } + + private Function newWebServerSessionPropertiesConfigurationFunction() { + + return springApplicationBuilder -> + springApplicationBuilder.properties(singletonProperties("server.servlet.session.timeout", "3600")); + } + + @Override + protected SpringApplicationBuilder processBeforeBuild(SpringApplicationBuilder springApplicationBuilder) { + return this.springApplicationBuilderFunction.apply(springApplicationBuilder); } @Override protected ConfigurableApplicationContext processBeforeRefresh(ConfigurableApplicationContext applicationContext) { - - applicationContext.getEnvironment().getPropertySources().addFirst(newSpringSessionGemFireProperties()); - - return applicationContext; + return this.applicationContextFunction.apply(applicationContext); } private String springSessionPropertyName(String propertyNameSuffix) { return String.format("%1$s.%2$s", SPRING_SESSION_DATA_GEMFIRE_PROPERTY, propertyNameSuffix); } + @Before + public void setup() { + GemFireMockObjectsSupport.destroy(); + } + @Test public void springSessionConfigurationCustomizedWithConfigurer() { + this.applicationContextFunction = newSpringSessionGemFirePropertiesConfigurationFunction(); + + this.springApplicationBuilderFunction = newSpringBootSessionPropertiesConfigurationFunction() + .andThen(newWebServerSessionPropertiesConfigurationFunction()); + newApplicationContext(TestConfiguration.class, SpringSessionGemFireConfigurerTestConfiguration.class); GemFireHttpSessionConfiguration sessionConfiguration = getBean(GemFireHttpSessionConfiguration.class); assertThat(sessionConfiguration).isNotNull(); - assertThat(this.invoke(sessionConfiguration, "getClientRegionShortcut")) + assertThat(ObjectUtils.invoke(sessionConfiguration, "getClientRegionShortcut")) .isEqualTo(ClientRegionShortcut.CACHING_PROXY); - assertThat(this.invoke(sessionConfiguration, "getIndexableSessionAttributes")) + assertThat(ObjectUtils.invoke(sessionConfiguration, "getIndexableSessionAttributes")) .contains("two", "four"); - assertThat(this.invoke(sessionConfiguration, "getMaxInactiveIntervalInSeconds")) + assertThat(ObjectUtils.invoke(sessionConfiguration, "getMaxInactiveIntervalInSeconds")) .isEqualTo(900); - assertThat(this.invoke(sessionConfiguration, "getPoolName")).isEqualTo("TestPool"); + assertThat(ObjectUtils.invoke(sessionConfiguration, "getPoolName")).isEqualTo("TestPool"); - assertThat(this.invoke(sessionConfiguration, "getServerRegionShortcut")) + assertThat(ObjectUtils.invoke(sessionConfiguration, "getServerRegionShortcut")) .isEqualTo(RegionShortcut.PARTITION_REDUNDANT_PERSISTENT_OVERFLOW); - assertThat(this.invoke(sessionConfiguration, "getSessionRegionName")) + assertThat(ObjectUtils.invoke(sessionConfiguration, "getSessionRegionName")) .isEqualTo("TestRegion"); - assertThat(this.invoke(sessionConfiguration, "getSessionSerializerBeanName")) + assertThat(ObjectUtils.invoke(sessionConfiguration, "getSessionSerializerBeanName")) .isEqualTo("TestSessionSerializer"); } @Test public void springSessionConfigurationCustomizedWithProperties() { + this.applicationContextFunction = newSpringSessionGemFirePropertiesConfigurationFunction(); + + this.springApplicationBuilderFunction = newSpringBootSessionPropertiesConfigurationFunction() + .andThen(newWebServerSessionPropertiesConfigurationFunction()); + newApplicationContext(TestConfiguration.class); GemFireHttpSessionConfiguration sessionConfiguration = getBean(GemFireHttpSessionConfiguration.class); assertThat(sessionConfiguration).isNotNull(); - assertThat(this.invoke(sessionConfiguration, "getClientRegionShortcut")) + assertThat(ObjectUtils.invoke(sessionConfiguration, "getClientRegionShortcut")) .isEqualTo(ClientRegionShortcut.LOCAL); - assertThat(this.invoke(sessionConfiguration, "getIndexableSessionAttributes")) + assertThat(ObjectUtils.invoke(sessionConfiguration, "getIndexableSessionAttributes")) .contains("one", "two"); - assertThat(this.invoke(sessionConfiguration, "getMaxInactiveIntervalInSeconds")) + assertThat(ObjectUtils.invoke(sessionConfiguration, "getMaxInactiveIntervalInSeconds")) .isEqualTo(600); - assertThat(this.invoke(sessionConfiguration, "getPoolName")).isEqualTo("MockPool"); + assertThat(ObjectUtils.invoke(sessionConfiguration, "getPoolName")).isEqualTo("MockPool"); - assertThat(this.invoke(sessionConfiguration, "getServerRegionShortcut")) + assertThat(ObjectUtils.invoke(sessionConfiguration, "getServerRegionShortcut")) .isEqualTo(RegionShortcut.REPLICATE); - assertThat(this.invoke(sessionConfiguration, "getSessionRegionName")) + assertThat(ObjectUtils.invoke(sessionConfiguration, "getSessionRegionName")) .isEqualTo("MockRegion"); - assertThat(this.invoke(sessionConfiguration, "getSessionSerializerBeanName")) + assertThat(ObjectUtils.invoke(sessionConfiguration, "getSessionSerializerBeanName")) .isEqualTo("MockSessionSerializer"); } + @Test + public void springSessionExpirationTimeoutConfiguredWithSpringBootProperties() { + + this.springApplicationBuilderFunction = newSpringBootSessionPropertiesConfigurationFunction() + .andThen(newWebServerSessionPropertiesConfigurationFunction()); + + newApplicationContext(TestConfiguration.class); + + GemFireHttpSessionConfiguration sessionConfiguration = getBean(GemFireHttpSessionConfiguration.class); + + assertThat(sessionConfiguration).isNotNull(); + + assertThat(ObjectUtils.invoke(sessionConfiguration, "getMaxInactiveIntervalInSeconds")) + .isEqualTo(300); + } + + @Test + public void springSessionExpirationTimeoutConfiguredWithWebContainerProperties() { + + this.springApplicationBuilderFunction = newWebServerSessionPropertiesConfigurationFunction(); + + newApplicationContext(TestConfiguration.class); + + GemFireHttpSessionConfiguration sessionConfiguration = getBean(GemFireHttpSessionConfiguration.class); + + assertThat(sessionConfiguration).isNotNull(); + + assertThat(ObjectUtils.invoke(sessionConfiguration, "getMaxInactiveIntervalInSeconds")) + .isEqualTo(3600); + } + @SpringBootConfiguration @EnableGemFireMockObjects @EnableAutoConfiguration(exclude = ContinuousQueryAutoConfiguration.class)