diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SimpleSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HashMapSessionConfiguration.java similarity index 77% rename from spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SimpleSessionConfiguration.java rename to spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HashMapSessionConfiguration.java index d13ca68db6..182992d083 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SimpleSessionConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HashMapSessionConfiguration.java @@ -16,7 +16,6 @@ package org.springframework.boot.autoconfigure.session; -import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @@ -26,24 +25,24 @@ import org.springframework.session.SessionRepository; import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; /** - * In-memory session configuration, intended as a fallback. + * HashMap based session configuration, intended as a fallback. * * @author Tommy Ludwig - * @since 1.4.0 + * @author Stephane Nicoll */ @Configuration @EnableSpringHttpSession @Conditional(SessionCondition.class) -class SimpleSessionConfiguration { +class HashMapSessionConfiguration { @Bean - public SessionRepository sessionRepository(ServerProperties serverProperties) { + public SessionRepository sessionRepository(SessionProperties sessionProperties) { MapSessionRepository sessionRepository = new MapSessionRepository(); - - Integer timeout = serverProperties.getSession().getTimeout(); - if (serverProperties.getSession().getTimeout() != null) { + Integer timeout = sessionProperties.getTimeout(); + if (timeout != null) { sessionRepository.setDefaultMaxInactiveInterval(timeout); } return sessionRepository; } + } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java index 95f2f15587..6667e2b923 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java @@ -16,44 +16,38 @@ package org.springframework.boot.autoconfigure.session; -import javax.annotation.PostConstruct; - import com.hazelcast.core.HazelcastInstance; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; -import org.springframework.session.MapSessionRepository; -import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession; +import org.springframework.session.hazelcast.config.annotation.web.http.HazelcastHttpSessionConfiguration; /** - * Hazelcast backed session auto-configuration. + * Hazelcast backed session configuration. * * @author Tommy Ludwig * @author Eddú Meléndez - * @since 1.4.0 + * @author Stephane Nicoll */ @Configuration -@ConditionalOnBean({ HazelcastInstance.class }) -@EnableHazelcastHttpSession +@ConditionalOnBean(HazelcastInstance.class) @Conditional(SessionCondition.class) class HazelcastSessionConfiguration { - private ServerProperties serverProperties; - - private MapSessionRepository sessionRepository; - - HazelcastSessionConfiguration(ServerProperties serverProperties, MapSessionRepository sessionRepository) { - this.serverProperties = serverProperties; - this.sessionRepository = sessionRepository; - } - - @PostConstruct - public void applyConfigurationProperties() { - Integer timeout = this.serverProperties.getSession().getTimeout(); - if (timeout != null) { - this.sessionRepository.setDefaultMaxInactiveInterval(timeout); + @Configuration + public static class SprigBootHazelcastHttpSessionConfiguration + extends HazelcastHttpSessionConfiguration { + + @Autowired + public void customize(SessionProperties sessionProperties) { + Integer timeout = sessionProperties.getTimeout(); + if (timeout != null) { + setMaxInactiveIntervalInSeconds(timeout); + } + setSessionMapName(sessionProperties.getHazelcast().getMapName()); } + } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.java index 7a4c8f7fad..ce82048e2c 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.java @@ -16,44 +16,38 @@ package org.springframework.boot.autoconfigure.session; -import javax.annotation.PostConstruct; import javax.sql.DataSource; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; -import org.springframework.session.jdbc.JdbcOperationsSessionRepository; -import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession; +import org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration; /** - * JDBC backed session auto-configuration. + * JDBC backed session configuration. * * @author Eddú Meléndez - * @since 1.4.0 + * @author Stephane Nicoll */ @Configuration @ConditionalOnBean(DataSource.class) -@EnableJdbcHttpSession @Conditional(SessionCondition.class) class JdbcSessionConfiguration { - private ServerProperties serverProperties; - - private JdbcOperationsSessionRepository sessionRepository; - - JdbcSessionConfiguration(ServerProperties serverProperties, - JdbcOperationsSessionRepository sessionRepository) { - this.serverProperties = serverProperties; - this.sessionRepository = sessionRepository; - } - - @PostConstruct - public void applyConfigurationProperties() { - Integer timeout = this.serverProperties.getSession().getTimeout(); - if (timeout != null) { - this.sessionRepository.setDefaultMaxInactiveInterval(timeout); + @Configuration + public static class SpringBootJdbcHttpSessionConfiguration + extends JdbcHttpSessionConfiguration { + + @Autowired + public void customize(SessionProperties sessionProperties) { + Integer timeout = sessionProperties.getTimeout(); + if (timeout != null) { + setMaxInactiveIntervalInSeconds(timeout); + } + setTableName(sessionProperties.getJdbc().getTableName()); } + } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionConfiguration.java index 1eb6f9d86c..2c8ac38703 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionConfiguration.java @@ -16,43 +16,37 @@ package org.springframework.boot.autoconfigure.session; -import javax.annotation.PostConstruct; - +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.session.data.mongo.MongoOperationsSessionRepository; -import org.springframework.session.data.mongo.config.annotation.web.http.EnableMongoHttpSession; +import org.springframework.session.data.mongo.config.annotation.web.http.MongoHttpSessionConfiguration; /** - * Mongo backed session auto-configuration. + * Mongo backed session configuration. * * @author Eddú Meléndez - * @since 1.4.0 + * @author Stephane Nicoll */ @Configuration @ConditionalOnBean(MongoOperations.class) -@EnableMongoHttpSession @Conditional(SessionCondition.class) class MongoSessionConfiguration { - private ServerProperties serverProperties; - - private MongoOperationsSessionRepository sessionRepository; - - MongoSessionConfiguration(ServerProperties serverProperties, MongoOperationsSessionRepository sessionRepository) { - this.serverProperties = serverProperties; - this.sessionRepository = sessionRepository; - } - - @PostConstruct - public void applyConfigurationProperties() { - Integer timeout = this.serverProperties.getSession().getTimeout(); - if (timeout != null) { - this.sessionRepository.setMaxInactiveIntervalInSeconds(timeout); + @Configuration + public static class SpringBootMongoHttpSessionConfiguration + extends MongoHttpSessionConfiguration { + + @Autowired + public void customize(SessionProperties sessionProperties) { + Integer timeout = sessionProperties.getTimeout(); + if (timeout != null) { + setMaxInactiveIntervalInSeconds(timeout); + } + setCollectionName(sessionProperties.getMongo().getCollectionName()); } + } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/NoOpSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/NoOpSessionConfiguration.java index 581ac958c0..c374c1bd2f 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/NoOpSessionConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/NoOpSessionConfiguration.java @@ -22,11 +22,9 @@ import org.springframework.context.annotation.Configuration; import org.springframework.session.SessionRepository; /** - * No-op session configuration used to disable Spring Session auto configuration via the - * environment. + * No-op session configuration used to disable Spring Session using the environment. * * @author Tommy Ludwig - * @since 1.4.0 */ @Configuration @ConditionalOnMissingBean(SessionRepository.class) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java index 43ae37ebd4..34413198b5 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java @@ -16,45 +16,42 @@ package org.springframework.boot.autoconfigure.session; -import javax.annotation.PostConstruct; - +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.session.data.redis.RedisOperationsSessionRepository; -import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; +import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration; /** - * Redis backed session auto-configuration. + * Redis backed session configuration. * * @author Andy Wilkinson * @author Tommy Ludwig * @author Eddú Meléndez - * @since 1.4.0 + * @author Stephane Nicoll */ @Configuration -@ConditionalOnBean({RedisTemplate.class}) -@EnableRedisHttpSession +@ConditionalOnBean({ RedisTemplate.class, RedisConnectionFactory.class }) @Conditional(SessionCondition.class) class RedisSessionConfiguration { - private ServerProperties serverProperties; - - private RedisOperationsSessionRepository sessionRepository; - - RedisSessionConfiguration(ServerProperties serverProperties, RedisOperationsSessionRepository sessionRepository) { - this.serverProperties = serverProperties; - this.sessionRepository = sessionRepository; - } - - @PostConstruct - public void applyConfigurationProperties() { - Integer timeout = this.serverProperties.getSession().getTimeout(); - if (timeout != null) { - this.sessionRepository.setDefaultMaxInactiveInterval(timeout); + @Configuration + public static class SpringBootRedisHttpSessionConfiguration + extends RedisHttpSessionConfiguration { + + @Autowired + public void customize(SessionProperties sessionProperties) { + Integer timeout = sessionProperties.getTimeout(); + if (timeout != null) { + setMaxInactiveIntervalInSeconds(timeout); + } + SessionProperties.Redis redis = sessionProperties.getRedis(); + setRedisNamespace(redis.getNamespace()); + setRedisFlushMode(redis.getFlushMode()); } + } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java index 34635ededb..27f45adcd3 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java @@ -21,15 +21,12 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; import org.springframework.boot.autoconfigure.session.SessionAutoConfiguration.SessionConfigurationImportSelector; -import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportSelector; @@ -43,7 +40,8 @@ import org.springframework.session.SessionRepository; * @author Andy Wilkinson * @author Tommy Ludwig * @author Eddú Meléndez - * @since 1.3.0 + * @author Stephane Nicoll + * @since 1.4.0 */ @Configuration @ConditionalOnClass(Session.class) @@ -55,19 +53,6 @@ import org.springframework.session.SessionRepository; @Import(SessionConfigurationImportSelector.class) public class SessionAutoConfiguration { - @Configuration - @ConditionalOnMissingBean(value = ServerProperties.class, search = SearchStrategy.CURRENT) - // Just in case user switches off ServerPropertiesAutoConfiguration - public static class ServerPropertiesConfiguration { - - @Bean - // Use the same bean name as the default one for any old webapp - public ServerProperties serverProperties() { - return new ServerProperties(); - } - - } - /** * {@link ImportSelector} to add {@link StoreType} configuration classes. */ diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java index 5eee81e6a1..bd9bbbeb98 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java @@ -24,7 +24,7 @@ import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.AnnotationMetadata; /** - * General condition used by all session auto-configuration classes. + * General condition used with all session configuration classes. * * @author Tommy Ludwig */ @@ -34,13 +34,13 @@ class SessionCondition extends SpringBootCondition { public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { RelaxedPropertyResolver resolver = new RelaxedPropertyResolver( - context.getEnvironment(), "spring.session.store."); - if (!resolver.containsProperty("type")) { + context.getEnvironment(), "spring.session."); + if (!resolver.containsProperty("store-type")) { return ConditionOutcome.match("Automatic session store type"); } StoreType sessionStoreType = SessionStoreMappings .getType(((AnnotationMetadata) metadata).getClassName()); - String value = resolver.getProperty("type").replace("-", "_").toUpperCase(); + String value = resolver.getProperty("store-type").replace("-", "_").toUpperCase(); if (value.equals(sessionStoreType.name())) { return ConditionOutcome.match("Session store type " + sessionStoreType); } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionProperties.java index ed2234195e..33e111c25e 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionProperties.java @@ -16,42 +16,153 @@ package org.springframework.boot.autoconfigure.session; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.session.data.redis.RedisFlushMode; /** - * Properties for configuring Spring Session's auto-configuration. + * Configuration properties for Spring Session. * * @author Tommy Ludwig + * @author Stephane Nicoll * @since 1.4.0 */ @ConfigurationProperties("spring.session") public class SessionProperties { - private Store store; + /** + * Session store type, auto-detected according to the environment by default. + */ + private StoreType storeType; + + private Integer timeout; + + private final Hazelcast hazelcast = new Hazelcast(); - public Store getStore() { - return this.store; + private final Jdbc jdbc = new Jdbc(); + + private final Mongo mongo = new Mongo(); + + private final Redis redis = new Redis(); + + public SessionProperties(ObjectProvider serverProperties) { + ServerProperties properties = serverProperties.getIfUnique(); + this.timeout = (properties != null ? properties.getSession().getTimeout() : null); } - public void setStore(Store store) { - this.store = store; + public StoreType getStoreType() { + return this.storeType; + } + + public void setStoreType(StoreType storeType) { + this.storeType = storeType; } /** - * Session store-specific properties. + * Return the session timeout in seconds. + * @return the session timeout in seconds + * @see ServerProperties#getSession() */ - public static class Store { + public Integer getTimeout() { + return this.timeout; + } + + public Hazelcast getHazelcast() { + return this.hazelcast; + } + + public Jdbc getJdbc() { + return this.jdbc; + } + + public Mongo getMongo() { + return this.mongo; + } + + public Redis getRedis() { + return this.redis; + } + + public static class Hazelcast { + /** - * Session data store type, auto-detected according to the environment by default. + * Name of the map used to store sessions. */ - private StoreType type; + private String mapName = "spring:session:sessions"; - public StoreType getType() { - return this.type; + public String getMapName() { + return this.mapName; } - public void setType(StoreType type) { - this.type = type; + public void setMapName(String mapName) { + this.mapName = mapName; } + } + + public static class Jdbc { + + /** + * Name of database table used to store sessions. + */ + private String tableName = "SPRING_SESSION"; + + public String getTableName() { + return this.tableName; + } + + public void setTableName(String tableName) { + this.tableName = tableName; + } + + } + + public static class Mongo { + + /** + * Collection name used to store sessions. + */ + private String collectionName = "sessions"; + + public String getCollectionName() { + return this.collectionName; + } + + public void setCollectionName(String collectionName) { + this.collectionName = collectionName; + } + + } + + public static class Redis { + + /** + * Namespace for keys used to store sessions. + */ + private String namespace = ""; + + /** + * Flush mode for the Redis sessions. + */ + private RedisFlushMode flushMode = RedisFlushMode.ON_SAVE; + + public String getNamespace() { + return this.namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public RedisFlushMode getFlushMode() { + return this.flushMode; + } + + public void setFlushMode(RedisFlushMode flushMode) { + this.flushMode = flushMode; + } + + } + } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java index f296e324d1..81ce310bbf 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java @@ -27,7 +27,6 @@ import org.springframework.util.Assert; * * @author Tommy Ludwig * @author Eddú Meléndez - * @since 1.4.0 */ final class SessionStoreMappings { @@ -39,7 +38,7 @@ final class SessionStoreMappings { mappings.put(StoreType.MONGO, MongoSessionConfiguration.class); mappings.put(StoreType.REDIS, RedisSessionConfiguration.class); mappings.put(StoreType.HAZELCAST, HazelcastSessionConfiguration.class); - mappings.put(StoreType.HASH_MAP, SimpleSessionConfiguration.class); + mappings.put(StoreType.HASH_MAP, HashMapSessionConfiguration.class); mappings.put(StoreType.NONE, NoOpSessionConfiguration.class); MAPPINGS = Collections.unmodifiableMap(mappings); } @@ -63,4 +62,5 @@ final class SessionStoreMappings { throw new IllegalStateException( "Unknown configuration class " + configurationClassName); } + } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/StoreType.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/StoreType.java index 4a86634029..0be5627260 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/StoreType.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/StoreType.java @@ -51,7 +51,7 @@ public enum StoreType { HASH_MAP, /** - * No session datastore. + * No session data-store. */ NONE; diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/AbstractSessionAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/AbstractSessionAutoConfigurationTests.java new file mode 100644 index 0000000000..0c7f462d4c --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/AbstractSessionAutoConfigurationTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2016 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.boot.autoconfigure.session; + +import java.util.Collection; + +import org.junit.After; + +import org.springframework.beans.DirectFieldAccessor; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.session.SessionRepository; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Share test utilities for {@link SessionAutoConfiguration} tests. + * + * @author Stephane Nicoll + */ +public abstract class AbstractSessionAutoConfigurationTests { + + protected AnnotationConfigWebApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + protected > T validateSessionRepository(Class type) { + SessionRepository cacheManager = this.context.getBean(SessionRepository.class); + assertThat(cacheManager).as("Wrong session repository type").isInstanceOf(type); + return type.cast(cacheManager); + } + + protected Integer getSessionTimeout(SessionRepository sessionRepository) { + return (Integer) new DirectFieldAccessor(sessionRepository) + .getPropertyValue("defaultMaxInactiveInterval"); + } + + protected void load(String... environment) { + load(null, environment); + } + + protected void load(Collection> configs, String... environment) { + AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); + EnvironmentTestUtils.addEnvironment(ctx, environment); + if (configs != null) { + ctx.register(configs.toArray(new Class[configs.size()])); + } + ctx.register(ServerPropertiesAutoConfiguration.class, + SessionAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + ctx.refresh(); + this.context = ctx; + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java new file mode 100644 index 0000000000..2647063842 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2016 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.boot.autoconfigure.session; + +import java.util.Collections; + +import org.junit.Rule; +import org.junit.Test; + +import org.springframework.beans.DirectFieldAccessor; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.boot.redis.RedisTestServer; +import org.springframework.session.data.redis.RedisFlushMode; +import org.springframework.session.data.redis.RedisOperationsSessionRepository; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Redis specific tests for {@link SessionAutoConfiguration}. + * + * @author Stephane Nicoll + */ +public class SessionAutoConfigurationRedisTests + extends AbstractSessionAutoConfigurationTests { + + @Rule + public final RedisTestServer redis = new RedisTestServer(); + + @Test + public void redisSessionStore() { + load(Collections.>singletonList(RedisAutoConfiguration.class), + "spring.session.store-type=redis"); + RedisOperationsSessionRepository repository = validateSessionRepository( + RedisOperationsSessionRepository.class); + assertThat(repository.getSessionCreatedChannelPrefix()) + .isEqualTo("spring:session:event:created:"); + assertThat(new DirectFieldAccessor(repository).getPropertyValue("redisFlushMode")) + .isEqualTo(RedisFlushMode.ON_SAVE); + } + + @Test + public void redisSessionStoreWithCustomizations() { + load(Collections.>singletonList(RedisAutoConfiguration.class), + "spring.session.store-type=redis", + "spring.session.redis.namespace=foo", + "spring.session.redis.flush-mode=immediate"); + RedisOperationsSessionRepository repository = validateSessionRepository( + RedisOperationsSessionRepository.class); + assertThat(repository.getSessionCreatedChannelPrefix()) + .isEqualTo("spring:session:foo:event:created:"); + assertThat(new DirectFieldAccessor(repository).getPropertyValue("redisFlushMode")) + .isEqualTo(RedisFlushMode.IMMEDIATE); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationTests.java index e07f8327d4..1620e71b59 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationTests.java @@ -16,76 +16,174 @@ package org.springframework.boot.autoconfigure.session; -import org.junit.After; -import org.junit.Rule; +import java.util.Arrays; +import java.util.Collections; + +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.core.IMap; import org.junit.Test; -import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration; -import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; -import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; -import org.springframework.boot.context.embedded.MockEmbeddedServletContainerFactory; -import org.springframework.boot.redis.RedisTestServer; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.beans.DirectFieldAccessor; +import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.session.ExpiringSession; +import org.springframework.session.MapSessionRepository; +import org.springframework.session.SessionRepository; +import org.springframework.session.data.mongo.MongoOperationsSessionRepository; +import org.springframework.session.jdbc.JdbcOperationsSessionRepository; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; /** * Tests for {@link SessionAutoConfiguration}. * * @author Dave Syer - * @since 1.3.0 + * @author Eddú Meléndez + * @author Stephane Nicoll */ -public class SessionAutoConfigurationTests { +public class SessionAutoConfigurationTests extends AbstractSessionAutoConfigurationTests { - @Rule - public RedisTestServer redis = new RedisTestServer(); + @Test + public void backOffIfSessionRepositoryIsPresent() { + load(Collections.>singletonList(SessionRepositoryConfiguration.class), + "spring.session.store-type=mongo"); + MapSessionRepository repository = validateSessionRepository( + MapSessionRepository.class); + assertThat(this.context.getBean("mySessionRepository")).isSameAs(repository); + } + + @Test + public void hashMapSessionStore() { + load("spring.session.store-type=hash-map"); + MapSessionRepository repository = validateSessionRepository( + MapSessionRepository.class); + assertThat(getSessionTimeout(repository)).isNull(); + } - private AnnotationConfigEmbeddedWebApplicationContext context; + @Test + public void hashMapSessionStoreCustomTimeout() { + load("spring.session.store-type=hash-map", + "server.session.timeout=3000"); + MapSessionRepository repository = validateSessionRepository( + MapSessionRepository.class); + assertThat(getSessionTimeout(repository)).isEqualTo(3000); + } - @After - public void close() { - if (this.context != null) { - this.context.close(); - } + @Test + public void springSessionTimeoutIsNotAValidProperty() { + load("spring.session.store-type=hash-map", + "spring.session.timeout=3000"); + MapSessionRepository repository = validateSessionRepository( + MapSessionRepository.class); + assertThat(getSessionTimeout(repository)).isNull(); + } + + @Test + public void hashMapSessionStoreIsDefault() { + load(); + validateSessionRepository(MapSessionRepository.class); + } + + @Test + public void jdbcSessionStore() { + load(Arrays.asList(EmbeddedDataSourceConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class), + "spring.session.store-type=jdbc"); + JdbcOperationsSessionRepository repository = validateSessionRepository( + JdbcOperationsSessionRepository.class); + assertThat(new DirectFieldAccessor(repository).getPropertyValue("tableName")) + .isEqualTo("SPRING_SESSION"); + } + + @Test + public void jdbcSessionStoreCustomTableName() { + load(Arrays.asList(EmbeddedDataSourceConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class), + "spring.session.store-type=jdbc", + "spring.session.jdbc.table-name=FOO_BAR"); + JdbcOperationsSessionRepository repository = validateSessionRepository( + JdbcOperationsSessionRepository.class); + assertThat(new DirectFieldAccessor(repository).getPropertyValue("tableName")) + .isEqualTo("FOO_BAR"); + } + + @Test + public void hazelcastSessionStore() { + load(Collections.>singletonList(HazelcastConfiguration.class), + "spring.session.store-type=hazelcast"); + validateSessionRepository(MapSessionRepository.class); + } + + @Test + public void hazelcastSessionStoreWithCustomizations() { + load(Collections.>singletonList(HazelcastSpecificMap.class), + "spring.session.store-type=hazelcast", + "spring.session.hazelcast.map-name=foo:bar:biz"); + validateSessionRepository(MapSessionRepository.class); + HazelcastInstance hazelcastInstance = this.context.getBean(HazelcastInstance.class); + verify(hazelcastInstance, times(1)).getMap("foo:bar:biz"); } @Test - public void flat() throws Exception { - this.context = new AnnotationConfigEmbeddedWebApplicationContext(); - this.context.register(Config.class, ServerPropertiesAutoConfiguration.class, - RedisAutoConfiguration.class, SessionAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); - ServerProperties server = this.context.getBean(ServerProperties.class); - assertThat(server).isNotNull(); + public void mongoSessionStore() { + load(Arrays.asList(EmbeddedMongoAutoConfiguration.class, + MongoAutoConfiguration.class, MongoDataAutoConfiguration.class), + "spring.session.store-type=mongo", "spring.data.mongodb.port=0"); + validateSessionRepository(MongoOperationsSessionRepository.class); } @Test - public void hierarchy() throws Exception { - AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(); - parent.register(RedisAutoConfiguration.class, SessionAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - parent.refresh(); - this.context = new AnnotationConfigEmbeddedWebApplicationContext(); - this.context.setParent(parent); - this.context.register(Config.class, ServerPropertiesAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); - ServerProperties server = this.context.getBean(ServerProperties.class); - assertThat(server).isNotNull(); + public void mongoSessionStoreWithCustomizations() { + load(Arrays.asList(EmbeddedMongoAutoConfiguration.class, + MongoAutoConfiguration.class, MongoDataAutoConfiguration.class), + "spring.session.store-type=mongo", "spring.data.mongodb.port=0", + "spring.session.mongo.collection-name=foobar"); + MongoOperationsSessionRepository repository = validateSessionRepository( + MongoOperationsSessionRepository.class); + assertThat(new DirectFieldAccessor(repository).getPropertyValue("collectionName")) + .isEqualTo("foobar"); + } + + + @Configuration + static class SessionRepositoryConfiguration { + + @Bean + public SessionRepository mySessionRepository() { + return new MapSessionRepository(Collections.emptyMap()); + } + + } + + @Configuration + static class HazelcastConfiguration { + + @Bean + public HazelcastInstance hazelcastInstance() { + return Hazelcast.newHazelcastInstance(); + } + } @Configuration - protected static class Config { + static class HazelcastSpecificMap { @Bean - public EmbeddedServletContainerFactory containerFactory() { - return new MockEmbeddedServletContainerFactory(); + public HazelcastInstance hazelcastInstance() { + IMap map = mock(IMap.class); + HazelcastInstance mock = mock(HazelcastInstance.class); + given(mock.getMap("foo:bar:biz")).willReturn(map); + return mock; } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/StoreTypesConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/StoreTypesConfigurationTests.java deleted file mode 100644 index 3f4fb87e66..0000000000 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/StoreTypesConfigurationTests.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2012-2016 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.boot.autoconfigure.session; - -import java.net.UnknownHostException; - -import com.hazelcast.core.Hazelcast; -import com.hazelcast.core.HazelcastInstance; - -import com.mongodb.MongoClient; -import org.junit.After; -import org.junit.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; -import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration; -import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration; -import org.springframework.boot.test.util.EnvironmentTestUtils; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.session.MapSessionRepository; -import org.springframework.session.data.mongo.MongoOperationsSessionRepository; -import org.springframework.session.jdbc.JdbcOperationsSessionRepository; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; - -public class StoreTypesConfigurationTests { - - private AnnotationConfigWebApplicationContext context; - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void hashMapSessionStore() { - load("spring.session.store.type=hash-map"); - MapSessionRepository sessionRepository = this.context.getBean(MapSessionRepository.class); - assertThat(sessionRepository).isNotNull(); - } - - @Test - public void jdbcSessionStore() { - load(new String[] {"spring.session.store.type=jdbc"}, EmbeddedDataSourceConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class); - assertThat(this.context.getBean(JdbcOperationsSessionRepository.class)).isNotNull(); - } - - @Test - public void hazelcastSessionStore() { - load(new String[] {"spring.session.store.type=hazelcast"}, MockHazelcastInstanceConfiguration.class); - assertThat(this.context.getBean(MapSessionRepository.class)).isNotNull(); - } - - @Test - public void mongoSessionStore() { - load(new String[] {"spring.session.store.type=mongo", "spring.data.mongodb.port=0"}, MockMongoConfiguration.class, MongoDataAutoConfiguration.class, EmbeddedMongoAutoConfiguration.class); - assertThat(this.context.getBean(MongoOperationsSessionRepository.class)).isNotNull(); - } - - private void load(String storeType) { - load(new String[] {storeType}, null); - } - - private void load(String[] storeType, Class... config) { - this.context = new AnnotationConfigWebApplicationContext(); - for (String property : storeType) { - EnvironmentTestUtils.addEnvironment(this.context, storeType); - } - if (config != null) { - this.context.register(config); - } - this.context.register(ServerPropertiesAutoConfiguration.class, - SessionAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); - } - - @Configuration - static class MockHazelcastInstanceConfiguration { - - @Bean - public HazelcastInstance hazelcastInstance() { - return Hazelcast.newHazelcastInstance(); - } - - } - - @Configuration - static class MockMongoConfiguration { - - @Bean - public MongoClient mongoClient(@Value("${local.mongo.port}") int port) - throws UnknownHostException { - return new MongoClient("localhost", port); - } - - } - -} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/FilterOrderingIntegrationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/FilterOrderingIntegrationTests.java index 8763fa4e70..7acc414a10 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/FilterOrderingIntegrationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/FilterOrderingIntegrationTests.java @@ -32,9 +32,9 @@ import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebAppl import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor; import org.springframework.boot.context.embedded.MockEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.MockEmbeddedServletContainerFactory.RegisteredFilter; +import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.boot.web.filter.OrderedCharacterEncodingFilter; import org.springframework.boot.web.filter.OrderedRequestContextFilter; -import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnection; @@ -84,9 +84,10 @@ public class FilterOrderingIntegrationTests { private void load() { this.context = new AnnotationConfigEmbeddedWebApplicationContext(); - EnvironmentTestUtils.addEnvironment(this.context, "spring.session.store.type=hash-map"); + EnvironmentTestUtils.addEnvironment(this.context, "spring.session.store-type=hash-map"); this.context.register(MockEmbeddedServletContainerConfiguration.class, TestRedisConfiguration.class, WebMvcAutoConfiguration.class, + ServerPropertiesAutoConfiguration.class, SecurityAutoConfiguration.class, SessionAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 2c25fc874b..a39573d989 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -157,7 +157,7 @@ 4.0.4.RELEASE 1.0.4.RELEASE 2.0.9.RELEASE - 1.2.0.RC2 + 1.2.0.RC3 1.1.4.RELEASE 2.0.3.RELEASE 1.0.2.RELEASE diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 60a8687f2b..67401c8dcb 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -356,8 +356,13 @@ content into your application; rather pick only the properties that you need. spring.resources.chain.strategy.fixed.version= # Version string to use for the Version Strategy. spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/ # Locations of static resources. - # SPRING SESSION ({sc-spring-boot-autoconfigure}/session/SocialWebAutoConfiguration.{sc-ext}[SessionAutoConfiguration]) - spring.session.store.store-type= # Session store type + # SPRING SESSION ({sc-spring-boot-autoconfigure}/session/SessionProperties.{sc-ext}[SessionProperties]) + spring.session.hazelcast.map-name=spring:session:sessions # Name of the map used to store sessions. + spring.session.jdbc.table-name=SPRING_SESSION # Name of database table used to store sessions. + spring.session.mongo.collection-name=sessions # Collection name used to store sessions. + spring.session.redis.flush-mode= # Flush mode for the Redis sessions. + spring.session.redis.namespace= # Namespace for keys used to store sessions. + spring.session.store-type= # Session store type, auto-detected according to the environment by default. # SPRING SOCIAL ({sc-spring-boot-autoconfigure}/social/SocialWebAutoConfiguration.{sc-ext}[SocialWebAutoConfiguration]) spring.social.auto-connection-views=false # Enable the connection status view for supported providers. diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index ebda36d136..9c872e013e 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -4392,21 +4392,24 @@ class for more details. [[boot-features-session]] == Spring Session -Spring Session provides support for managing a user's session information. If you are -writing a web application and Spring Session and one the following providers: +Spring Boot provides Spring Session auto-configuration for a wide range of stores. If +Spring Session is available and you haven't defined a bean of type `SessionRepository`, +Spring Boot tries to detect the following session stores (in this order): * JDBC +* MongoDB +* Redis * Hazelcast -* Spring Data Mongo -* Spring Data Redis +* HashMap -on the classpath, Spring Boot will auto-configure Spring Session through its -`@EnableJdbcHttpSession`, `@EnableHazelcastHttpSession`, `@EnableMongoHttpSession`, -`@EnableRedisHttpSession`. Session data will be stored in the provider and the session timeout -can be configured using the `server.session.timeout` property. +It is also possible to _force_ the store to use via the `spring.session.store-type` +property. Each store have specific additional settings. For instance it is possible +to customize the name of the table for the jdbc store: -It is also possible to _force_ the session provider to use via the `spring.session.store.type` -property. +[source,properties,indent=0] +---- + spring.session.jdbc.table-name=SESSIONS +----