Add Configurer based configuration option when enabling Spring Session and configuring either Apache Geode or Pivotal GemFire as an HTTP Session state management provider.

This commit is contained in:
John Blum
2018-08-09 13:55:26 -07:00
parent ebfaff614d
commit bfb101b9e8
4 changed files with 375 additions and 11 deletions

View File

@@ -0,0 +1,178 @@
/*
* Copyright 2017 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.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
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.Session;
import org.springframework.session.data.gemfire.config.annotation.web.http.support.SpringSessionGemFireConfigurer;
import org.springframework.session.data.gemfire.serialization.SessionSerializer;
/**
* Integration tests testing {@link SpringSessionGemFireConfigurer} based configuration of either Apache Geode
* or Pivotal GemFire * as the (HTTP) {@link Session} state management provider in Spring Session.
*
* @author John Blum
* @see org.junit.Test
* @see org.springframework.context.ConfigurableApplicationContext
* @see org.springframework.context.annotation.AnnotationConfigApplicationContext
* @see org.springframework.core.env.PropertySource
* @see org.springframework.data.gemfire.config.annotation.ClientCacheApplication
* @see org.springframework.data.gemfire.tests.mock.annotation.EnableGemFireMockObjects
* @see org.springframework.mock.env.MockPropertySource
* @see org.springframework.session.data.gemfire.config.annotation.web.http.support.SpringSessionGemFireConfigurer
* @since 2.0.4
*/
@SuppressWarnings("unused")
public class ConfigurerBasedGemFireHttpSessionConfigurationIntegrationTests {
private ConfigurableApplicationContext applicationContext;
@After
public void tearDow() {
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 springSessionGemFireConfigurerOverridesAnnotationAttributeAndPropertyConfiguration() {
MockPropertySource testPropertySource = new MockPropertySource("TestProperties")
.withProperty("spring.session.data.gemfire.cache.client.pool.name", "Car")
.withProperty("spring.session.data.gemfire.cache.client.region.shortcut", ClientRegionShortcut.LOCAL_PERSISTENT.name())
.withProperty("spring.session.data.gemfire.cache.server.region.shortcut", RegionShortcut.REPLICATE_PERSISTENT_OVERFLOW.name())
.withProperty("spring.session.data.gemfire.session.attributes.indexable", "firstName, lastName")
.withProperty("spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds", "600")
.withProperty("spring.session.data.gemfire.session.region.name", "PropertyRegionName")
.withProperty("spring.session.data.gemfire.session.serializer.bean-name", "MockSessionSerializer");
this.applicationContext = newApplicationContext(testPropertySource, TestConfiguration.class);
GemFireHttpSessionConfiguration sessionConfiguration =
this.applicationContext.getBean(GemFireHttpSessionConfiguration.class);
assertThat(sessionConfiguration).isNotNull();
assertThat(sessionConfiguration.getClientRegionShortcut()).isEqualTo(ClientRegionShortcut.CACHING_PROXY);
assertThat(sessionConfiguration.getIndexableSessionAttributes()).containsExactly("two", "four");
assertThat(sessionConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(3600);
assertThat(sessionConfiguration.getPoolName()).isEqualTo("Dead");
assertThat(sessionConfiguration.getServerRegionShortcut()).isEqualTo(RegionShortcut.PARTITION);
assertThat(sessionConfiguration.getSessionRegionName()).isEqualTo("ConfigurerRegionName");
assertThat(sessionConfiguration.getSessionSerializerBeanName()).isEqualTo("SessionPdxSerializer");
}
@Test
public void usesSpringSessionGemFireConfigurerWhenPresent() {
this.applicationContext = newApplicationContext(TestConfiguration.class);
GemFireHttpSessionConfiguration sessionConfiguration =
this.applicationContext.getBean(GemFireHttpSessionConfiguration.class);
assertThat(sessionConfiguration).isNotNull();
assertThat(sessionConfiguration.getClientRegionShortcut()).isEqualTo(ClientRegionShortcut.CACHING_PROXY);
assertThat(sessionConfiguration.getIndexableSessionAttributes()).containsExactly("two", "four");
assertThat(sessionConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(3600);
assertThat(sessionConfiguration.getPoolName()).isEqualTo("Dead");
assertThat(sessionConfiguration.getServerRegionShortcut()).isEqualTo(RegionShortcut.PARTITION);
assertThat(sessionConfiguration.getSessionRegionName()).isEqualTo("ConfigurerRegionName");
assertThat(sessionConfiguration.getSessionSerializerBeanName()).isEqualTo("SessionPdxSerializer");
}
@ClientCacheApplication
@EnableGemFireMockObjects
@EnableGemFireHttpSession(
clientRegionShortcut = ClientRegionShortcut.LOCAL,
indexableSessionAttributes = { "one", "two" },
maxInactiveIntervalInSeconds = 900,
poolName = "Swimming",
regionName = "AnnotationAttributeRegionName",
serverRegionShortcut = RegionShortcut.REPLICATE,
sessionSerializerBeanName = "TestSessionSerializer"
)
static class TestConfiguration {
@Bean("TestSessionSerializer")
Object testSessionSerializer() {
return mock(SessionSerializer.class);
}
@Bean
SpringSessionGemFireConfigurer testSpringSessionGemFireConfigurer() {
return new SpringSessionGemFireConfigurer() {
@Override
public ClientRegionShortcut getClientRegionShortcut() {
return ClientRegionShortcut.CACHING_PROXY;
}
@Override
public String[] getIndexableSessionAttributes() {
return new String[] { "two", "four" };
}
@Override
public int getMaxInactiveIntervalInSeconds() {
return 3600;
}
@Override
public String getPoolName() {
return "Dead";
}
@Override
public String getRegionName() {
return "ConfigurerRegionName";
}
};
}
}
}

View File

@@ -95,6 +95,8 @@ public @interface EnableGemFireHttpSession {
/**
* Defines the {@link ClientCache} {@link Region} data management policy.
*
* Defaults to {@link ClientRegionShortcut#PROXY}.
*
* @return a {@link ClientRegionShortcut} used to configure the {@link ClientCache} {@link Region}
* data management policy.
* @see org.apache.geode.cache.client.ClientRegionShortcut
@@ -104,7 +106,10 @@ public @interface EnableGemFireHttpSession {
/**
* Identifies the {@link Session} attributes by name that will be indexed for query operations.
*
* For instance, find all {@link Session Sessions} in Pivotal GemFire or Apache Geode having attribute A defined with value X.
* For instance, find all {@link Session Sessions} in Apache Geode or Pivotal GemFire having attribute A
* defined with value X.
*
* Defaults to empty {@link String} array.
*
* @return an array of {@link String Strings} identifying the names of {@link Session} attributes to index.
*/
@@ -113,36 +118,42 @@ public @interface EnableGemFireHttpSession {
/**
* Defines the maximum interval in seconds that a {@link Session} can remain inactive before it expires.
*
* Defaults to 1800 seconds, or 30 minutes.
* Defaults to {@literal 1800} seconds, or {@literal 30} minutes.
*
* @return an integer value defining the maximum inactive interval in seconds before the {@link Session} expires.
*/
int maxInactiveIntervalInSeconds() default 1800;
/**
* Specifies the name of the specific {@link Pool} used by the client cache {@link Region}
* Specifies the name of the specific {@link Pool} used by the {@link ClientCache} {@link Region}
* (i.e. {@literal ClusteredSpringSessions}) when performing cache data access operations.
*
* This is attribute is only used in the client/server topology.
*
* @return the name of the {@link Pool} to be used by the client cache Region to send {@link Session} state
* to the cluster of servers.
* @see GemFireHttpSessionConfiguration#DEFAULT_POOL_NAME
* Defaults to {@literal gemfirePool}.
*
* @return the name of the {@link Pool} used by the {@link ClientCache} {@link Region}
* to send {@link Session} state to the cluster of servers.
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration#DEFAULT_POOL_NAME
*/
String poolName() default GemFireHttpSessionConfiguration.DEFAULT_POOL_NAME;
/**
* Defines the name of the (client)cache {@link Region} used to store {@link Session} state.
* Defines the {@link String name} of the (client)cache {@link Region} used to store {@link Session} state.
*
* @return a {@link String} specifying the name of the (client)cace {@link Region}
* Defaults to {@literal ClusteredSpringSessions}.
*
* @return a {@link String} specifying the name of the (client)cache {@link Region}
* used to store {@link Session} state.
* @see GemFireHttpSessionConfiguration#DEFAULT_SESSION_REGION_NAME
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration#DEFAULT_SESSION_REGION_NAME
*/
String regionName() default GemFireHttpSessionConfiguration.DEFAULT_SESSION_REGION_NAME;
/**
* Defines the {@link Cache} {@link Region} data management policy.
*
* Defaults to {@link RegionShortcut#PARTITION}.
*
* @return a {@link RegionShortcut} used to specify and configure the {@link Cache} {@link Region}
* data management policy.
* @see org.apache.geode.cache.RegionShortcut
@@ -155,10 +166,10 @@ public @interface EnableGemFireHttpSession {
*
* The bean referred to by its name must be of type {@link SessionSerializer}.
*
* Defaults to {@literal SessionDataSerializer}.
* Defaults to {@literal SessionPdxSerializer}.
*
* @return a {@link String} containing the bean name of the configured {@link SessionSerializer}.
* @see org.springframework.session.data.gemfire.serialization.data.provider.DataSerializableSessionSerializer
* @see org.springframework.session.data.gemfire.serialization.pdx.provider.PdxSerializableSessionSerializer
* @see org.springframework.session.data.gemfire.serialization.SessionSerializer
*/
String sessionSerializerBeanName() default GemFireHttpSessionConfiguration.DEFAULT_SESSION_SERIALIZER_BEAN_NAME;

View File

@@ -35,6 +35,7 @@ import org.apache.geode.cache.client.Pool;
import org.apache.geode.pdx.PdxSerializer;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
@@ -58,6 +59,7 @@ import org.springframework.session.data.gemfire.AbstractGemFireOperationsSession
import org.springframework.session.data.gemfire.GemFireOperationsSessionRepository;
import org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean;
import org.springframework.session.data.gemfire.config.annotation.web.http.support.SessionAttributesIndexFactoryBean;
import org.springframework.session.data.gemfire.config.annotation.web.http.support.SpringSessionGemFireConfigurer;
import org.springframework.session.data.gemfire.serialization.SessionSerializer;
import org.springframework.session.data.gemfire.serialization.data.provider.DataSerializableSessionSerializer;
import org.springframework.session.data.gemfire.serialization.data.support.DataSerializerSessionSerializerAdapter;
@@ -410,6 +412,36 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC
setSessionSerializerBeanName(resolveProperty(sessionSerializerBeanNamePropertyName(),
defaultSessionSerializerBeanName));
applySpringSessionGemFireConfigurer();
}
private Optional<SpringSessionGemFireConfigurer> resolveSpringSessionGemFireConfigurer() {
try {
return Optional.of(getApplicationContext().getBean(SpringSessionGemFireConfigurer.class));
}
catch (BeansException cause) {
if (cause instanceof NoSuchBeanDefinitionException) {
return Optional.empty();
}
throw cause;
}
}
private void applySpringSessionGemFireConfigurer() {
resolveSpringSessionGemFireConfigurer().ifPresent(configurer -> {
setClientRegionShortcut(configurer.getClientRegionShortcut());
setIndexableSessionAttributes(configurer.getIndexableSessionAttributes());
setMaxInactiveIntervalInSeconds(configurer.getMaxInactiveIntervalInSeconds());
setPoolName(configurer.getPoolName());
setServerRegionShortcut(configurer.getServerRegionShortcut());
setSessionRegionName(configurer.getRegionName());
setSessionSerializerBeanName(configurer.getSessionSerializerBeanName());
});
}
@PostConstruct

View File

@@ -0,0 +1,143 @@
/*
* Copyright 2017 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.support;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionShortcut;
import org.apache.geode.cache.client.ClientCache;
import org.apache.geode.cache.client.ClientRegionShortcut;
import org.apache.geode.cache.client.Pool;
import org.springframework.session.Session;
import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
import org.springframework.session.data.gemfire.serialization.SessionSerializer;
/**
* The {@link SpringSessionGemFireConfigurer} interface defines a contract for programmatically controlling
* the configuration of either Apache Geode or Pivotal GemFire as a (HTTP) {@link Session} state management provider
* in Spring Session.
*
* @author John Blum
* @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
* @since 1.0.0
*/
@SuppressWarnings("unused")
public interface SpringSessionGemFireConfigurer {
/**
* Defines the {@link ClientCache} {@link Region} data management policy.
*
* Defaults to {@link ClientRegionShortcut#PROXY}.
*
* @return a {@link ClientRegionShortcut} used to configure the {@link ClientCache} {@link Region}
* data management policy.
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration#DEFAULT_CLIENT_REGION_SHORTCUT
* @see org.apache.geode.cache.client.ClientRegionShortcut
*/
default ClientRegionShortcut getClientRegionShortcut() {
return GemFireHttpSessionConfiguration.DEFAULT_CLIENT_REGION_SHORTCUT;
}
/**
* Identifies the {@link Session} attributes by name that will be indexed for query operations.
*
* For instance, find all {@link Session Sessions} in Apache Geode or Pivotal GemFire having attribute A
* defined with value X.
*
* Defaults to empty {@link String} array.
*
* @return an array of {@link String Strings} identifying the names of {@link Session} attributes to index.
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration#DEFAULT_INDEXABLE_SESSION_ATTRIBUTES
*/
default String[] getIndexableSessionAttributes() {
return GemFireHttpSessionConfiguration.DEFAULT_INDEXABLE_SESSION_ATTRIBUTES;
}
/**
* Defines the maximum interval in seconds that a {@link Session} can remain inactive before it expires.
*
* Defaults to {@literal 1800} seconds, or {@literal 30} minutes.
*
* @return an integer value defining the maximum inactive interval in seconds before the {@link Session} expires.
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration#DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS
*/
default int getMaxInactiveIntervalInSeconds() {
return GemFireHttpSessionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS;
}
/**
* Specifies the name of the specific {@link Pool} used by the {@link ClientCache} {@link Region}
* (i.e. {@literal ClusteredSpringSessions}) when performing cache data access operations.
*
* This is attribute is only used in the client/server topology.
*
* Defaults to {@literal gemfirePool}.
*
* @return the name of the {@link Pool} used by the {@link ClientCache} {@link Region}
* to send {@link Session} state to the cluster of servers.
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration#DEFAULT_POOL_NAME
*/
default String getPoolName() {
return GemFireHttpSessionConfiguration.DEFAULT_POOL_NAME;
}
/**
* Defines the {@link String name} of the (client)cache {@link Region} used to store {@link Session} state.
*
* Defaults to {@literal ClusteredSpringSessions}.
*
* @return a {@link String} specifying the name of the (client)cache {@link Region}
* used to store {@link Session} state.
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration#DEFAULT_SESSION_REGION_NAME
*/
default String getRegionName() {
return GemFireHttpSessionConfiguration.DEFAULT_SESSION_REGION_NAME;
}
/**
* Defines the {@link Cache} {@link Region} data management policy.
*
* Defaults to {@link RegionShortcut#PARTITION}.
*
* @return a {@link RegionShortcut} used to specify and configure the {@link Cache} {@link Region}
* data management policy.
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration#DEFAULT_SERVER_REGION_SHORTCUT
* @see org.apache.geode.cache.RegionShortcut
*/
default RegionShortcut getServerRegionShortcut() {
return GemFireHttpSessionConfiguration.DEFAULT_SERVER_REGION_SHORTCUT;
}
/**
* Defines the bean name of the {@link SessionSerializer} used to serialize {@link Session} state
* between client and server or to disk when persisting or overflowing {@link Session} state.
*
* The bean referred to by its name must be of type {@link SessionSerializer}.
*
* Defaults to {@literal SessionPdxSerializer}.
*
* @return a {@link String} containing the bean name of the configured {@link SessionSerializer}.
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration#DEFAULT_SESSION_SERIALIZER_BEAN_NAME
* @see org.springframework.session.data.gemfire.serialization.pdx.provider.PdxSerializableSessionSerializer
* @see org.springframework.session.data.gemfire.serialization.SessionSerializer
*/
default String getSessionSerializerBeanName() {
return GemFireHttpSessionConfiguration.DEFAULT_SESSION_SERIALIZER_BEAN_NAME;
}
}