diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/AbstractSessionCondition.java similarity index 82% rename from spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java rename to spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/AbstractSessionCondition.java index d90c4ecc91..dc8378c418 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/AbstractSessionCondition.java @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.session; +import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.condition.ConditionMessage; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; @@ -27,13 +28,20 @@ import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.AnnotationMetadata; /** - * General condition used with all session configuration classes. + * Base class for Servlet and reactive session conditions. * * @author Tommy Ludwig * @author Stephane Nicoll * @author Madhura Bhave + * @author Andy Wilkinson */ -class SessionCondition extends SpringBootCondition { +class AbstractSessionCondition extends SpringBootCondition { + + private final WebApplicationType webApplicationType; + + protected AbstractSessionCondition(WebApplicationType webApplicationType) { + this.webApplicationType = webApplicationType; + } @Override public ConditionOutcome getMatchOutcome(ConditionContext context, @@ -41,8 +49,8 @@ class SessionCondition extends SpringBootCondition { ConditionMessage.Builder message = ConditionMessage .forCondition("Session Condition"); Environment environment = context.getEnvironment(); - StoreType required = SessionStoreMappings - .getType(((AnnotationMetadata) metadata).getClassName()); + StoreType required = SessionStoreMappings.getType(this.webApplicationType, + ((AnnotationMetadata) metadata).getClassName()); if (!environment.containsProperty("spring.session.store-type")) { return ConditionOutcome.match(message.didNotFind("property", "properties") .items(ConditionMessage.Style.QUOTE, "spring.session.store-type")); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java index f5a3242719..960231d5f4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java @@ -41,7 +41,7 @@ import org.springframework.session.hazelcast.config.annotation.web.http.Hazelcas @ConditionalOnClass(HazelcastSessionRepository.class) @ConditionalOnMissingBean(SessionRepository.class) @ConditionalOnBean(HazelcastInstance.class) -@Conditional(SessionCondition.class) +@Conditional(ServletSessionCondition.class) @EnableConfigurationProperties(HazelcastSessionProperties.class) class HazelcastSessionConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.java index faab33edca..2cf29c0d75 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.java @@ -43,7 +43,7 @@ import org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessi @ConditionalOnClass({ JdbcTemplate.class, JdbcOperationsSessionRepository.class }) @ConditionalOnMissingBean(SessionRepository.class) @ConditionalOnBean(DataSource.class) -@Conditional(SessionCondition.class) +@Conditional(ServletSessionCondition.class) @EnableConfigurationProperties(JdbcSessionProperties.class) class JdbcSessionConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoReactiveSessionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoReactiveSessionConfiguration.java new file mode 100644 index 0000000000..d1d4647993 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoReactiveSessionConfiguration.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.session; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.core.ReactiveMongoOperations; +import org.springframework.session.ReactiveSessionRepository; +import org.springframework.session.data.mongo.config.annotation.web.reactive.ReactiveMongoWebSessionConfiguration; + +/** + * Mongo-backed reactive session configuration. + * + * @author Andy Wilkinson + */ +@Configuration +@ConditionalOnClass(ReactiveMongoWebSessionConfiguration.class) +@ConditionalOnMissingBean(ReactiveSessionRepository.class) +@ConditionalOnBean(ReactiveMongoOperations.class) +@Conditional(ReactiveSessionCondition.class) +@EnableConfigurationProperties(MongoSessionProperties.class) +class MongoReactiveSessionConfiguration { + + @Configuration + static class SpringBootReactiveMongoWebSessionConfiguration + extends ReactiveMongoWebSessionConfiguration { + + @Autowired + public void customize(SessionProperties sessionProperties, + MongoSessionProperties mongoSessionProperties) { + Integer timeout = sessionProperties.getTimeout(); + if (timeout != null) { + setMaxInactiveIntervalInSeconds(timeout); + } + setCollectionName(mongoSessionProperties.getCollectionName()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionConfiguration.java index d966cd8f56..eb8b7a8737 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionConfiguration.java @@ -37,7 +37,7 @@ import org.springframework.session.data.mongo.config.annotation.web.http.MongoHt @ConditionalOnClass(MongoHttpSessionConfiguration.class) @ConditionalOnMissingBean(SessionRepository.class) @ConditionalOnBean(MongoOperations.class) -@Conditional(SessionCondition.class) +@Conditional(ServletSessionCondition.class) @EnableConfigurationProperties(MongoSessionProperties.class) class MongoSessionConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/NoOpReactiveSessionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/NoOpReactiveSessionConfiguration.java new file mode 100644 index 0000000000..4f96e45264 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/NoOpReactiveSessionConfiguration.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.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.ReactiveSessionRepository; + +/** + * No-op session configuration used to disable Spring Session using the environment. + * + * @author Tommy Ludwig + * @author Andy Wilkinson + */ +@Configuration +@ConditionalOnMissingBean(ReactiveSessionRepository.class) +@Conditional(ReactiveSessionCondition.class) +class NoOpReactiveSessionConfiguration { + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/NoOpSessionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/NoOpSessionConfiguration.java index 6357a0ea97..9894eb01b0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/NoOpSessionConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/NoOpSessionConfiguration.java @@ -28,7 +28,7 @@ import org.springframework.session.SessionRepository; */ @Configuration @ConditionalOnMissingBean(SessionRepository.class) -@Conditional(SessionCondition.class) +@Conditional(ServletSessionCondition.class) class NoOpSessionConfiguration { } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/ReactiveSessionCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/ReactiveSessionCondition.java new file mode 100644 index 0000000000..b4e81cd040 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/ReactiveSessionCondition.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.session; + +import org.springframework.boot.WebApplicationType; + +/** + * General condition used with all reactive session configuration classes. + * + * @author Andy Wilkinson + */ +class ReactiveSessionCondition extends AbstractSessionCondition { + + ReactiveSessionCondition() { + super(WebApplicationType.REACTIVE); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisReactiveSessionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisReactiveSessionConfiguration.java new file mode 100644 index 0000000000..fd26366439 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisReactiveSessionConfiguration.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.session; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; +import org.springframework.session.ReactiveSessionRepository; +import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository; +import org.springframework.session.data.redis.config.annotation.web.server.RedisWebSessionConfiguration; + +/** + * Redis-backed reactive session configuration. + * + * @author Andy Wilkinson + */ +@Configuration +@ConditionalOnClass({ ReactiveRedisConnectionFactory.class, + ReactiveRedisOperationsSessionRepository.class }) +@ConditionalOnMissingBean(ReactiveSessionRepository.class) +@ConditionalOnBean(ReactiveRedisConnectionFactory.class) +@Conditional(ReactiveSessionCondition.class) +@EnableConfigurationProperties(RedisSessionProperties.class) +class RedisReactiveSessionConfiguration { + + @Configuration + static class SpringBootRedisWebSessionConfiguration + extends RedisWebSessionConfiguration { + + private SessionProperties sessionProperties; + + @Autowired + public void customize(SessionProperties sessionProperties, + RedisSessionProperties redisSessionProperties) { + this.sessionProperties = sessionProperties; + Integer timeout = this.sessionProperties.getTimeout(); + if (timeout != null) { + setMaxInactiveIntervalInSeconds(timeout); + } + setRedisNamespace(redisSessionProperties.getNamespace()); + setRedisFlushMode(redisSessionProperties.getFlushMode()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java index cb8a0db77a..2ca21fa073 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java @@ -42,7 +42,7 @@ import org.springframework.session.data.redis.config.annotation.web.http.RedisHt @ConditionalOnClass({ RedisTemplate.class, RedisOperationsSessionRepository.class }) @ConditionalOnMissingBean(SessionRepository.class) @ConditionalOnBean(RedisConnectionFactory.class) -@Conditional(SessionCondition.class) +@Conditional(ServletSessionCondition.class) @EnableConfigurationProperties(RedisSessionProperties.class) class RedisSessionConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/ServletSessionCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/ServletSessionCondition.java new file mode 100644 index 0000000000..bb3a66912d --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/ServletSessionCondition.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.session; + +import org.springframework.boot.WebApplicationType; + +/** + * General condition used with all servlet session configuration classes. + * + * @author Tommy Ludwig + * @author Stephane Nicoll + * @author Madhura Bhave + * @author Andy Wilkinson + */ +class ServletSessionCondition extends AbstractSessionCondition { + + ServletSessionCondition() { + super(WebApplicationType.SERVLET); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java index e2f7965fc7..79189ddaff 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java @@ -17,11 +17,13 @@ package org.springframework.boot.autoconfigure.session; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import javax.annotation.PostConstruct; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -43,6 +45,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata; +import org.springframework.session.ReactiveSessionRepository; import org.springframework.session.Session; import org.springframework.session.SessionRepository; @@ -69,77 +72,116 @@ public class SessionAutoConfiguration { @Configuration @ConditionalOnWebApplication(type = Type.SERVLET) - @Import({ SessionRepositoryConfiguration.class, SessionRepositoryValidator.class, + @Import({ ServletSessionRepositoryValidator.class, SessionRepositoryFilterConfiguration.class }) static class ServletSessionConfiguration { + @Configuration + @ConditionalOnMissingBean(SessionRepository.class) + @Import({ ServletSessionRepositoryImplementationValidator.class, + ServletSessionConfigurationImportSelector.class }) + static class ServletSessionRepositoryConfiguration { + + } + } @Configuration @ConditionalOnWebApplication(type = Type.REACTIVE) + @Import(ReactiveSessionRepositoryValidator.class) static class ReactiveSessionConfiguration { - } + @Configuration + @ConditionalOnMissingBean(ReactiveSessionRepository.class) + @Import({ ReactiveSessionRepositoryImplementationValidator.class, + ReactiveSessionConfigurationImportSelector.class }) + static class ReactiveSessionRepositoryConfiguration { - @Configuration - @ConditionalOnMissingBean(SessionRepository.class) - @Import({ SessionRepositoryImplementationValidator.class, - SessionConfigurationImportSelector.class }) - static class SessionRepositoryConfiguration { + } } /** - * {@link ImportSelector} to add {@link StoreType} configuration classes. + * {@link ImportSelector} base class to add {@link StoreType} configuration classes. */ - static class SessionConfigurationImportSelector implements ImportSelector { + static abstract class SessionConfigurationImportSelector implements ImportSelector { - @Override - public String[] selectImports(AnnotationMetadata importingClassMetadata) { + protected final String[] selectImports(AnnotationMetadata importingClassMetadata, + WebApplicationType webApplicationType) { + List imports = new ArrayList<>(); StoreType[] types = StoreType.values(); - String[] imports = new String[types.length]; for (int i = 0; i < types.length; i++) { - imports[i] = SessionStoreMappings.getConfigurationClass(types[i]); + imports.add(SessionStoreMappings.getConfigurationClass(webApplicationType, + types[i])); } - return imports; + return imports.toArray(new String[imports.size()]); } } /** - * Bean used to validate that only one supported implementation is available in the - * classpath when the store-type property is not set. + * {@link ImportSelector} to add {@link StoreType} configuration classes for reactive + * web applications. + */ + static class ReactiveSessionConfigurationImportSelector + extends SessionConfigurationImportSelector { + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return super.selectImports(importingClassMetadata, + WebApplicationType.REACTIVE); + } + + } + + /** + * {@link ImportSelector} to add {@link StoreType} configuration classes for Servlet + * web applications. */ - static class SessionRepositoryImplementationValidator { + static class ServletSessionConfigurationImportSelector + extends SessionConfigurationImportSelector { + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return super.selectImports(importingClassMetadata, + WebApplicationType.SERVLET); + } + + } + + /** + * Base class for beans used to validate that only one supported implementation is + * available in the classpath when the store-type property is not set. + */ + static class AbstractSessionRepositoryImplementationValidator { + + private final List candidates; private final ClassLoader classLoader; private final SessionProperties sessionProperties; - SessionRepositoryImplementationValidator(ApplicationContext applicationContext, - SessionProperties sessionProperties) { + AbstractSessionRepositoryImplementationValidator( + ApplicationContext applicationContext, + SessionProperties sessionProperties, List candidates) { this.classLoader = applicationContext.getClassLoader(); this.sessionProperties = sessionProperties; + this.candidates = candidates; } @PostConstruct public void checkAvailableImplementations() { - List> candidates = new ArrayList<>(); - addCandidate(candidates, - "org.springframework.session.hazelcast.HazelcastSessionRepository"); - addCandidate(candidates, - "org.springframework.session.jdbc.JdbcOperationsSessionRepository"); - addCandidate(candidates, - "org.springframework.session.data.mongo.MongoOperationsSessionRepository"); - addCandidate(candidates, - "org.springframework.session.data.redis.RedisOperationsSessionRepository"); + List> availableCandidates = new ArrayList<>(); + for (String candidate : this.candidates) { + addCandidateIfAvailable(availableCandidates, candidate); + } StoreType storeType = this.sessionProperties.getStoreType(); - if (candidates.size() > 1 && storeType == null) { - throw new NonUniqueSessionRepositoryException(candidates); + if (availableCandidates.size() > 1 && storeType == null) { + throw new NonUniqueSessionRepositoryException(availableCandidates); } } - private void addCandidate(List> candidates, String type) { + private void addCandidateIfAvailable(List> candidates, String type) { try { Class candidate = this.classLoader.loadClass(type); if (candidate != null) { @@ -153,17 +195,52 @@ public class SessionAutoConfiguration { } /** - * Bean used to validate that a {@link SessionRepository} exists and provide a - * meaningful message if that's not the case. + * Bean used to validate that only one supported implementation is available in the + * classpath when the store-type property is not set. + */ + static class ServletSessionRepositoryImplementationValidator + extends AbstractSessionRepositoryImplementationValidator { + + ServletSessionRepositoryImplementationValidator( + ApplicationContext applicationContext, + SessionProperties sessionProperties) { + super(applicationContext, sessionProperties, Arrays.asList( + "org.springframework.session.hazelcast.HazelcastSessionRepository", + "org.springframework.session.jdbc.JdbcOperationsSessionRepository", + "org.springframework.session.data.mongo.MongoOperationsSessionRepository", + "org.springframework.session.data.redis.RedisOperationsSessionRepository")); + } + + } + + /** + * Bean used to validate that only one supported implementation is available in the + * classpath when the store-type property is not set. */ - static class SessionRepositoryValidator { + static class ReactiveSessionRepositoryImplementationValidator + extends AbstractSessionRepositoryImplementationValidator { - private SessionProperties sessionProperties; + ReactiveSessionRepositoryImplementationValidator( + ApplicationContext applicationContext, + SessionProperties sessionProperties) { + super(applicationContext, sessionProperties, Arrays.asList( + "org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository", + "org.springframework.session.data.mongo.ReactiveMongoOperationsSessionRepository")); + } - private ObjectProvider> sessionRepositoryProvider; + } - SessionRepositoryValidator(SessionProperties sessionProperties, - ObjectProvider> sessionRepositoryProvider) { + /** + * Base class for validating that a (reactive) session repository bean exists. + */ + static class AbstractSessionRepositoryValidator { + + private final SessionProperties sessionProperties; + + private final ObjectProvider sessionRepositoryProvider; + + protected AbstractSessionRepositoryValidator(SessionProperties sessionProperties, + ObjectProvider sessionRepositoryProvider) { this.sessionProperties = sessionProperties; this.sessionRepositoryProvider = sessionRepositoryProvider; } @@ -184,4 +261,32 @@ public class SessionAutoConfiguration { } + /** + * Bean used to validate that a {@link SessionRepository} exists and provide a + * meaningful message if that's not the case. + */ + static class ServletSessionRepositoryValidator + extends AbstractSessionRepositoryValidator { + + ServletSessionRepositoryValidator(SessionProperties sessionProperties, + ObjectProvider> sessionRepositoryProvider) { + super(sessionProperties, sessionRepositoryProvider); + } + + } + + /** + * Bean used to validate that a {@link SessionRepository} exists and provide a + * meaningful message if that's not the case. + */ + static class ReactiveSessionRepositoryValidator + extends AbstractSessionRepositoryValidator { + + ReactiveSessionRepositoryValidator(SessionProperties sessionProperties, + ObjectProvider> sessionRepositoryProvider) { + super(sessionProperties, sessionRepositoryProvider); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java index dc3cb499fc..912a2a1bbd 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import org.springframework.boot.WebApplicationType; import org.springframework.util.Assert; /** @@ -30,32 +31,62 @@ import org.springframework.util.Assert; */ final class SessionStoreMappings { - private static final Map> MAPPINGS; + private static final Map>> MAPPINGS; static { - Map> mappings = new HashMap<>(); - mappings.put(StoreType.REDIS, RedisSessionConfiguration.class); - mappings.put(StoreType.MONGODB, MongoSessionConfiguration.class); - mappings.put(StoreType.JDBC, JdbcSessionConfiguration.class); - mappings.put(StoreType.HAZELCAST, HazelcastSessionConfiguration.class); - mappings.put(StoreType.NONE, NoOpSessionConfiguration.class); + Map>> mappings = new HashMap<>(); + mappings.put(StoreType.REDIS, createMapping(RedisSessionConfiguration.class, + RedisReactiveSessionConfiguration.class)); + mappings.put(StoreType.MONGODB, createMapping(MongoSessionConfiguration.class, + MongoReactiveSessionConfiguration.class)); + mappings.put(StoreType.JDBC, createMapping(JdbcSessionConfiguration.class)); + mappings.put(StoreType.HAZELCAST, + createMapping(HazelcastSessionConfiguration.class)); + mappings.put(StoreType.NONE, createMapping(NoOpSessionConfiguration.class, + NoOpReactiveSessionConfiguration.class)); MAPPINGS = Collections.unmodifiableMap(mappings); } + static Map> createMapping( + Class servletConfiguration) { + return createMapping(servletConfiguration, null); + } + + static Map> createMapping(Class servletConfiguration, + Class reactiveConfiguration) { + Map> mapping = new HashMap<>(); + mapping.put(WebApplicationType.SERVLET, servletConfiguration); + if (reactiveConfiguration != null) { + mapping.put(WebApplicationType.REACTIVE, reactiveConfiguration); + } + return mapping; + } + private SessionStoreMappings() { } - static String getConfigurationClass(StoreType sessionStoreType) { - Class configurationClass = MAPPINGS.get(sessionStoreType); - Assert.state(configurationClass != null, + static String getConfigurationClass(WebApplicationType webApplicationType, + StoreType sessionStoreType) { + Map> configurationClasses = MAPPINGS + .get(sessionStoreType); + Assert.state(configurationClasses != null, () -> "Unknown session store type " + sessionStoreType); + Class configurationClass = configurationClasses.get(webApplicationType); + if (configurationClass == null) { + return null; + } return configurationClass.getName(); } - static StoreType getType(String configurationClassName) { - for (Map.Entry> entry : MAPPINGS.entrySet()) { - if (entry.getValue().getName().equals(configurationClassName)) { - return entry.getKey(); + static StoreType getType(WebApplicationType webApplicationType, + String configurationClassName) { + for (Map.Entry>> storeEntry : MAPPINGS + .entrySet()) { + for (Map.Entry> entry : storeEntry.getValue() + .entrySet()) { + if (entry.getValue().getName().equals(configurationClassName)) { + return storeEntry.getKey(); + } } } throw new IllegalStateException( diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfiguration.java index d2aa905cdf..f5dec008d2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfiguration.java @@ -16,7 +16,6 @@ package org.springframework.boot.autoconfigure.web.reactive; -import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -30,14 +29,12 @@ import org.springframework.core.Ordered; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.web.reactive.DispatcherHandler; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; -import org.springframework.web.server.session.WebSessionManager; /** * {@link EnableAutoConfiguration Auto-configuration} for {@link HttpHandler}. * * @author Brian Clozel * @author Stephane Nicoll - * @author Andy Wilkinson * @since 2.0.0 */ @Configuration @@ -58,16 +55,9 @@ public class HttpHandlerAutoConfiguration { } @Bean - public HttpHandler httpHandler( - ObjectProvider webSessionManagerProvider) { - WebHttpHandlerBuilder builder = WebHttpHandlerBuilder - .applicationContext(this.applicationContext); - WebSessionManager webSessionManager = webSessionManagerProvider - .getIfAvailable(); - if (webSessionManager != null) { - builder.sessionManager(webSessionManager); - } - return builder.build(); + public HttpHandler httpHandler() { + return WebHttpHandlerBuilder.applicationContext(this.applicationContext) + .build(); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/AbstractSessionAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/AbstractSessionAutoConfigurationTests.java index a459696e16..ea75880d42 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/AbstractSessionAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/AbstractSessionAutoConfigurationTests.java @@ -17,7 +17,9 @@ package org.springframework.boot.autoconfigure.session; import org.springframework.beans.DirectFieldAccessor; +import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; +import org.springframework.session.ReactiveSessionRepository; import org.springframework.session.SessionRepository; import static org.assertj.core.api.Assertions.assertThat; @@ -42,4 +44,18 @@ public abstract class AbstractSessionAutoConfigurationTests { .getPropertyValue("defaultMaxInactiveInterval"); } + protected > T validateSessionRepository( + AssertableReactiveWebApplicationContext context, Class type) { + assertThat(context).hasSingleBean(ReactiveSessionRepository.class); + ReactiveSessionRepository repository = context + .getBean(ReactiveSessionRepository.class); + assertThat(repository).as("Wrong session repository type").isInstanceOf(type); + return type.cast(repository); + } + + protected Integer getSessionTimeout(ReactiveSessionRepository sessionRepository) { + return (Integer) new DirectFieldAccessor(sessionRepository) + .getPropertyValue("defaultMaxInactiveInterval"); + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationMongoTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationMongoTests.java new file mode 100644 index 0000000000..5a836854e1 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationMongoTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.session; + +import org.junit.Test; + +import org.springframework.beans.DirectFieldAccessor; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; +import org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration; +import org.springframework.boot.test.context.HideClassesClassLoader; +import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; +import org.springframework.boot.test.context.runner.ContextConsumer; +import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.session.data.mongo.ReactiveMongoOperationsSessionRepository; +import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Mongo-specific tests for {@link SessionAutoConfiguration}. + * + * @author Andy Wilkinson + */ +public class ReactiveSessionAutoConfigurationMongoTests + extends AbstractSessionAutoConfigurationTests { + + private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class)); + + @Test + public void defaultConfig() { + this.contextRunner.withPropertyValues("spring.session.store-type=mongodb") + .withConfiguration(AutoConfigurations.of( + EmbeddedMongoAutoConfiguration.class, + MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, + MongoReactiveAutoConfiguration.class, + MongoReactiveDataAutoConfiguration.class)) + .run(validateSpringSessionUsesMongo("sessions")); + } + + @Test + public void defaultConfigWithUniqueStoreImplementation() { + this.contextRunner + .withClassLoader(new HideClassesClassLoader( + ReactiveRedisOperationsSessionRepository.class)) + .withConfiguration(AutoConfigurations.of( + EmbeddedMongoAutoConfiguration.class, + MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, + MongoReactiveAutoConfiguration.class, + MongoReactiveDataAutoConfiguration.class)) + .run(validateSpringSessionUsesMongo("sessions")); + } + + @Test + public void mongoSessionStoreWithCustomizations() { + this.contextRunner + .withConfiguration(AutoConfigurations.of( + EmbeddedMongoAutoConfiguration.class, + MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, + MongoReactiveAutoConfiguration.class, + MongoReactiveDataAutoConfiguration.class)) + .withPropertyValues("spring.session.store-type=mongodb", + "spring.session.mongodb.collection-name=foo") + .run(validateSpringSessionUsesMongo("foo")); + } + + private ContextConsumer validateSpringSessionUsesMongo( + String collectionName) { + return (context) -> { + ReactiveMongoOperationsSessionRepository repository = validateSessionRepository( + context, ReactiveMongoOperationsSessionRepository.class); + assertThat(new DirectFieldAccessor(repository) + .getPropertyValue("collectionName")).isEqualTo(collectionName); + }; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationRedisTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationRedisTests.java new file mode 100644 index 0000000000..4f5f6684c3 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationRedisTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.session; + +import org.junit.Rule; +import org.junit.Test; + +import org.springframework.beans.DirectFieldAccessor; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration; +import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportMessage; +import org.springframework.boot.test.context.HideClassesClassLoader; +import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; +import org.springframework.boot.test.context.runner.ContextConsumer; +import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.boot.testsupport.rule.RedisTestServer; +import org.springframework.session.data.mongo.ReactiveMongoOperationsSessionRepository; +import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository; +import org.springframework.session.data.redis.RedisFlushMode; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Reactive Redis-specific tests for {@link SessionAutoConfiguration}. + * + * @author Stephane Nicoll + * @author Andy Wilkinson + */ +public class ReactiveSessionAutoConfigurationRedisTests + extends AbstractSessionAutoConfigurationTests { + + @Rule + public final RedisTestServer redis = new RedisTestServer(); + + protected final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class)); + + @Test + public void defaultConfig() { + this.contextRunner.withPropertyValues("spring.session.store-type=redis") + .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class, + RedisReactiveAutoConfiguration.class)) + .run(validateSpringSessionUsesRedis(RedisFlushMode.ON_SAVE)); + } + + @Test + public void defaultConfigWithUniqueStoreImplementation() { + this.contextRunner + .withClassLoader(new HideClassesClassLoader( + ReactiveMongoOperationsSessionRepository.class)) + .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class, + RedisReactiveAutoConfiguration.class)) + .run(validateSpringSessionUsesRedis(RedisFlushMode.ON_SAVE)); + } + + @Test + public void redisSessionStoreWithCustomizations() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class, + RedisReactiveAutoConfiguration.class)) + .withPropertyValues("spring.session.store-type=redis", + "spring.session.redis.namespace=foo", + "spring.session.redis.flush-mode=immediate") + .run(validateSpringSessionUsesRedis(RedisFlushMode.IMMEDIATE)); + } + + private ContextConsumer validateSpringSessionUsesRedis( + RedisFlushMode flushMode) { + return (context) -> { + System.out.println(new ConditionEvaluationReportMessage( + context.getBean(ConditionEvaluationReport.class))); + ReactiveRedisOperationsSessionRepository repository = validateSessionRepository( + context, ReactiveRedisOperationsSessionRepository.class); + assertThat(new DirectFieldAccessor(repository) + .getPropertyValue("redisFlushMode")).isEqualTo(flushMode); + }; + } + +} diff --git a/spring-boot-project/spring-boot-dependencies/pom.xml b/spring-boot-project/spring-boot-dependencies/pom.xml index 0bbdc9bf2f..45383cb0a4 100644 --- a/spring-boot-project/spring-boot-dependencies/pom.xml +++ b/spring-boot-project/spring-boot-dependencies/pom.xml @@ -157,7 +157,7 @@ 1.2.1.RELEASE 5.0.0.BUILD-SNAPSHOT 2.0.0.BUILD-SNAPSHOT - 2.0.0.M4 + 2.0.0.BUILD-SNAPSHOT 2.0.0.M4 3.0.0.M3 2.0.0.M3 diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index c94f64c1a6..d1f190a27c 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -5506,13 +5506,19 @@ classes for more details. [[boot-features-session]] == Spring Session -Spring Boot provides Spring Session auto-configuration for a wide range of stores: +Spring Boot provides Spring Session auto-configuration for a wide range of stores. When +building a Servlet web application, the following stores can be auto-configured: * JDBC * Redis * Hazelcast * MongoDB +When building a reactive web applicaiton, the following stores can be auto-configured: + +* Redis +* MongoDB + If Spring Session is available, you must choose the {sc-spring-boot-autoconfigure}/session/StoreType.{sc-ext}[`StoreType`] that you wish to use to store the sessions. For instance to use JDBC as backend store, you'd configure