Compare commits

...

17 Commits

Author SHA1 Message Date
Rob Winch
3e6b3fda0f Release 2.2.0.M4 2019-09-06 11:36:11 -05:00
Rob Winch
840da7fb5a Update to Spring Security 5.2.0.RC1
Fixes gh-1487
2019-09-06 09:31:24 -05:00
Vedran Pavic
560ee5ff4f Upgrade Spring Data to Moore-RC3
Resolves: #1486
2019-09-06 13:17:30 +02:00
Vedran Pavic
072348e28f Upgrade Gradle to 5.6.2 2019-09-05 22:19:39 +02:00
Vedran Pavic
99dfdda7b7 Upgrade Spring Framework to 5.2.0.RC2
Resolves: #1485
2019-09-05 13:12:34 +02:00
Vedran Pavic
18b097d9c7 Upgrade Reactor to Dysprosium-RC1
Resolves: #1498
2019-09-04 07:12:21 +02:00
Vedran Pavic
702a35fac6 Update integration tests 2019-09-03 22:54:57 +02:00
Vedran Pavic
df3e4c5bc1 Add support for customizing session repository before initialization
This commit adds support for customizing session repository implementations (both SessionRepository and ReactiveSessionRepository) before initialization by introducing SessionRepositoryCustomizer and ReactiveSessionRepositoryCustomizer strategies.

