Relax use of spring.session.store-type

This commit makes the "spring.session.store-type" property optional,
adding an additional check when it is not present that validates only
one supported implementation is available on the classpath.

As Spring Session has been modularized, the chance that multiple
implementations are available on the classpath are lower. When only
one implementation is present, we attempt to auto-configure it. When
more than one implementation is present and no session store is
configured, a NonUniqueSessionRepositoryException is thrown.

Closes gh-9863
This commit is contained in:
Stephane Nicoll
2017-09-13 11:20:06 +02:00
parent de47827eb4
commit 4670cc7795
14 changed files with 493 additions and 44 deletions

View File

@@ -0,0 +1,64 @@
/*
* 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 java.util.Arrays;
import org.junit.Test;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.diagnostics.FailureAnalyzer;
import org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter;
import org.springframework.session.SessionRepository;
import org.springframework.session.hazelcast.HazelcastSessionRepository;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link NonUniqueSessionRepositoryFailureAnalyzer}.
*
* @author Stephane Nicoll
*/
public class NonUniqueSessionRepositoryFailureAnalyzerTests {
private final FailureAnalyzer analyzer = new NonUniqueSessionRepositoryFailureAnalyzer();
@Test
public void failureAnalysisWithMultipleCandidates() {
FailureAnalysis analysis = analyzeFailure(createFailure(
JdbcOperationsSessionRepository.class, HazelcastSessionRepository.class));
assertThat(analysis).isNotNull();
assertThat(analysis.getDescription()).contains(
JdbcOperationsSessionRepository.class.getName(),
HazelcastSessionRepository.class.getName());
assertThat(analysis.getAction()).contains("spring.session.store-type");
}
private Exception createFailure(Class<? extends SessionRepository>... candidates) {
return new NonUniqueSessionRepositoryException(Arrays.asList(candidates));
}
private FailureAnalysis analyzeFailure(Exception failure) {
FailureAnalysis analysis = this.analyzer.analyze(failure);
if (analysis != null) {
new LoggingFailureAnalysisReporter().report(analysis);
}
return analysis;
}
}

View File

@@ -22,11 +22,15 @@ import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.HideClassesClassLoader;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import org.springframework.session.hazelcast.HazelcastFlushMode;
import org.springframework.session.hazelcast.HazelcastSessionRepository;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
@@ -48,13 +52,24 @@ public class SessionAutoConfigurationHazelcastTests
@Test
public void defaultConfig() {
this.contextRunner.withPropertyValues("spring.session.store-type=hazelcast")
.run((context) -> {
validateSessionRepository(context, HazelcastSessionRepository.class);
HazelcastInstance hazelcastInstance = context
.getBean(HazelcastInstance.class);
verify(hazelcastInstance, times(1)).getMap("spring:session:sessions");
});
this.contextRunner
.withPropertyValues("spring.session.store-type=hazelcast")
.run(this::validateDefaultConfig);
}
@Test
public void defaultConfigWithUniqueStoreImplementation() {
this.contextRunner.withClassLoader(new HideClassesClassLoader(
JdbcOperationsSessionRepository.class,
RedisOperationsSessionRepository.class)).run(
this::validateDefaultConfig);
}
private void validateDefaultConfig(AssertableWebApplicationContext context) {
validateSessionRepository(context, HazelcastSessionRepository.class);
HazelcastInstance hazelcastInstance = context
.getBean(HazelcastInstance.class);
verify(hazelcastInstance, times(1)).getMap("spring:session:sessions");
}
@Test
@@ -80,7 +95,7 @@ public class SessionAutoConfigurationHazelcastTests
context, HazelcastSessionRepository.class);
assertThat(new DirectFieldAccessor(repository)
.getPropertyValue("hazelcastFlushMode"))
.isEqualTo(HazelcastFlushMode.IMMEDIATE);
.isEqualTo(HazelcastFlushMode.IMMEDIATE);
});
}

