diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HashMapSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HashMapSessionConfiguration.java new file mode 100644 index 0000000000..182992d083 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HashMapSessionConfiguration.java @@ -0,0 +1,48 @@ +/* + * 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 org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +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.config.annotation.web.http.EnableSpringHttpSession; + +/** + * HashMap based session configuration, intended as a fallback. + * + * @author Tommy Ludwig + * @author Stephane Nicoll + */ +@Configuration +@EnableSpringHttpSession +@Conditional(SessionCondition.class) +class HashMapSessionConfiguration { + + @Bean + public SessionRepository sessionRepository(SessionProperties sessionProperties) { + MapSessionRepository sessionRepository = new MapSessionRepository(); + 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 new file mode 100644 index 0000000000..6667e2b923 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java @@ -0,0 +1,53 @@ +/* + * 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 com.hazelcast.core.HazelcastInstance; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.session.hazelcast.config.annotation.web.http.HazelcastHttpSessionConfiguration; + +/** + * Hazelcast backed session configuration. + * + * @author Tommy Ludwig + * @author Eddú Meléndez + * @author Stephane Nicoll + */ +@Configuration +@ConditionalOnBean(HazelcastInstance.class) +@Conditional(SessionCondition.class) +class HazelcastSessionConfiguration { + + @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 new file mode 100644 index 0000000000..ce82048e2c --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.java @@ -0,0 +1,53 @@ +/* + * 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 javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration; + +/** + * JDBC backed session configuration. + * + * @author Eddú Meléndez + * @author Stephane Nicoll + */ +@Configuration +@ConditionalOnBean(DataSource.class) +@Conditional(SessionCondition.class) +class JdbcSessionConfiguration { + + @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 new file mode 100644 index 0000000000..2c8ac38703 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionConfiguration.java @@ -0,0 +1,52 @@ +/* + * 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 org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +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.config.annotation.web.http.MongoHttpSessionConfiguration; + +/** + * Mongo backed session configuration. + * + * @author Eddú Meléndez + * @author Stephane Nicoll + */ +@Configuration +@ConditionalOnBean(MongoOperations.class) +@Conditional(SessionCondition.class) +class MongoSessionConfiguration { + + @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 new file mode 100644 index 0000000000..c374c1bd2f --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/NoOpSessionConfiguration.java @@ -0,0 +1,33 @@ +/* + * 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 org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.session.SessionRepository; + +/** + * No-op session configuration used to disable Spring Session using the environment. + * + * @author Tommy Ludwig + */ +@Configuration +@ConditionalOnMissingBean(SessionRepository.class) +@Conditional(SessionCondition.class) +class NoOpSessionConfiguration { +} 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 new file mode 100644 index 0000000000..34413198b5 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java @@ -0,0 +1,57 @@ +/* + * 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 org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +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.config.annotation.web.http.RedisHttpSessionConfiguration; + +/** + * Redis backed session configuration. + * + * @author Andy Wilkinson + * @author Tommy Ludwig + * @author Eddú Meléndez + * @author Stephane Nicoll + */ +@Configuration +@ConditionalOnBean({ RedisTemplate.class, RedisConnectionFactory.class }) +@Conditional(SessionCondition.class) +class RedisSessionConfiguration { + + @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 b26de37a33..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,66 +21,51 @@ 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.web.ServerProperties; +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.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; import org.springframework.session.Session; -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; +import org.springframework.session.SessionRepository; /** * {@link EnableAutoConfiguration Auto-configuration} for Spring Session. * * @author Andy Wilkinson - * @since 1.3.0 + * @author Tommy Ludwig + * @author Eddú Meléndez + * @author Stephane Nicoll + * @since 1.4.0 */ @Configuration @ConditionalOnClass(Session.class) -@AutoConfigureAfter(RedisAutoConfiguration.class) +@ConditionalOnWebApplication +@ConditionalOnMissingBean(SessionRepository.class) +@EnableConfigurationProperties(SessionProperties.class) +@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HazelcastAutoConfiguration.class, + MongoAutoConfiguration.class, RedisAutoConfiguration.class }) +@Import(SessionConfigurationImportSelector.class) public class SessionAutoConfiguration { - @EnableConfigurationProperties - @ConditionalOnClass(RedisConnectionFactory.class) - @ConditionalOnWebApplication - @ConditionalOnMissingBean(RedisHttpSessionConfiguration.class) - @EnableRedisHttpSession - @Configuration - public static class SessionRedisHttpConfiguration { + /** + * {@link ImportSelector} to add {@link StoreType} configuration classes. + */ + static class SessionConfigurationImportSelector implements ImportSelector { - private final ServerProperties serverProperties; - - private final RedisOperationsSessionRepository sessionRepository; - - public SessionRedisHttpConfiguration(ServerProperties serverProperties, - RedisOperationsSessionRepository sessionRepository) { - this.serverProperties = serverProperties; - this.sessionRepository = sessionRepository; - applyConfigurationProperties(); - } - - private void applyConfigurationProperties() { - Integer timeout = this.serverProperties.getSession().getTimeout(); - if (timeout != null) { - this.sessionRepository.setDefaultMaxInactiveInterval(timeout); + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + StoreType[] types = StoreType.values(); + String[] imports = new String[types.length]; + for (int i = 0; i < types.length; i++) { + imports[i] = SessionStoreMappings.getConfigurationClass(types[i]); } - } - - @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(); - } - + return imports; } } 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 new file mode 100644 index 0000000000..bd9bbbeb98 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java @@ -0,0 +1,50 @@ +/* + * 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 org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.bind.RelaxedPropertyResolver; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.core.type.AnnotationMetadata; + +/** + * General condition used with all session configuration classes. + * + * @author Tommy Ludwig + */ +class SessionCondition extends SpringBootCondition { + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + RelaxedPropertyResolver resolver = new RelaxedPropertyResolver( + 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("store-type").replace("-", "_").toUpperCase(); + if (value.equals(sessionStoreType.name())) { + return ConditionOutcome.match("Session store type " + sessionStoreType); + } + return ConditionOutcome.noMatch("Session store type " + value); + } + +} 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 new file mode 100644 index 0000000000..33e111c25e --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionProperties.java @@ -0,0 +1,168 @@ +/* + * 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 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; + +/** + * Configuration properties for Spring Session. + * + * @author Tommy Ludwig + * @author Stephane Nicoll + * @since 1.4.0 + */ +@ConfigurationProperties("spring.session") +public class SessionProperties { + + /** + * Session store type, auto-detected according to the environment by default. + */ + private StoreType storeType; + + private Integer timeout; + + private final Hazelcast hazelcast = new Hazelcast(); + + 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 StoreType getStoreType() { + return this.storeType; + } + + public void setStoreType(StoreType storeType) { + this.storeType = storeType; + } + + /** + * Return the session timeout in seconds. + * @return the session timeout in seconds + * @see ServerProperties#getSession() + */ + 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 { + + /** + * Name of the map used to store sessions. + */ + private String mapName = "spring:session:sessions"; + + public String getMapName() { + return this.mapName; + } + + 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 new file mode 100644 index 0000000000..81ce310bbf --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java @@ -0,0 +1,66 @@ +/* + * 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 java.util.HashMap; +import java.util.Map; + +import org.springframework.util.Assert; + +/** + * Mappings between {@link StoreType} and {@code @Configuration}. + * + * @author Tommy Ludwig + * @author Eddú Meléndez + */ +final class SessionStoreMappings { + + private static final Map> MAPPINGS; + + static { + Map> mappings = new HashMap>(); + mappings.put(StoreType.JDBC, JdbcSessionConfiguration.class); + mappings.put(StoreType.MONGO, MongoSessionConfiguration.class); + mappings.put(StoreType.REDIS, RedisSessionConfiguration.class); + mappings.put(StoreType.HAZELCAST, HazelcastSessionConfiguration.class); + mappings.put(StoreType.HASH_MAP, HashMapSessionConfiguration.class); + mappings.put(StoreType.NONE, NoOpSessionConfiguration.class); + MAPPINGS = Collections.unmodifiableMap(mappings); + } + + private SessionStoreMappings() { + } + + public static String getConfigurationClass(StoreType sessionStoreType) { + Class configurationClass = MAPPINGS.get(sessionStoreType); + Assert.state(configurationClass != null, + "Unknown session store type " + sessionStoreType); + return configurationClass.getName(); + } + + public static StoreType getType(String configurationClassName) { + for (Map.Entry> entry : MAPPINGS.entrySet()) { + if (entry.getValue().getName().equals(configurationClassName)) { + return entry.getKey(); + } + } + 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 new file mode 100644 index 0000000000..0be5627260 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/StoreType.java @@ -0,0 +1,58 @@ +/* + * 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; + +/** + * Supported Spring Session data store types. + * + * @author Tommy Ludwig + * @author Eddú Meléndez + * @since 1.4.0 + */ +public enum StoreType { + + /** + * JDBC backed sessions. + */ + JDBC, + + /** + * Mongo backed sessions. + */ + MONGO, + + /** + * Redis backed sessions. + */ + REDIS, + + /** + * Hazelcast backed sessions. + */ + HAZELCAST, + + /** + * Simple in-memory map of sessions. + */ + HASH_MAP, + + /** + * No session data-store. + */ + NONE; + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/package-info.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/package-info.java new file mode 100644 index 0000000000..8534bf744d --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Auto-configuration for Spring Session. + */ +package org.springframework.boot.autoconfigure.session; 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); + } - private AnnotationConfigEmbeddedWebApplicationContext context; + @Test + public void hashMapSessionStore() { + load("spring.session.store-type=hash-map"); + MapSessionRepository repository = validateSessionRepository( + MapSessionRepository.class); + assertThat(getSessionTimeout(repository)).isNull(); + } - @After - public void close() { - if (this.context != null) { - this.context.close(); + @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); + } + + @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 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 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()); } - } - @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(); - } - - @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(); } @Configuration - protected static class Config { + static class HazelcastConfiguration { @Bean - public EmbeddedServletContainerFactory containerFactory() { - return new MockEmbeddedServletContainerFactory(); + public HazelcastInstance hazelcastInstance() { + return Hazelcast.newHazelcastInstance(); + } + + } + + @Configuration + static class HazelcastSpecificMap { + + @Bean + 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/web/FilterOrderingIntegrationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/FilterOrderingIntegrationTests.java index cbc9f0c9a7..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,6 +32,7 @@ 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.context.annotation.Bean; @@ -49,6 +50,7 @@ import static org.mockito.Mockito.mock; * Integration tests that verify the ordering of various filters that are auto-configured. * * @author Andy Wilkinson + * @author Eddú Meléndez */ public class FilterOrderingIntegrationTests { @@ -82,8 +84,10 @@ public class FilterOrderingIntegrationTests { private void load() { this.context = new AnnotationConfigEmbeddedWebApplicationContext(); + 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 11888e8804..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,6 +356,14 @@ 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/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 ea500f111a..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,11 +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 Spring Data Redis are both on the -classpath, Spring Boot will auto-configure Spring Session through its -`@EnableRedisHttpSession`. Session data will be stored in Redis and the session timeout -can be configured using the `server.session.timeout` property. +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 +* HashMap + +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: + +[source,properties,indent=0] +---- + spring.session.jdbc.table-name=SESSIONS +----