Resolves: #1499
2019-09-03 22:17:36 +02:00
Lars Grefer
f746233255 Upgrade Gradle to 5.6.1
Resolves: #1496
2019-08-30 22:44:42 +02:00
Vedran Pavic
f6c82f1eee Improve support for customizing JDBC session store transaction behavior
Resolves: #1469
2019-08-23 23:26:11 +02:00
Josh Cummings
bcdd05a0bc Add OnCommittedResponseWrapper.setContentLengthLong
Add setContentLengthLong tracking to OnCommittedResponseWrapper in
order to detect commits on servlets that use setContentLengthLong to
announce the entity size they are about to write (as used in the
Apache Tomcat's DefaultServlet).

Fixes gh-1489
2019-08-20 13:29:52 -06:00
Vedran Pavic
5d26ab4df4 Add support for AuthenticatedPrincipal in SpringSessionBackedSessionRegistry
Resolves: #1488
2019-08-10 11:23:25 +02:00
Vedran Pavic
e55d86f5e2 Start building against Spring Security 5.2.0.RC1 snapshots
See: #1487
2019-08-07 21:21:34 +02:00
Vedran Pavic
fe480b338c Start building against Spring Data Moore-RC3 snapshots
See: #1486
2019-08-07 21:13:03 +02:00
Vedran Pavic
4b13392430 Start building against Spring Framework 5.2.0.RC2 snapshots
See: #1485
2019-08-07 21:11:34 +02:00
Vedran Pavic
e5d9ce6ead Upgrade samples to Spring Boot 2.2.0.M5
Resolves: #1484
2019-08-06 18:07:18 +02:00
Vedran Pavic
bc1ef4359a Next development version 2019-08-05 22:18:02 +02:00
24 changed files with 519 additions and 277 deletions

View File

@@ -4,7 +4,7 @@ buildscript {
snapshotBuild = version.endsWith('SNAPSHOT')
milestoneBuild = !(releaseBuild || snapshotBuild)
springBootVersion = '2.2.0.M4'
springBootVersion = '2.2.0.M5'
}
repositories {

View File

@@ -1 +1 @@
version=2.2.0.M3
version=2.2.0.M4

View File

@@ -1,11 +1,11 @@
dependencyManagement {
imports {
mavenBom 'com.fasterxml.jackson:jackson-bom:2.9.6'
mavenBom 'io.projectreactor:reactor-bom:Dysprosium-M3'
mavenBom 'io.projectreactor:reactor-bom:Dysprosium-RC1'
mavenBom 'org.junit:junit-bom:5.5.1'
mavenBom 'org.springframework:spring-framework-bom:5.2.0.RC1'
mavenBom 'org.springframework.data:spring-data-releasetrain:Moore-RC2'
mavenBom 'org.springframework.security:spring-security-bom:5.2.0.M4'
mavenBom 'org.springframework:spring-framework-bom:5.2.0.RC2'
mavenBom 'org.springframework.data:spring-data-releasetrain:Moore-RC3'
mavenBom 'org.springframework.security:spring-security-bom:5.2.0.RC1'
mavenBom 'org.testcontainers:testcontainers-bom:1.12.0'
}

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

4
gradlew vendored
View File

@@ -125,8 +125,8 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2014-2019 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
*
* https://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.session.config;
import org.springframework.session.ReactiveSessionRepository;
/**
* Strategy that can be used to customize the {@link ReactiveSessionRepository} before it
* is fully initialized, in particular to tune its configuration.
*
* @param <T> the {@link ReactiveSessionRepository} type
* @author Vedran Pavic
* @since 2.2.0
*/
@FunctionalInterface
public interface ReactiveSessionRepositoryCustomizer<T extends ReactiveSessionRepository> {
/**
* Customize the {@link ReactiveSessionRepository}.
* @param sessionRepository the {@link ReactiveSessionRepository} to customize
*/
void customize(T sessionRepository);
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2014-2019 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
*
* https://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.session.config;
import org.springframework.session.SessionRepository;
/**
* Strategy that can be used to customize the {@link SessionRepository} before it is fully
* initialized, in particular to tune its configuration.
*
* @param <T> the {@link SessionRepository} type
* @author Vedran Pavic
* @since 2.2.0
*/
@FunctionalInterface
public interface SessionRepositoryCustomizer<T extends SessionRepository> {
/**
* Customize the {@link SessionRepository}.
* @param sessionRepository the {@link SessionRepository} to customize
*/
void customize(T sessionRepository);
}

View File

@@ -16,14 +16,13 @@
package org.springframework.session.security;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.util.Assert;
@@ -110,13 +109,8 @@ public class SpringSessionBackedSessionRegistry<S extends Session> implements Se
* could be derived
*/
protected String name(Object principal) {
if (principal instanceof UserDetails) {
return ((UserDetails) principal).getUsername();
}
if (principal instanceof Principal) {
return ((Principal) principal).getName();
}
return principal.toString();
// We are reusing the logic from AbstractAuthenticationToken#getName
return new TestingAuthenticationToken(principal, null).getName();
}
}

View File

@@ -69,6 +69,12 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
super.addHeader(name, value);
}
@Override
public void setContentLengthLong(long len) {
setContentLength(len);
super.setContentLengthLong(len);
}
@Override
public void setContentLength(int len) {
setContentLength((long) len);

View File

@@ -16,6 +16,7 @@
package org.springframework.session.security;
import java.security.Principal;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
@@ -30,6 +31,7 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.security.core.AuthenticatedPrincipal;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.session.SessionInformation;
@@ -104,11 +106,25 @@ class SpringSessionBackedSessionRegistryTest {
}
@Test
void getAllSessions() {
void getAllSessionsForUserDetails() {
setUpSessions();
List<SessionInformation> allSessionInfos = this.sessionRegistry.getAllSessions(PRINCIPAL, true);
assertThat(allSessionInfos).extracting("sessionId").containsExactly(SESSION_ID, SESSION_ID2);
}
@Test
void getAllSessionsForAuthenticatedPrincipal() {
setUpSessions();
List<SessionInformation> allSessionInfos = this.sessionRegistry
.getAllSessions((AuthenticatedPrincipal) () -> USER_NAME, true);
assertThat(allSessionInfos).extracting("sessionId").containsExactly(SESSION_ID, SESSION_ID2);
}
@Test
void getAllSessionsForPrincipal() {
setUpSessions();
List<SessionInformation> allSessionInfos = this.sessionRegistry.getAllSessions(new TestPrincipal(USER_NAME),
true);
assertThat(allSessionInfos).extracting("sessionId").containsExactly(SESSION_ID, SESSION_ID2);
}
@@ -159,4 +175,40 @@ class SpringSessionBackedSessionRegistryTest {
when(this.sessionRepository.findByPrincipalName(USER_NAME)).thenReturn(sessions);
}
private static final class TestPrincipal implements Principal {
private final String name;
private TestPrincipal(String name) {
this.name = name;
}
@Override
public String getName() {
return this.name;
}
@Override
public boolean equals(Object another) {
if (this == another) {
return true;
}
if (another instanceof TestPrincipal) {
return this.name.equals(((TestPrincipal) another).name);
}
return false;
}
@Override
public int hashCode() {
return this.name.hashCode();
}
@Override
public String toString() {
return this.name;
}
}
}

View File

@@ -1100,6 +1100,17 @@ class OnCommittedResponseWrapperTests {
assertThat(this.committed).isTrue();
}
// gh-7261
@Test
void contentLengthLongOutputStreamWriteStringCommits() throws IOException {
String body = "something";
this.response.setContentLengthLong(body.length());
this.response.getOutputStream().print(body);
assertThat(this.committed).isTrue();
}
@Test
void bufferSizeCommitsOnce() throws Exception {
String expected = "1234567890";

View File

@@ -18,8 +18,10 @@ package org.springframework.session.data.redis.config.annotation.web.http;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
import org.apache.commons.logging.LogFactory;
@@ -51,6 +53,7 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.session.FlushMode;
import org.springframework.session.MapSession;
import org.springframework.session.SaveMode;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
import org.springframework.session.data.redis.RedisFlushMode;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
@@ -103,6 +106,8 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
private Executor redisSubscriptionExecutor;
private List<SessionRepositoryCustomizer<RedisOperationsSessionRepository>> sessionRepositoryCustomizers;
private ClassLoader classLoader;
private StringValueResolver embeddedValueResolver;
@@ -123,6 +128,8 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
sessionRepository.setSaveMode(this.saveMode);
int database = resolveDatabase();
sessionRepository.setDatabase(database);
this.sessionRepositoryCustomizers
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
return sessionRepository;
}
@@ -221,6 +228,12 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
this.redisSubscriptionExecutor = redisSubscriptionExecutor;
}
@Autowired(required = false)
public void setSessionRepositoryCustomizer(
ObjectProvider<SessionRepositoryCustomizer<RedisOperationsSessionRepository>> sessionRepositoryCustomizers) {
this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;

View File

@@ -16,7 +16,9 @@
package org.springframework.session.data.redis.config.annotation.web.server;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.ObjectProvider;
@@ -36,6 +38,7 @@ import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.session.MapSession;
import org.springframework.session.SaveMode;
import org.springframework.session.config.ReactiveSessionRepositoryCustomizer;
import org.springframework.session.config.annotation.web.server.SpringWebSessionConfiguration;
import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository;
import org.springframework.session.data.redis.RedisFlushMode;
@@ -68,6 +71,8 @@ public class RedisWebSessionConfiguration extends SpringWebSessionConfiguration
private RedisSerializer<Object> defaultRedisSerializer;
private List<ReactiveSessionRepositoryCustomizer<ReactiveRedisOperationsSessionRepository>> sessionRepositoryCustomizers;
private ClassLoader classLoader;
private StringValueResolver embeddedValueResolver;
@@ -82,6 +87,8 @@ public class RedisWebSessionConfiguration extends SpringWebSessionConfiguration
sessionRepository.setRedisKeyNamespace(this.redisNamespace);
}
sessionRepository.setSaveMode(this.saveMode);
this.sessionRepositoryCustomizers
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
return sessionRepository;
}
@@ -120,6 +127,12 @@ public class RedisWebSessionConfiguration extends SpringWebSessionConfiguration
this.defaultRedisSerializer = defaultRedisSerializer;
}
@Autowired(required = false)
public void setSessionRepositoryCustomizer(
ObjectProvider<ReactiveSessionRepositoryCustomizer<ReactiveRedisOperationsSessionRepository>> sessionRepositoryCustomizers) {
this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;

View File

@@ -29,6 +29,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
@@ -36,6 +37,7 @@ import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.session.FlushMode;
import org.springframework.session.SaveMode;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.data.redis.RedisFlushMode;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
@@ -57,6 +59,8 @@ import static org.mockito.Mockito.mock;
@SuppressWarnings("deprecation")
class RedisHttpSessionConfigurationTests {
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600;
private static final String CLEANUP_CRON_EXPRESSION = "0 0 * * * *";
private AnnotationConfigApplicationContext context;
@@ -238,6 +242,15 @@ class RedisHttpSessionConfigurationTests {
assertThat(beans).containsKeys("springSessionRedisMessageListenerContainer", "redisMessageListenerContainer");
}
@Test
void sessionRepositoryCustomizer() {
registerAndRefresh(RedisConfig.class, SessionRepositoryCustomizerConfiguration.class);
RedisOperationsSessionRepository sessionRepository = this.context
.getBean(RedisOperationsSessionRepository.class);
assertThat(sessionRepository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
MAX_INACTIVE_INTERVAL_IN_SECONDS);
}
private void registerAndRefresh(Class<?>... annotatedClasses) {
this.context.register(annotatedClasses);
this.context.refresh();
@@ -416,4 +429,22 @@ class RedisHttpSessionConfigurationTests {
}
@EnableRedisHttpSession
static class SessionRepositoryCustomizerConfiguration {
@Bean
@Order(0)
public SessionRepositoryCustomizer<RedisOperationsSessionRepository> sessionRepositoryCustomizerOne() {
return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(0);
}
@Bean
@Order(1)
public SessionRepositoryCustomizer<RedisOperationsSessionRepository> sessionRepositoryCustomizerTwo() {
return (sessionRepository) -> sessionRepository
.setDefaultMaxInactiveInterval(MAX_INACTIVE_INTERVAL_IN_SECONDS);
}
}
}

View File

@@ -25,11 +25,13 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisOperations;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.SaveMode;
import org.springframework.session.config.ReactiveSessionRepositoryCustomizer;
import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository;
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisOperations;
@@ -222,6 +224,15 @@ class RedisWebSessionConfigurationTests {
"serializer")).isEqualTo(redisSerializer);
}
@Test
void sessionRepositoryCustomizer() {
registerAndRefresh(RedisConfig.class, SessionRepositoryCustomizerConfiguration.class);
ReactiveRedisOperationsSessionRepository sessionRepository = this.context
.getBean(ReactiveRedisOperationsSessionRepository.class);
assertThat(sessionRepository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
MAX_INACTIVE_INTERVAL_IN_SECONDS);
}
private void registerAndRefresh(Class<?>... annotatedClasses) {
this.context.register(annotatedClasses);
this.context.refresh();
@@ -348,4 +359,22 @@ class RedisWebSessionConfigurationTests {
}
@EnableRedisWebSession
static class SessionRepositoryCustomizerConfiguration {
@Bean
@Order(0)
public ReactiveSessionRepositoryCustomizer<ReactiveRedisOperationsSessionRepository> sessionRepositoryCustomizerOne() {
return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(0);
}
@Bean
@Order(1)
public ReactiveSessionRepositoryCustomizer<ReactiveRedisOperationsSessionRepository> sessionRepositoryCustomizerTwo() {
return (sessionRepository) -> sessionRepository
.setDefaultMaxInactiveInterval(MAX_INACTIVE_INTERVAL_IN_SECONDS);
}
}
}

