From f37010c5d7b0471eb1cb51bf82c07e520f0c1598 Mon Sep 17 00:00:00 2001 From: John Blum Date: Sun, 25 Nov 2018 18:26:36 -0800 Subject: [PATCH] Add support to expose configuration as properties in the Spring Environment. Edit Javadoc. Resolves gh-14. --- ...nGemFireConfigurationIntegrationTests.java | 413 ++++++++++++++++++ ...stractGemFireHttpSessionConfiguration.java | 99 +++-- .../web/http/EnableGemFireHttpSession.java | 92 +++- .../http/GemFireHttpSessionConfiguration.java | 216 ++++++++- .../SpringSessionGemFireConfigurer.java | 32 ++ .../GemFireHttpSessionConfigurationTests.java | 355 +++++++++++++-- 6 files changed, 1096 insertions(+), 111 deletions(-) create mode 100644 spring-session-data-geode/src/integration-test/java/org/springframework/session/data/gemfire/config/annotation/web/http/ExposingSpringSessionGemFireConfigurationIntegrationTests.java diff --git a/spring-session-data-geode/src/integration-test/java/org/springframework/session/data/gemfire/config/annotation/web/http/ExposingSpringSessionGemFireConfigurationIntegrationTests.java b/spring-session-data-geode/src/integration-test/java/org/springframework/session/data/gemfire/config/annotation/web/http/ExposingSpringSessionGemFireConfigurationIntegrationTests.java new file mode 100644 index 0000000..21bd891 --- /dev/null +++ b/spring-session-data-geode/src/integration-test/java/org/springframework/session/data/gemfire/config/annotation/web/http/ExposingSpringSessionGemFireConfigurationIntegrationTests.java @@ -0,0 +1,413 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.session.data.gemfire.config.annotation.web.http; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import java.util.Optional; + +import org.junit.After; +import org.junit.Test; + +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.apache.geode.cache.client.Pool; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.core.env.Environment; +import org.springframework.core.env.PropertySource; +import org.springframework.data.gemfire.config.annotation.ClientCacheApplication; +import org.springframework.data.gemfire.tests.mock.annotation.EnableGemFireMockObjects; +import org.springframework.mock.env.MockPropertySource; +import org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests; +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.serialization.SessionSerializer; + +/** + * The ExposingSpringSessionGemFireConfigurationIntegrationTests class... + * + * @author John Blum + * @since 1.0.0 + */ +@SuppressWarnings("unused") +public class ExposingSpringSessionGemFireConfigurationIntegrationTests extends AbstractGemFireIntegrationTests { + + private ConfigurableApplicationContext applicationContext; + + @After + public void tearDown() { + Optional.ofNullable(this.applicationContext).ifPresent(ConfigurableApplicationContext::close); + } + + private ConfigurableApplicationContext newApplicationContext(Class... annotatedClasses) { + return newApplicationContext(new MockPropertySource("TestProperties"), annotatedClasses); + } + + private ConfigurableApplicationContext newApplicationContext(PropertySource testPropertySource, + Class... annotatedClasses) { + + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + + applicationContext.getEnvironment().getPropertySources().addFirst(testPropertySource); + applicationContext.register(annotatedClasses); + applicationContext.registerShutdownHook(); + applicationContext.refresh(); + + return applicationContext; + } + + @Test + public void exposeAnnotationAttributesAsProperties() { + + this.applicationContext = newApplicationContext(TestGemFireHttpSessionConfiguration.class); + + Environment environment = this.applicationContext.getEnvironment(); + + assertThat(environment).isNotNull(); + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.cache.client.region.shortcut")) + .isEqualTo(ClientRegionShortcut.LOCAL.name()); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.configuration.expose")) + .isEqualTo(Boolean.TRUE.toString()); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.attributes.indexed")) + .isEqualTo("one,two"); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds")) + .isEqualTo("600"); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.cache.client.pool.name")) + .isEqualTo("Car"); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.region.name")) + .isEqualTo("Sessions"); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.cache.server.region.shortcut")) + .isEqualTo(RegionShortcut.REPLICATE.name()); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.expiration.bean-name")) + .isEqualTo("AttributeSessionExpirationPolicy"); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.serializer.bean-name")) + .isEqualTo("AttributeSessionSerializer"); + } + + @Test + public void exposePropertiesAsPropertiesOverridesAnnotationAttributes() { + + MockPropertySource testPropertySource = new MockPropertySource("TestProperties") + .withProperty("spring.session.data.gemfire.cache.client.region.shortcut", ClientRegionShortcut.LOCAL_PERSISTENT.name()) + .withProperty("spring.session.data.gemfire.session.configuration.expose", "true") + .withProperty("spring.session.data.gemfire.session.attributes.indexed", "one, two, three") + .withProperty("spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds", "900") + .withProperty("spring.session.data.gemfire.cache.client.pool.name", "Dead") + .withProperty("spring.session.data.gemfire.session.region.name", "PropertySessions") + .withProperty("spring.session.data.gemfire.cache.server.region.shortcut", RegionShortcut.REPLICATE_PERSISTENT.name()) + .withProperty("spring.session.data.gemfire.session.expiration.bean-name", "PropertySessionExpirationPolicy") + .withProperty("spring.session.data.gemfire.session.serializer.bean-name", "PropertySessionSerializer"); + + this.applicationContext = newApplicationContext(testPropertySource, TestGemFireHttpSessionConfiguration.class); + + Environment environment = this.applicationContext.getEnvironment(); + + assertThat(environment).isNotNull(); + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.cache.client.region.shortcut")) + .isEqualTo(ClientRegionShortcut.LOCAL_PERSISTENT.name()); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.configuration.expose")) + .isEqualTo(Boolean.TRUE.toString()); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.attributes.indexed")) + .isEqualTo("one,two,three"); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds")) + .isEqualTo("900"); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.cache.client.pool.name")) + .isEqualTo("Dead"); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.region.name")) + .isEqualTo("PropertySessions"); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.cache.server.region.shortcut")) + .isEqualTo(RegionShortcut.REPLICATE_PERSISTENT.name()); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.expiration.bean-name")) + .isEqualTo("PropertySessionExpirationPolicy"); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.serializer.bean-name")) + .isEqualTo("PropertySessionSerializer"); + } + + @Test + public void exposeConfigurerConfigurationAsPropertiesOverridesAnnotationAttributesAndProperties() { + + MockPropertySource testPropertySource = new MockPropertySource("TestProperties") + .withProperty("spring.session.data.gemfire.cache.client.region.shortcut", ClientRegionShortcut.LOCAL_PERSISTENT.name()) + .withProperty("spring.session.data.gemfire.session.configuration.expose", "false") + .withProperty("spring.session.data.gemfire.session.attributes.indexed", "one, two, three") + .withProperty("spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds", "900") + .withProperty("spring.session.data.gemfire.cache.client.pool.name", "Dead") + .withProperty("spring.session.data.gemfire.session.region.name", "PropertySessions") + .withProperty("spring.session.data.gemfire.cache.server.region.shortcut", RegionShortcut.REPLICATE_PERSISTENT.name()) + .withProperty("spring.session.data.gemfire.session.expiration.bean-name", "PropertySessionExpirationPolicy") + .withProperty("spring.session.data.gemfire.session.serializer.bean-name", "PropertySessionSerializer"); + + this.applicationContext = newApplicationContext(testPropertySource, TestGemFireHttpSessionConfiguration.class, + TestSpringSessionGemFireConfigurerConfiguration.class); + + Environment environment = this.applicationContext.getEnvironment(); + + assertThat(environment).isNotNull(); + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.cache.client.region.shortcut")) + .isEqualTo(ClientRegionShortcut.CACHING_PROXY.name()); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.configuration.expose")) + .isEqualTo(Boolean.TRUE.toString()); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.attributes.indexed")) + .isEqualTo("two,four"); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds")) + .isEqualTo("300"); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.cache.client.pool.name")) + .isEqualTo("Swimming"); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.region.name")) + .isEqualTo("ConfigurerSessions"); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.cache.server.region.shortcut")) + .isEqualTo(RegionShortcut.PARTITION_REDUNDANT.name()); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.expiration.bean-name")) + .isEqualTo("ConfigurerSessionExpirationPolicy"); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.serializer.bean-name")) + .isEqualTo("ConfigurerSessionSerializer"); + } + + @Test + public void exposeConfigurationAsPropertiesUsesAnnotationAttributesConfigurerConfigurationAndProperties() { + + MockPropertySource testPropertySource = new MockPropertySource("TestProperties") + .withProperty("spring.session.data.gemfire.cache.client.region.shortcut", ClientRegionShortcut.LOCAL_PERSISTENT.name()) + .withProperty("spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds", "300") + .withProperty("spring.session.data.gemfire.cache.client.pool.name", "Dead") + .withProperty("spring.session.data.gemfire.session.region.name", "PropertySessions"); + + this.applicationContext = newApplicationContext(testPropertySource, TestGemFireHttpSessionConfiguration.class, + MockSpringSessionGemFirerConfigurerConfiguration.class); + + Environment environment = this.applicationContext.getEnvironment(); + + assertThat(environment).isNotNull(); + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.cache.client.region.shortcut")) + .isEqualTo(ClientRegionShortcut.CACHING_PROXY.name()); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.configuration.expose")) + .isEqualTo(Boolean.TRUE.toString()); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.attributes.indexed")) + .isEqualTo("one,two"); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds")) + .isEqualTo("300"); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.cache.client.pool.name")) + .isEqualTo("Dead"); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.region.name")) + .isEqualTo("PropertySessions"); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.cache.server.region.shortcut")) + .isEqualTo(RegionShortcut.PARTITION_REDUNDANT.name()); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.expiration.bean-name")) + .isEqualTo("AttributeSessionExpirationPolicy"); + + assertThat(environment.getRequiredProperty("spring.session.data.gemfire.session.serializer.bean-name")) + .isEqualTo("ConfigurerSessionSerializer"); + } + + @ClientCacheApplication + @EnableGemFireMockObjects + @EnableGemFireHttpSession( + clientRegionShortcut = ClientRegionShortcut.LOCAL, + exposeConfigurationAsProperties = true, + indexableSessionAttributes = { "one", "two" }, + maxInactiveIntervalInSeconds = 600, + poolName = "Car", + regionName = "Sessions", + serverRegionShortcut = RegionShortcut.REPLICATE, + sessionExpirationPolicyBeanName = "AttributeSessionExpirationPolicy", + sessionSerializerBeanName = "AttributeSessionSerializer" + ) + @SuppressWarnings("unused") + static class TestGemFireHttpSessionConfiguration { + + @Bean("Car") + Pool carPool() { + return mock(Pool.class); + } + + @Bean("Dead") + Pool deadPool() { + return mock(Pool.class); + } + + @Bean + SpringSessionGemFireConfigurer emptySpringSessionGemFireConfigurer() { + return new SpringSessionGemFireConfigurer() { }; + } + + @Bean("AttributeSessionExpirationPolicy") + SessionExpirationPolicy attributeSessionExpirationPolicy() { + return mock(SessionExpirationPolicy.class); + } + + @Bean("PropertySessionExpirationPolicy") + SessionExpirationPolicy propertySessionExpirationPolicy() { + return mock(SessionExpirationPolicy.class); + } + + @Bean("AttributeSessionSerializer") + SessionSerializer attributeSessionSerializer() { + return mock(SessionSerializer.class); + } + + @Bean("PropertySessionSerializer") + SessionSerializer propertySessionSerializer() { + return mock(SessionSerializer.class); + } + } + + @Configuration + static class TestSpringSessionGemFireConfigurerConfiguration { + + @Bean("Swimming") + Pool swimmingPool() { + return mock(Pool.class); + } + + @Bean("ConfigurerSessionExpirationPolicy") + SessionExpirationPolicy configurerSessionExpirationPolicy() { + return mock(SessionExpirationPolicy.class); + } + + @Bean("ConfigurerSessionSerializer") + SessionSerializer configurerSessionSerializer() { + return mock(SessionSerializer.class); + } + + @Bean + @Primary + SpringSessionGemFireConfigurer testSpringSessionGemFireConfigurer() { + + return new SpringSessionGemFireConfigurer() { + + @Override + public ClientRegionShortcut getClientRegionShortcut() { + return ClientRegionShortcut.CACHING_PROXY; + } + + @Override + public boolean getExposeConfigurationAsProperties() { + return true; + } + + @Override + public String[] getIndexableSessionAttributes() { + return new String[] { "two", "four" }; + } + + @Override + public int getMaxInactiveIntervalInSeconds() { + return 300; + } + + @Override + public String getPoolName() { + return "Swimming"; + } + + @Override + public String getRegionName() { + return "ConfigurerSessions"; + } + + @Override + public RegionShortcut getServerRegionShortcut() { + return RegionShortcut.PARTITION_REDUNDANT; + } + + @Override + public String getSessionExpirationPolicyBeanName() { + return "ConfigurerSessionExpirationPolicy"; + } + + @Override + public String getSessionSerializerBeanName() { + return "ConfigurerSessionSerializer"; + } + }; + } + } + + @Configuration + static class MockSpringSessionGemFirerConfigurerConfiguration { + + @Bean("ConfigurerSessionSerializer") + SessionSerializer configurerSessionSerializer() { + return mock(SessionSerializer.class); + } + + @Bean + @Primary + SpringSessionGemFireConfigurer mockSpringSessionGemFireConfigurer() { + + return new SpringSessionGemFireConfigurer() { + + @Override + public ClientRegionShortcut getClientRegionShortcut() { + return ClientRegionShortcut.CACHING_PROXY; + } + + @Override + public RegionShortcut getServerRegionShortcut() { + return RegionShortcut.PARTITION_REDUNDANT; + } + + @Bean("ConfigurerSessionSerializer") + SessionSerializer configurerSessionSerializer() { + return mock(SessionSerializer.class); + } + + @Override + public String getSessionSerializerBeanName() { + return "ConfigurerSessionSerializer"; + } + }; + } + } +} diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/AbstractGemFireHttpSessionConfiguration.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/AbstractGemFireHttpSessionConfiguration.java index 5c23003..3b210e9 100644 --- a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/AbstractGemFireHttpSessionConfiguration.java +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/AbstractGemFireHttpSessionConfiguration.java @@ -43,10 +43,10 @@ import org.slf4j.LoggerFactory; * @see java.lang.ClassLoader * @see org.springframework.beans.factory.BeanClassLoaderAware * @see org.springframework.beans.factory.config.ConfigurableBeanFactory - * @see org.springframework.core.env.Environment * @see org.springframework.context.ApplicationContext * @see org.springframework.context.ConfigurableApplicationContext * @see org.springframework.context.EnvironmentAware + * @see org.springframework.core.env.Environment * @see org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration * @since 2.0.4 */ @@ -124,8 +124,9 @@ public abstract class AbstractGemFireHttpSessionConfiguration extends SpringHttp ApplicationContext applicationContext = getApplicationContext(); return Optional.ofNullable(applicationContext) - .filter(it -> it instanceof ConfigurableApplicationContext) - .map(it -> ((ConfigurableApplicationContext) it).getBeanFactory()) + .filter(ConfigurableApplicationContext.class::isInstance) + .map(ConfigurableApplicationContext.class::cast) + .map(ConfigurableApplicationContext::getBeanFactory) .orElseThrow(() -> newIllegalStateException("Unable to resolve a reference to a [%1$s] from a [%2$s]", ConfigurableBeanFactory.class.getName(), ObjectUtils.nullSafeClassName(applicationContext))); } @@ -161,28 +162,55 @@ public abstract class AbstractGemFireHttpSessionConfiguration extends SpringHttp return this.logger; } + /** + * Returns the fully-qualified {@link String property name}. + * + * The fully qualified {@link String property name} consists of the {@link String base property name} + * concatenated with the {@code propertyNameSuffix}. + * + * @param propertyNameSuffix {@link String} containing the property name suffix concatenated with + * the {@link String base property name}. + * @return the fully-qualified {@link String property name}. + * @see java.lang.String + */ + private String propertyName(String propertyNameSuffix) { + return String.format("%1$s%2$s", SPRING_SESSION_PROPERTY_PREFIX, propertyNameSuffix); + } + + private String cachePropertyName(String propertyNameSuffix) { + return propertyName(String.format("cache.%s", propertyNameSuffix)); + } + + private String sessionPropertyName(String propertyNameSuffix) { + return propertyName(String.format("session.%s", propertyNameSuffix)); + } + protected String clientRegionShortcutPropertyName() { - return propertyName("cache.client.region.shortcut"); + return cachePropertyName("client.region.shortcut"); + } + + protected String exposeConfigurationAsPropertiesPropertyName() { + return sessionPropertyName("configuration.expose"); } protected String indexableSessionAttributesPropertyName() { return sessionPropertyName("attributes.indexable"); } + protected String indexedSessionAttributesPropertyName() { + return sessionPropertyName("attributes.indexed"); + } + protected String maxInactiveIntervalInSecondsPropertyName() { return sessionPropertyName("expiration.max-inactive-interval-seconds"); } protected String poolNamePropertyName() { - return propertyName("cache.client.pool.name"); + return cachePropertyName("client.pool.name"); } protected String serverRegionShortcutPropertyName() { - return propertyName("cache.server.region.shortcut"); - } - - protected String sessionPropertyName(String propertyNameSuffix) { - return propertyName(String.format("session.%s", propertyNameSuffix)); + return cachePropertyName("server.region.shortcut"); } protected String sessionExpirationPolicyBeanNamePropertyName() { @@ -197,21 +225,6 @@ public abstract class AbstractGemFireHttpSessionConfiguration extends SpringHttp return sessionPropertyName("serializer.bean-name"); } - /** - * Returns the fully-qualified {@link String property name}. - * - * The fully qualified {@link String property name} consists of the {@link String base property name} - * concatenated with the {@code propertyNameSuffix}. - * - * @param propertyNameSuffix {@link String} containing the property name suffix concatenated with - * the {@link String base property name}. - * @return the fully-qualified {@link String property name}. - * @see java.lang.String - */ - protected String propertyName(String propertyNameSuffix) { - return String.format("%1$s%2$s", SPRING_SESSION_PROPERTY_PREFIX, propertyNameSuffix); - } - /** * Resolves the value for the given property identified by {@link String name} from the Spring {@link Environment} * as an instance of the specified {@link Class type}. @@ -246,18 +259,32 @@ public abstract class AbstractGemFireHttpSessionConfiguration extends SpringHttp * @see java.lang.Enum */ protected > T resolveEnumeratedProperty(String propertyName, Class targetType, T defaultValue) { - return resolveProperty(propertyName, targetType, defaultValue); } + /** + * Attempts to resolve the property with the given {@link String name} from the Spring {@link Environment} + * as an {@link Boolean}. + * + * @param propertyName {@link String name} of the property to resolve. + * @param defaultValue default value to return if the property is not defined or not set. + * @return the value of the property identified by {@link String name} or the default value + * if the property is not defined or not set. + * @see #resolveProperty(String, Class, Object) + * @see java.lang.Boolean + */ + protected Boolean resolveProperty(String propertyName, Boolean defaultValue) { + return resolveProperty(propertyName, Boolean.class, defaultValue); + } + /** * Attempts to resolve the property with the given {@link String name} from the Spring {@link Environment} * as an {@link Integer}. * * @param propertyName {@link String name} of the property to resolve. * @param defaultValue default value to return if the property is not defined or not set. - * @return the value of the property identified by {@link String name} or default value if the property - * is not defined or not set. + * @return the value of the property identified by {@link String name} or the default value + * if the property is not defined or not set. * @see #resolveProperty(String, Class, Object) * @see java.lang.Integer */ @@ -271,8 +298,8 @@ public abstract class AbstractGemFireHttpSessionConfiguration extends SpringHttp * * @param propertyName {@link String name} of the property to resolve. * @param defaultValue default value to return if the property is not defined or not set. - * @return the value of the property identified by {@link String name} or default value if the property - * is not defined or not set. + * @return the value of the property identified by {@link String name} or the default value + * if the property is not defined or not set. * @see #resolveProperty(String, Class, Object) * @see java.lang.String */ @@ -286,8 +313,8 @@ public abstract class AbstractGemFireHttpSessionConfiguration extends SpringHttp * * @param propertyName {@link String name} of the property to resolve. * @param defaultValue default value to return if the property is not defined or not set. - * @return the value of the property identified by {@link String name} or default value if the property - * is not defined or not set. + * @return the value of the property identified by {@link String name} or the default value + * if the property is not defined or not set. * @see #resolveProperty(String, Class, Object) * @see java.lang.String */ @@ -298,11 +325,11 @@ public abstract class AbstractGemFireHttpSessionConfiguration extends SpringHttp /** * Attempts to resolve the property with the given {@link String name} from the Spring {@link Environment}. * - * @param {@link Class} type of the property value. + * @param {@link Class type} of the property value. * @param propertyName {@link String name} of the property to resolve. - * @param targetType {@link Class} type of the property's value. - * @return the value of the property identified by {@link String name} or {@literal null} if the property - * is not defined or not set. + * @param targetType {@link Class type} of the property's value. + * @return the {@link Object value} of the property identified by {@link String name} + * or {@literal null} if the property is not defined or not set. * @see #resolveProperty(String, Class, Object) */ protected T resolveProperty(String propertyName, Class targetType) { diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSession.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSession.java index f50b9e5..d33d451 100644 --- a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSession.java +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSession.java @@ -16,11 +16,13 @@ package org.springframework.session.data.gemfire.config.annotation.web.http; +import java.lang.annotation.Annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.Properties; import javax.servlet.http.HttpSession; @@ -33,62 +35,77 @@ import org.apache.geode.cache.client.Pool; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.core.env.Environment; import org.springframework.session.Session; +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.serialization.SessionSerializer; import org.springframework.session.web.http.SessionRepositoryFilter; /** - * Add this annotation to a Spring application defined {@code @Configuration} class exposing - * the {@link SessionRepositoryFilter} as a bean named {@literal springSessionRepositoryFilter} - * to back the {@link HttpSession} by Apache Geode or Pivotal GemFire. + * Add this {@link Annotation annotation} to a Spring application defined {@code @Configuration} {@link Class} + * exposing the {@link SessionRepositoryFilter} as a bean named {@literal springSessionRepositoryFilter} + * to back the {@link HttpSession} with either Apache Geode or Pivotal GemFire. * - * In order to use this annotation, a single Apache Geode / Pivotal GemFire {@link Cache} or {@link ClientCache} - * instance must be provided. + * In order to use this {@link Annotation annotation}, a single Apache Geode / Pivotal GemFire {@link ClientCache} + * or {@link Cache Peer Cache} instance must be provided. + * + * The most common use case is to use Apache Geode or Pivotal GemFire's client/server topology, where your + * Spring Session enabled application uses a {@link ClientCache} to manage {@link Session} state in a cluster + * of dedicated Apache Geode or Pivotal GemFire servers. + * + * For example: + * + *
+ * 
+ * {@literal @ClientCacheApplication(subscriptionEnabled = true)}
+ * {@literal @EnableGemFireHttpSession(poolName = "DEFAULT"}
+ * public class ClientCacheHttpSessionConfiguration {
+ *
+ * }
+ * 
+ * 
+ * + * Alternatively, though less common (and not recommended), you can use Spring Session with Apache Geode + * or Pivotal GemFire in the embedded {@link Cache Peer Cache} scenario, where your Spring Session enabled application + * is technically a {@literal peer} in the Apache Geode or Pivotal GemFire cluster. * * For example: * *
  * 
- * {@literal @Configuration}
  * {@literal @PeerCacheApplication}
  * {@literal @EnableGemFireHttpSession}
  * public class PeerCacheHttpSessionConfiguration {
  *
  * }
- *  
- * - * Alternatively, Spring Session can be configured to use Apache Geode / Pivotal GemFire as a cache client - * with a dedicated Apache Geode / Pivotal GemFire cluster. - * - * For example: - * - * - * {@literal @Configuration} - * {@literal @ClientCacheApplication} - * {@literal @EnableGemFireHttpSession} - * public class ClientCacheHttpSessionConfiguration { - * - * } * + * * * More advanced configurations can extend {@link GemFireHttpSessionConfiguration} instead. * * @author John Blum + * @see java.lang.annotation.Annotation + * @see java.util.Properties + * @see javax.servlet.http.HttpSession * @see org.apache.geode.cache.Cache * @see org.apache.geode.cache.Region * @see org.apache.geode.cache.client.ClientCache * @see org.apache.geode.cache.client.Pool * @see org.springframework.context.annotation.Configuration * @see org.springframework.context.annotation.Import + * @see org.springframework.core.env.Environment * @see org.springframework.session.Session * @see org.springframework.session.config.annotation.web.http.EnableSpringHttpSession * @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration + * @see org.springframework.session.data.gemfire.config.annotation.web.http.support.SpringSessionGemFireConfigurer + * @see org.springframework.session.data.gemfire.expiration.SessionExpirationPolicy + * @see org.springframework.session.data.gemfire.serialization.SessionSerializer * @since 1.1.0 */ -@Documented -@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented @Configuration @Import(GemFireHttpSessionConfiguration.class) public @interface EnableGemFireHttpSession { @@ -107,6 +124,33 @@ public @interface EnableGemFireHttpSession { */ ClientRegionShortcut clientRegionShortcut() default ClientRegionShortcut.PROXY; + /** + * Determines whether the configuration for Spring Session using Apache Geode or Pivotal GemFire should be exposed + * in the Spring {@link Environment} as {@link Properties}. + * + * Currently, users may configure Spring Session for Apache Geode or Pivotal GemFire using attributes on this + * {@link Annotation}, using the well-known and documented {@link Properties} + * (e.g. {@literal spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds}) + * or using the {@link SpringSessionGemFireConfigurer} declared as a bean in the Spring application context. + * + * The {@link Properties} that are exposed will use the well-known property {@link String names} that are documented + * in this {@link Annotation Annotation's} attributes. + * + * The values of the resulting {@link Properties} follows the precedence as outlined in the documentation: + * first any {@link SpringSessionGemFireConfigurer} bean defined takes precedence, followed by explicit + * {@link Properties} declared in Spring Boot {@literal application.properties} and finally, this + * {@link Annotation Annotation's} attribute values. + * + * Defaults to {@literal false}. + * + * Use {@literal spring.session.data.gemfire.session.configuration.expose} in Spring Boot + * {@literal application.properties}. + * + * @return a boolean value indicating whether to expose the configuration of Spring Session using Apache Geode + * or Pivotal GemFire in the Spring {@link Environment} as {@link Properties}. + */ + boolean exposeConfigurationAsProperties() default GemFireHttpSessionConfiguration.DEFAULT_EXPOSE_CONFIGURATION_AS_PROPERTIES; + /** * Identifies the {@link Session} attributes by name that will be indexed for query operations. * @@ -115,8 +159,8 @@ public @interface EnableGemFireHttpSession { * * Defaults to empty {@link String} array. * - * Use the {@literal spring.session.data.gemfire.session.attributes.indexable} in Spring Boot - * {@literal application.properties}. + * Use the {@literal spring.session.data.gemfire.session.attributes.indexed} + * in Spring Boot {@literal application.properties}. * * @return an array of {@link String Strings} identifying the names of {@link Session} attributes to index. */ diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfiguration.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfiguration.java index e981b44..9d1d2a4 100644 --- a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfiguration.java +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfiguration.java @@ -18,10 +18,12 @@ package org.springframework.session.data.gemfire.config.annotation.web.http; import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; +import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.time.Duration; import java.util.Arrays; import java.util.Optional; +import java.util.Properties; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Function; @@ -46,12 +48,17 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportAware; import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertySource; import org.springframework.core.type.AnnotationMetadata; import org.springframework.data.gemfire.CacheFactoryBean; import org.springframework.data.gemfire.GemfireOperations; @@ -133,6 +140,12 @@ import org.springframework.util.StringUtils; @SuppressWarnings("unused") public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionConfiguration implements ImportAware { + /** + * Default expose Spring Session using Apache Geode or Pivotal GemFire configuration as {@link Properties} + * in Spring's {@link Environment}. + */ + public static final boolean DEFAULT_EXPOSE_CONFIGURATION_AS_PROPERTIES = false; + /** * Default maximum interval in seconds in which a {@link Session} can remain inactive before it expires. */ @@ -162,6 +175,9 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC public static final String CONFIGURER_GET_CLIENT_REGION_SHORTCUT_METHOD_NAME = findByMethodName(SpringSessionGemFireConfigurer.class, "getClientRegionShortcut"); + public static final String CONFIGURER_GET_EXPOSE_CONFIGURATION_IN_PROPERTIES_METHOD_NAME = + findByMethodName(SpringSessionGemFireConfigurer.class, "getExposeConfigurationAsProperties"); + public static final String CONFIGURER_GET_INDEXABLE_SESSION_ATTRIBUTES_METHOD_NAME = findByMethodName(SpringSessionGemFireConfigurer.class, "getIndexableSessionAttributes"); @@ -208,11 +224,16 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC public static final String DEFAULT_SESSION_SERIALIZER_BEAN_NAME = SESSION_PDX_SERIALIZER_BEAN_NAME; + protected static final String SPRING_SESSION_GEMFIRE_PROPERTY_SOURCE = + GemFireHttpSessionConfiguration.class.getName().concat(".PROPERTY_SOURCE"); + /** * Defaults names of all {@link Session} attributes that will be indexed by Apache Geode. */ public static final String[] DEFAULT_INDEXABLE_SESSION_ATTRIBUTES = {}; + private boolean exposeConfigurationAsProperties = DEFAULT_EXPOSE_CONFIGURATION_AS_PROPERTIES; + private int maxInactiveIntervalInSeconds = DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS; private ClientRegionShortcut clientRegionShortcut = DEFAULT_CLIENT_REGION_SHORTCUT; @@ -229,7 +250,7 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC private String[] indexableSessionAttributes = DEFAULT_INDEXABLE_SESSION_ATTRIBUTES; - private static String findByMethodName(@NonNull Class type, @NonNull String methodName) { + private static @NonNull String findByMethodName(@NonNull Class type, @NonNull String methodName) { return Arrays.stream(type.getDeclaredMethods()) .map(Method::getName) @@ -288,6 +309,48 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC .orElse(DEFAULT_CLIENT_REGION_SHORTCUT); } + /** + * Sets whether to expose the configuration of Spring Session using Apache Geode or Pivotal GemFire + * as {@link Properties} in the Spring {@link Environment}. + * + * @param exposeConfigurationAsProperties boolean indicating whether to expose the configuration + * of Spring Session using Apache Geode or Pivotal GemFire as {@link Properties} in the Spring {@link Environment}. + * + * @see EnableGemFireHttpSession#exposeConfigurationAsProperties() + */ + public void setExposeConfigurationAsProperties(boolean exposeConfigurationAsProperties) { + this.exposeConfigurationAsProperties = exposeConfigurationAsProperties; + } + + /** + * Determines whether the configuration for Spring Session using Apache Geode or Pivotal GemFire should be exposed + * in the Spring {@link org.springframework.core.env.Environment} as {@link Properties}. + * + * Currently, users may configure Spring Session for Apache Geode or Pivotal GemFire using attributes on this + * {@link Annotation}, using the well-known and documented {@link Properties} + * (e.g. {@literal spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds}) + * or using the {@link SpringSessionGemFireConfigurer} declared as a bean in the Spring application context. + * + * The {@link Properties} that are exposed will use the well-known property {@link String names} that are documented + * in this {@link Annotation Annotation's} attributes. + * + * The values of the resulting {@link Properties} follows the precedence as outlined in the documentation: + * first any {@link SpringSessionGemFireConfigurer} bean defined takes precedence, followed by explicit + * {@link Properties} declared in Spring Boot {@literal application.properties} and finally, this + * {@link Annotation Annotation's} attribute values. + * + * Defaults to {@literal false}. + * + * Use {@literal spring.session.data.gemfire.session.configuration.expose} in Spring Boot + * {@literal application.properties}. + * + * @return a boolean value indicating whether to expose the configuration of Spring Session using Apache Geode + * or Pivotal GemFire in the Spring {@link org.springframework.core.env.Environment} as {@link Properties}. + */ + public boolean isExposeConfigurationAsProperties() { + return this.exposeConfigurationAsProperties; + } + /** * Sets the names of all {@link Session} attributes that will be indexed. * @@ -388,10 +451,24 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC .orElse(DEFAULT_SERVER_REGION_SHORTCUT); } + /** + * Sets the {@link String name} of the bean configured in the Spring application context implementing + * the {@link SessionExpirationPolicy} for {@link Session} expiration. + * + * @param sessionExpirationPolicyBeanName {@link String} containing the name of the bean configured in + * the Spring application context implementing the {@link SessionExpirationPolicy} for {@link Session} expiration. + */ public void setSessionExpirationPolicyBeanName(String sessionExpirationPolicyBeanName) { this.sessionExpirationPolicyBeanName = sessionExpirationPolicyBeanName; } + /** + * Returns an {@link Optional} {@link String name} of the bean configured in the Spring application context + * implementing the {@link SessionExpirationPolicy} for {@link Session} expiration. + * + * @return an {@link Optional} {@link String name} of the bean configured in the Spring application context + * implementing the {@link SessionExpirationPolicy} for {@link Session} expiration. + */ public Optional getSessionExpirationPolicyBeanName() { return Optional.ofNullable(this.sessionExpirationPolicyBeanName) @@ -477,11 +554,13 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC * Callback with the {@link AnnotationMetadata} of the class containing {@link Import @Import} annotation * that imported this {@link Configuration @Configuration} class. * - * The {@link Configuration @Configuration} class should have been annotated with {@link EnableGemFireHttpSession}. + * The {@link Configuration @Configuration} class should also be annotated with {@link EnableGemFireHttpSession}. * * @param importMetadata {@link AnnotationMetadata} of the application class importing * this {@link Configuration} class. * @see org.springframework.core.type.AnnotationMetadata + * @see #applySpringSessionGemFireConfigurer() + * @see #exposeSpringSessionGemFireConfigurationAsProperties() */ public void setImportMetadata(AnnotationMetadata importMetadata) { @@ -489,8 +568,11 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC AnnotationAttributes.fromMap(importMetadata.getAnnotationAttributes( EnableGemFireHttpSession.class.getName())); + // Apply configuration from {@link EnableGemFireHttpSession} annotation + // and well-known, documented {@link Properties}. configureClientRegionShortcut(enableGemFireHttpSessionAttributes); - configureIndexableSessionAttributes(enableGemFireHttpSessionAttributes); + configureExposeConfigurationAsProperties(enableGemFireHttpSessionAttributes); + configureIndexedSessionAttributes(enableGemFireHttpSessionAttributes); configureMaxInactiveIntervalInSeconds(enableGemFireHttpSessionAttributes); configurePoolName(enableGemFireHttpSessionAttributes); configureServerRegionShortcut(enableGemFireHttpSessionAttributes); @@ -498,7 +580,12 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC configureSessionRegionName(enableGemFireHttpSessionAttributes); configureSessionSerializerBeanName(enableGemFireHttpSessionAttributes); + // Apply configuration from {@link SpringSessionGemFireConfigurer}. applySpringSessionGemFireConfigurer(); + + // Expose configuration as {@link Properties} in the Spring {@link Environment} + // if {@link EnableGemFireHttpSession#exposeConfigurationAsProperties} is set to {@literal true}. + exposeSpringSessionGemFireConfigurationAsProperties(); } private void configureClientRegionShortcut(AnnotationAttributes enableGemFireHttpSessionAttributes) { @@ -510,13 +597,22 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC ClientRegionShortcut.class, defaultClientRegionShortcut)); } - private void configureIndexableSessionAttributes(AnnotationAttributes enableGemFireHttpSessionAttributes) { + private void configureExposeConfigurationAsProperties(AnnotationAttributes enableGemFireHttpSessionAttributes) { - String[] defaultIndexableSessionAttributes = + boolean defaultExposeConfigurationAsProperties = Boolean.TRUE + .equals(enableGemFireHttpSessionAttributes.getBoolean("exposeConfigurationAsProperties")); + + setExposeConfigurationAsProperties(resolveProperty(exposeConfigurationAsPropertiesPropertyName(), + defaultExposeConfigurationAsProperties)); + } + + private void configureIndexedSessionAttributes(AnnotationAttributes enableGemFireHttpSessionAttributes) { + + String[] defaultIndexedSessionAttributes = enableGemFireHttpSessionAttributes.getStringArray("indexableSessionAttributes"); - setIndexableSessionAttributes(resolveProperty(indexableSessionAttributesPropertyName(), - defaultIndexableSessionAttributes)); + setIndexableSessionAttributes(resolveProperty(indexedSessionAttributesPropertyName(), + resolveProperty(indexableSessionAttributesPropertyName(), defaultIndexedSessionAttributes))); } private void configureMaxInactiveIntervalInSeconds(AnnotationAttributes enableGemFireHttpSessionAttributes) { @@ -569,10 +665,18 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC defaultSessionSerializerBeanName)); } - private void applySpringSessionGemFireConfigurer() { + /** + * Applies configuration from a single {@link SpringSessionGemFireConfigurer} bean + * declared in the Spring {@link ApplicationContext}. + * + * @see org.springframework.session.data.gemfire.config.annotation.web.http.support.SpringSessionGemFireConfigurer + * @see #resolveSpringSessionGemFireConfigurer() + */ + void applySpringSessionGemFireConfigurer() { resolveSpringSessionGemFireConfigurer() .map(this::applyClientRegionShortcut) + .map(this::applyExposeConfigurationAsProperties) .map(this::applyIndexableSessionAttributes) .map(this::applyMaxInactiveIntervalInSeconds) .map(this::applyPoolName) @@ -582,6 +686,25 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC .map(this::applySessionSerializerBeanName); } + private Optional resolveSpringSessionGemFireConfigurer() { + + try { + return Optional.of(getApplicationContext().getBean(SpringSessionGemFireConfigurer.class)); + } + catch (BeansException cause) { + + if (isCauseBecauseNoSpringSessionGemFireConfigurerPresent(cause)) { + return Optional.empty(); + } + + throw cause; + } + } + + private boolean isCauseBecauseNoSpringSessionGemFireConfigurerPresent(Exception cause) { + return (!(cause instanceof NoUniqueBeanDefinitionException) && cause instanceof NoSuchBeanDefinitionException); + } + private SpringSessionGemFireConfigurer applySpringSessionGemFireConfigurerConfiguration( @Nullable SpringSessionGemFireConfigurer configurer, @NonNull String methodName, @NonNull Function getter, @NonNull Consumer setter) { @@ -601,6 +724,13 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC SpringSessionGemFireConfigurer::getClientRegionShortcut, this::setClientRegionShortcut); } + private SpringSessionGemFireConfigurer applyExposeConfigurationAsProperties(SpringSessionGemFireConfigurer configurer) { + + return applySpringSessionGemFireConfigurerConfiguration(configurer, + CONFIGURER_GET_EXPOSE_CONFIGURATION_IN_PROPERTIES_METHOD_NAME, + SpringSessionGemFireConfigurer::getExposeConfigurationAsProperties, this::setExposeConfigurationAsProperties); + } + private SpringSessionGemFireConfigurer applyIndexableSessionAttributes(SpringSessionGemFireConfigurer configurer) { return applySpringSessionGemFireConfigurerConfiguration(configurer, @@ -650,23 +780,63 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC SpringSessionGemFireConfigurer::getSessionSerializerBeanName, this::setSessionSerializerBeanName); } - private Optional resolveSpringSessionGemFireConfigurer() { + /** + * Exposes the configuration of Spring Session using either Apache Geode or Pivotal GemFire as {@link Properties} + * in the Spring {@link Environment}. + * + * @see #isExposeConfigurationAsProperties() + */ + void exposeSpringSessionGemFireConfigurationAsProperties() { - try { - return Optional.of(getApplicationContext().getBean(SpringSessionGemFireConfigurer.class)); + if (isExposeConfigurationAsProperties()) { + + Optional.ofNullable(getEnvironment()) + .filter(ConfigurableEnvironment.class::isInstance) + .map(ConfigurableEnvironment.class::cast) + .map(ConfigurableEnvironment::getPropertySources) + .map(propertySources -> { + + Properties springSessionGemFireProperties = new Properties(); + + PropertySource springSessionGemFirePropertySource = + new PropertiesPropertySource(SPRING_SESSION_GEMFIRE_PROPERTY_SOURCE, + springSessionGemFireProperties); + + propertySources.addFirst(springSessionGemFirePropertySource); + + return springSessionGemFireProperties; + }) + .ifPresent(properties -> { + + properties.setProperty(clientRegionShortcutPropertyName(), + getClientRegionShortcut().name()); + + properties.setProperty(exposeConfigurationAsPropertiesPropertyName(), + String.valueOf(isExposeConfigurationAsProperties())); + + // TODO: deprecate and remove indexableSessionAttributes + properties.setProperty(indexableSessionAttributesPropertyName(), + StringUtils.arrayToCommaDelimitedString(getIndexableSessionAttributes())); + + properties.setProperty(indexedSessionAttributesPropertyName(), + StringUtils.arrayToCommaDelimitedString(getIndexableSessionAttributes())); + + properties.setProperty(maxInactiveIntervalInSecondsPropertyName(), + String.valueOf(getMaxInactiveIntervalInSeconds())); + + properties.setProperty(poolNamePropertyName(), getPoolName()); + + properties.setProperty(sessionRegionNamePropertyName(), getSessionRegionName()); + + properties.setProperty(serverRegionShortcutPropertyName(), + getServerRegionShortcut().name()); + + getSessionExpirationPolicyBeanName() + .ifPresent(it -> properties.setProperty(sessionExpirationPolicyBeanNamePropertyName(), it)); + + properties.setProperty(sessionSerializerBeanNamePropertyName(), getSessionSerializerBeanName()); + }); } - catch (BeansException cause) { - - if (isCauseBecauseNoSpringSessionGemFireConfigurerPresent(cause)) { - return Optional.empty(); - } - - throw cause; - } - } - - private boolean isCauseBecauseNoSpringSessionGemFireConfigurerPresent(Exception cause) { - return (!(cause instanceof NoUniqueBeanDefinitionException) && cause instanceof NoSuchBeanDefinitionException); } @PostConstruct diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/support/SpringSessionGemFireConfigurer.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/support/SpringSessionGemFireConfigurer.java index caf316d..2f1c1a1 100644 --- a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/support/SpringSessionGemFireConfigurer.java +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/support/SpringSessionGemFireConfigurer.java @@ -16,6 +16,9 @@ package org.springframework.session.data.gemfire.config.annotation.web.http.support; +import java.lang.annotation.Annotation; +import java.util.Properties; + import org.apache.geode.cache.Cache; import org.apache.geode.cache.Region; import org.apache.geode.cache.RegionShortcut; @@ -63,6 +66,35 @@ public interface SpringSessionGemFireConfigurer { return GemFireHttpSessionConfiguration.DEFAULT_CLIENT_REGION_SHORTCUT; } + /** + * Determines whether the configuration for Spring Session using Apache Geode or Pivotal GemFire should be exposed + * in the Spring {@link org.springframework.core.env.Environment} as {@link Properties}. + * + * Currently, users may configure Spring Session for Apache Geode or Pivotal GemFire using attributes on this + * {@link Annotation}, using the well-known and documented {@link Properties} + * (e.g. {@literal spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds}) + * or using the {@link SpringSessionGemFireConfigurer} declared as a bean in the Spring application context. + * + * The {@link Properties} that are exposed will use the well-known property {@link String names} that are documented + * in this {@link Annotation Annotation's} attributes. + * + * The values of the resulting {@link Properties} follows the precedence as outlined in the documentation: + * first any {@link SpringSessionGemFireConfigurer} bean defined takes precedence, followed by explicit + * {@link Properties} declared in Spring Boot {@literal application.properties} and finally, this + * {@link Annotation Annotation's} attribute values. + * + * Defaults to {@literal false}. + * + * Use {@literal spring.session.data.gemfire.session.configuration.expose} in Spring Boot + * {@literal application.properties}. + * + * @return a boolean value indicating whether to expose the configuration of Spring Session using Apache Geode + * or Pivotal GemFire in the Spring {@link org.springframework.core.env.Environment} as {@link Properties}. + */ + default boolean getExposeConfigurationAsProperties() { + return GemFireHttpSessionConfiguration.DEFAULT_EXPOSE_CONFIGURATION_AS_PROPERTIES; + } + /** * Identifies the {@link Session} attributes by name that will be indexed for query operations. * diff --git a/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationTests.java b/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationTests.java index d0c7fe6..6bfecca 100644 --- a/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationTests.java +++ b/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationTests.java @@ -17,9 +17,15 @@ package org.springframework.session.data.gemfire.config.annotation.web.http; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; @@ -43,13 +49,19 @@ import org.apache.geode.cache.client.ClientCache; import org.apache.geode.cache.client.ClientRegionShortcut; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.StandardEnvironment; import org.springframework.core.type.AnnotationMetadata; import org.springframework.data.gemfire.GemfireOperations; import org.springframework.data.gemfire.GemfireTemplate; import org.springframework.data.gemfire.RegionAttributesFactoryBean; +import org.springframework.data.gemfire.util.ArrayUtils; import org.springframework.session.Session; import org.springframework.session.data.gemfire.GemFireOperationsSessionRepository; import org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean; @@ -83,10 +95,8 @@ import org.springframework.util.ReflectionUtils; */ public class GemFireHttpSessionConfigurationTests { - private GemFireHttpSessionConfiguration gemfireConfiguration; - @SuppressWarnings("unchecked") - protected T getField(Object obj, String fieldName) { + private T getField(Object obj, String fieldName) { try { Field field = resolveField(obj, fieldName); @@ -118,20 +128,17 @@ public class GemFireHttpSessionConfigurationTests { return field; } - @SafeVarargs - private static T[] toArray(T... array) { - return array; - } + private GemFireHttpSessionConfiguration gemfireConfiguration; @Before public void setup() { - this.gemfireConfiguration = new GemFireHttpSessionConfiguration(); + this.gemfireConfiguration = spy(new GemFireHttpSessionConfiguration()); ApplicationContext mockApplicationContext = mock(ApplicationContext.class); when(mockApplicationContext.getBean(eq(SpringSessionGemFireConfigurer.class))) - .thenThrow(new NoSuchBeanDefinitionException("No SpringSessionGemFireConfigurer bean available")); + .thenThrow(new NoSuchBeanDefinitionException("No SpringSessionGemFireConfigurer bean present")); this.gemfireConfiguration.setApplicationContext(mockApplicationContext); } @@ -169,6 +176,42 @@ public class GemFireHttpSessionConfigurationTests { GemFireHttpSessionConfiguration.DEFAULT_CLIENT_REGION_SHORTCUT); } + @Test + public void setAndGetExposeConfigurationAsProperties() { + + assertThat(this.gemfireConfiguration.isExposeConfigurationAsProperties()).isFalse(); + + this.gemfireConfiguration.setExposeConfigurationAsProperties(true); + + assertThat(this.gemfireConfiguration.isExposeConfigurationAsProperties()).isTrue(); + + this.gemfireConfiguration.setExposeConfigurationAsProperties(false); + + assertThat(this.gemfireConfiguration.isExposeConfigurationAsProperties()).isFalse(); + } + + @Test + public void setAndGetIndexedSessionAttributes() { + + assertThat(this.gemfireConfiguration.getIndexableSessionAttributes()).isEmpty(); + + this.gemfireConfiguration.setIndexableSessionAttributes(ArrayUtils.asArray("one", "two")); + + assertThat(this.gemfireConfiguration.getIndexableSessionAttributes()).containsExactly("one", "two"); + + this.gemfireConfiguration.setIndexableSessionAttributes(new String[0]); + + assertThat(this.gemfireConfiguration.getIndexableSessionAttributes()).isEmpty(); + + this.gemfireConfiguration.setIndexableSessionAttributes(ArrayUtils.asArray("two")); + + assertThat(this.gemfireConfiguration.getIndexableSessionAttributes()).containsExactly("two"); + + this.gemfireConfiguration.setIndexableSessionAttributes(null); + + assertThat(this.gemfireConfiguration.getIndexableSessionAttributes()).isEmpty(); + } + @Test public void setAndGetMaxInactiveIntervalInSeconds() { @@ -235,6 +278,39 @@ public class GemFireHttpSessionConfigurationTests { GemFireHttpSessionConfiguration.DEFAULT_SERVER_REGION_SHORTCUT); } + @Test + public void setAndGetSessionExpirationPolicyBeanName() { + + assertThat(this.gemfireConfiguration.getSessionExpirationPolicyBeanName().orElse(null)).isNull(); + + this.gemfireConfiguration.setSessionExpirationPolicyBeanName("TestSessionExpirationPolicy"); + + assertThat(this.gemfireConfiguration.getSessionExpirationPolicyBeanName().orElse(null)) + .isEqualTo("TestSessionExpirationPolicy"); + + this.gemfireConfiguration.setSessionExpirationPolicyBeanName(" "); + + assertThat(this.gemfireConfiguration.getSessionExpirationPolicyBeanName().orElse(null)).isNull(); + + this.gemfireConfiguration.setSessionExpirationPolicyBeanName("MockSessionExpirationPolicy"); + + assertThat(this.gemfireConfiguration.getSessionExpirationPolicyBeanName().orElse(null)) + .isEqualTo("MockSessionExpirationPolicy"); + + this.gemfireConfiguration.setSessionExpirationPolicyBeanName(""); + + assertThat(this.gemfireConfiguration.getSessionExpirationPolicyBeanName().orElse(null)).isNull(); + + this.gemfireConfiguration.setSessionExpirationPolicyBeanName("SessionExpirationPolicySpy"); + + assertThat(this.gemfireConfiguration.getSessionExpirationPolicyBeanName().orElse(null)) + .isEqualTo("SessionExpirationPolicySpy"); + + this.gemfireConfiguration.setSessionExpirationPolicyBeanName(null); + + assertThat(this.gemfireConfiguration.getSessionExpirationPolicyBeanName().orElse(null)).isNull(); + } + @Test public void setAndGetSessionRegionName() { @@ -286,7 +362,7 @@ public class GemFireHttpSessionConfigurationTests { } @Test - public void isUsingDataSerializationIsFalse() { + public void isUsingDataSerializationReturnsFalse() { this.gemfireConfiguration.setSessionSerializerBeanName("test"); @@ -303,7 +379,7 @@ public class GemFireHttpSessionConfigurationTests { } @Test - public void isUsingPdxSerializationIsTrue() { + public void isUsingPdxSerializationReturnsTrue() { assertThat(this.gemfireConfiguration.getSessionSerializerBeanName()) .isEqualTo(GemFireHttpSessionConfiguration.SESSION_PDX_SERIALIZER_BEAN_NAME); @@ -324,7 +400,8 @@ public class GemFireHttpSessionConfigurationTests { Map annotationAttributes = new HashMap<>(4); annotationAttributes.put("clientRegionShortcut", ClientRegionShortcut.CACHING_PROXY); - annotationAttributes.put("indexableSessionAttributes", toArray("one", "two", "three")); + annotationAttributes.put("exposeConfigurationAsProperties", Boolean.TRUE); + annotationAttributes.put("indexableSessionAttributes", ArrayUtils.asArray("one", "two", "three")); annotationAttributes.put("maxInactiveIntervalInSeconds", 600); annotationAttributes.put("poolName", "TestPool"); annotationAttributes.put("serverRegionShortcut", RegionShortcut.REPLICATE); @@ -332,13 +409,15 @@ public class GemFireHttpSessionConfigurationTests { annotationAttributes.put("sessionExpirationPolicyBeanName", "testSessionExpirationPolicy"); annotationAttributes.put("sessionSerializerBeanName", "testSessionSerializer"); - given(mockAnnotationMetadata.getAnnotationAttributes(eq(EnableGemFireHttpSession.class.getName()))) - .willReturn(annotationAttributes); + when(mockAnnotationMetadata.getAnnotationAttributes(eq(EnableGemFireHttpSession.class.getName()))) + .thenReturn(annotationAttributes); this.gemfireConfiguration.setImportMetadata(mockAnnotationMetadata); assertThat(this.gemfireConfiguration.getClientRegionShortcut()).isEqualTo(ClientRegionShortcut.CACHING_PROXY); - assertThat(this.gemfireConfiguration.getIndexableSessionAttributes()).isEqualTo(toArray("one", "two", "three")); + assertThat(this.gemfireConfiguration.isExposeConfigurationAsProperties()).isTrue(); + assertThat(this.gemfireConfiguration.getIndexableSessionAttributes()) + .isEqualTo(ArrayUtils.asArray("one", "two", "three")); assertThat(this.gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(600); assertThat(this.gemfireConfiguration.getPoolName()).isEqualTo("TestPool"); assertThat(this.gemfireConfiguration.getServerRegionShortcut()).isEqualTo(RegionShortcut.REPLICATE); @@ -349,7 +428,227 @@ public class GemFireHttpSessionConfigurationTests { verify(mockAnnotationMetadata, times(1)) .getAnnotationAttributes(eq(EnableGemFireHttpSession.class.getName())); + } + @Test + public void applyConfigurationFromSpringSessionGemFireConfigurer() { + + ApplicationContext mockApplicationContext = mock(ApplicationContext.class); + + SpringSessionGemFireConfigurer mockConfigurer = mock(SpringSessionGemFireConfigurer.class); + + when(mockApplicationContext.getBean(eq(SpringSessionGemFireConfigurer.class))).thenReturn(mockConfigurer); + when(mockConfigurer.getClientRegionShortcut()).thenReturn(ClientRegionShortcut.CACHING_PROXY); + when(mockConfigurer.getExposeConfigurationAsProperties()).thenReturn(true); + when(mockConfigurer.getIndexableSessionAttributes()).thenReturn(new String[] { "one", "two" }); + when(mockConfigurer.getMaxInactiveIntervalInSeconds()).thenReturn(300); + when(mockConfigurer.getPoolName()).thenReturn("DeadPool"); + when(mockConfigurer.getRegionName()).thenReturn("Sessions"); + when(mockConfigurer.getServerRegionShortcut()).thenReturn(RegionShortcut.PARTITION_REDUNDANT); + when(mockConfigurer.getSessionExpirationPolicyBeanName()).thenReturn("TestSessionExpirationPolicy"); + when(mockConfigurer.getSessionSerializerBeanName()).thenReturn("TestSessionSerializer"); + + this.gemfireConfiguration.setApplicationContext(mockApplicationContext); + this.gemfireConfiguration.applySpringSessionGemFireConfigurer(); + + assertThat(this.gemfireConfiguration.getClientRegionShortcut()).isEqualTo(ClientRegionShortcut.CACHING_PROXY); + assertThat(this.gemfireConfiguration.isExposeConfigurationAsProperties()).isEqualTo(true); + assertThat(this.gemfireConfiguration.getIndexableSessionAttributes()).containsExactly("one", "two"); + assertThat(this.gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(300); + assertThat(this.gemfireConfiguration.getPoolName()).isEqualTo("DeadPool"); + assertThat(this.gemfireConfiguration.getServerRegionShortcut()).isEqualTo(RegionShortcut.PARTITION_REDUNDANT); + assertThat(this.gemfireConfiguration.getSessionRegionName()).isEqualTo("Sessions"); + assertThat(this.gemfireConfiguration.getSessionExpirationPolicyBeanName().orElse(null)) + .isEqualTo("TestSessionExpirationPolicy"); + assertThat(this.gemfireConfiguration.getSessionSerializerBeanName()).isEqualTo("TestSessionSerializer"); + + verify(mockConfigurer, times(1)).getClientRegionShortcut(); + verify(mockConfigurer, times(1)).getExposeConfigurationAsProperties(); + verify(mockConfigurer, times(1)).getIndexableSessionAttributes(); + verify(mockConfigurer, times(1)).getMaxInactiveIntervalInSeconds(); + verify(mockConfigurer, times(1)).getPoolName(); + verify(mockConfigurer, times(1)).getRegionName(); + verify(mockConfigurer, times(1)).getServerRegionShortcut(); + verify(mockConfigurer, times(1)).getSessionExpirationPolicyBeanName(); + verify(mockConfigurer, times(1)).getSessionSerializerBeanName(); + } + + @Test + public void applyConfigurationFromNonExistingSpringSessionGemFireConfigurer() { + + this.gemfireConfiguration.applySpringSessionGemFireConfigurer(); + + verify(this.gemfireConfiguration, never()).setClientRegionShortcut(any(ClientRegionShortcut.class)); + verify(this.gemfireConfiguration, never()).setExposeConfigurationAsProperties(anyBoolean()); + verify(this.gemfireConfiguration, never()).setIndexableSessionAttributes(any(String[].class)); + verify(this.gemfireConfiguration, never()).setMaxInactiveIntervalInSeconds(anyInt()); + verify(this.gemfireConfiguration, never()).setPoolName(anyString()); + verify(this.gemfireConfiguration, never()).setServerRegionShortcut(any(RegionShortcut.class)); + verify(this.gemfireConfiguration, never()).setSessionExpirationPolicyBeanName(anyString()); + verify(this.gemfireConfiguration, never()).setSessionRegionName(anyString()); + verify(this.gemfireConfiguration, never()).setSessionSerializerBeanName(anyString()); + } + + @Test(expected = NoUniqueBeanDefinitionException.class) + public void applyConfigurationFromMultipleSpringSessionGemFireConfigurersThrowsException() { + + ApplicationContext mockApplicationContext = mock(ApplicationContext.class); + + when(mockApplicationContext.getBean(eq(SpringSessionGemFireConfigurer.class))) + .thenThrow(new NoUniqueBeanDefinitionException(SpringSessionGemFireConfigurer.class, 2, "TEST")); + + this.gemfireConfiguration.setApplicationContext(mockApplicationContext); + + try { + this.gemfireConfiguration.applySpringSessionGemFireConfigurer(); + } + catch (NoUniqueBeanDefinitionException expected) { + + assertThat(expected).hasMessageContaining("TEST"); + assertThat(expected).hasNoCause(); + + throw expected; + } + finally { + verify(this.gemfireConfiguration, never()).setClientRegionShortcut(any(ClientRegionShortcut.class)); + verify(this.gemfireConfiguration, never()).setExposeConfigurationAsProperties(anyBoolean()); + verify(this.gemfireConfiguration, never()).setIndexableSessionAttributes(any(String[].class)); + verify(this.gemfireConfiguration, never()).setMaxInactiveIntervalInSeconds(anyInt()); + verify(this.gemfireConfiguration, never()).setPoolName(anyString()); + verify(this.gemfireConfiguration, never()).setServerRegionShortcut(any(RegionShortcut.class)); + verify(this.gemfireConfiguration, never()).setSessionExpirationPolicyBeanName(anyString()); + verify(this.gemfireConfiguration, never()).setSessionRegionName(anyString()); + verify(this.gemfireConfiguration, never()).setSessionSerializerBeanName(anyString()); + } + } + + @Test + public void exposeSpringSessionGemFireConfigurationAsPropertiesMutatesSpringEnvironment() { + + ConfigurableEnvironment environment = new StandardEnvironment(); + + this.gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.CACHING_PROXY); + this.gemfireConfiguration.setEnvironment(environment); + this.gemfireConfiguration.setExposeConfigurationAsProperties(true); + this.gemfireConfiguration.setIndexableSessionAttributes(ArrayUtils.asArray("one", "two")); + this.gemfireConfiguration.setMaxInactiveIntervalInSeconds(300); + this.gemfireConfiguration.setPoolName("DeadPool"); + this.gemfireConfiguration.setServerRegionShortcut(RegionShortcut.PARTITION_REDUNDANT); + this.gemfireConfiguration.setSessionExpirationPolicyBeanName("TestSessionExpirationPolicy"); + this.gemfireConfiguration.setSessionRegionName("Sessions"); + this.gemfireConfiguration.setSessionSerializerBeanName("TestSessionSerializer"); + this.gemfireConfiguration.setExposeConfigurationAsProperties(true); + this.gemfireConfiguration.exposeSpringSessionGemFireConfigurationAsProperties(); + + PropertySource springSessionGemFirePropertySource = environment.getPropertySources() + .get(GemFireHttpSessionConfiguration.SPRING_SESSION_GEMFIRE_PROPERTY_SOURCE); + + assertThat(springSessionGemFirePropertySource).isNotNull(); + + assertThat(springSessionGemFirePropertySource.getProperty("spring.session.data.gemfire.cache.client.region.shortcut")) + .isEqualTo(ClientRegionShortcut.CACHING_PROXY.name()); + + assertThat(springSessionGemFirePropertySource.getProperty("spring.session.data.gemfire.session.configuration.expose")) + .isEqualTo(Boolean.TRUE.toString()); + + assertThat(springSessionGemFirePropertySource.getProperty("spring.session.data.gemfire.session.attributes.indexable")) + .isEqualTo("one,two"); + + assertThat(springSessionGemFirePropertySource.getProperty("spring.session.data.gemfire.session.attributes.indexed")) + .isEqualTo("one,two"); + + assertThat(springSessionGemFirePropertySource.getProperty("spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds")) + .isEqualTo("300"); + + assertThat(springSessionGemFirePropertySource.getProperty("spring.session.data.gemfire.cache.client.pool.name")) + .isEqualTo("DeadPool"); + + assertThat(springSessionGemFirePropertySource.getProperty("spring.session.data.gemfire.cache.server.region.shortcut")) + .isEqualTo(RegionShortcut.PARTITION_REDUNDANT.name()); + + assertThat(springSessionGemFirePropertySource.getProperty("spring.session.data.gemfire.session.expiration.bean-name")) + .isEqualTo("TestSessionExpirationPolicy"); + + assertThat(springSessionGemFirePropertySource.getProperty("spring.session.data.gemfire.session.region.name")) + .isEqualTo("Sessions"); + + assertThat(springSessionGemFirePropertySource.getProperty("spring.session.data.gemfire.session.serializer.bean-name")) + .isEqualTo("TestSessionSerializer"); + + verify(this.gemfireConfiguration, times(1)).getClientRegionShortcut(); + verify(this.gemfireConfiguration, times(1)).getEnvironment(); + verify(this.gemfireConfiguration, times(2)).isExposeConfigurationAsProperties(); + verify(this.gemfireConfiguration, times(2)).getIndexableSessionAttributes(); + verify(this.gemfireConfiguration, times(1)).getMaxInactiveIntervalInSeconds(); + verify(this.gemfireConfiguration, times(1)).getPoolName(); + verify(this.gemfireConfiguration, times(1)).getSessionRegionName(); + verify(this.gemfireConfiguration, times(1)).getServerRegionShortcut(); + verify(this.gemfireConfiguration, times(1)).getSessionExpirationPolicyBeanName(); + verify(this.gemfireConfiguration, times(1)).getSessionSerializerBeanName(); + } + + @Test + @SuppressWarnings("all") + public void exposeSpringSessionGemFireConfigurationAsPropertiesIsNullSafe() { + + this.gemfireConfiguration.setEnvironment(null); + this.gemfireConfiguration.setExposeConfigurationAsProperties(true); + this.gemfireConfiguration.exposeSpringSessionGemFireConfigurationAsProperties(); + + verify(this.gemfireConfiguration, never()).getClientRegionShortcut(); + verify(this.gemfireConfiguration, times(1)).getEnvironment(); + verify(this.gemfireConfiguration, times(1)).isExposeConfigurationAsProperties(); + verify(this.gemfireConfiguration, never()).getIndexableSessionAttributes(); + verify(this.gemfireConfiguration, never()).getMaxInactiveIntervalInSeconds(); + verify(this.gemfireConfiguration, never()).getPoolName(); + verify(this.gemfireConfiguration, never()).getSessionRegionName(); + verify(this.gemfireConfiguration, never()).getServerRegionShortcut(); + verify(this.gemfireConfiguration, never()).getSessionExpirationPolicyBeanName(); + verify(this.gemfireConfiguration, never()).getSessionSerializerBeanName(); + } + + @Test + public void exposeSpringSessionGemFireConfigurationAsPropertiesWhenExposureIsFalse() { + + ConfigurableEnvironment mockEnvironment = mock(ConfigurableEnvironment.class); + + this.gemfireConfiguration.setEnvironment(mockEnvironment); + this.gemfireConfiguration.setExposeConfigurationAsProperties(false); + this.gemfireConfiguration.exposeSpringSessionGemFireConfigurationAsProperties(); + + verify(this.gemfireConfiguration, never()).getClientRegionShortcut(); + verify(this.gemfireConfiguration, never()).getEnvironment(); + verify(this.gemfireConfiguration, times(1)).isExposeConfigurationAsProperties(); + verify(this.gemfireConfiguration, never()).getIndexableSessionAttributes(); + verify(this.gemfireConfiguration, never()).getMaxInactiveIntervalInSeconds(); + verify(this.gemfireConfiguration, never()).getPoolName(); + verify(this.gemfireConfiguration, never()).getSessionRegionName(); + verify(this.gemfireConfiguration, never()).getServerRegionShortcut(); + verify(this.gemfireConfiguration, never()).getSessionExpirationPolicyBeanName(); + verify(this.gemfireConfiguration, never()).getSessionSerializerBeanName(); + verifyZeroInteractions(mockEnvironment); + } + + @Test + public void exposeSpringSessionGemFireConfigurationAsPropertiesWithNonConfigurableEnvironment() { + + Environment mockEnvironment = mock(Environment.class); + + this.gemfireConfiguration.setEnvironment(mockEnvironment); + this.gemfireConfiguration.setExposeConfigurationAsProperties(true); + this.gemfireConfiguration.exposeSpringSessionGemFireConfigurationAsProperties(); + + verify(this.gemfireConfiguration, never()).getClientRegionShortcut(); + verify(this.gemfireConfiguration, times(1)).getEnvironment(); + verify(this.gemfireConfiguration, times(1)).isExposeConfigurationAsProperties(); + verify(this.gemfireConfiguration, never()).getIndexableSessionAttributes(); + verify(this.gemfireConfiguration, never()).getMaxInactiveIntervalInSeconds(); + verify(this.gemfireConfiguration, never()).getPoolName(); + verify(this.gemfireConfiguration, never()).getSessionRegionName(); + verify(this.gemfireConfiguration, never()).getServerRegionShortcut(); + verify(this.gemfireConfiguration, never()).getSessionExpirationPolicyBeanName(); + verify(this.gemfireConfiguration, never()).getSessionSerializerBeanName(); + verifyZeroInteractions(mockEnvironment); } @Test @@ -382,8 +681,8 @@ public class GemFireHttpSessionConfigurationTests { this.gemfireConfiguration.setMaxInactiveIntervalInSeconds(120); - GemFireOperationsSessionRepository sessionRepository = this.gemfireConfiguration.sessionRepository( - mockGemfireOperations); + GemFireOperationsSessionRepository sessionRepository = + this.gemfireConfiguration.sessionRepository(mockGemfireOperations); assertThat(sessionRepository).isNotNull(); assertThat(sessionRepository.getTemplate()).isSameAs(mockGemfireOperations); @@ -421,7 +720,7 @@ public class GemFireHttpSessionConfigurationTests { this.gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.CACHING_PROXY); this.gemfireConfiguration.setPoolName("TestPool"); - this.gemfireConfiguration.setServerRegionShortcut(RegionShortcut.REPLICATE_PERSISTENT); + this.gemfireConfiguration.setServerRegionShortcut(RegionShortcut.PARTITION_REDUNDANT); this.gemfireConfiguration.setSessionRegionName("TestRegion"); GemFireCacheTypeAwareRegionFactoryBean sessionRegionFactoryBean = @@ -434,7 +733,7 @@ public class GemFireHttpSessionConfigurationTests { assertThat(this.>getField(sessionRegionFactoryBean, "regionAttributes")).isEqualTo(mockRegionAttributes); assertThat(this.getField(sessionRegionFactoryBean, "regionName")).isEqualTo("TestRegion"); - assertThat(sessionRegionFactoryBean.getServerRegionShortcut()).isEqualTo(RegionShortcut.REPLICATE_PERSISTENT); + assertThat(sessionRegionFactoryBean.getServerRegionShortcut()).isEqualTo(RegionShortcut.PARTITION_REDUNDANT); verifyZeroInteractions(mockGemFireCache); verifyZeroInteractions(mockRegionAttributes); @@ -459,10 +758,10 @@ public class GemFireHttpSessionConfigurationTests { RegionAttributes sessionRegionAttributes = regionAttributesFactory.getObject(); assertThat(sessionRegionAttributes).isNotNull(); - assertThat(sessionRegionAttributes.getKeyConstraint()).isEqualTo( - GemFireHttpSessionConfiguration.SESSION_REGION_KEY_CONSTRAINT); - assertThat(sessionRegionAttributes.getValueConstraint()).isEqualTo( - GemFireHttpSessionConfiguration.SESSION_REGION_VALUE_CONSTRAINT); + assertThat(sessionRegionAttributes.getKeyConstraint()) + .isEqualTo(GemFireHttpSessionConfiguration.SESSION_REGION_KEY_CONSTRAINT); + assertThat(sessionRegionAttributes.getValueConstraint()) + .isEqualTo(GemFireHttpSessionConfiguration.SESSION_REGION_VALUE_CONSTRAINT); ExpirationAttributes entryIdleTimeoutExpiration = sessionRegionAttributes.getEntryIdleTimeout(); @@ -489,10 +788,10 @@ public class GemFireHttpSessionConfigurationTests { RegionAttributes sessionRegionAttributes = regionAttributesFactory.getObject(); assertThat(sessionRegionAttributes).isNotNull(); - assertThat(sessionRegionAttributes.getKeyConstraint()).isEqualTo( - GemFireHttpSessionConfiguration.SESSION_REGION_KEY_CONSTRAINT); - assertThat(sessionRegionAttributes.getValueConstraint()).isEqualTo( - GemFireHttpSessionConfiguration.SESSION_REGION_VALUE_CONSTRAINT); + assertThat(sessionRegionAttributes.getKeyConstraint()) + .isEqualTo(GemFireHttpSessionConfiguration.SESSION_REGION_KEY_CONSTRAINT); + assertThat(sessionRegionAttributes.getValueConstraint()) + .isEqualTo(GemFireHttpSessionConfiguration.SESSION_REGION_VALUE_CONSTRAINT); ExpirationAttributes entryIdleTimeoutExpiration = sessionRegionAttributes.getEntryIdleTimeout(); @@ -500,8 +799,8 @@ public class GemFireHttpSessionConfigurationTests { assertThat(entryIdleTimeoutExpiration.getAction()).isEqualTo(ExpirationAction.INVALIDATE); assertThat(entryIdleTimeoutExpiration.getTimeout()).isEqualTo(0); } - @Test + @Test public void clientExpirationIsAllowed() { ClientCache mockClientCache = mock(ClientCache.class);