View File

@@ -0,0 +1,101 @@
/*
* 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 com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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;
/**
* Integration tests for {@link SessionAutoConfiguration}.
*
* @author Stephane Nicoll
*/
public class SessionAutoConfigurationIntegrationTests
extends AbstractSessionAutoConfigurationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
SessionAutoConfiguration.class))
.withPropertyValues("spring.datasource.generate-unique-name=true");
@Test
public void severalCandidatesWithNoSessionStore() {
this.contextRunner.withUserConfiguration(HazelcastConfiguration.class).run((context) -> {
assertThat(context).hasFailed();
assertThat(context).getFailure()
.hasCauseInstanceOf(NonUniqueSessionRepositoryException.class);
assertThat(context).getFailure().hasMessageContaining(
"Multiple session repository candidates are available");
assertThat(context).getFailure().hasMessageContaining(
"set the 'spring.session.store-type' property accordingly");
});
}
@Test
public void severalCandidatesWithWrongSessionStore() {
this.contextRunner.withUserConfiguration(HazelcastConfiguration.class)
.withPropertyValues("spring.session.store-type=redis").run((context) -> {
assertThat(context).hasFailed();
assertThat(context).getFailure()
.hasCauseInstanceOf(SessionRepositoryUnavailableException.class);
assertThat(context).getFailure().hasMessageContaining(
"No session repository could be auto-configured");
assertThat(context).getFailure().hasMessageContaining(
"session store type is 'redis'");
});
}
@Test
public void severalCandidatesWithValidSessionStore() {
this.contextRunner.withUserConfiguration(HazelcastConfiguration.class)
.withPropertyValues("spring.session.store-type=jdbc")
.run((context) -> validateSessionRepository(context,
JdbcOperationsSessionRepository.class));
}
@Configuration
static class HazelcastConfiguration {
@Bean
@SuppressWarnings("unchecked")
public HazelcastInstance hazelcastInstance() {
IMap<Object, Object> map = mock(IMap.class);
HazelcastInstance mock = mock(HazelcastInstance.class);
given(mock.getMap("spring:session:sessions")).willReturn(map);
given(mock.getMap("foo:bar:biz")).willReturn(map);
return mock;
}
}
}

View File

@@ -26,10 +26,14 @@ import org.springframework.boot.autoconfigure.DatabaseInitializationMode;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
import org.springframework.boot.test.context.HideClassesClassLoader;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import org.springframework.session.hazelcast.HazelcastSessionRepository;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
import static org.assertj.core.api.Assertions.assertThat;
@@ -55,19 +59,35 @@ public class SessionAutoConfigurationJdbcTests
@Test
public void defaultConfig() {
this.contextRunner
.withPropertyValues("spring.session.store-type=jdbc")
.withConfiguration(
AutoConfigurations.of(JdbcTemplateAutoConfiguration.class))
.withPropertyValues("spring.session.store-type=jdbc").run((context) -> {
JdbcOperationsSessionRepository repository = validateSessionRepository(
context, JdbcOperationsSessionRepository.class);
assertThat(new DirectFieldAccessor(repository)
.getPropertyValue("tableName")).isEqualTo("SPRING_SESSION");
assertThat(context.getBean(JdbcSessionProperties.class)
.getInitializeSchema())
.isEqualTo(DatabaseInitializationMode.EMBEDDED);
assertThat(context.getBean(JdbcOperations.class)
.queryForList("select * from SPRING_SESSION")).isEmpty();
});
.run(this::validateDefaultConfig);
}
@Test
public void defaultConfigWithUniqueStoreImplementation() {
this.contextRunner
.withClassLoader(new HideClassesClassLoader(
HazelcastSessionRepository.class,
RedisOperationsSessionRepository.class)
)
.withConfiguration(
AutoConfigurations.of(JdbcTemplateAutoConfiguration.class))
.run(this::validateDefaultConfig);
}
private void validateDefaultConfig(AssertableWebApplicationContext context) {
JdbcOperationsSessionRepository repository = validateSessionRepository(
context, JdbcOperationsSessionRepository.class);
assertThat(new DirectFieldAccessor(repository)
.getPropertyValue("tableName")).isEqualTo("SPRING_SESSION");
assertThat(context.getBean(JdbcSessionProperties.class)
.getInitializeSchema())
.isEqualTo(DatabaseInitializationMode.EMBEDDED);
assertThat(context.getBean(JdbcOperations.class)
.queryForList("select * from SPRING_SESSION")).isEmpty();
}
@Test

View File

@@ -22,12 +22,15 @@ import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.test.context.HideClassesClassLoader;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.testsupport.rule.RedisTestServer;
import org.springframework.session.data.redis.RedisFlushMode;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import org.springframework.session.hazelcast.HazelcastSessionRepository;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
import static org.assertj.core.api.Assertions.assertThat;
@@ -46,10 +49,21 @@ public class SessionAutoConfigurationRedisTests
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class));
@Test
public void redisSessionStore() {
public void defaultConfig() {
this.contextRunner
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
.withPropertyValues("spring.session.store-type=redis")
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
.run(validateSpringSessionUsesRedis("spring:session:event:created:",
RedisFlushMode.ON_SAVE));
}
@Test
public void defaultConfigWithUniqueStoreImplementation() {
this.contextRunner
.withClassLoader(new HideClassesClassLoader(
HazelcastSessionRepository.class,
JdbcOperationsSessionRepository.class))
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
.run(validateSpringSessionUsesRedis("spring:session:event:created:",
RedisFlushMode.ON_SAVE));
}

View File

@@ -51,13 +51,13 @@ public class SessionAutoConfigurationTests extends AbstractSessionAutoConfigurat
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class));
@Test
public void contextFailsIfStoreTypeNotSet() {
public void contextFailsIfMultipleStoresAreAvailable() {
this.contextRunner.run((context) -> {
assertThat(context).hasFailed();
assertThat(context).getFailure()
.hasMessageContaining("No Spring Session store is configured");
assertThat(context).getFailure()
.hasMessageContaining("set the 'spring.session.store-type' property");
.hasCauseInstanceOf(NonUniqueSessionRepositoryException.class);
assertThat(context).getFailure().hasMessageContaining(
"Multiple session repository candidates are available");
});
}
@@ -67,11 +67,11 @@ public class SessionAutoConfigurationTests extends AbstractSessionAutoConfigurat
.run((context) -> {
assertThat(context).hasFailed();
assertThat(context).getFailure()
.isInstanceOf(BeanCreationException.class);
.hasCauseInstanceOf(SessionRepositoryUnavailableException.class);
assertThat(context).getFailure().hasMessageContaining(
"No session repository could be auto-configured");
assertThat(context).getFailure()
.hasMessageContaining("session store type is 'jdbc'");
assertThat(context).getFailure().hasMessageContaining(
"session store type is 'jdbc'");
});
}
@@ -86,16 +86,17 @@ public class SessionAutoConfigurationTests extends AbstractSessionAutoConfigurat
public void backOffIfSessionRepositoryIsPresent() {
this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class)
.withPropertyValues("spring.session.store-type=redis").run((context) -> {
MapSessionRepository repository = validateSessionRepository(context,
MapSessionRepository.class);
assertThat(context).getBean("mySessionRepository")
.isSameAs(repository);
});
MapSessionRepository repository = validateSessionRepository(context,
MapSessionRepository.class);
assertThat(context).getBean("mySessionRepository")
.isSameAs(repository);
});
}
@Test
public void springSessionTimeoutIsNotAValidProperty() {
this.contextRunner.withPropertyValues("spring.session.timeout=3000")
this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class)
.withPropertyValues("spring.session.timeout=3000")
.run((context) -> {
assertThat(context).hasFailed();
assertThat(context).getFailure()