View File

@@ -30,7 +30,6 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.mock.web.MockServletContext;
import org.springframework.session.MapSession;
import org.springframework.session.MapSessionRepository;
@@ -42,7 +41,7 @@ import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import org.springframework.session.hazelcast.HazelcastSessionRepository;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
@@ -162,14 +161,14 @@ class IndexDocTests {
// tag::new-jdbcoperationssessionrepository[]
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// ... configure JdbcTemplate ...
// ... configure jdbcTemplate ...
PlatformTransactionManager transactionManager = new DataSourceTransactionManager();
TransactionTemplate transactionTemplate = new TransactionTemplate();
// ... configure transactionManager ...
// ... configure transactionTemplate ...
SessionRepository<? extends Session> repository = new JdbcOperationsSessionRepository(jdbcTemplate,
transactionManager);
transactionTemplate);
// end::new-jdbcoperationssessionrepository[]
}

View File

@@ -16,7 +16,9 @@
package org.springframework.session.hazelcast.config.annotation.web.http;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import com.hazelcast.core.HazelcastInstance;
@@ -31,6 +33,7 @@ import org.springframework.core.type.AnnotationMetadata;
import org.springframework.session.FlushMode;
import org.springframework.session.MapSession;
import org.springframework.session.SaveMode;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
import org.springframework.session.hazelcast.HazelcastFlushMode;
import org.springframework.session.hazelcast.HazelcastSessionRepository;
@@ -63,6 +66,8 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur
private ApplicationEventPublisher applicationEventPublisher;
private List<SessionRepositoryCustomizer<HazelcastSessionRepository>> sessionRepositoryCustomizers;
@Bean
public HazelcastSessionRepository sessionRepository() {
HazelcastSessionRepository sessionRepository = new HazelcastSessionRepository(this.hazelcastInstance);
@@ -73,6 +78,8 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur
sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
sessionRepository.setFlushMode(this.flushMode);
sessionRepository.setSaveMode(this.saveMode);
this.sessionRepositoryCustomizers
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
return sessionRepository;
}
@@ -113,6 +120,12 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur
this.applicationEventPublisher = applicationEventPublisher;
}
@Autowired(required = false)
public void setSessionRepositoryCustomizer(
ObjectProvider<SessionRepositoryCustomizer<HazelcastSessionRepository>> sessionRepositoryCustomizers) {
this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
}
@Override
@SuppressWarnings("deprecation")
public void setImportMetadata(AnnotationMetadata importMetadata) {

View File

@@ -26,8 +26,10 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.session.FlushMode;
import org.springframework.session.SaveMode;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.hazelcast.HazelcastFlushMode;
import org.springframework.session.hazelcast.HazelcastSessionRepository;
import org.springframework.session.hazelcast.config.annotation.SpringSessionHazelcastInstance;
@@ -222,6 +224,14 @@ class HazelcastHttpSessionConfigurationTests {
.withMessageContaining("expected single matching bean but found 2");
}
@Test
void sessionRepositoryCustomizer() {
registerAndRefresh(SessionRepositoryCustomizerConfiguration.class);
HazelcastSessionRepository sessionRepository = this.context.getBean(HazelcastSessionRepository.class);
assertThat(sessionRepository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
MAX_INACTIVE_INTERVAL_IN_SECONDS);
}
private void registerAndRefresh(Class<?>... annotatedClasses) {
this.context.register(annotatedClasses);
this.context.refresh();
@@ -421,4 +431,22 @@ class HazelcastHttpSessionConfigurationTests {
}
@EnableHazelcastHttpSession
static class SessionRepositoryCustomizerConfiguration extends BaseConfiguration {
@Bean
@Order(0)
public SessionRepositoryCustomizer<HazelcastSessionRepository> sessionRepositoryCustomizerOne() {
return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(0);
}
@Bean
@Order(1)
public SessionRepositoryCustomizer<HazelcastSessionRepository> sessionRepositoryCustomizerTwo() {
return (sessionRepository) -> sessionRepository
.setDefaultMaxInactiveInterval(MAX_INACTIVE_INTERVAL_IN_SECONDS);
}
}
}

View File

@@ -152,7 +152,7 @@ final class DatabaseContainers {
private static class PostgreSql9Container extends PostgreSQLContainer<PostgreSql9Container> {
PostgreSql9Container() {
super("postgres:9.6.14");
super("postgres:9.6.15");
}
}
@@ -160,7 +160,7 @@ final class DatabaseContainers {
private static class PostgreSql10Container extends PostgreSQLContainer<PostgreSql10Container> {
PostgreSql10Container() {
super("postgres:10.9");
super("postgres:10.10");
}
}
@@ -168,7 +168,7 @@ final class DatabaseContainers {
private static class PostgreSql11Container extends PostgreSQLContainer<PostgreSql11Container> {
PostgreSql11Container() {
super("postgres:11.4");
super("postgres:11.5");
}
}

View File

@@ -42,7 +42,6 @@ import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobHandler;
@@ -56,8 +55,6 @@ import org.springframework.session.SaveMode;
import org.springframework.session.Session;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionOperations;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;
@@ -75,16 +72,16 @@ import org.springframework.util.StringUtils;
*
* // ... configure jdbcTemplate ...
*
* PlatformTransactionManager transactionManager = new DataSourceTransactionManager();
* TransactionTemplate transactionTemplate = new TransactionTemplate();
*
* // ... configure transactionManager ...
* // ... configure transactionTemplate ...
*
* JdbcOperationsSessionRepository sessionRepository =
* new JdbcOperationsSessionRepository(jdbcTemplate, transactionManager);
* new JdbcOperationsSessionRepository(jdbcTemplate, transactionTemplate);
* </pre>
*
* For additional information on how to create and configure {@link JdbcTemplate} and
* {@link PlatformTransactionManager}, refer to the <a href=
* For additional information on how to create and configure {@code JdbcTemplate} and
* {@code TransactionTemplate}, refer to the <a href=
* "https://docs.spring.io/spring/docs/current/spring-framework-reference/html/spring-data-tier.html">
* Spring Framework Reference Documentation</a>.
* <p>
@@ -200,12 +197,12 @@ public class JdbcOperationsSessionRepository
private final JdbcOperations jdbcOperations;
private final TransactionOperations transactionOperations;
private final ResultSetExtractor<List<JdbcSession>> extractor = new SessionResultSetExtractor();
private final IndexResolver<JdbcSession> indexResolver;
private TransactionOperations transactionOperations = TransactionOperations.withoutTransaction();
/**
* The name of database table used by Spring Session to store sessions.
*/
@@ -237,12 +234,30 @@ public class JdbcOperationsSessionRepository
private ConversionService conversionService;
private LobHandler lobHandler = new DefaultLobHandler();
private LobHandler lobHandler;
private FlushMode flushMode = FlushMode.ON_SAVE;
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
/**
* Create a new {@link JdbcOperationsSessionRepository} instance which uses the
* provided {@link JdbcOperations} and {@link TransactionOperations} to manage
* sessions.
* @param jdbcOperations the {@link JdbcOperations} to use
* @param transactionOperations the {@link TransactionOperations} to use
*/
public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations, TransactionOperations transactionOperations) {
Assert.notNull(jdbcOperations, "jdbcOperations must not be null");
Assert.notNull(transactionOperations, "transactionOperations must not be null");
this.jdbcOperations = jdbcOperations;
this.transactionOperations = transactionOperations;
this.indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>());
this.conversionService = createDefaultConversionService();
this.lobHandler = new DefaultLobHandler();
prepareQueries();
}
/**
* Create a new {@link JdbcOperationsSessionRepository} instance which uses the
* provided {@link JdbcOperations} to manage sessions.
@@ -251,12 +266,13 @@ public class JdbcOperationsSessionRepository
* propagation level of {@link TransactionDefinition#PROPAGATION_REQUIRES_NEW}.
* @param jdbcOperations the {@link JdbcOperations} to use
* @param transactionManager the {@link PlatformTransactionManager} to use
* @deprecated since 2.2.0 in favor of
* {@link #JdbcOperationsSessionRepository(JdbcOperations, TransactionOperations)}
*/
@Deprecated
public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations,
PlatformTransactionManager transactionManager) {
this(jdbcOperations);
Assert.notNull(transactionManager, "TransactionManager must not be null");
this.transactionOperations = createTransactionTemplate(transactionManager);
this(jdbcOperations, createTransactionTemplate(transactionManager));
}
/**
@@ -265,13 +281,12 @@ public class JdbcOperationsSessionRepository
* <p>
* The created instance will not execute data access operations in a transaction.
* @param jdbcOperations the {@link JdbcOperations} to use
* @deprecated since 2.2.0 in favor of
* {@link #JdbcOperationsSessionRepository(JdbcOperations, TransactionOperations)}
*/
@Deprecated
public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations) {
Assert.notNull(jdbcOperations, "JdbcOperations must not be null");
this.jdbcOperations = jdbcOperations;
this.indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>());
this.conversionService = createDefaultConversionService();
prepareQueries();
this(jdbcOperations, TransactionOperations.withoutTransaction());
}
/**
@@ -448,15 +463,8 @@ public class JdbcOperationsSessionRepository
@Override
public void deleteById(final String id) {
this.transactionOperations.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
JdbcOperationsSessionRepository.this.jdbcOperations
.update(JdbcOperationsSessionRepository.this.deleteSessionQuery, id);
}
});
this.transactionOperations.execute(() -> JdbcOperationsSessionRepository.this.jdbcOperations
.update(JdbcOperationsSessionRepository.this.deleteSessionQuery, id));
}
@Override
@@ -581,6 +589,7 @@ public class JdbcOperationsSessionRepository
}
private static TransactionTemplate createTransactionTemplate(PlatformTransactionManager transactionManager) {
Assert.notNull(transactionManager, "transactionManager must not be null");
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
transactionTemplate.afterPropertiesSet();
@@ -805,71 +814,59 @@ public class JdbcOperationsSessionRepository
private void save() {
if (this.isNew) {
JdbcOperationsSessionRepository.this.transactionOperations
.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
Map<String, String> indexes = JdbcOperationsSessionRepository.this.indexResolver
.resolveIndexesFor(JdbcSession.this);
JdbcOperationsSessionRepository.this.jdbcOperations
.update(JdbcOperationsSessionRepository.this.createSessionQuery, (ps) -> {
ps.setString(1, JdbcSession.this.primaryKey);
ps.setString(2, getId());
ps.setLong(3, getCreationTime().toEpochMilli());
ps.setLong(4, getLastAccessedTime().toEpochMilli());
ps.setInt(5, (int) getMaxInactiveInterval().getSeconds());
ps.setLong(6, getExpiryTime().toEpochMilli());
ps.setString(7, indexes.get(PRINCIPAL_NAME_INDEX_NAME));
});
Set<String> attributeNames = getAttributeNames();
if (!attributeNames.isEmpty()) {
insertSessionAttributes(JdbcSession.this, new ArrayList<>(attributeNames));
}
}
});
JdbcOperationsSessionRepository.this.transactionOperations.execute(() -> {
Map<String, String> indexes = JdbcOperationsSessionRepository.this.indexResolver
.resolveIndexesFor(JdbcSession.this);
JdbcOperationsSessionRepository.this.jdbcOperations
.update(JdbcOperationsSessionRepository.this.createSessionQuery, (ps) -> {
ps.setString(1, JdbcSession.this.primaryKey);
ps.setString(2, getId());
ps.setLong(3, getCreationTime().toEpochMilli());
ps.setLong(4, getLastAccessedTime().toEpochMilli());
ps.setInt(5, (int) getMaxInactiveInterval().getSeconds());
ps.setLong(6, getExpiryTime().toEpochMilli());
ps.setString(7, indexes.get(PRINCIPAL_NAME_INDEX_NAME));
});
Set<String> attributeNames = getAttributeNames();
if (!attributeNames.isEmpty()) {
insertSessionAttributes(JdbcSession.this, new ArrayList<>(attributeNames));
}
});
}
else {
JdbcOperationsSessionRepository.this.transactionOperations
.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
if (JdbcSession.this.changed) {
Map<String, String> indexes = JdbcOperationsSessionRepository.this.indexResolver
.resolveIndexesFor(JdbcSession.this);
JdbcOperationsSessionRepository.this.jdbcOperations
.update(JdbcOperationsSessionRepository.this.updateSessionQuery, (ps) -> {
ps.setString(1, getId());
ps.setLong(2, getLastAccessedTime().toEpochMilli());
ps.setInt(3, (int) getMaxInactiveInterval().getSeconds());
ps.setLong(4, getExpiryTime().toEpochMilli());
ps.setString(5, indexes.get(PRINCIPAL_NAME_INDEX_NAME));
ps.setString(6, JdbcSession.this.primaryKey);
});
}
List<String> addedAttributeNames = JdbcSession.this.delta.entrySet().stream()
.filter((entry) -> entry.getValue() == DeltaValue.ADDED).map(Map.Entry::getKey)
.collect(Collectors.toList());
if (!addedAttributeNames.isEmpty()) {
insertSessionAttributes(JdbcSession.this, addedAttributeNames);
}
List<String> updatedAttributeNames = JdbcSession.this.delta.entrySet().stream()
.filter((entry) -> entry.getValue() == DeltaValue.UPDATED)
.map(Map.Entry::getKey).collect(Collectors.toList());
if (!updatedAttributeNames.isEmpty()) {
updateSessionAttributes(JdbcSession.this, updatedAttributeNames);
}
List<String> removedAttributeNames = JdbcSession.this.delta.entrySet().stream()
.filter((entry) -> entry.getValue() == DeltaValue.REMOVED)
.map(Map.Entry::getKey).collect(Collectors.toList());
if (!removedAttributeNames.isEmpty()) {
deleteSessionAttributes(JdbcSession.this, removedAttributeNames);
}
}
});
JdbcOperationsSessionRepository.this.transactionOperations.execute(() -> {
if (JdbcSession.this.changed) {
Map<String, String> indexes = JdbcOperationsSessionRepository.this.indexResolver
.resolveIndexesFor(JdbcSession.this);
JdbcOperationsSessionRepository.this.jdbcOperations
.update(JdbcOperationsSessionRepository.this.updateSessionQuery, (ps) -> {
ps.setString(1, getId());
ps.setLong(2, getLastAccessedTime().toEpochMilli());
ps.setInt(3, (int) getMaxInactiveInterval().getSeconds());
ps.setLong(4, getExpiryTime().toEpochMilli());
ps.setString(5, indexes.get(PRINCIPAL_NAME_INDEX_NAME));
ps.setString(6, JdbcSession.this.primaryKey);
});
}
List<String> addedAttributeNames = JdbcSession.this.delta.entrySet().stream()
.filter((entry) -> entry.getValue() == DeltaValue.ADDED).map(Map.Entry::getKey)
.collect(Collectors.toList());
if (!addedAttributeNames.isEmpty()) {
insertSessionAttributes(JdbcSession.this, addedAttributeNames);
}
List<String> updatedAttributeNames = JdbcSession.this.delta.entrySet().stream()
.filter((entry) -> entry.getValue() == DeltaValue.UPDATED).map(Map.Entry::getKey)
.collect(Collectors.toList());
if (!updatedAttributeNames.isEmpty()) {
updateSessionAttributes(JdbcSession.this, updatedAttributeNames);
}
List<String> removedAttributeNames = JdbcSession.this.delta.entrySet().stream()
.filter((entry) -> entry.getValue() == DeltaValue.REMOVED).map(Map.Entry::getKey)
.collect(Collectors.toList());
if (!removedAttributeNames.isEmpty()) {
deleteSessionAttributes(JdbcSession.this, removedAttributeNames);
}
});
}
clearChangeFlags();
}

View File

@@ -16,7 +16,9 @@
package org.springframework.session.jdbc.config.annotation.web.http;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.sql.DataSource;
@@ -45,11 +47,15 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.session.FlushMode;
import org.springframework.session.MapSession;
import org.springframework.session.SaveMode;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionOperations;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
@@ -87,12 +93,16 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
private PlatformTransactionManager transactionManager;
private TransactionOperations transactionOperations;
private LobHandler lobHandler;
private ConversionService springSessionConversionService;
private ConversionService conversionService;
private List<SessionRepositoryCustomizer<JdbcOperationsSessionRepository>> sessionRepositoryCustomizers;
private ClassLoader classLoader;
private StringValueResolver embeddedValueResolver;
@@ -100,8 +110,11 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
@Bean
public JdbcOperationsSessionRepository sessionRepository() {
JdbcTemplate jdbcTemplate = createJdbcTemplate(this.dataSource);
if (this.transactionOperations == null) {
this.transactionOperations = createTransactionTemplate(this.transactionManager);
}
JdbcOperationsSessionRepository sessionRepository = new JdbcOperationsSessionRepository(jdbcTemplate,
this.transactionManager);
this.transactionOperations);
if (StringUtils.hasText(this.tableName)) {
sessionRepository.setTableName(this.tableName);
}
@@ -123,8 +136,10 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
sessionRepository.setConversionService(this.conversionService);
}
else {
sessionRepository.setConversionService(createConversionServiceWithBeanClassLoader());
sessionRepository.setConversionService(createConversionServiceWithBeanClassLoader(this.classLoader));
}
this.sessionRepositoryCustomizers
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
return sessionRepository;
}
@@ -173,6 +188,12 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
this.transactionManager = transactionManager;
}
@Autowired(required = false)
@Qualifier("springSessionTransactionOperations")
public void setTransactionOperations(TransactionOperations transactionOperations) {
this.transactionOperations = transactionOperations;
}
@Autowired(required = false)
@Qualifier("springSessionLobHandler")
public void setLobHandler(LobHandler lobHandler) {
@@ -191,6 +212,12 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
this.conversionService = conversionService;
}
@Autowired(required = false)
public void setSessionRepositoryCustomizer(
ObjectProvider<SessionRepositoryCustomizer<JdbcOperationsSessionRepository>> sessionRepositoryCustomizers) {
this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
@@ -230,10 +257,17 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
return jdbcTemplate;
}
private GenericConversionService createConversionServiceWithBeanClassLoader() {
private static TransactionTemplate createTransactionTemplate(PlatformTransactionManager transactionManager) {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
transactionTemplate.afterPropertiesSet();
return transactionTemplate;
}
private static GenericConversionService createConversionServiceWithBeanClassLoader(ClassLoader classLoader) {
GenericConversionService conversionService = new GenericConversionService();
conversionService.addConverter(Object.class, byte[].class, new SerializingConverter());
conversionService.addConverter(byte[].class, Object.class, new DeserializingConverter(this.classLoader));
conversionService.addConverter(byte[].class, Object.class, new DeserializingConverter(classLoader));
return conversionService;
}

View File

@@ -28,6 +28,8 @@ import java.util.function.Supplier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
@@ -43,24 +45,18 @@ import org.springframework.session.SaveMode;
import org.springframework.session.Session;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository.JdbcSession;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionOperations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.endsWith;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
/**
* Tests for {@link JdbcOperationsSessionRepository}.
@@ -73,29 +69,39 @@ class JdbcOperationsSessionRepositoryTests {
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
private JdbcOperations jdbcOperations = mock(JdbcOperations.class);
private PlatformTransactionManager transactionManager = mock(PlatformTransactionManager.class);
@Mock
private JdbcOperations jdbcOperations;
private JdbcOperationsSessionRepository repository;
@BeforeEach
void setUp() {
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations, this.transactionManager);
MockitoAnnotations.initMocks(this);
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations,
TransactionOperations.withoutTransaction());
}
@Test
void constructorNullJdbcOperations() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new JdbcOperationsSessionRepository(null, this.transactionManager))
.withMessage("JdbcOperations must not be null");
.isThrownBy(() -> new JdbcOperationsSessionRepository(null, TransactionOperations.withoutTransaction()))
.withMessage("jdbcOperations must not be null");
}
@Test
void constructorNullTransactionManager() {
void constructorNullTransactionOperations() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new JdbcOperationsSessionRepository(this.jdbcOperations, null))
.withMessage("TransactionManager must not be null");
.isThrownBy(
() -> new JdbcOperationsSessionRepository(this.jdbcOperations, (TransactionOperations) null))
.withMessage("transactionOperations must not be null");
}
@Test
@SuppressWarnings("deprecation")
void constructorNullTransactionManager() {
assertThatIllegalArgumentException().isThrownBy(
() -> new JdbcOperationsSessionRepository(this.jdbcOperations, (PlatformTransactionManager) null))
.withMessage("transactionManager must not be null");
}
@Test
@@ -248,7 +254,7 @@ class JdbcOperationsSessionRepositoryTests {
assertThat(session.isNew()).isTrue();
assertThat(session.getMaxInactiveInterval()).isEqualTo(new MapSession().getMaxInactiveInterval());
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -260,7 +266,7 @@ class JdbcOperationsSessionRepositoryTests {
assertThat(session.isNew()).isTrue();
assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofSeconds(interval));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -268,7 +274,6 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.setFlushMode(FlushMode.IMMEDIATE);
JdbcSession session = this.repository.createSession();
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations).update(startsWith("INSERT"), isA(PreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
}
@@ -280,9 +285,8 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT"), isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -293,12 +297,11 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION("),
isA(PreparedStatementSetter.class));
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -310,12 +313,11 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION("),
isA(PreparedStatementSetter.class));
verify(this.jdbcOperations, times(1)).batchUpdate(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
isA(BatchPreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -327,10 +329,9 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -343,10 +344,9 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).batchUpdate(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
isA(BatchPreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -360,10 +360,9 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("UPDATE SPRING_SESSION_ATTRIBUTES SET"),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -379,10 +378,9 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).batchUpdate(startsWith("UPDATE SPRING_SESSION_ATTRIBUTES SET"),
isA(BatchPreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -396,10 +394,9 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("DELETE FROM SPRING_SESSION_ATTRIBUTES WHERE"),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -411,8 +408,7 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -428,10 +424,9 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).batchUpdate(startsWith("DELETE FROM SPRING_SESSION_ATTRIBUTES WHERE"),
isA(BatchPreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test // gh-1070
@@ -444,10 +439,9 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test // gh-1070
@@ -460,8 +454,7 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test // gh-1070
@@ -476,10 +469,9 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations).update(startsWith("DELETE FROM SPRING_SESSION_ATTRIBUTES WHERE"),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test // gh-1070
@@ -494,10 +486,9 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations).update(startsWith("UPDATE SPRING_SESSION_ATTRIBUTES SET"),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -509,10 +500,9 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("UPDATE SPRING_SESSION SET"),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -523,7 +513,7 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -536,7 +526,6 @@ class JdbcOperationsSessionRepositoryTests {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(sessionId);
assertThat(session).isNull();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(isA(String.class), isA(PreparedStatementSetter.class),
isA(ResultSetExtractor.class));
}
@@ -552,7 +541,6 @@ class JdbcOperationsSessionRepositoryTests {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(expired.getId());
assertThat(session).isNull();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(isA(String.class), isA(PreparedStatementSetter.class),
isA(ResultSetExtractor.class));
verify(this.jdbcOperations, times(1)).update(startsWith("DELETE"), eq(expired.getId()));
@@ -571,7 +559,6 @@ class JdbcOperationsSessionRepositoryTests {
assertThat(session.getId()).isEqualTo(saved.getId());
assertThat(session.isNew()).isFalse();
assertThat(session.<String>getAttribute("savedName")).isEqualTo("savedValue");
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(isA(String.class), isA(PreparedStatementSetter.class),
isA(ResultSetExtractor.class));
}
@@ -582,7 +569,6 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.deleteById(sessionId);
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("DELETE"), eq(sessionId));
}
@@ -594,7 +580,7 @@ class JdbcOperationsSessionRepositoryTests {
.findByIndexNameAndIndexValue("testIndexName", indexValue);
assertThat(sessions).isEmpty();
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -608,7 +594,6 @@ class JdbcOperationsSessionRepositoryTests {
.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal);
assertThat(sessions).isEmpty();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(isA(String.class), isA(PreparedStatementSetter.class),
isA(ResultSetExtractor.class));
}
@@ -633,7 +618,6 @@ class JdbcOperationsSessionRepositoryTests {
.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal);
assertThat(sessions).hasSize(2);
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(isA(String.class), isA(PreparedStatementSetter.class),
isA(ResultSetExtractor.class));
}
@@ -642,7 +626,6 @@ class JdbcOperationsSessionRepositoryTests {
void cleanupExpiredSessions() {
this.repository.cleanUpExpiredSessions();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("DELETE"), anyLong());
}
@@ -659,84 +642,6 @@ class JdbcOperationsSessionRepositoryTests {
assertThat(session.getAttributeNames()).isEmpty();
}
@Test
void saveNewWithoutTransaction() {
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
this.repository.save(session);
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION"),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
void saveUpdatedWithoutTransaction() {
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
"primaryKey", false);
session.setLastAccessedTime(Instant.now());
this.repository.save(session);
verify(this.jdbcOperations, times(1)).update(startsWith("UPDATE SPRING_SESSION"),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
@SuppressWarnings("unchecked")
void findByIdWithoutTransaction() {
given(this.jdbcOperations.query(anyString(), any(PreparedStatementSetter.class), any(ResultSetExtractor.class)))
.willReturn(Collections.emptyList());
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
this.repository.findById("testSessionId");
verify(this.jdbcOperations, times(1)).query(endsWith("WHERE S.SESSION_ID = ?"),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class));
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
void deleteByIdWithoutTransaction() {
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
this.repository.deleteById("testSessionId");
verify(this.jdbcOperations, times(1)).update(eq("DELETE FROM SPRING_SESSION WHERE SESSION_ID = ?"),
anyString());
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
@SuppressWarnings("unchecked")
void findByIndexNameAndIndexValueWithoutTransaction() {
given(this.jdbcOperations.query(anyString(), any(PreparedStatementSetter.class), any(ResultSetExtractor.class)))
.willReturn(Collections.emptyList());
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
this.repository.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
"testIndexValue");
verify(this.jdbcOperations, times(1)).query(endsWith("WHERE S.PRINCIPAL_NAME = ?"),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class));
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
void cleanUpExpiredSessionsWithoutTransaction() {
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
this.repository.cleanUpExpiredSessions();
verify(this.jdbcOperations, times(1)).update(eq("DELETE FROM SPRING_SESSION WHERE EXPIRY_TIME < ?"), anyLong());
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
void saveWithSaveModeOnSetAttribute() {
this.repository.setSaveMode(SaveMode.ON_SET_ATTRIBUTE);
@@ -751,7 +656,7 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
verify(this.jdbcOperations).update(startsWith("UPDATE SPRING_SESSION_ATTRIBUTES SET"),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -770,7 +675,7 @@ class JdbcOperationsSessionRepositoryTests {
.forClass(BatchPreparedStatementSetter.class);
verify(this.jdbcOperations).batchUpdate(startsWith("UPDATE SPRING_SESSION_ATTRIBUTES SET"), captor.capture());
assertThat(captor.getValue().getBatchSize()).isEqualTo(2);
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -789,7 +694,7 @@ class JdbcOperationsSessionRepositoryTests {
.forClass(BatchPreparedStatementSetter.class);
verify(this.jdbcOperations).batchUpdate(startsWith("UPDATE SPRING_SESSION_ATTRIBUTES SET"), captor.capture());
assertThat(captor.getValue().getBatchSize()).isEqualTo(3);
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -798,7 +703,6 @@ class JdbcOperationsSessionRepositoryTests {
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
String attrName = "someAttribute";
session.setAttribute(attrName, "someValue");
assertPropagationRequiresNew();
verify(this.jdbcOperations).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
isA(PreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
@@ -811,7 +715,6 @@ class JdbcOperationsSessionRepositoryTests {
cached.setAttribute("attribute1", "value1");
JdbcSession session = this.repository.new JdbcSession(cached, "primaryKey", false);
session.removeAttribute("attribute1");
assertPropagationRequiresNew();
verify(this.jdbcOperations).update(startsWith("DELETE FROM SPRING_SESSION_ATTRIBUTES WHERE"),
isA(PreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
@@ -822,7 +725,6 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.setFlushMode(FlushMode.IMMEDIATE);
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
session.setMaxInactiveInterval(Duration.ofSeconds(1));
assertPropagationRequiresNew();
verify(this.jdbcOperations).update(startsWith("UPDATE SPRING_SESSION SET"), isA(PreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
}
@@ -832,16 +734,8 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.setFlushMode(FlushMode.IMMEDIATE);
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
session.setLastAccessedTime(Instant.now());
assertPropagationRequiresNew();
verify(this.jdbcOperations).update(startsWith("UPDATE SPRING_SESSION SET"), isA(PreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
}
private void assertPropagationRequiresNew() {
ArgumentCaptor<TransactionDefinition> argument = ArgumentCaptor.forClass(TransactionDefinition.class);
verify(this.transactionManager, atLeastOnce()).getTransaction(argument.capture());
assertThat(argument.getValue().getPropagationBehavior())
.isEqualTo(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
}
}

View File

@@ -27,16 +27,20 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.annotation.Order;
import org.springframework.core.convert.ConversionService;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.session.FlushMode;
import org.springframework.session.SaveMode;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionOperations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -77,7 +81,10 @@ class JdbcHttpSessionConfigurationTests {
void defaultConfiguration() {
registerAndRefresh(DataSourceConfiguration.class, DefaultConfiguration.class);
assertThat(this.context.getBean(JdbcOperationsSessionRepository.class)).isNotNull();
JdbcOperationsSessionRepository sessionRepository = this.context.getBean(JdbcOperationsSessionRepository.class);
assertThat(sessionRepository).isNotNull();
assertThat(sessionRepository).extracting("transactionOperations")
.hasFieldOrPropertyWithValue("propagationBehavior", TransactionDefinition.PROPAGATION_REQUIRES_NEW);
}
@Test
@@ -225,6 +232,17 @@ class JdbcHttpSessionConfigurationTests {
.withMessageContaining("expected single matching bean but found 2");
}
@Test
void customTransactionOperationsConfiguration() {
registerAndRefresh(DataSourceConfiguration.class, CustomTransactionOperationsConfiguration.class);
JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class);
TransactionOperations transactionOperations = this.context.getBean(TransactionOperations.class);
assertThat(repository).isNotNull();
assertThat(transactionOperations).isNotNull();
assertThat(repository).hasFieldOrPropertyWithValue("transactionOperations", transactionOperations);
}
@Test
void customLobHandlerConfiguration() {
registerAndRefresh(DataSourceConfiguration.class, CustomLobHandlerConfiguration.class);
@@ -258,6 +276,14 @@ class JdbcHttpSessionConfigurationTests {
assertThat(ReflectionTestUtils.getField(configuration, "tableName")).isEqualTo("custom_session_table");
}
@Test
void sessionRepositoryCustomizer() {
registerAndRefresh(DataSourceConfiguration.class, SessionRepositoryCustomizerConfiguration.class);
JdbcOperationsSessionRepository sessionRepository = this.context.getBean(JdbcOperationsSessionRepository.class);
assertThat(sessionRepository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
MAX_INACTIVE_INTERVAL_IN_SECONDS);
}
private void registerAndRefresh(Class<?>... annotatedClasses) {
this.context.register(annotatedClasses);
this.context.refresh();
@@ -417,6 +443,16 @@ class JdbcHttpSessionConfigurationTests {
}
@EnableJdbcHttpSession
static class CustomTransactionOperationsConfiguration {
@Bean
public TransactionOperations springSessionTransactionOperations() {
return TransactionOperations.withoutTransaction();
}
}
@EnableJdbcHttpSession
static class CustomLobHandlerConfiguration {
@@ -447,4 +483,22 @@ class JdbcHttpSessionConfigurationTests {
}
@EnableJdbcHttpSession
static class SessionRepositoryCustomizerConfiguration {
@Bean
@Order(0)
public SessionRepositoryCustomizer<JdbcOperationsSessionRepository> sessionRepositoryCustomizerOne() {
return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(0);
}
@Bean
@Order(1)
public SessionRepositoryCustomizer<JdbcOperationsSessionRepository> sessionRepositoryCustomizerTwo() {
return (sessionRepository) -> sessionRepository
.setDefaultMaxInactiveInterval(MAX_INACTIVE_INTERVAL_IN_SECONDS);
}
}
}

View File

@@ -1,5 +1,3 @@
ext['spring-framework.version'] = '5.2.0.RC1'
dependencyManagement {
dependencies {
dependency 'ch.qos.logback:logback-classic:1.2.3'