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:
parent
de47827eb4
commit
4670cc7795
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* 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.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.session.SessionRepository;
|
||||||
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when multiple {@link SessionRepository} implementations are
|
||||||
|
* available with no way to know which implementation should be used.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public class NonUniqueSessionRepositoryException extends RuntimeException {
|
||||||
|
|
||||||
|
private final List<Class<? extends SessionRepository>> availableCandidates;
|
||||||
|
|
||||||
|
public NonUniqueSessionRepositoryException(
|
||||||
|
List<Class<? extends SessionRepository>> availableCandidates) {
|
||||||
|
super("Multiple session repository candidates are available, set the "
|
||||||
|
+ "'spring.session.store-type' property accordingly");
|
||||||
|
this.availableCandidates = (!ObjectUtils.isEmpty(availableCandidates)
|
||||||
|
? availableCandidates : Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Class<? extends SessionRepository>> getAvailableCandidates() {
|
||||||
|
return this.availableCandidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* 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.diagnostics.AbstractFailureAnalyzer;
|
||||||
|
import org.springframework.boot.diagnostics.FailureAnalysis;
|
||||||
|
import org.springframework.session.SessionRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link AbstractFailureAnalyzer} for {@link NonUniqueSessionRepositoryException}.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
class NonUniqueSessionRepositoryFailureAnalyzer
|
||||||
|
extends AbstractFailureAnalyzer<NonUniqueSessionRepositoryException> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected FailureAnalysis analyze(Throwable rootFailure,
|
||||||
|
NonUniqueSessionRepositoryException cause) {
|
||||||
|
StringBuilder message = new StringBuilder();
|
||||||
|
message.append(String.format("Multiple Spring Session store implementations are "
|
||||||
|
+ "available on the classpath:%n"));
|
||||||
|
for (Class<? extends SessionRepository> candidate : cause.getAvailableCandidates()) {
|
||||||
|
message.append(String.format(" - %s%n", candidate.getName()));
|
||||||
|
}
|
||||||
|
StringBuilder action = new StringBuilder();
|
||||||
|
action.append(String.format("Consider any of the following:%n"));
|
||||||
|
action.append(String.format(" - Define the `spring.session.store-type` "
|
||||||
|
+ "property to the store you want to use%n"));
|
||||||
|
action.append(String.format(" - Review your classpath and remove the unwanted "
|
||||||
|
+ "store implementation(s)%n"));
|
||||||
|
return new FailureAnalysis(message.toString(), action.toString(), cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,9 @@
|
||||||
|
|
||||||
package org.springframework.boot.autoconfigure.session;
|
package org.springframework.boot.autoconfigure.session;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
|
|
@ -32,6 +35,7 @@ import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration
|
||||||
import org.springframework.boot.autoconfigure.session.SessionAutoConfiguration.SessionRepositoryConfiguration;
|
import org.springframework.boot.autoconfigure.session.SessionAutoConfiguration.SessionRepositoryConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.session.SessionAutoConfiguration.SessionRepositoryValidator;
|
import org.springframework.boot.autoconfigure.session.SessionAutoConfiguration.SessionRepositoryValidator;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.context.annotation.ImportSelector;
|
import org.springframework.context.annotation.ImportSelector;
|
||||||
|
|
@ -61,7 +65,8 @@ public class SessionAutoConfiguration {
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConditionalOnMissingBean(SessionRepository.class)
|
@ConditionalOnMissingBean(SessionRepository.class)
|
||||||
@Import(SessionConfigurationImportSelector.class)
|
@Import({ SessionRepositoryImplementationValidator.class,
|
||||||
|
SessionConfigurationImportSelector.class })
|
||||||
static class SessionRepositoryConfiguration {
|
static class SessionRepositoryConfiguration {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -83,6 +88,51 @@ public class SessionAutoConfiguration {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bean used to validate that only one supported implementation is available in the
|
||||||
|
* classpath when the store-type property is not set.
|
||||||
|
*/
|
||||||
|
static class SessionRepositoryImplementationValidator {
|
||||||
|
|
||||||
|
private final ClassLoader classLoader;
|
||||||
|
|
||||||
|
private final SessionProperties sessionProperties;
|
||||||
|
|
||||||
|
SessionRepositoryImplementationValidator(ApplicationContext applicationContext,
|
||||||
|
SessionProperties sessionProperties) {
|
||||||
|
this.classLoader = applicationContext.getClassLoader();
|
||||||
|
this.sessionProperties = sessionProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void checkAvailableImplementations() {
|
||||||
|
List<Class<? extends SessionRepository>> candidates = new ArrayList<>();
|
||||||
|
addCandidate(candidates,
|
||||||
|
"org.springframework.session.hazelcast.HazelcastSessionRepository");
|
||||||
|
addCandidate(candidates,
|
||||||
|
"org.springframework.session.jdbc.JdbcOperationsSessionRepository");
|
||||||
|
addCandidate(candidates,
|
||||||
|
"org.springframework.session.data.redis.RedisOperationsSessionRepository");
|
||||||
|
StoreType storeType = this.sessionProperties.getStoreType();
|
||||||
|
if (candidates.size() > 1 && storeType == null) {
|
||||||
|
throw new NonUniqueSessionRepositoryException(candidates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addCandidate(
|
||||||
|
List<Class<? extends SessionRepository>> candidates, String fqn) {
|
||||||
|
try {
|
||||||
|
Class<? extends SessionRepository> candidate = (Class<? extends SessionRepository>) this.classLoader.loadClass(fqn);
|
||||||
|
if (candidate != null) {
|
||||||
|
candidates.add(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Throwable ex) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bean used to validate that a {@link SessionRepository} exists and provide a
|
* Bean used to validate that a {@link SessionRepository} exists and provide a
|
||||||
* meaningful message if that's not the case.
|
* meaningful message if that's not the case.
|
||||||
|
|
@ -105,12 +155,11 @@ public class SessionAutoConfiguration {
|
||||||
if (storeType != StoreType.NONE
|
if (storeType != StoreType.NONE
|
||||||
&& this.sessionRepositoryProvider.getIfAvailable() == null) {
|
&& this.sessionRepositoryProvider.getIfAvailable() == null) {
|
||||||
if (storeType != null) {
|
if (storeType != null) {
|
||||||
throw new IllegalArgumentException("No session repository could be "
|
throw new SessionRepositoryUnavailableException("No session "
|
||||||
+ "auto-configured, check your configuration (session store "
|
+ "repository could be auto-configured, check your "
|
||||||
+ "type is '" + storeType.name().toLowerCase() + "')");
|
+ "configuration (session store type is '"
|
||||||
|
+ storeType.name().toLowerCase() + "')", storeType);
|
||||||
}
|
}
|
||||||
throw new IllegalArgumentException("No Spring Session store is "
|
|
||||||
+ "configured: set the 'spring.session.store-type' property");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,8 @@ class SessionCondition extends SpringBootCondition {
|
||||||
StoreType required = SessionStoreMappings
|
StoreType required = SessionStoreMappings
|
||||||
.getType(((AnnotationMetadata) metadata).getClassName());
|
.getType(((AnnotationMetadata) metadata).getClassName());
|
||||||
if (!environment.containsProperty("spring.session.store-type")) {
|
if (!environment.containsProperty("spring.session.store-type")) {
|
||||||
return ConditionOutcome.noMatch(
|
return ConditionOutcome.match(message.didNotFind("property", "properties")
|
||||||
message.didNotFind("spring.session.store-type property").atAll());
|
.items(ConditionMessage.Style.QUOTE, "spring.session.store-type"));
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Binder binder = Binder.get(environment);
|
Binder binder = Binder.get(environment);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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.session.SessionRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when no {@link SessionRepository} is available.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public class SessionRepositoryUnavailableException extends RuntimeException {
|
||||||
|
|
||||||
|
private final StoreType storeType;
|
||||||
|
|
||||||
|
public SessionRepositoryUnavailableException(String message, StoreType storeType) {
|
||||||
|
super(message);
|
||||||
|
this.storeType = storeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StoreType getStoreType() {
|
||||||
|
return this.storeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -130,7 +130,8 @@ org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration
|
||||||
org.springframework.boot.diagnostics.FailureAnalyzer=\
|
org.springframework.boot.diagnostics.FailureAnalyzer=\
|
||||||
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
|
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
|
||||||
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
|
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
|
||||||
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer
|
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
|
||||||
|
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer
|
||||||
|
|
||||||
# Template availability providers
|
# Template availability providers
|
||||||
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
|
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -22,11 +22,15 @@ import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.beans.DirectFieldAccessor;
|
import org.springframework.beans.DirectFieldAccessor;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
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.boot.test.context.runner.WebApplicationContextRunner;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
|
||||||
import org.springframework.session.hazelcast.HazelcastFlushMode;
|
import org.springframework.session.hazelcast.HazelcastFlushMode;
|
||||||
import org.springframework.session.hazelcast.HazelcastSessionRepository;
|
import org.springframework.session.hazelcast.HazelcastSessionRepository;
|
||||||
|
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
|
|
@ -48,13 +52,24 @@ public class SessionAutoConfigurationHazelcastTests
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void defaultConfig() {
|
public void defaultConfig() {
|
||||||
this.contextRunner.withPropertyValues("spring.session.store-type=hazelcast")
|
this.contextRunner
|
||||||
.run((context) -> {
|
.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);
|
validateSessionRepository(context, HazelcastSessionRepository.class);
|
||||||
HazelcastInstance hazelcastInstance = context
|
HazelcastInstance hazelcastInstance = context
|
||||||
.getBean(HazelcastInstance.class);
|
.getBean(HazelcastInstance.class);
|
||||||
verify(hazelcastInstance, times(1)).getMap("spring:session:sessions");
|
verify(hazelcastInstance, times(1)).getMap("spring:session:sessions");
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -26,10 +26,14 @@ import org.springframework.boot.autoconfigure.DatabaseInitializationMode;
|
||||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
|
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
|
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.test.context.runner.WebApplicationContextRunner;
|
||||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
import org.springframework.jdbc.BadSqlGrammarException;
|
import org.springframework.jdbc.BadSqlGrammarException;
|
||||||
import org.springframework.jdbc.core.JdbcOperations;
|
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 org.springframework.session.jdbc.JdbcOperationsSessionRepository;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
@ -55,9 +59,25 @@ public class SessionAutoConfigurationJdbcTests
|
||||||
@Test
|
@Test
|
||||||
public void defaultConfig() {
|
public void defaultConfig() {
|
||||||
this.contextRunner
|
this.contextRunner
|
||||||
|
.withPropertyValues("spring.session.store-type=jdbc")
|
||||||
.withConfiguration(
|
.withConfiguration(
|
||||||
AutoConfigurations.of(JdbcTemplateAutoConfiguration.class))
|
AutoConfigurations.of(JdbcTemplateAutoConfiguration.class))
|
||||||
.withPropertyValues("spring.session.store-type=jdbc").run((context) -> {
|
.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(
|
JdbcOperationsSessionRepository repository = validateSessionRepository(
|
||||||
context, JdbcOperationsSessionRepository.class);
|
context, JdbcOperationsSessionRepository.class);
|
||||||
assertThat(new DirectFieldAccessor(repository)
|
assertThat(new DirectFieldAccessor(repository)
|
||||||
|
|
@ -67,7 +87,7 @@ public class SessionAutoConfigurationJdbcTests
|
||||||
.isEqualTo(DatabaseInitializationMode.EMBEDDED);
|
.isEqualTo(DatabaseInitializationMode.EMBEDDED);
|
||||||
assertThat(context.getBean(JdbcOperations.class)
|
assertThat(context.getBean(JdbcOperations.class)
|
||||||
.queryForList("select * from SPRING_SESSION")).isEmpty();
|
.queryForList("select * from SPRING_SESSION")).isEmpty();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,15 @@ import org.junit.Test;
|
||||||
import org.springframework.beans.DirectFieldAccessor;
|
import org.springframework.beans.DirectFieldAccessor;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
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.assertj.AssertableWebApplicationContext;
|
||||||
import org.springframework.boot.test.context.runner.ContextConsumer;
|
import org.springframework.boot.test.context.runner.ContextConsumer;
|
||||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||||
import org.springframework.boot.testsupport.rule.RedisTestServer;
|
import org.springframework.boot.testsupport.rule.RedisTestServer;
|
||||||
import org.springframework.session.data.redis.RedisFlushMode;
|
import org.springframework.session.data.redis.RedisFlushMode;
|
||||||
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
|
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;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
|
@ -46,10 +49,21 @@ public class SessionAutoConfigurationRedisTests
|
||||||
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class));
|
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class));
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void redisSessionStore() {
|
public void defaultConfig() {
|
||||||
this.contextRunner
|
this.contextRunner
|
||||||
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
|
|
||||||
.withPropertyValues("spring.session.store-type=redis")
|
.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:",
|
.run(validateSpringSessionUsesRedis("spring:session:event:created:",
|
||||||
RedisFlushMode.ON_SAVE));
|
RedisFlushMode.ON_SAVE));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,13 +51,13 @@ public class SessionAutoConfigurationTests extends AbstractSessionAutoConfigurat
|
||||||
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class));
|
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class));
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void contextFailsIfStoreTypeNotSet() {
|
public void contextFailsIfMultipleStoresAreAvailable() {
|
||||||
this.contextRunner.run((context) -> {
|
this.contextRunner.run((context) -> {
|
||||||
assertThat(context).hasFailed();
|
assertThat(context).hasFailed();
|
||||||
assertThat(context).getFailure()
|
assertThat(context).getFailure()
|
||||||
.hasMessageContaining("No Spring Session store is configured");
|
.hasCauseInstanceOf(NonUniqueSessionRepositoryException.class);
|
||||||
assertThat(context).getFailure()
|
assertThat(context).getFailure().hasMessageContaining(
|
||||||
.hasMessageContaining("set the 'spring.session.store-type' property");
|
"Multiple session repository candidates are available");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,11 +67,11 @@ public class SessionAutoConfigurationTests extends AbstractSessionAutoConfigurat
|
||||||
.run((context) -> {
|
.run((context) -> {
|
||||||
assertThat(context).hasFailed();
|
assertThat(context).hasFailed();
|
||||||
assertThat(context).getFailure()
|
assertThat(context).getFailure()
|
||||||
.isInstanceOf(BeanCreationException.class);
|
.hasCauseInstanceOf(SessionRepositoryUnavailableException.class);
|
||||||
assertThat(context).getFailure().hasMessageContaining(
|
assertThat(context).getFailure().hasMessageContaining(
|
||||||
"No session repository could be auto-configured");
|
"No session repository could be auto-configured");
|
||||||
assertThat(context).getFailure()
|
assertThat(context).getFailure().hasMessageContaining(
|
||||||
.hasMessageContaining("session store type is 'jdbc'");
|
"session store type is 'jdbc'");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,7 +95,8 @@ public class SessionAutoConfigurationTests extends AbstractSessionAutoConfigurat
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void springSessionTimeoutIsNotAValidProperty() {
|
public void springSessionTimeoutIsNotAValidProperty() {
|
||||||
this.contextRunner.withPropertyValues("spring.session.timeout=3000")
|
this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class)
|
||||||
|
.withPropertyValues("spring.session.timeout=3000")
|
||||||
.run((context) -> {
|
.run((context) -> {
|
||||||
assertThat(context).hasFailed();
|
assertThat(context).hasFailed();
|
||||||
assertThat(context).getFailure()
|
assertThat(context).getFailure()
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1 @@
|
||||||
spring.session.store-type=redis
|
|
||||||
server.session.timeout=5
|
server.session.timeout=5
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* 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.test.context;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test {@link URLClassLoader} that hides configurable classes.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public class HideClassesClassLoader extends URLClassLoader {
|
||||||
|
|
||||||
|
private final Class<?>[] hiddenClasses;
|
||||||
|
|
||||||
|
public HideClassesClassLoader(Class<?>... hiddenClasses) {
|
||||||
|
super(new URL[0], HideClassesClassLoader.class.getClassLoader());
|
||||||
|
this.hiddenClasses = hiddenClasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?> loadClass(String name, boolean resolve)
|
||||||
|
throws ClassNotFoundException {
|
||||||
|
for (Class<?> hiddenClass : this.hiddenClasses) {
|
||||||
|
if (name.equals(hiddenClass.getName())) {
|
||||||
|
throw new ClassNotFoundException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.loadClass(name, resolve);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue