Compare commits

..

97 Commits

Author SHA1 Message Date
Rob Winch
4d3a01919c Release 2.1.0.RELEASE 2018-10-15 20:04:27 -05:00
Rob Winch
e408d7f557 Update to Spring Security 5.1.1.RELEASE
Fixes: gh-1222
2018-10-15 20:04:04 -05:00
Vedran Pavic
f34acebf84 Upgrade integration tests 2018-10-15 18:42:12 +02:00
Vedran Pavic
1aab3e8285 Upgrade test dependencies 2018-10-15 18:35:18 +02:00
Vedran Pavic
c3528996d2 Upgrade Hazelcast to 3.10.6
Resolves: #1223
2018-10-15 18:34:44 +02:00
Vedran Pavic
3ccc3eb6e1 Upgrade Reactor to Californium-SR1
Resolves: #1221
2018-10-15 18:34:10 +02:00
Vedran Pavic
de76be95ac Upgrade Spring Data to Lovelace-SR1
Resolves: #1220
2018-10-15 18:33:25 +02:00
Vedran Pavic
bc127ab3fc Upgrade Spring Framework to 5.1.1.RELEASE
Resolves: #1219
2018-10-15 18:32:47 +02:00
Vedran Pavic
3e9f6a35c4 Fix root project name 2018-10-01 22:46:10 +02:00
Vedran Pavic
49daa3a9c7 Polish 2018-09-26 14:16:02 +02:00
Vedran Pavic
a67bd634d9 Disable network join in Hazelcast samples 2018-09-26 14:16:00 +02:00
Vedran Pavic
2762f001bf Add Oracle integration tests 2018-09-25 19:10:15 +02:00
Vedran Pavic
93aee206fb Configure default LobHandler to use temporary LOBs on Oracle
JdbcOperationsSessionRepository recently introduced validation when inserting new session attributes in order to prevent data integrity violations in highly concurrent environments. This is done by using INSERT INTO ... SELECT statement to verify existence of session record in parent table. Such arrangement causes problems with Oracle if inserted attribute is of size 4 kb or more.

This commit enhances JdbcHttpSessionConfiguration to detect Oracle database is used, and set createTemporaryLob option on default LobHandler to true.

Resolves: #1203
See also: #1031
2018-09-25 18:45:02 +02:00
Vedran Pavic
3df3b30117 Upgrade Testcontainers to 1.9.1 2018-09-25 18:31:52 +02:00
Vedran Pavic
5fb0c4dd35 Improve JDBC integration tests 2018-09-24 06:30:47 +02:00
Vedran Pavic
6fbce6e3e8 Next development version 2018-09-21 21:27:42 +02:00
Vedran Pavic
a3fd05326a Release 2.1.0.RC1 2018-09-21 21:26:28 +02:00
Vedran Pavic
4c6dc976b3 Upgrade Testcontainers to 1.9.0-rc2 2018-09-21 19:22:12 +02:00
Vedran Pavic
58ae28b0a0 Fix SpringSessionRememberMeServices documentation example
Resolves: #1157
2018-09-21 19:05:33 +02:00
Vedran Pavic
3e98ecf234 Upgrade Spring Security to 5.1.0.RELEASE
Resolves: #1188
2018-09-21 19:01:15 +02:00
Vedran Pavic
41ed429f98 Upgrade Spring Data to Lovelace-RELEASE
Resolves: #1190
2018-09-21 19:00:38 +02:00
Vedran Pavic
def15b05ca Upgrade Spring Framework to 5.1.0.RELEASE
Resolves: #1187
2018-09-21 11:10:33 +02:00
Vedran Pavic
eae8592f2b Upgrade integration tests 2018-09-20 19:48:33 +02:00
Vedran Pavic
81460ede09 Make SessionUpdateEntryProcessor implement Offloadable
Resolves: #1204
2018-09-20 19:31:55 +02:00
Vedran Pavic
ca4ec9a557 Upgrade test dependencies 2018-09-20 19:23:24 +02:00
Vedran Pavic
fd2165f471 Upgrade Hazelcast to 3.10.5
Resolves: #1206
2018-09-20 19:23:24 +02:00
Vedran Pavic
ad1e57a1fe Upgrade Gradle to 4.10.2 2018-09-20 19:15:26 +02:00
Vedran Pavic
0ffcaa2d35 Upgrade Reactor to Californium-RELEASE
Resolves: #1189
2018-09-20 11:45:33 +02:00
Vedran Pavic
b61937def7 Polish contribution
Resolves: #1133
2018-09-19 23:53:38 +02:00
Craig Andrews
c523fb591d Deserialize attributes lazily in JdbcOperationsSessionRepository
Instead of deserializing all of the session attributes as they are read from the database, deserialize as #getAttribute requests them.

See: #1133
2018-09-19 23:48:15 +02:00
Vedran Pavic
227fab2e42 Adjust CI build timeouts 2018-09-19 00:45:20 +02:00
Vedran Pavic
7f7815d80c Upgrade spring-build-conventions to 0.0.19.RELEASE 2018-09-19 00:01:06 +02:00
Vedran Pavic
002136bad4 Align WebSession#save implementations with API
Closes gh-1135
2018-09-18 23:58:59 +02:00
Vedran Pavic
1085661984 Enable integration tests for JDK 10 and 11 builds
See: #1196, #1197
2018-09-18 20:04:23 +02:00
Vedran Pavic
12bb0741bb Add Java 11 CI build
Closes gh-1197
2018-09-17 18:02:07 +02:00
Vedran Pavic
eecdcb49d9 Remove node designation from JDK 10 build
See gh-1196
2018-09-17 17:59:40 +02:00
Vedran Pavic
3e1a22102d Ensure compatibility with Java 9 and 10
Closes gh-1196
2018-09-16 22:13:56 +02:00
Vedran Pavic
9f6e791e5d Upgrade samples to Spring Boot 2.1.0.M3
Closes gh-1195
2018-09-13 21:04:43 +02:00
Vedran Pavic
efc35eddad Upgrade Gradle to 4.10.1 2018-09-13 20:59:49 +02:00
Vedran Pavic
4c37ec9f4a Update Jenkinsfile to specify node label 2018-09-13 18:08:17 +02:00
Vedran Pavic
1a3da5944d Polish
See gh-1128
2018-09-13 08:55:13 +02:00
Vedran Pavic
5d0775b802 Ensure RedisHttpSessionConfiguration handles events for configured database
At present, RedisHttpSessionConfiguration doesn't take into account database index when handlng events. In situations where multiple apps use Spring Session with same Redis instance, but different database, this results in invalid session events.

This commits improves event handling in RedisHttpSessionConfiguration to ensure currently used database is considered.

Closes gh-1128
2018-09-12 23:07:52 +02:00
Vedran Pavic
603a258172 Upgrade Testcontainers to 1.9.0-rc1 2018-09-11 23:06:10 +02:00
Vedran Pavic
22ebe65931 Next development version 2018-09-10 22:42:32 +02:00
Vedran Pavic
55033bcb64 Release 2.1.0.M3 2018-09-10 22:40:21 +02:00
Vedran Pavic
57955b7d7b Polish
See gh-1111
2018-09-10 17:03:10 +02:00
Vedran Pavic
d5da38f2e0 Upgrade test dependencies 2018-09-10 16:56:08 +02:00
Vedran Pavic
6cc4bcd13d Verify session existence before update in ReactiveRedisOperationsSessionRepository
Currently, ReactiveRedisOperationsSessionRepository#save does not ensure session's existence before executing update. This can result in an invalid session record in Redis, since write use only delta, and in turn to error while retrieving the invalid session record.

This commit adds check for session existence if session is being updated.

Closes gh-1111
2018-09-09 23:55:27 +02:00
Vedran Pavic
dc43f5bd2d Upgrade Spring Security to 5.1.0.RC2
Closes gh-1171
2018-09-07 23:48:18 +02:00
Vedran Pavic
7584cbd54c Upgrade Spring Framework to 5.1.0.RC3
Closes gh-1170
2018-09-07 17:40:18 +02:00
Vedran Pavic
0db1160dc4 Upgrade Reactor to Californium-RC1
Closes gh-1172
2018-09-07 07:48:08 +02:00
Vedran Pavic
10a18366f9 Update integration tests 2018-09-07 07:46:10 +02:00
Vedran Pavic
7ea5e2f3ee Upgrade test dependencies 2018-09-06 21:15:47 +02:00
Vedran Pavic
d3134ad065 Ignore failed rename operation for deleted session
Attempting to change session id for a deleted session currently results in "ERR no such key" error on rename operation of expired key. This commit addressed the problem by ignoring the aforementioned error.

Closes #1137
2018-09-04 23:07:27 +02:00
Vedran Pavic
6208d0298d Upgrade Gradle to 4.10 2018-09-04 21:57:04 +02:00
Vedran Pavic
c031ee278d Add javadoc for RedisOperationsSessionRepository#getSessionRedisOperations
Closes #1175
2018-09-03 23:29:50 +02:00
Vedran Pavic
8267a90fcc Polish contribution
See #1173
2018-09-03 23:28:14 +02:00
Johnny Lim
2113b330a7 Add @since for ReactiveRedisOperationsSR.getSessionRedisOperations() 2018-08-31 10:29:09 -05:00
Vedran Pavic
c4ac68b777 Fix Jenkinsfile 2018-08-27 09:26:55 +02:00
Vedran Pavic
0be2759e68 Fix Jenkinsfile 2018-08-27 08:24:36 +02:00
Vedran Pavic
1181e52bb0 Upgrade spring-build-conventions to 0.0.18.RELEASE 2018-08-24 23:50:23 +02:00
Vedran Pavic
5277d945ed Upgrade samples to Spring Boot 2.1.0.M2
Closes gh-1168
2018-08-22 18:31:30 +02:00
Rob Winch
1fc0162fe9 Fix settings.gradle on Windows
Fixes: gh-1167
2018-08-22 10:23:29 -05:00
Vedran Pavic
9df259b1ae Next development version 2018-08-21 06:34:09 +02:00
Vedran Pavic
0c2f756533 Release 2.1.0.M2 2018-08-21 06:33:12 +02:00
Vedran Pavic
de16c304ea Add support using JDBC repository without transactions
Closes gh-1046
2018-08-21 06:05:52 +02:00
Vedran Pavic
3ce3962ebd Upgrade Spring Security to 5.1.0.RC1
Closes gh-1144
2018-08-20 20:41:38 +02:00
Vedran Pavic
3c4a309a0f Upgrade Spring Data to Lovelace-RC2
Closes gh-1143
2018-08-20 11:51:28 +02:00
Vedran Pavic
38de434158 Add support for @SpringSessionRedisOperations in reactive Redis repository
Closes gh-1164
2018-08-20 07:23:23 +02:00
Vedran Pavic
7ef0faf259 Update integration tests 2018-08-20 06:31:10 +02:00
Vedran Pavic
f65cee0a7b Upgrade dependencies 2018-08-20 06:31:10 +02:00
Vedran Pavic
a2cd1e37fa Add support for configuring custom RedisSerializer in reactive config
Closes gh-1149
2018-08-20 06:31:09 +02:00
Vedran Pavic
b768042506 Upgrade Spring Framework to 5.1.0.RC2
Closes gh-1141
2018-08-17 12:21:48 +02:00
Vedran Pavic
3140bd06b2 Add FindByIndexNameSessionRepository#findByPrincipalName default method
Closes gh-1158
2018-08-17 08:04:09 +02:00
Vedran Pavic
172c18d666 Upgrade Reactor to Californium-M2
Closes gh-1142
2018-08-16 07:13:59 +02:00
Vedran Pavic
7fdf2876b2 Polish 2018-08-13 07:44:47 +02:00
Vedran Pavic
87c2e53b5a Insert new attributes conditionally in JDBC repo
At present, the insert of new attributes in JdbcOperationsSessionRepository is done unconditionally. This can cause data integrity violation errors with concurrent requests, where one request attempts to add new session attribute while the other, concurrent request, deletes the session.

This commit addresses the described scenario by executing insert of new attributes conditionally on presence of parent record.

Closes gh-1031
2018-08-13 06:28:42 +02:00
Vedran Pavic
268ba663e5 Remove SpringSessionWebSessionStore#storeSession
Closes gh-1150
2018-08-09 16:32:14 +02:00
Vedran Pavic
3f4873f0eb Simplify tests related to SameSite cookie directive support
Closes gh-1147
2018-08-03 23:20:55 +02:00
Vedran Pavic
644239ee14 Start building against Spring Framework 5.1.0 snapshots
See gh-1141
2018-08-03 23:20:52 +02:00
Johnny Lim
97e52de41b Make MapSession.originalId final
Closes gh-1146
2018-08-02 18:46:59 +02:00
Vedran Pavic
f4bbc18f94 Fix Jenkinsfile 2018-08-01 02:00:48 +02:00
Vedran Pavic
dfe216b482 Update Jenkinsfile
- set check stage timeout to 30 minutes
 - set build discared to keep last 10 builds
 - handle deploy stage errors
 - general formatting improvements
2018-08-01 01:01:32 +02:00
Vedran Pavic
a976c9dd6d Upgrade samples to Spring Boot 2.1.0.M1
Closes gh-1139
2018-07-31 22:22:50 +02:00
Vedran Pavic
deb2863507 Next development version 2018-07-30 02:49:33 +02:00
Vedran Pavic
7bdb3f6ded Release 2.1.0.M1 2018-07-30 02:36:01 +02:00
Vedran Pavic
7d3472f55d Remove Spring IO check from build 2018-07-30 02:31:00 +02:00
Vedran Pavic
00465a6f00 Add support for SameSite cookie directive
Closes gh-1005
2018-07-30 02:13:57 +02:00
Vedran Pavic
ad35d7ca30 Add support for HttpSessionBindingListener
Closes gh-1018
2018-07-29 08:09:00 +02:00
Vedran Pavic
18e9ab4c0f Polish 2018-07-27 13:14:04 +02:00
Vedran Pavic
1c9a6d3e5d Upgrade Spring Security to 5.1.0.M2
Closes gh-1125
2018-07-27 13:13:19 +02:00
Vedran Pavic
d2936ed0b4 Upgrade dependencies 2018-07-27 11:10:14 +02:00
Vedran Pavic
cdf6089ccd Upgrade Spring Data to Lovelace-RC1
Closes gh-1126
2018-07-26 23:14:16 +02:00
Vedran Pavic
1ca8a6476a Upgrade Spring Framework to 5.1.0.RC1
Closes gh-1124
2018-07-26 23:13:31 +02:00
Vedran Pavic
cf926045dc Upgrade Reactor to Californium-M1
Closes gh-1127
2018-07-25 22:05:19 +02:00
Vedran Pavic
7123df8656 Remove MapSession#setOriginalId
Closes gh-1100
2018-07-25 22:03:19 +02:00
Rob Winch
096a5683cb Spring Session Core 2.1.0.BUILD-SNAPSHOT 2018-07-25 10:32:29 -07:00
73 changed files with 1347 additions and 603 deletions

45
Jenkinsfile vendored
View File

@@ -11,7 +11,7 @@ currentBuild.result = SUCCESS
try {
parallel check: {
stage('Check') {
timeout(time: 30, unit: 'MINUTES') {
timeout(time: 45, unit: 'MINUTES') {
node('ubuntu1804') {
checkout scm
try {
@@ -28,19 +28,38 @@ try {
}
}
},
springio: {
stage('Spring IO') {
node {
checkout scm
try {
sh "./gradlew clean springIoCheck --stacktrace --no-daemon --refresh-dependencies -PplatformVersion=Cairo-BUILD-SNAPSHOT -PexcludeProjects='**/samples/**'"
jdk10: {
stage('JDK 10') {
timeout(time: 45, unit: 'MINUTES') {
node('ubuntu1804') {
checkout scm
try {
withEnv(["JAVA_HOME=${tool 'jdk10'}"]) {
sh './gradlew clean test integrationTest --no-daemon --refresh-dependencies'
}
}
catch (e) {
currentBuild.result = 'FAILED: jdk10'
throw e
}
}
catch(e) {
currentBuild.result = 'FAILED: springio'
throw e
}
finally {
junit '**/build/spring-io*-results/*.xml'
}
}
},
jdk11: {
stage('JDK 11') {
timeout(time: 45, unit: 'MINUTES') {
node('ubuntu1804') {
checkout scm
try {
withEnv(["JAVA_HOME=${tool 'jdk11'}"]) {
sh './gradlew clean test integrationTest --no-daemon --refresh-dependencies'
}
}
catch (e) {
currentBuild.result = 'FAILED: jdk11'
throw e
}
}
}
}

View File

@@ -1,20 +1,38 @@
buildscript {
ext {
releaseBuild = version.endsWith('RELEASE')
snapshotBuild = version.endsWith('SNAPSHOT')
milestoneBuild = !(releaseBuild || snapshotBuild)
springBootVersion = '2.1.0.M3'
}
repositories {
gradlePluginPortal()
maven { url 'https://repo.spring.io/plugins-release/' }
}
dependencies {
classpath 'io.spring.gradle:spring-build-conventions:0.0.18.RELEASE'
classpath 'io.spring.gradle:spring-build-conventions:0.0.19.RELEASE'
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
}
repositories {
maven { url 'https://repo.spring.io/plugins-release' }
}
}
apply plugin: 'io.spring.convention.root'
group = 'org.springframework.session'
description = 'Spring Session'
ext.releaseBuild = version.endsWith('RELEASE')
ext.snapshotBuild = version.endsWith('SNAPSHOT')
ext.milestoneBuild = !(releaseBuild || snapshotBuild)
gradle.taskGraph.whenReady { graph ->
def jacocoEnabled = graph.allTasks.any { it instanceof JacocoReport }
subprojects {
plugins.withType(JavaPlugin) {
sourceCompatibility = 1.8
}
plugins.withType(JacocoPlugin) {
tasks.withType(Test) {
jacoco.enabled = jacocoEnabled
}
}
}
}

View File

@@ -614,9 +614,10 @@ Spring Session's most basic API for using a `Session` is the `SessionRepository`
This API is intentionally very simple, so that it is easy to provide additional implementations with basic functionality.
Some `SessionRepository` implementations may choose to implement `FindByIndexNameSessionRepository` also.
For example, Spring's Redis support implements `FindByIndexNameSessionRepository`.
For example, Spring's Redis, JDBC and Hazelcast support all implement `FindByIndexNameSessionRepository`.
The `FindByIndexNameSessionRepository` adds a single method to look up all the sessions for a particular user.
The `FindByIndexNameSessionRepository` provides a method to look up all the sessions with a given index name and index value.
As a common use case that is supported by all provided `FindByIndexNameSessionRepository` implementations, there's a convenient method to look up all the sessions for a particular user.
This is done by ensuring that the session attribute with the name `FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME` is populated with the username.
It is the responsibility of the developer to ensure the attribute is populated since Spring Session is not aware of the authentication mechanism being used.
An example of how this might be used can be seen below:

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.
@@ -52,9 +52,7 @@ public class FindByIndexNameSessionRepositoryTests {
// tag::findby-username[]
String username = "username";
Map<String, Session> sessionIdToSession = this.sessionRepository
.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
username);
.findByPrincipalName(username);
// end::findby-username[]
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.
@@ -43,6 +43,7 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMv
/**
* @author rwinch
* @author Vedran Pavic
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = RememberMeSecurityConfiguration.class)
@@ -86,5 +87,6 @@ public class RememberMeSecurityConfigurationTests<T extends Session> {
.isEqualTo(Duration.ofDays(30));
}
}
// end::class[]

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.
@@ -43,6 +43,7 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMv
/**
* @author rwinch
* @author Vedran Pavic
*/
@RunWith(SpringRunner.class)
@ContextConfiguration
@@ -86,5 +87,6 @@ public class RememberMeSecurityConfigurationXmlTests<T extends Session> {
.isEqualTo(Duration.ofDays(30));
}
}
// end::class[]

View File

@@ -1,2 +1 @@
springBootVersion=2.0.5.RELEASE
version=2.0.7.RELEASE
version=2.1.0.RELEASE

View File

@@ -1,25 +1,26 @@
dependencyManagement {
imports {
mavenBom 'com.fasterxml.jackson:jackson-bom:2.9.6'
mavenBom 'io.projectreactor:reactor-bom:Bismuth-SR12'
mavenBom 'org.springframework:spring-framework-bom:5.0.10.RELEASE'
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-SR11'
mavenBom 'org.springframework.security:spring-security-bom:5.0.9.RELEASE'
mavenBom 'io.projectreactor:reactor-bom:Californium-SR1'
mavenBom 'org.springframework:spring-framework-bom:5.1.1.RELEASE'
mavenBom 'org.springframework.data:spring-data-releasetrain:Lovelace-SR1'
mavenBom 'org.springframework.security:spring-security-bom:5.1.1.RELEASE'
mavenBom 'org.testcontainers:testcontainers-bom:1.9.1'
}
dependencies {
dependencySet(group: 'com.hazelcast', version: '3.9.4') {
dependencySet(group: 'com.hazelcast', version: '3.10.6') {
entry 'hazelcast'
entry 'hazelcast-client'
}
dependency 'com.h2database:h2:1.4.197'
dependency 'com.microsoft.sqlserver:mssql-jdbc:7.0.0.jre8'
dependency 'com.zaxxer:HikariCP:3.2.0'
dependency 'edu.umd.cs.mtc:multithreadedtc:1.01'
dependency 'io.lettuce:lettuce-core:5.0.5.RELEASE'
dependency 'io.lettuce:lettuce-core:5.1.1.RELEASE'
dependency 'javax.annotation:javax.annotation-api:1.3.2'
dependency 'javax.servlet:javax.servlet-api:3.1.0'
dependency 'javax.servlet:javax.servlet-api:4.0.1'
dependency 'junit:junit:4.12'
dependency 'mysql:mysql-connector-java:8.0.12'
dependency 'org.apache.derby:derby:10.14.2.0'

Binary file not shown.

View File

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

View File

@@ -0,0 +1 @@
ryuk.container.timeout=120

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.
@@ -44,10 +44,7 @@ public class IndexController {
@RequestMapping("/")
public String index(Principal principal, Model model) {
Collection<? extends Session> usersSessions = this.sessions
.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
principal.getName())
.values();
.findByPrincipalName(principal.getName()).values();
model.addAttribute("sessions", usersSessions);
return "index";
}
@@ -56,9 +53,8 @@ public class IndexController {
@RequestMapping(value = "/sessions/{sessionIdToDelete}", method = RequestMethod.DELETE)
public String removeSession(Principal principal,
@PathVariable String sessionIdToDelete) {
Set<String> usersSessionIds = this.sessions.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
principal.getName()).keySet();
Set<String> usersSessionIds = this.sessions
.findByPrincipalName(principal.getName()).keySet();
if (usersSessionIds.contains(sessionIdToDelete)) {
this.sessions.deleteById(sessionIdToDelete);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.
@@ -34,6 +34,7 @@ import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDr
/**
* @author Eddú Meléndez
* @author Vedran Pavic
*/
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.
@@ -36,7 +36,7 @@ public class LoginPage extends BasePage {
}
public void assertAt() {
assertThat(getDriver().getTitle()).isEqualTo("Login Page");
assertThat(getDriver().getTitle()).isEqualTo("Please sign in");
}
public Form form() {
@@ -51,7 +51,7 @@ public class LoginPage extends BasePage {
@FindBy(name = "password")
private WebElement password;
@FindBy(name = "submit")
@FindBy(tagName = "button")
private WebElement button;
public Form(SearchContext context) {

View File

@@ -0,0 +1 @@
ryuk.container.timeout=120

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.
@@ -36,7 +36,7 @@ public class LoginPage extends BasePage {
}
public void assertAt() {
assertThat(getDriver().getTitle()).isEqualTo("Login Page");
assertThat(getDriver().getTitle()).isEqualTo("Please sign in");
}
public Form form() {
@@ -51,7 +51,7 @@ public class LoginPage extends BasePage {
@FindBy(name = "password")
private WebElement password;
@FindBy(name = "submit")
@FindBy(tagName = "button")
private WebElement button;
public Form(SearchContext context) {

View File

@@ -0,0 +1 @@
ryuk.container.timeout=120

View File

@@ -0,0 +1 @@
ryuk.container.timeout=120

View File

@@ -23,5 +23,6 @@ dependencies {
testCompile "org.springframework.boot:spring-boot-starter-test"
testCompile "org.springframework.security:spring-security-test"
testCompile "org.testcontainers:testcontainers"
integrationTestCompile "org.testcontainers:testcontainers"
}

View File

@@ -0,0 +1 @@
ryuk.container.timeout=120

View File

@@ -5,7 +5,7 @@ dependencyManagement {
dependency 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.1'
dependency 'javax.servlet.jsp:javax.servlet.jsp-api:2.3.2-b02'
dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5'
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.29.3'
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.32.0'
dependency 'org.slf4j:jcl-over-slf4j:1.7.25'
dependency 'org.slf4j:log4j-over-slf4j:1.7.25'
dependency 'org.webjars:bootstrap:2.3.2'

View File

@@ -0,0 +1 @@
ryuk.container.timeout=120

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.
@@ -36,7 +36,7 @@ public class LoginPage extends BasePage {
}
public void assertAt() {
assertThat(getDriver().getTitle()).isEqualTo("Login Page");
assertThat(getDriver().getTitle()).isEqualTo("Please sign in");
}
public Form form() {
@@ -51,7 +51,7 @@ public class LoginPage extends BasePage {
@FindBy(name = "password")
private WebElement password;
@FindBy(name = "submit")
@FindBy(tagName = "button")
private WebElement button;
public Form(SearchContext context) {

View File

@@ -0,0 +1 @@
ryuk.container.timeout=120

View File

@@ -17,8 +17,6 @@ dependencies {
testCompile "org.springframework.security:spring-security-test"
testCompile "org.assertj:assertj-core"
testCompile "org.springframework:spring-test"
integrationTestCompile "org.testcontainers:testcontainers"
}
gretty {

View File

@@ -0,0 +1 @@
ryuk.container.timeout=120

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.
@@ -34,7 +34,7 @@ public class LoginPage extends BasePage {
@FindBy(name = "password")
private WebElement password;
@FindBy(css = "input[type='submit']")
@FindBy(tagName = "button")
private WebElement button;
public LoginPage(WebDriver driver) {
@@ -47,7 +47,7 @@ public class LoginPage extends BasePage {
}
public void assertAt() {
assertThat(getDriver().getTitle()).isEqualTo("Login Page");
assertThat(getDriver().getTitle()).isEqualTo("Please sign in");
}
public HomePage login(String user, String password) {

View File

@@ -0,0 +1 @@
ryuk.container.timeout=120

View File

@@ -0,0 +1 @@
ryuk.container.timeout=120

View File

@@ -2,12 +2,15 @@ rootProject.name = 'spring-session-build'
FileTree buildFiles = fileTree(rootDir) {
include '**/*.gradle'
exclude '**/gradle', 'settings.gradle', 'buildSrc', '/build.gradle', '.*'
exclude 'build', '**/gradle', 'settings.gradle', 'buildSrc', '/build.gradle', '.*', 'out'
exclude '**/grails3'
gradle.startParameter.projectProperties.get('excludeProjects')?.split(',')?.each { excludeProject ->
exclude excludeProject
}
}
String rootDirPath = rootDir.absolutePath + File.separator
buildFiles.each { File buildFile ->
buildFiles.each { buildFile ->
if (buildFile.name == 'build.gradle') {
String buildFilePath = buildFile.parentFile.absolutePath
String projectPath = buildFilePath.replace(rootDirPath, '').replace(File.separator, ':')

View File

@@ -6,6 +6,7 @@ dependencies {
compile "org.springframework:spring-jcl"
optional "io.projectreactor:reactor-core"
optional "javax.annotation:javax.annotation-api"
optional "javax.servlet:javax.servlet-api"
optional "org.springframework:spring-context"
optional "org.springframework:spring-jdbc"

View File

@@ -19,27 +19,22 @@ package org.springframework.session;
import java.util.Map;
/**
* Extends a basic {@link SessionRepository} to allow finding a session id by the
* principal name. The principal name is defined by the {@link Session} attribute with the
* name {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME}.
* Extends a basic {@link SessionRepository} to allow finding sessions by the specified
* index name and index value.
*
* @param <S> the type of Session being managed by this
* {@link FindByIndexNameSessionRepository}
* @author Rob Winch
* @author Vedran Pavic
*/
public interface FindByIndexNameSessionRepository<S extends Session>
extends SessionRepository<S> {
/**
* A session index that contains the current principal name (i.e. username).
* <p>
* A common session attribute that contains the current principal name (i.e.
* username).
* </p>
*
* <p>
* It is the responsibility of the developer to ensure the attribute is populated
* since Spring Session is not aware of the authentication mechanism being used.
* </p>
* It is the responsibility of the developer to ensure the index is populated since
* Spring Session is not aware of the authentication mechanism being used.
*
* @since 1.1
*/
@@ -47,17 +42,34 @@ public interface FindByIndexNameSessionRepository<S extends Session>
.concat(".PRINCIPAL_NAME_INDEX_NAME");
/**
* Find a Map of the session id to the {@link Session} of all sessions that contain
* the session attribute with the name
* {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME} and the value of
* the specified principal name.
* Find a {@link Map} of the session id to the {@link Session} of all sessions that
* contain the specified index name index value.
*
* @param indexName the name of the index (i.e.
* {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME})
* @param indexValue the value of the index to search for.
* @return a Map (never null) of the session id to the {@link Session} of all sessions
* that contain the session specified index name and the value of the specified index
* name. If no results are found, an empty Map is returned.
* @return a {@code Map} (never {@code null}) of the session id to the {@code Session}
* of all sessions that contain the specified index name and index value. If no
* results are found, an empty {@code Map} is returned.
*/
Map<String, S> findByIndexNameAndIndexValue(String indexName, String indexValue);
/**
* Find a {@link Map} of the session id to the {@link Session} of all sessions that
* contain the index with the name
* {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME} and the
* specified principal name.
*
* @param principalName the principal name
* @return a {@code Map} (never {@code null}) of the session id to the {@code Session}
* of all sessions that contain the specified principal name. If no results are found,
* an empty {@code Map} is returned.
* @since 2.1.0
*/
default Map<String, S> findByPrincipalName(String principalName) {
return findByIndexNameAndIndexValue(PRINCIPAL_NAME_INDEX_NAME, principalName);
}
}

View File

@@ -53,7 +53,7 @@ public final class MapSession implements Session, Serializable {
public static final int DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS = 1800;
private String id;
private String originalId;
private final String originalId;
private Map<String, Object> sessionAttrs = new HashMap<>();
private Instant creationTime = Instant.now();
private Instant lastAccessedTime = this.creationTime;
@@ -132,10 +132,6 @@ public final class MapSession implements Session, Serializable {
return this.originalId;
}
void setOriginalId(String originalId) {
this.originalId = originalId;
}
@Override
public String changeSessionId() {
String changedId = generateId();

View File

@@ -73,7 +73,6 @@ public class MapSessionRepository implements SessionRepository<MapSession> {
public void save(MapSession session) {
if (!session.getId().equals(session.getOriginalId())) {
this.sessions.remove(session.getOriginalId());
session.setOriginalId(session.getId());
}
this.sessions.put(session.getId(), new MapSession(session));
}

View File

@@ -76,7 +76,6 @@ public class ReactiveMapSessionRepository implements ReactiveSessionRepository<M
return Mono.fromRunnable(() -> {
if (!session.getId().equals(session.getOriginalId())) {
this.sessions.remove(session.getOriginalId());
session.setOriginalId(session.getId());
}
this.sessions.put(session.getId(), new MapSession(session));
});

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.
@@ -65,9 +65,8 @@ public class SpringSessionBackedSessionRegistry<S extends Session>
@Override
public List<SessionInformation> getAllSessions(Object principal,
boolean includeExpiredSessions) {
Collection<S> sessions = this.sessionRepository.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
name(principal)).values();
Collection<S> sessions = this.sessionRepository
.findByPrincipalName(name(principal)).values();
List<SessionInformation> infos = new ArrayList<>();
for (S session : sessions) {
if (includeExpiredSessions || !Boolean.TRUE.equals(session

View File

@@ -16,8 +16,13 @@
package org.springframework.session.web.http;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Base64;
import java.util.BitSet;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -41,6 +46,22 @@ public class DefaultCookieSerializer implements CookieSerializer {
private static final Log logger = LogFactory.getLog(DefaultCookieSerializer.class);
private static final BitSet domainValid = new BitSet(128);
static {
for (char c = '0'; c <= '9'; c++) {
domainValid.set(c);
}
for (char c = 'a'; c <= 'z'; c++) {
domainValid.set(c);
}
for (char c = 'A'; c <= 'Z'; c++) {
domainValid.set(c);
}
domainValid.set('.');
domainValid.set('-');
}
private String cookieName = "SESSION";
private Boolean useSecureCookie;
@@ -61,6 +82,8 @@ public class DefaultCookieSerializer implements CookieSerializer {
private String rememberMeRequestAttribute;
private String sameSite = "Lax";
/*
* (non-Javadoc)
*
@@ -75,7 +98,8 @@ public class DefaultCookieSerializer implements CookieSerializer {
for (Cookie cookie : cookies) {
if (this.cookieName.equals(cookie.getName())) {
String sessionId = (this.useBase64Encoding
? base64Decode(cookie.getValue()) : cookie.getValue());
? base64Decode(cookie.getValue())
: cookie.getValue());
if (sessionId == null) {
continue;
}
@@ -101,38 +125,43 @@ public class DefaultCookieSerializer implements CookieSerializer {
HttpServletRequest request = cookieValue.getRequest();
HttpServletResponse response = cookieValue.getResponse();
String requestedCookieValue = cookieValue.getCookieValue();
String actualCookieValue = (this.jvmRoute != null)
? requestedCookieValue + this.jvmRoute
: requestedCookieValue;
Cookie sessionCookie = new Cookie(this.cookieName, this.useBase64Encoding
? base64Encode(actualCookieValue) : actualCookieValue);
sessionCookie.setSecure(isSecureCookie(request));
sessionCookie.setPath(getCookiePath(request));
String domainName = getDomainName(request);
if (domainName != null) {
sessionCookie.setDomain(domainName);
StringBuilder sb = new StringBuilder();
sb.append(this.cookieName).append('=');
String value = getValue(cookieValue);
if (value != null && value.length() > 0) {
validateValue(value);
sb.append(value);
}
int maxAge = getMaxAge(cookieValue);
if (maxAge > -1) {
sb.append("; Max-Age=").append(cookieValue.getCookieMaxAge());
OffsetDateTime expires = (maxAge != 0)
? OffsetDateTime.now().plusSeconds(maxAge)
: Instant.EPOCH.atOffset(ZoneOffset.UTC);
sb.append("; Expires=")
.append(expires.format(DateTimeFormatter.RFC_1123_DATE_TIME));
}
String domain = getDomainName(request);
if (domain != null && domain.length() > 0) {
validateDomain(domain);
sb.append("; Domain=").append(domain);
}
String path = getCookiePath(request);
if (path != null && path.length() > 0) {
validatePath(path);
sb.append("; Path=").append(path);
}
if (isSecureCookie(request)) {
sb.append("; Secure");
}
if (this.useHttpOnlyCookie) {
sessionCookie.setHttpOnly(true);
sb.append("; HttpOnly");
}
if (this.sameSite != null) {
sb.append("; SameSite=").append(this.sameSite);
}
if (cookieValue.getCookieMaxAge() < 0) {
if (this.rememberMeRequestAttribute != null
&& request.getAttribute(this.rememberMeRequestAttribute) != null) {
// the cookie is only written at time of session creation, so we rely on
// session expiration rather than cookie expiration if remember me is enabled
cookieValue.setCookieMaxAge(Integer.MAX_VALUE);
}
else if (this.cookieMaxAge != null) {
cookieValue.setCookieMaxAge(this.cookieMaxAge);
}
}
sessionCookie.setMaxAge(cookieValue.getCookieMaxAge());
response.addCookie(sessionCookie);
response.addHeader("Set-Cookie", sb.toString());
}
/**
@@ -163,6 +192,81 @@ public class DefaultCookieSerializer implements CookieSerializer {
return new String(encodedCookieBytes);
}
private String getValue(CookieValue cookieValue) {
String requestedCookieValue = cookieValue.getCookieValue();
String actualCookieValue = requestedCookieValue;
if (this.jvmRoute != null) {
actualCookieValue = requestedCookieValue + this.jvmRoute;
}
if (this.useBase64Encoding) {
actualCookieValue = base64Encode(actualCookieValue);
}
return actualCookieValue;
}
private void validateValue(String value) {
int start = 0;
int end = value.length();
if ((end > 1) && (value.charAt(0) == '"') && (value.charAt(end - 1) == '"')) {
start = 1;
end--;
}
char[] chars = value.toCharArray();
for (int i = start; i < end; i++) {
char c = chars[i];
if (c < 0x21 || c == 0x22 || c == 0x2c || c == 0x3b || c == 0x5c
|| c == 0x7f) {
throw new IllegalArgumentException(
"Invalid character in cookie value: " + Integer.toString(c));
}
}
}
private int getMaxAge(CookieValue cookieValue) {
int maxAge = cookieValue.getCookieMaxAge();
if (maxAge < 0) {
if (this.rememberMeRequestAttribute != null && cookieValue.getRequest()
.getAttribute(this.rememberMeRequestAttribute) != null) {
// the cookie is only written at time of session creation, so we rely on
// session expiration rather than cookie expiration if remember me is
// enabled
cookieValue.setCookieMaxAge(Integer.MAX_VALUE);
}
else if (this.cookieMaxAge != null) {
cookieValue.setCookieMaxAge(this.cookieMaxAge);
}
}
return cookieValue.getCookieMaxAge();
}
private void validateDomain(String domain) {
int i = 0;
int cur = -1;
int prev;
char[] chars = domain.toCharArray();
while (i < chars.length) {
prev = cur;
cur = chars[i];
if (!domainValid.get(cur)
|| ((prev == '.' || prev == -1) && (cur == '.' || cur == '-'))
|| (prev == '-' && cur == '.')) {
throw new IllegalArgumentException("Invalid cookie domain: " + domain);
}
i++;
}
if (cur == '.' || cur == '-') {
throw new IllegalArgumentException("Invalid cookie domain: " + domain);
}
}
private void validatePath(String path) {
for (char ch : path.toCharArray()) {
if (ch < 0x20 || ch > 0x7E || ch == ';') {
throw new IllegalArgumentException("Invalid cookie path: " + path);
}
}
}
/**
* Sets if a Cookie marked as secure should be used. The default is to use the value
* of {@link HttpServletRequest#isSecure()}.
@@ -318,6 +422,16 @@ public class DefaultCookieSerializer implements CookieSerializer {
this.rememberMeRequestAttribute = rememberMeRequestAttribute;
}
/**
* Set the value for the {@code SameSite} cookie directive. The default value is
* {@code Lax}.
* @param sameSite the SameSite directive value
* @since 2.1.0
*/
public void setSameSite(String sameSite) {
this.sameSite = sameSite;
}
private String getDomainName(HttpServletRequest request) {
if (this.domainName != null) {
return this.domainName;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.
@@ -24,8 +24,13 @@ import java.util.Set;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.session.Session;
/**
@@ -33,11 +38,14 @@ import org.springframework.session.Session;
*
* @param <S> the {@link Session} type
* @author Rob Winch
* @author Vedran Pavic
* @since 1.1
*/
@SuppressWarnings("deprecation")
class HttpSessionAdapter<S extends Session> implements HttpSession {
private static final Log logger = LogFactory.getLog(HttpSessionAdapter.class);
private S session;
private final ServletContext servletContext;
@@ -129,7 +137,28 @@ class HttpSessionAdapter<S extends Session> implements HttpSession {
@Override
public void setAttribute(String name, Object value) {
checkState();
Object oldValue = this.session.getAttribute(name);
this.session.setAttribute(name, value);
if (value != oldValue) {
if (oldValue instanceof HttpSessionBindingListener) {
try {
((HttpSessionBindingListener) oldValue).valueUnbound(
new HttpSessionBindingEvent(this, name, oldValue));
}
catch (Throwable th) {
logger.error("Error invoking session binding event listener", th);
}
}
if (value instanceof HttpSessionBindingListener) {
try {
((HttpSessionBindingListener) value)
.valueBound(new HttpSessionBindingEvent(this, name, value));
}
catch (Throwable th) {
logger.error("Error invoking session binding event listener", th);
}
}
}
}
@Override
@@ -140,7 +169,17 @@ class HttpSessionAdapter<S extends Session> implements HttpSession {
@Override
public void removeAttribute(String name) {
checkState();
Object oldValue = this.session.getAttribute(name);
this.session.removeAttribute(name);
if (oldValue instanceof HttpSessionBindingListener) {
try {
((HttpSessionBindingListener) oldValue)
.valueUnbound(new HttpSessionBindingEvent(this, name, oldValue));
}
catch (Throwable th) {
logger.error("Error invoking session binding event listener", th);
}
}
}
@Override

View File

@@ -87,12 +87,6 @@ public class SpringSessionWebSessionStore<S extends Session> implements WebSessi
return Mono.just(session);
}
public Mono<Void> storeSession(WebSession session) {
@SuppressWarnings("unchecked")
SpringSessionWebSession springWebSession = (SpringSessionWebSession) session;
return this.sessions.save(springWebSession.session);
}
@Override
public Mono<WebSession> retrieveSession(String sessionId) {
return this.sessions.findById(sessionId)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.
@@ -17,6 +17,7 @@
package org.springframework.session.security;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@@ -74,7 +75,9 @@ public class SpringSessionBackedSessionRegistryTest {
.getSessionInformation(SESSION_ID);
assertThat(sessionInfo.getSessionId()).isEqualTo(SESSION_ID);
assertThat(sessionInfo.getLastRequest().toInstant()).isEqualTo(NOW);
assertThat(
sessionInfo.getLastRequest().toInstant().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(NOW.truncatedTo(ChronoUnit.MILLIS));
assertThat(sessionInfo.getPrincipal()).isEqualTo(USER_NAME);
assertThat(sessionInfo.isExpired()).isFalse();
}
@@ -90,7 +93,9 @@ public class SpringSessionBackedSessionRegistryTest {
.getSessionInformation(SESSION_ID);
assertThat(sessionInfo.getSessionId()).isEqualTo(SESSION_ID);
assertThat(sessionInfo.getLastRequest().toInstant()).isEqualTo(NOW);
assertThat(
sessionInfo.getLastRequest().toInstant().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(NOW.truncatedTo(ChronoUnit.MILLIS));
assertThat(sessionInfo.getPrincipal()).isEqualTo(USER_NAME);
assertThat(sessionInfo.isExpired()).isTrue();
}
@@ -162,9 +167,8 @@ public class SpringSessionBackedSessionRegistryTest {
Map<String, Session> sessions = new LinkedHashMap<>();
sessions.put(session1.getId(), session1);
sessions.put(session2.getId(), session2);
when(this.sessionRepository.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, USER_NAME))
.thenReturn(sessions);
when(this.sessionRepository.findByPrincipalName(USER_NAME))
.thenReturn(sessions);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.

View File

@@ -26,6 +26,7 @@ import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.mock.web.MockCookie;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.session.web.http.CookieSerializer.CookieValue;
@@ -466,6 +467,39 @@ public class DefaultCookieSerializerTests {
assertThat(getCookie().getMaxAge()).isEqualTo(100);
}
// --- sameSite ---
@Test
public void writeCookieDefaultSameSiteLax() {
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getSameSite()).isEqualTo("Lax");
}
@Test
public void writeCookieSetSameSiteLax() {
this.serializer.setSameSite("Lax");
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getSameSite()).isEqualTo("Lax");
}
@Test
public void writeCookieSetSameSiteStrict() {
this.serializer.setSameSite("Strict");
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getSameSite()).isEqualTo("Strict");
}
@Test
public void writeCookieSetSameSiteNull() {
this.serializer.setSameSite(null);
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getSameSite()).isNull();
}
public void setCookieName(String cookieName) {
this.cookieName = cookieName;
this.serializer.setCookieName(cookieName);
@@ -478,8 +512,8 @@ public class DefaultCookieSerializerTests {
return new Cookie(name, value);
}
private Cookie getCookie() {
return this.response.getCookie(this.cookieName);
private MockCookie getCookie() {
return (MockCookie) this.response.getCookie(this.cookieName);
}
private String getCookieValue() {

View File

@@ -27,6 +27,8 @@ import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
@@ -36,6 +38,8 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionContext;
import org.assertj.core.data.Offset;
@@ -1386,6 +1390,122 @@ public class SessionRepositoryFilterTests {
.hasMessage("httpSessionIdResolver cannot be null");
}
@Test
public void bindingListenerBindListener() throws Exception {
String bindingListenerName = "bindingListener";
CountingHttpSessionBindingListener bindingListener = new CountingHttpSessionBindingListener();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.setAttribute(bindingListenerName, bindingListener);
}
});
assertThat(bindingListener.getCounter()).isEqualTo(1);
}
@Test
public void bindingListenerBindListenerThenUnbind() throws Exception {
String bindingListenerName = "bindingListener";
CountingHttpSessionBindingListener bindingListener = new CountingHttpSessionBindingListener();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.setAttribute(bindingListenerName, bindingListener);
session.removeAttribute(bindingListenerName);
}
});
assertThat(bindingListener.getCounter()).isEqualTo(0);
}
@Test
public void bindingListenerBindSameListenerTwice() throws Exception {
String bindingListenerName = "bindingListener";
CountingHttpSessionBindingListener bindingListener = new CountingHttpSessionBindingListener();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.setAttribute(bindingListenerName, bindingListener);
session.setAttribute(bindingListenerName, bindingListener);
}
});
assertThat(bindingListener.getCounter()).isEqualTo(1);
}
@Test
public void bindingListenerBindListenerOverwrite() throws Exception {
String bindingListenerName = "bindingListener";
CountingHttpSessionBindingListener bindingListener1 = new CountingHttpSessionBindingListener();
CountingHttpSessionBindingListener bindingListener2 = new CountingHttpSessionBindingListener();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.setAttribute(bindingListenerName, bindingListener1);
session.setAttribute(bindingListenerName, bindingListener2);
}
});
assertThat(bindingListener1.getCounter()).isEqualTo(0);
assertThat(bindingListener2.getCounter()).isEqualTo(1);
}
@Test
public void bindingListenerBindThrowsException() throws Exception {
String bindingListenerName = "bindingListener";
CountingHttpSessionBindingListener bindingListener = new CountingHttpSessionBindingListener();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
bindingListener.setThrowException();
session.setAttribute(bindingListenerName, bindingListener);
}
});
assertThat(bindingListener.getCounter()).isEqualTo(0);
}
@Test
public void bindingListenerBindListenerThenUnbindThrowsException() throws Exception {
String bindingListenerName = "bindingListener";
CountingHttpSessionBindingListener bindingListener = new CountingHttpSessionBindingListener();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.setAttribute(bindingListenerName, bindingListener);
bindingListener.setThrowException();
session.removeAttribute(bindingListenerName);
}
});
assertThat(bindingListener.getCounter()).isEqualTo(1);
}
// --- helper methods
private void assertNewSession() {
@@ -1488,4 +1608,39 @@ public class SessionRepositoryFilterTests {
}
private static class CountingHttpSessionBindingListener
implements HttpSessionBindingListener {
private final AtomicInteger counter = new AtomicInteger(0);
private final AtomicBoolean throwException = new AtomicBoolean(false);
@Override
public void valueBound(HttpSessionBindingEvent event) {
if (this.throwException.get()) {
this.throwException.compareAndSet(true, false);
throw new RuntimeException("bind exception");
}
this.counter.incrementAndGet();
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
if (this.throwException.get()) {
this.throwException.compareAndSet(true, false);
throw new RuntimeException("unbind exception");
}
this.counter.decrementAndGet();
}
int getCounter() {
return this.counter.get();
}
void setThrowException() {
this.throwException.compareAndSet(false, true);
}
}
}

View File

@@ -253,17 +253,6 @@ public class SpringSessionWebSessionStoreTests<S extends Session> {
.containsExactly(new AbstractMap.SimpleEntry<>(attrName, attrValue));
}
@Test
public void storeSessionWhenInvokedThenSessionSaved() {
given(this.sessionRepository.save(this.createSession)).willReturn(Mono.empty());
WebSession createdSession = this.webSessionStore.createWebSession()
.block();
this.webSessionStore.storeSession(createdSession).block();
verify(this.sessionRepository).save(this.createSession);
}
@Test
public void retrieveSessionThenStarted() {
String id = "id";

View File

@@ -30,6 +30,7 @@ import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Integration tests for {@link ReactiveRedisOperationsSessionRepository}.
@@ -209,7 +210,10 @@ public class ReactiveRedisOperationsSessionRepositoryITests extends AbstractRedi
this.repository.save(session).block();
toSave.setLastAccessedTime(Instant.now());
this.repository.save(toSave).block();
assertThatExceptionOfType(IllegalStateException.class)
.isThrownBy(() -> this.repository.save(toSave).block())
.withMessage("Session was invalidated");
assertThat(this.repository.findById(sessionId).block()).isNull();
assertThat(this.repository.findById(session.getId()).block()).isNotNull();

View File

@@ -0,0 +1 @@
ryuk.container.timeout=120

View File

@@ -118,6 +118,15 @@ public class ReactiveRedisOperationsSessionRepository implements
this.redisFlushMode = redisFlushMode;
}
/**
* Returns the {@link ReactiveRedisOperations} used for sessions.
* @return the {@link ReactiveRedisOperations} used for sessions
* @since 2.1.0
*/
public ReactiveRedisOperations<String, Object> getSessionRedisOperations() {
return this.sessionRedisOperations;
}
@Override
public Mono<RedisSession> createSession() {
return Mono.defer(() -> {
@@ -147,7 +156,9 @@ public class ReactiveRedisOperationsSessionRepository implements
session.hasChangedSessionId() ? session.originalSessionId
: session.getId());
return this.sessionRedisOperations.hasKey(sessionKey)
.flatMap((exists) -> exists ? result : Mono.empty());
.flatMap((exists) -> exists ? result
: Mono.error(new IllegalStateException(
"Session was invalidated")));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.
@@ -23,14 +23,14 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
/**
* Annotation used to inject the {@link RedisOperations} instance used by Spring Session's
* {@link RedisOperationsSessionRepository}.
* Annotation used to inject the Redis accessor used by Spring Session's Redis session
* repository.
*
* @author Vedran Pavic
* @see org.springframework.session.data.redis.RedisOperationsSessionRepository#getSessionRedisOperations()
* @see org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository#getSessionRedisOperations()
* @since 2.0.0
*/
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,

View File

@@ -21,6 +21,7 @@ import java.util.Map;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -64,6 +65,8 @@ public class RedisWebSessionConfiguration extends SpringWebSessionConfiguration
private ReactiveRedisConnectionFactory redisConnectionFactory;
private RedisSerializer<Object> defaultRedisSerializer;
private ClassLoader classLoader;
private StringValueResolver embeddedValueResolver;
@@ -107,6 +110,13 @@ public class RedisWebSessionConfiguration extends SpringWebSessionConfiguration
this.redisConnectionFactory = redisConnectionFactoryToUse;
}
@Autowired(required = false)
@Qualifier("springSessionDefaultRedisSerializer")
public void setDefaultRedisSerializer(
RedisSerializer<Object> defaultRedisSerializer) {
this.defaultRedisSerializer = defaultRedisSerializer;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
@@ -134,10 +144,11 @@ public class RedisWebSessionConfiguration extends SpringWebSessionConfiguration
private ReactiveRedisTemplate<String, Object> createReactiveRedisTemplate() {
RedisSerializer<String> keySerializer = new StringRedisSerializer();
RedisSerializer<Object> valueSerializer = new JdkSerializationRedisSerializer(
this.classLoader);
RedisSerializer<Object> defaultSerializer = (this.defaultRedisSerializer != null)
? this.defaultRedisSerializer
: new JdkSerializationRedisSerializer(this.classLoader);
RedisSerializationContext<String, Object> serializationContext = RedisSerializationContext
.<String, Object>newSerializationContext(valueSerializer)
.<String, Object>newSerializationContext(defaultSerializer)
.key(keySerializer).hashKey(keySerializer).build();
return new ReactiveRedisTemplate<>(this.redisConnectionFactory,
serializationContext);

View File

@@ -346,12 +346,16 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
.isEqualTo(expected.getAttribute(attribute1));
assertThat(session.<String>getAttribute(attribute2))
.isEqualTo(expected.getAttribute(attribute2));
assertThat(session.getCreationTime()).isEqualTo(expected.getCreationTime());
assertThat(session.getMaxInactiveInterval())
assertThat(session.getCreationTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(expected.getCreationTime()
.truncatedTo(ChronoUnit.MILLIS));
assertThat(session.getMaxInactiveInterval())
.isEqualTo(expected.getMaxInactiveInterval());
assertThat(session.getLastAccessedTime())
.isEqualTo(expected.getLastAccessedTime());
}).verifyComplete();
assertThat(
session.getLastAccessedTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(expected.getLastAccessedTime()
.truncatedTo(ChronoUnit.MILLIS));
}).verifyComplete();
}
@Test

View File

@@ -432,12 +432,12 @@ public class RedisOperationsSessionRepositoryTests {
.isEqualTo(expected.getAttribute(attribute1));
assertThat(session.<String>getAttribute(attribute2))
.isEqualTo(expected.getAttribute(attribute2));
assertThat(session.getCreationTime()).isEqualTo(expected.getCreationTime());
assertThat(session.getCreationTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(expected.getCreationTime().truncatedTo(ChronoUnit.MILLIS));
assertThat(session.getMaxInactiveInterval())
.isEqualTo(expected.getMaxInactiveInterval());
assertThat(session.getLastAccessedTime())
.isEqualTo(expected.getLastAccessedTime());
assertThat(session.getLastAccessedTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(expected.getLastAccessedTime().truncatedTo(ChronoUnit.MILLIS));
}
@Test
@@ -498,9 +498,11 @@ public class RedisOperationsSessionRepositoryTests {
RedisSession session = sessionIdToSessions.get(sessionId);
assertThat(session).isNotNull();
assertThat(session.getId()).isEqualTo(sessionId);
assertThat(session.getLastAccessedTime()).isEqualTo(lastAccessed);
assertThat(session.getLastAccessedTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(lastAccessed.truncatedTo(ChronoUnit.MILLIS));
assertThat(session.getMaxInactiveInterval()).isEqualTo(maxInactive);
assertThat(session.getCreationTime()).isEqualTo(createdTime);
assertThat(session.getCreationTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(createdTime.truncatedTo(ChronoUnit.MILLIS));
}
@Test

View File

@@ -27,9 +27,12 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
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.data.redis.ReactiveRedisOperationsSessionRepository;
import org.springframework.session.data.redis.RedisFlushMode;
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisOperations;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -70,6 +73,22 @@ public class RedisWebSessionConfigurationTests {
assertThat(repository).isNotNull();
}
@Test
public void springSessionRedisOperationsResolvingConfiguration() {
registerAndRefresh(RedisConfig.class,
SpringSessionRedisOperationsResolvingConfig.class);
ReactiveRedisOperationsSessionRepository repository = this.context
.getBean(ReactiveRedisOperationsSessionRepository.class);
assertThat(repository).isNotNull();
ReactiveRedisOperations<String, Object> springSessionRedisOperations = this.context
.getBean(SpringSessionRedisOperationsResolvingConfig.class)
.getSpringSessionRedisOperations();
assertThat(springSessionRedisOperations).isNotNull();
assertThat((ReactiveRedisOperations) ReflectionTestUtils.getField(repository,
"sessionRedisOperations")).isEqualTo(springSessionRedisOperations);
}
@Test
public void customNamespace() {
registerAndRefresh(RedisConfig.class, CustomNamespaceConfig.class);
@@ -181,6 +200,36 @@ public class RedisWebSessionConfigurationTests {
.hasMessageContaining("expected single matching bean but found 2");
}
@Test
@SuppressWarnings("unchecked")
public void customRedisSerializerConfig() {
registerAndRefresh(RedisConfig.class, CustomRedisSerializerConfig.class);
ReactiveRedisOperationsSessionRepository repository = this.context
.getBean(ReactiveRedisOperationsSessionRepository.class);
RedisSerializer<Object> redisSerializer = this.context
.getBean("springSessionDefaultRedisSerializer", RedisSerializer.class);
assertThat(repository).isNotNull();
assertThat(redisSerializer).isNotNull();
ReactiveRedisOperations redisOperations = (ReactiveRedisOperations) ReflectionTestUtils
.getField(repository, "sessionRedisOperations");
assertThat(redisOperations).isNotNull();
RedisSerializationContext serializationContext = redisOperations
.getSerializationContext();
assertThat(ReflectionTestUtils.getField(
serializationContext.getValueSerializationPair().getReader(),
"serializer")).isEqualTo(redisSerializer);
assertThat(ReflectionTestUtils.getField(
serializationContext.getValueSerializationPair().getWriter(),
"serializer")).isEqualTo(redisSerializer);
assertThat(ReflectionTestUtils.getField(
serializationContext.getHashValueSerializationPair().getReader(),
"serializer")).isEqualTo(redisSerializer);
assertThat(ReflectionTestUtils.getField(
serializationContext.getHashValueSerializationPair().getWriter(),
"serializer")).isEqualTo(redisSerializer);
}
private void registerAndRefresh(Class<?>... annotatedClasses) {
this.context.register(annotatedClasses);
this.context.refresh();
@@ -201,6 +250,18 @@ public class RedisWebSessionConfigurationTests {
}
@EnableRedisWebSession
static class SpringSessionRedisOperationsResolvingConfig {
@SpringSessionRedisOperations
private ReactiveRedisOperations<String, Object> springSessionRedisOperations;
public ReactiveRedisOperations<String, Object> getSpringSessionRedisOperations() {
return this.springSessionRedisOperations;
}
}
@EnableRedisWebSession(redisNamespace = REDIS_NAMESPACE)
static class CustomNamespaceConfig {
@@ -275,4 +336,15 @@ public class RedisWebSessionConfigurationTests {
}
@EnableRedisWebSession
static class CustomRedisSerializerConfig {
@Bean
@SuppressWarnings("unchecked")
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return mock(RedisSerializer.class);
}
}
}

View File

@@ -3,6 +3,7 @@ apply plugin: 'io.spring.convention.spring-module'
dependencies {
compile project(':spring-session-core')
compile "com.hazelcast:hazelcast"
compile "javax.annotation:javax.annotation-api"
compile "org.springframework:spring-context"
testCompile "javax.servlet:javax.servlet-api"

View File

@@ -48,7 +48,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
public class HazelcastClientRepositoryITests extends AbstractHazelcastRepositoryITests {
private static GenericContainer container = new GenericContainer<>(
"hazelcast/hazelcast:3.9.4")
"hazelcast/hazelcast:3.10.6")
.withExposedPorts(5701)
.withEnv("JAVA_OPTS",
"-Dhazelcast.config=/opt/hazelcast/config_ext/hazelcast.xml")

View File

@@ -0,0 +1 @@
ryuk.container.timeout=120

View File

@@ -20,6 +20,7 @@ import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import com.hazelcast.core.Offloadable;
import com.hazelcast.map.AbstractEntryProcessor;
import com.hazelcast.map.EntryProcessor;
@@ -32,7 +33,8 @@ import org.springframework.session.MapSession;
* @since 2.0.5
* @see HazelcastSessionRepository#save(HazelcastSessionRepository.HazelcastSession)
*/
class SessionUpdateEntryProcessor extends AbstractEntryProcessor<String, MapSession> {
class SessionUpdateEntryProcessor extends AbstractEntryProcessor<String, MapSession>
implements Offloadable {
private Instant lastAccessedTime;
@@ -66,6 +68,11 @@ class SessionUpdateEntryProcessor extends AbstractEntryProcessor<String, MapSess
return Boolean.TRUE;
}
@Override
public String getExecutorName() {
return OFFLOADABLE_EXECUTOR;
}
void setLastAccessedTime(Instant lastAccessedTime) {
this.lastAccessedTime = lastAccessedTime;
}

View File

@@ -13,6 +13,7 @@ dependencies {
integrationTestCompile "com.h2database:h2"
integrationTestCompile "com.microsoft.sqlserver:mssql-jdbc"
integrationTestCompile "com.zaxxer:HikariCP"
integrationTestCompile "mysql:mysql-connector-java"
integrationTestCompile "org.apache.derby:derby"
integrationTestCompile "org.hsqldb:hsqldb"
@@ -21,5 +22,6 @@ dependencies {
integrationTestCompile "org.testcontainers:mariadb"
integrationTestCompile "org.testcontainers:mssqlserver"
integrationTestCompile "org.testcontainers:mysql"
integrationTestCompile "org.testcontainers:oracle-xe"
integrationTestCompile "org.testcontainers:postgresql"
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2014-2018 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.session.jdbc;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
import org.testcontainers.containers.JdbcDatabaseContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.DatabasePopulator;
/**
* Abstract base class for Testcontainers based {@link JdbcOperationsSessionRepository}
* integration tests.
*
* @author Vedran Pavic
*/
public abstract class AbstractContainerJdbcOperationsSessionRepositoryITests
extends AbstractJdbcOperationsSessionRepositoryITests {
static class BaseContainerConfig extends BaseConfig {
@Bean
public HikariDataSource dataSource(JdbcDatabaseContainer databaseContainer) {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(databaseContainer.getJdbcUrl());
dataSource.setUsername(databaseContainer.getUsername());
dataSource.setPassword(databaseContainer.getPassword());
return dataSource;
}
@Bean
public DataSourceInitializer dataSourceInitializer(DataSource dataSource,
DatabasePopulator databasePopulator) {
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(databasePopulator);
return initializer;
}
}
}

View File

@@ -21,6 +21,7 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.UUID;
import java.util.function.Supplier;
import javax.sql.DataSource;
@@ -38,6 +39,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
@@ -47,7 +49,6 @@ import static org.assertj.core.api.Assertions.assertThat;
* Abstract base class for {@link JdbcOperationsSessionRepository} integration tests.
*
* @author Vedran Pavic
* @since 1.2.0
*/
public abstract class AbstractJdbcOperationsSessionRepositoryITests {
@@ -63,7 +64,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
private SecurityContext changedContext;
@Before
public void setup() throws Exception {
public void setUp() {
this.context = SecurityContextHolder.createEmptyContext();
this.context.setAuthentication(
new UsernamePasswordAuthenticationToken("username-" + UUID.randomUUID(),
@@ -76,12 +77,13 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@Test
public void saveWhenNoAttributesThenCanBeFound() throws Exception {
public void saveWhenNoAttributesThenCanBeFound() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
this.repository.save(toSave);
JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(toSave.getId());
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.findById(toSave.getId());
assertThat(session).isNotNull();
assertThat(session.isChanged()).isFalse();
@@ -89,7 +91,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@Test
public void saves() throws InterruptedException {
public void saves() {
String username = "saves-" + System.currentTimeMillis();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
@@ -106,7 +108,8 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
this.repository.save(toSave);
JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(toSave.getId());
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.findById(toSave.getId());
assertThat(session.getId()).isEqualTo(toSave.getId());
assertThat(session.isChanged()).isFalse();
@@ -143,7 +146,8 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
this.repository.save(toSave);
toSave = this.repository.findById(toSave.getId());
JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(toSave.getId());
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.findById(toSave.getId());
assertThat(session.isChanged()).isFalse();
assertThat(session.getDelta()).isEmpty();
assertThat(session.getAttributeNames().size()).isEqualTo(2);
@@ -157,8 +161,8 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
public void updateLastAccessedTime() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setLastAccessedTime(Instant.now().minusSeconds(
MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS + 1));
toSave.setLastAccessedTime(Instant.now()
.minusSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS + 1));
this.repository.save(toSave);
@@ -166,17 +170,19 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
toSave.setLastAccessedTime(lastAccessedTime);
this.repository.save(toSave);
JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(toSave.getId());
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.findById(toSave.getId());
assertThat(session).isNotNull();
assertThat(session.isChanged()).isFalse();
assertThat(session.getDelta()).isEmpty();
assertThat(session.isExpired()).isFalse();
assertThat(session.getLastAccessedTime()).isEqualTo(lastAccessedTime);
assertThat(session.getLastAccessedTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(lastAccessedTime.truncatedTo(ChronoUnit.MILLIS));
}
@Test
public void findByPrincipalName() throws Exception {
public void findByPrincipalName() {
String principalName = "findByPrincipalName" + UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
@@ -200,14 +206,14 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@Test
public void findByPrincipalNameExpireRemovesIndex() throws Exception {
public void findByPrincipalNameExpireRemovesIndex() {
String principalName = "findByPrincipalNameExpireRemovesIndex"
+ UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
toSave.setLastAccessedTime(Instant.now().minusSeconds(
MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS + 1));
toSave.setLastAccessedTime(Instant.now()
.minusSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS + 1));
this.repository.save(toSave);
this.repository.cleanUpExpiredSessions();
@@ -220,7 +226,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@Test
public void findByPrincipalNameNoPrincipalNameChange() throws Exception {
public void findByPrincipalNameNoPrincipalNameChange() {
String principalName = "findByPrincipalNameNoPrincipalNameChange"
+ UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
@@ -244,7 +250,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@Test
public void findByPrincipalNameNoPrincipalNameChangeReload() throws Exception {
public void findByPrincipalNameNoPrincipalNameChangeReload() {
String principalName = "findByPrincipalNameNoPrincipalNameChangeReload"
+ UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
@@ -270,7 +276,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@Test
public void findByDeletedPrincipalName() throws Exception {
public void findByDeletedPrincipalName() {
String principalName = "findByDeletedPrincipalName" + UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
@@ -288,7 +294,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@Test
public void findByChangedPrincipalName() throws Exception {
public void findByChangedPrincipalName() {
String principalName = "findByChangedPrincipalName" + UUID.randomUUID();
String principalNameChanged = "findByChangedPrincipalName" + UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
@@ -316,7 +322,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@Test
public void findByDeletedPrincipalNameReload() throws Exception {
public void findByDeletedPrincipalNameReload() {
String principalName = "findByDeletedPrincipalName" + UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
@@ -336,7 +342,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@Test
public void findByChangedPrincipalNameReload() throws Exception {
public void findByChangedPrincipalNameReload() {
String principalName = "findByChangedPrincipalName" + UUID.randomUUID();
String principalNameChanged = "findByChangedPrincipalName" + UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
@@ -367,7 +373,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@Test
public void findBySecurityPrincipalName() throws Exception {
public void findBySecurityPrincipalName() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
@@ -390,12 +396,12 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@Test
public void findBySecurityPrincipalNameExpireRemovesIndex() throws Exception {
public void findBySecurityPrincipalNameExpireRemovesIndex() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
toSave.setLastAccessedTime(Instant.now().minusSeconds(
MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS + 1));
toSave.setLastAccessedTime(Instant.now()
.minusSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS + 1));
this.repository.save(toSave);
this.repository.cleanUpExpiredSessions();
@@ -408,7 +414,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@Test
public void findByPrincipalNameNoSecurityPrincipalNameChange() throws Exception {
public void findByPrincipalNameNoSecurityPrincipalNameChange() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
@@ -430,8 +436,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@Test
public void findByPrincipalNameNoSecurityPrincipalNameChangeReload()
throws Exception {
public void findByPrincipalNameNoSecurityPrincipalNameChangeReload() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
@@ -455,7 +460,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@Test
public void findByDeletedSecurityPrincipalName() throws Exception {
public void findByDeletedSecurityPrincipalName() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
@@ -472,7 +477,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@Test
public void findByChangedSecurityPrincipalName() throws Exception {
public void findByChangedSecurityPrincipalName() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
@@ -498,7 +503,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@Test
public void findByDeletedSecurityPrincipalNameReload() throws Exception {
public void findByDeletedSecurityPrincipalNameReload() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
@@ -517,7 +522,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@Test
public void findByChangedSecurityPrincipalNameReload() throws Exception {
public void findByChangedSecurityPrincipalNameReload() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
@@ -604,15 +609,17 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@Test
public void changeSessionIdWhenOnlyChangeId() throws Exception {
public void changeSessionIdWhenOnlyChangeId() {
String attrName = "changeSessionId";
String attrValue = "changeSessionId-value";
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
toSave.setAttribute(attrName, attrValue);
this.repository.save(toSave);
JdbcOperationsSessionRepository.JdbcSession findById = this.repository.findById(toSave.getId());
JdbcOperationsSessionRepository.JdbcSession findById = this.repository
.findById(toSave.getId());
assertThat(findById.<String>getAttribute(attrName)).isEqualTo(attrValue);
@@ -623,16 +630,19 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(this.repository.findById(originalFindById)).isNull();
JdbcOperationsSessionRepository.JdbcSession findByChangeSessionId = this.repository.findById(changeSessionId);
JdbcOperationsSessionRepository.JdbcSession findByChangeSessionId = this.repository
.findById(changeSessionId);
assertThat(findByChangeSessionId.isChanged()).isFalse();
assertThat(findByChangeSessionId.getDelta()).isEmpty();
assertThat(findByChangeSessionId.<String>getAttribute(attrName)).isEqualTo(attrValue);
assertThat(findByChangeSessionId.<String>getAttribute(attrName))
.isEqualTo(attrValue);
}
@Test
public void changeSessionIdWhenChangeTwice() throws Exception {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
public void changeSessionIdWhenChangeTwice() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
this.repository.save(toSave);
@@ -648,15 +658,17 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@Test
public void changeSessionIdWhenSetAttributeOnChangedSession() throws Exception {
public void changeSessionIdWhenSetAttributeOnChangedSession() {
String attrName = "changeSessionId";
String attrValue = "changeSessionId-value";
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
this.repository.save(toSave);
JdbcOperationsSessionRepository.JdbcSession findById = this.repository.findById(toSave.getId());
JdbcOperationsSessionRepository.JdbcSession findById = this.repository
.findById(toSave.getId());
findById.setAttribute(attrName, attrValue);
@@ -667,19 +679,19 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(this.repository.findById(originalFindById)).isNull();
JdbcOperationsSessionRepository.JdbcSession findByChangeSessionId = this.repository.findById(changeSessionId);
JdbcOperationsSessionRepository.JdbcSession findByChangeSessionId = this.repository
.findById(changeSessionId);
assertThat(findByChangeSessionId.isChanged()).isFalse();
assertThat(findByChangeSessionId.getDelta()).isEmpty();
assertThat(findByChangeSessionId.<String>getAttribute(attrName)).isEqualTo(attrValue);
assertThat(findByChangeSessionId.<String>getAttribute(attrName))
.isEqualTo(attrValue);
}
@Test
public void changeSessionIdWhenHasNotSaved() throws Exception {
String attrName = "changeSessionId";
String attrValue = "changeSessionId-value";
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
public void changeSessionIdWhenHasNotSaved() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
.createSession();
String originalId = toSave.getId();
toSave.changeSessionId();
@@ -691,7 +703,8 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test // gh-1070
public void saveUpdatedAddAndModifyAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.createSession();
this.repository.save(session);
session = this.repository.findById(session.getId());
session.setAttribute("testName", "testValue1");
@@ -704,7 +717,8 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test // gh-1070
public void saveUpdatedAddAndRemoveAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.createSession();
this.repository.save(session);
session = this.repository.findById(session.getId());
session.setAttribute("testName", "testValue");
@@ -717,7 +731,8 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test // gh-1070
public void saveUpdatedModifyAndRemoveAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.createSession();
session.setAttribute("testName", "testValue1");
this.repository.save(session);
session = this.repository.findById(session.getId());
@@ -731,7 +746,8 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test // gh-1070
public void saveUpdatedRemoveAndAddAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.createSession();
session.setAttribute("testName", "testValue1");
this.repository.save(session);
session = this.repository.findById(session.getId());
@@ -743,9 +759,10 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(session.<String>getAttribute("testName")).isEqualTo("testValue2");
}
@Test // gh-1151
@Test // gh-1031
public void saveDeleted() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.createSession();
this.repository.save(session);
session = this.repository.findById(session.getId());
this.repository.deleteById(session.getId());
@@ -755,9 +772,10 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(this.repository.findById(session.getId())).isNull();
}
@Test // gh-1151
@Test // gh-1031
public void saveDeletedAddAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.createSession();
this.repository.save(session);
session = this.repository.findById(session.getId());
this.repository.deleteById(session.getId());
@@ -768,6 +786,31 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(this.repository.findById(session.getId())).isNull();
}
@Test // gh-1133
public void sessionFromStoreResolvesAttributesLazily() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.createSession();
session.setAttribute("attribute1", "value1");
session.setAttribute("attribute2", "value2");
this.repository.save(session);
session = this.repository.findById(session.getId());
MapSession delegate = (MapSession) ReflectionTestUtils.getField(session,
"delegate");
assertThat((String) session.getAttribute("attribute1")).isEqualTo("value1");
assertThat(delegate).isNotNull();
assertThat(ReflectionTestUtils
.getField((Supplier) delegate.getAttribute("attribute1"), "value"))
.isEqualTo("value1");
assertThat(ReflectionTestUtils
.getField((Supplier) delegate.getAttribute("attribute2"), "value"))
.isNull();
assertThat((String) session.getAttribute("attribute2")).isEqualTo("value2");
assertThat(ReflectionTestUtils
.getField((Supplier) delegate.getAttribute("attribute2"), "value"))
.isEqualTo("value2");
}
@Test // gh-1203
public void saveWithLargeAttribute() {
String attributeName = "largeAttribute";
@@ -792,7 +835,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@EnableJdbcHttpSession
protected static class BaseConfig {
static class BaseConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
@@ -800,4 +843,5 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
}
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright 2014-2018 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.session.jdbc;
import org.testcontainers.containers.JdbcDatabaseContainer;
import org.testcontainers.containers.MSSQLServerContainer;
import org.testcontainers.containers.MariaDBContainer;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.containers.OracleContainer;
import org.testcontainers.containers.PostgreSQLContainer;
/**
* Factories for various {@link JdbcDatabaseContainer}s.
*
* @author Vedran Pavic
*/
final class DatabaseContainers {
private DatabaseContainers() {
}
static MariaDBContainer mariaDb5() {
return new MariaDb5Container();
}
static MariaDBContainer mariaDb10() {
return new MariaDb10Container();
}
static MySQLContainer mySql5() {
return new MySql5Container();
}
static MySQLContainer mySql8() {
return new MySql8Container();
}
static OracleContainer oracle() {
return new OracleContainer();
}
static PostgreSQLContainer postgreSql9() {
return new PostgreSql9Container();
}
static PostgreSQLContainer postgreSql10() {
return new PostgreSql10Container();
}
static MSSQLServerContainer sqlServer2017() {
return new SqlServer2017Container();
}
private static class MariaDb5Container extends MariaDBContainer<MariaDb5Container> {
MariaDb5Container() {
super("mariadb:5.5.61");
}
@Override
protected void configure() {
super.configure();
setCommand("mysqld", "--character-set-server=utf8mb4",
"--collation-server=utf8mb4_unicode_ci", "--innodb_large_prefix",
"--innodb_file_format=barracuda", "--innodb-file-per-table");
}
}
private static class MariaDb10Container extends MariaDBContainer<MariaDb10Container> {
MariaDb10Container() {
super("mariadb:10.3.10");
}
@Override
protected void configure() {
super.configure();
setCommand("mysqld", "--character-set-server=utf8mb4",
"--collation-server=utf8mb4_unicode_ci");
}
}
private static class MySql5Container extends MySQLContainer<MySql5Container> {
MySql5Container() {
super("mysql:5.7.23");
}
@Override
protected void configure() {
super.configure();
setCommand("mysqld", "--character-set-server=utf8mb4",
"--collation-server=utf8mb4_unicode_ci");
}
@Override
public String getDriverClassName() {
return "com.mysql.cj.jdbc.Driver";
}
}
private static class MySql8Container extends MySQLContainer<MySql8Container> {
MySql8Container() {
super("mysql:8.0.12");
}
@Override
protected void configure() {
super.configure();
setCommand("mysqld", "--default-authentication-plugin=mysql_native_password");
}
@Override
public String getDriverClassName() {
return "com.mysql.cj.jdbc.Driver";
}
}
private static class PostgreSql9Container
extends PostgreSQLContainer<PostgreSql9Container> {
PostgreSql9Container() {
super("postgres:9.6.10");
}
}
private static class PostgreSql10Container
extends PostgreSQLContainer<PostgreSql10Container> {
PostgreSql10Container() {
super("postgres:10.5");
}
}
private static class SqlServer2017Container
extends MSSQLServerContainer<SqlServer2017Container> {
SqlServer2017Container() {
super("microsoft/mssql-server-linux:2017-CU11");
}
@Override
protected void configure() {
super.configure();
withStartupTimeoutSeconds(240);
withConnectTimeoutSeconds(240);
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2014-2018 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.session.jdbc;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.init.DatabasePopulator;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
/**
* Factories for various {@link DatabasePopulator}s.
*
* @author Vedran Pavic
*/
final class DatabasePopulators {
private DatabasePopulators() {
}
static ResourceDatabasePopulator mySql() {
return new ResourceDatabasePopulator(new ClassPathResource(
"org/springframework/session/jdbc/schema-mysql.sql"));
}
static ResourceDatabasePopulator oracle() {
return new ResourceDatabasePopulator(new ClassPathResource(
"org/springframework/session/jdbc/schema-oracle.sql"));
}
static ResourceDatabasePopulator postgreSql() {
return new ResourceDatabasePopulator(new ClassPathResource(
"org/springframework/session/jdbc/schema-postgresql.sql"));
}
static ResourceDatabasePopulator sqlServer() {
return new ResourceDatabasePopulator(new ClassPathResource(
"org/springframework/session/jdbc/schema-sqlserver.sql"));
}
}

View File

@@ -16,20 +16,11 @@
package org.springframework.session.jdbc;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.mariadb.jdbc.MariaDbDataSource;
import org.testcontainers.containers.MariaDBContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@@ -45,55 +36,21 @@ import org.springframework.test.context.web.WebAppConfiguration;
@WebAppConfiguration
@ContextConfiguration
public class MariaDb10JdbcOperationsSessionRepositoryITests
extends AbstractJdbcOperationsSessionRepositoryITests {
private static MariaDBContainer container = new MariaDb10Container();
@BeforeClass
public static void setUpClass() {
container.start();
}
@AfterClass
public static void tearDownClass() {
container.stop();
}
extends AbstractContainerJdbcOperationsSessionRepositoryITests {
@Configuration
static class Config extends BaseConfig {
static class Config extends BaseContainerConfig {
@Bean
public DataSource dataSource() throws SQLException {
MariaDbDataSource dataSource = new MariaDbDataSource(container.getJdbcUrl());
dataSource.setUserName(container.getUsername());
dataSource.setPassword(container.getPassword());
return dataSource;
public MariaDBContainer databaseContainer() {
MariaDBContainer databaseContainer = DatabaseContainers.mariaDb10();
databaseContainer.start();
return databaseContainer;
}
@Bean
public DataSourceInitializer initializer(DataSource dataSource,
ResourceLoader resourceLoader) {
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(
new ResourceDatabasePopulator(resourceLoader.getResource(
"classpath:org/springframework/session/jdbc/schema-mysql.sql")));
return initializer;
}
}
private static class MariaDb10Container extends MariaDBContainer<MariaDb10Container> {
MariaDb10Container() {
super("mariadb:10.3.10");
}
@Override
protected void configure() {
super.configure();
setCommand("mysqld", "--character-set-server=utf8mb4",
"--collation-server=utf8mb4_unicode_ci");
public ResourceDatabasePopulator databasePopulator() {
return DatabasePopulators.mySql();
}
}

View File

@@ -16,20 +16,11 @@
package org.springframework.session.jdbc;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.mariadb.jdbc.MariaDbDataSource;
import org.testcontainers.containers.MariaDBContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@@ -45,56 +36,21 @@ import org.springframework.test.context.web.WebAppConfiguration;
@WebAppConfiguration
@ContextConfiguration
public class MariaDb5JdbcOperationsSessionRepositoryITests
extends AbstractJdbcOperationsSessionRepositoryITests {
private static MariaDBContainer container = new MariaDb5Container();
@BeforeClass
public static void setUpClass() {
container.start();
}
@AfterClass
public static void tearDownClass() {
container.stop();
}
extends AbstractContainerJdbcOperationsSessionRepositoryITests {
@Configuration
static class Config extends BaseConfig {
static class Config extends BaseContainerConfig {
@Bean
public DataSource dataSource() throws SQLException {
MariaDbDataSource dataSource = new MariaDbDataSource(container.getJdbcUrl());
dataSource.setUserName(container.getUsername());
dataSource.setPassword(container.getPassword());
return dataSource;
public MariaDBContainer databaseContainer() {
MariaDBContainer databaseContainer = DatabaseContainers.mariaDb5();
databaseContainer.start();
return databaseContainer;
}
@Bean
public DataSourceInitializer initializer(DataSource dataSource,
ResourceLoader resourceLoader) {
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(
new ResourceDatabasePopulator(resourceLoader.getResource(
"classpath:org/springframework/session/jdbc/schema-mysql.sql")));
return initializer;
}
}
private static class MariaDb5Container extends MariaDBContainer<MariaDb5Container> {
MariaDb5Container() {
super("mariadb:5.5.61");
}
@Override
protected void configure() {
super.configure();
setCommand("mysqld", "--character-set-server=utf8mb4",
"--collation-server=utf8mb4_unicode_ci", "--innodb_large_prefix",
"--innodb_file_format=barracuda", "--innodb-file-per-table");
public ResourceDatabasePopulator databasePopulator() {
return DatabasePopulators.mySql();
}
}

View File

@@ -16,19 +16,11 @@
package org.springframework.session.jdbc;
import javax.sql.DataSource;
import com.mysql.cj.jdbc.Driver;
import com.mysql.cj.jdbc.MysqlDataSource;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.testcontainers.containers.MySQLContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@@ -43,61 +35,21 @@ import org.springframework.test.context.web.WebAppConfiguration;
@WebAppConfiguration
@ContextConfiguration
public class MySql5JdbcOperationsSessionRepositoryITests
extends AbstractJdbcOperationsSessionRepositoryITests {
private static MySQLContainer container = new MySql5Container();
@BeforeClass
public static void setUpClass() {
container.start();
}
@AfterClass
public static void tearDownClass() {
container.stop();
}
extends AbstractContainerJdbcOperationsSessionRepositoryITests {
@Configuration
static class Config extends BaseConfig {
static class Config extends BaseContainerConfig {
@Bean
public DataSource dataSource() {
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setUrl(container.getJdbcUrl());
dataSource.setUser(container.getUsername());
dataSource.setPassword(container.getPassword());
return dataSource;
public MySQLContainer databaseContainer() {
MySQLContainer databaseContainer = DatabaseContainers.mySql5();
databaseContainer.start();
return databaseContainer;
}
@Bean
public DataSourceInitializer initializer(DataSource dataSource,
ResourceLoader resourceLoader) {
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(
new ResourceDatabasePopulator(resourceLoader.getResource(
"classpath:org/springframework/session/jdbc/schema-mysql.sql")));
return initializer;
}
}
private static class MySql5Container extends MySQLContainer<MySql5Container> {
MySql5Container() {
super("mysql:5.7.23");
}
@Override
protected void configure() {
super.configure();
setCommand("mysqld", "--character-set-server=utf8mb4",
"--collation-server=utf8mb4_unicode_ci");
}
@Override
public String getDriverClassName() {
return Driver.class.getName();
public ResourceDatabasePopulator databasePopulator() {
return DatabasePopulators.mySql();
}
}

View File

@@ -16,19 +16,11 @@
package org.springframework.session.jdbc;
import javax.sql.DataSource;
import com.mysql.cj.jdbc.Driver;
import com.mysql.cj.jdbc.MysqlDataSource;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.testcontainers.containers.MySQLContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@@ -43,60 +35,21 @@ import org.springframework.test.context.web.WebAppConfiguration;
@WebAppConfiguration
@ContextConfiguration
public class MySql8JdbcOperationsSessionRepositoryITests
extends AbstractJdbcOperationsSessionRepositoryITests {
private static MySQLContainer container = new MySql8Container();
@BeforeClass
public static void setUpClass() {
container.start();
}
@AfterClass
public static void tearDownClass() {
container.stop();
}
extends AbstractContainerJdbcOperationsSessionRepositoryITests {
@Configuration
static class Config extends BaseConfig {
static class Config extends BaseContainerConfig {
@Bean
public DataSource dataSource() {
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setUrl(container.getJdbcUrl());
dataSource.setUser(container.getUsername());
dataSource.setPassword(container.getPassword());
return dataSource;
public MySQLContainer databaseContainer() {
MySQLContainer databaseContainer = DatabaseContainers.mySql8();
databaseContainer.start();
return databaseContainer;
}
@Bean
public DataSourceInitializer initializer(DataSource dataSource,
ResourceLoader resourceLoader) {
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(
new ResourceDatabasePopulator(resourceLoader.getResource(
"classpath:org/springframework/session/jdbc/schema-mysql.sql")));
return initializer;
}
}
private static class MySql8Container extends MySQLContainer<MySql8Container> {
MySql8Container() {
super("mysql:8.0.12");
}
@Override
protected void configure() {
super.configure();
setCommand("mysqld", "--default-authentication-plugin=mysql_native_password");
}
@Override
public String getDriverClassName() {
return Driver.class.getName();
public ResourceDatabasePopulator databasePopulator() {
return DatabasePopulators.mySql();
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2014-2018 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.session.jdbc;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.testcontainers.containers.OracleContainer;
import org.testcontainers.utility.TestcontainersConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.util.ClassUtils;
/**
* Integration tests for {@link JdbcOperationsSessionRepository} using Oracle database.
* <p>
* This test is conditional on presence of Oracle JDBC driver on the classpath and
* Testcontainers property {@code oracle.container.image} being set.
*
* @author Vedran Pavic
*/
@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration
public class OracleJdbcOperationsSessionRepositoryITests
extends AbstractContainerJdbcOperationsSessionRepositoryITests {
@BeforeClass
public static void setUpClass() {
Assume.assumeTrue("Oracle JDBC driver is present on the classpath",
ClassUtils.isPresent("oracle.jdbc.OracleDriver", null));
Assume.assumeTrue("Testcontainers property `oracle.container.image` is set",
TestcontainersConfiguration.getInstance().getProperties()
.getProperty("oracle.container.image") != null);
}
@Configuration
static class Config extends BaseContainerConfig {
@Bean
public OracleContainer databaseContainer() {
OracleContainer databaseContainer = DatabaseContainers.oracle();
databaseContainer.start();
return databaseContainer;
}
@Bean
public ResourceDatabasePopulator databasePopulator() {
return DatabasePopulators.oracle();
}
}
}

View File

@@ -16,18 +16,11 @@
package org.springframework.session.jdbc;
import javax.sql.DataSource;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.postgresql.ds.PGSimpleDataSource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@@ -43,50 +36,21 @@ import org.springframework.test.context.web.WebAppConfiguration;
@WebAppConfiguration
@ContextConfiguration
public class PostgreSql10JdbcOperationsSessionRepositoryITests
extends AbstractJdbcOperationsSessionRepositoryITests {
private static PostgreSQLContainer container = new PostgreSql10Container();
@BeforeClass
public static void setUpClass() {
container.start();
}
@AfterClass
public static void tearDownClass() {
container.stop();
}
extends AbstractContainerJdbcOperationsSessionRepositoryITests {
@Configuration
static class Config extends BaseConfig {
static class Config extends BaseContainerConfig {
@Bean
public DataSource dataSource() {
PGSimpleDataSource dataSource = new PGSimpleDataSource();
dataSource.setUrl(container.getJdbcUrl());
dataSource.setUser(container.getUsername());
dataSource.setPassword(container.getPassword());
return dataSource;
public PostgreSQLContainer databaseContainer() {
PostgreSQLContainer databaseContainer = DatabaseContainers.postgreSql10();
databaseContainer.start();
return databaseContainer;
}
@Bean
public DataSourceInitializer initializer(DataSource dataSource,
ResourceLoader resourceLoader) {
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(
new ResourceDatabasePopulator(resourceLoader.getResource(
"classpath:org/springframework/session/jdbc/schema-postgresql.sql")));
return initializer;
}
}
private static class PostgreSql10Container
extends PostgreSQLContainer<PostgreSql10Container> {
PostgreSql10Container() {
super("postgres:10.5");
public ResourceDatabasePopulator databasePopulator() {
return DatabasePopulators.postgreSql();
}
}

View File

@@ -16,18 +16,11 @@
package org.springframework.session.jdbc;
import javax.sql.DataSource;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.postgresql.ds.PGSimpleDataSource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@@ -43,50 +36,21 @@ import org.springframework.test.context.web.WebAppConfiguration;
@WebAppConfiguration
@ContextConfiguration
public class PostgreSql9JdbcOperationsSessionRepositoryITests
extends AbstractJdbcOperationsSessionRepositoryITests {
private static PostgreSQLContainer container = new PostgreSql9Container();
@BeforeClass
public static void setUpClass() {
container.start();
}
@AfterClass
public static void tearDownClass() {
container.stop();
}
extends AbstractContainerJdbcOperationsSessionRepositoryITests {
@Configuration
static class Config extends BaseConfig {
static class Config extends BaseContainerConfig {
@Bean
public DataSource dataSource() {
PGSimpleDataSource dataSource = new PGSimpleDataSource();
dataSource.setUrl(container.getJdbcUrl());
dataSource.setUser(container.getUsername());
dataSource.setPassword(container.getPassword());
return dataSource;
public PostgreSQLContainer databaseContainer() {
PostgreSQLContainer databaseContainer = DatabaseContainers.postgreSql9();
databaseContainer.start();
return databaseContainer;
}
@Bean
public DataSourceInitializer initializer(DataSource dataSource,
ResourceLoader resourceLoader) {
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(
new ResourceDatabasePopulator(resourceLoader.getResource(
"classpath:org/springframework/session/jdbc/schema-postgresql.sql")));
return initializer;
}
}
private static class PostgreSql9Container
extends PostgreSQLContainer<PostgreSql9Container> {
PostgreSql9Container() {
super("postgres:9.6.10");
public ResourceDatabasePopulator databasePopulator() {
return DatabasePopulators.postgreSql();
}
}

View File

@@ -16,18 +16,11 @@
package org.springframework.session.jdbc;
import javax.sql.DataSource;
import com.microsoft.sqlserver.jdbc.SQLServerDataSource;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.testcontainers.containers.MSSQLServerContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@@ -43,52 +36,21 @@ import org.springframework.test.context.web.WebAppConfiguration;
@WebAppConfiguration
@ContextConfiguration
public class SqlServerJdbcOperationsSessionRepositoryITests
extends AbstractJdbcOperationsSessionRepositoryITests {
private static MSSQLServerContainer container = new SqlServer2007Container();
@BeforeClass
public static void setUpClass() {
container.start();
}
@AfterClass
public static void tearDownClass() {
container.stop();
}
extends AbstractContainerJdbcOperationsSessionRepositoryITests {
@Configuration
static class Config extends BaseConfig {
static class Config extends BaseContainerConfig {
@Bean
public DataSource dataSource() {
SQLServerDataSource dataSource = new SQLServerDataSource();
dataSource.setURL(container.getJdbcUrl());
dataSource.setUser(container.getUsername());
dataSource.setPassword(container.getPassword());
return dataSource;
public MSSQLServerContainer databaseContainer() {
MSSQLServerContainer databaseContainer = DatabaseContainers.sqlServer2017();
databaseContainer.start();
return databaseContainer;
}
@Bean
public DataSourceInitializer initializer(DataSource dataSource,
ResourceLoader resourceLoader) {
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(
new ResourceDatabasePopulator(resourceLoader.getResource(
"classpath:org/springframework/session/jdbc/schema-sqlserver.sql")));
return initializer;
}
}
private static class SqlServer2007Container
extends MSSQLServerContainer<SqlServer2007Container> {
SqlServer2007Container() {
super("microsoft/mssql-server-linux:2017-CU11");
withStartupTimeoutSeconds(240);
withConnectTimeoutSeconds(240);
public ResourceDatabasePopulator databasePopulator() {
return DatabasePopulators.sqlServer();
}
}

View File

@@ -0,0 +1 @@
ryuk.container.timeout=120

View File

@@ -28,6 +28,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
@@ -52,7 +53,9 @@ import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionOperations;
import org.springframework.transaction.support.TransactionTemplate;
@@ -126,6 +129,7 @@ import org.springframework.util.StringUtils;
* target database type.
*
* @author Vedran Pavic
* @author Craig Andrews
* @since 1.2.0
*/
public class JdbcOperationsSessionRepository implements
@@ -189,10 +193,17 @@ public class JdbcOperationsSessionRepository implements
private final JdbcOperations jdbcOperations;
private final TransactionOperations transactionOperations;
private final ResultSetExtractor<List<JdbcSession>> extractor = new SessionResultSetExtractor();
private TransactionOperations transactionOperations = new TransactionOperations() {
@Override
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
return action.doInTransaction(null);
}
};
/**
* The name of database table used by Spring Session to store sessions.
*/
@@ -229,14 +240,29 @@ public class JdbcOperationsSessionRepository implements
/**
* Create a new {@link JdbcOperationsSessionRepository} instance which uses the
* provided {@link JdbcOperations} to manage sessions.
* <p>
* The created instance will execute all data access operations in a transaction with
* propagation level of {@link TransactionDefinition#PROPAGATION_REQUIRES_NEW}.
* @param jdbcOperations the {@link JdbcOperations} to use
* @param transactionManager the {@link PlatformTransactionManager} to use
*/
public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations,
PlatformTransactionManager transactionManager) {
this(jdbcOperations);
Assert.notNull(transactionManager, "TransactionManager must not be null");
this.transactionOperations = createTransactionTemplate(transactionManager);
}
/**
* Create a new {@link JdbcOperationsSessionRepository} instance which uses the
* provided {@link JdbcOperations} to manage sessions.
* <p>
* The created instance will not execute data access operations in a transaction.
* @param jdbcOperations the {@link JdbcOperations} to use
*/
public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations) {
Assert.notNull(jdbcOperations, "JdbcOperations must not be null");
this.jdbcOperations = jdbcOperations;
this.transactionOperations = createTransactionTemplate(transactionManager);
this.conversionService = createDefaultConversionService();
prepareQueries();
}
@@ -506,7 +532,7 @@ public class JdbcOperationsSessionRepository implements
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
ps.setString(1, attributeName);
serialize(ps, 2, session.getAttribute(attributeName));
setObjectAsBlob(ps, 2, session.getAttribute(attributeName));
ps.setString(3, session.getId());
}
@@ -521,7 +547,7 @@ public class JdbcOperationsSessionRepository implements
this.jdbcOperations.update(this.createSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
ps.setString(1, attributeName);
serialize(ps, 2, session.getAttribute(attributeName));
setObjectAsBlob(ps, 2, session.getAttribute(attributeName));
ps.setString(3, session.getId());
});
}
@@ -535,7 +561,7 @@ public class JdbcOperationsSessionRepository implements
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
serialize(ps, 1, session.getAttribute(attributeName));
setObjectAsBlob(ps, 1, session.getAttribute(attributeName));
ps.setString(2, session.primaryKey);
ps.setString(3, attributeName);
}
@@ -550,7 +576,7 @@ public class JdbcOperationsSessionRepository implements
else {
this.jdbcOperations.update(this.updateSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
serialize(ps, 1, session.getAttribute(attributeName));
setObjectAsBlob(ps, 1, session.getAttribute(attributeName));
ps.setString(2, session.primaryKey);
ps.setString(3, attributeName);
});
@@ -633,19 +659,17 @@ public class JdbcOperationsSessionRepository implements
getQuery(DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY);
}
private void serialize(PreparedStatement ps, int paramIndex, Object attributeValue)
private void setObjectAsBlob(PreparedStatement ps, int paramIndex, Object object)
throws SQLException {
this.lobHandler.getLobCreator().setBlobAsBytes(ps, paramIndex,
(byte[]) this.conversionService.convert(attributeValue,
TypeDescriptor.valueOf(Object.class),
TypeDescriptor.valueOf(byte[].class)));
byte[] bytes = (byte[]) this.conversionService.convert(object,
TypeDescriptor.valueOf(Object.class),
TypeDescriptor.valueOf(byte[].class));
this.lobHandler.getLobCreator().setBlobAsBytes(ps, paramIndex, bytes);
}
private Object deserialize(ResultSet rs, String columnName)
throws SQLException {
return this.conversionService.convert(
this.lobHandler.getBlobAsBytes(rs, columnName),
TypeDescriptor.valueOf(byte[].class),
private Object getBlobAsObject(ResultSet rs, String columnName) throws SQLException {
byte[] bytes = this.lobHandler.getBlobAsBytes(rs, columnName);
return this.conversionService.convert(bytes, TypeDescriptor.valueOf(byte[].class),
TypeDescriptor.valueOf(Object.class));
}
@@ -655,6 +679,28 @@ public class JdbcOperationsSessionRepository implements
}
private static <T> Supplier<T> value(T value) {
return (value != null) ? () -> value : null;
}
private static <T> Supplier<T> lazily(Supplier<T> supplier) {
Supplier<T> lazySupplier = new Supplier<T>() {
private T value;
@Override
public T get() {
if (this.value == null) {
this.value = supplier.get();
}
return this.value;
}
};
return (supplier != null) ? lazySupplier : null;
}
/**
* The {@link Session} to use for {@link JdbcOperationsSessionRepository}.
*
@@ -724,7 +770,8 @@ public class JdbcOperationsSessionRepository implements
@Override
public <T> T getAttribute(String attributeName) {
return this.delegate.getAttribute(attributeName);
Supplier<T> supplier = this.delegate.getAttribute(attributeName);
return (supplier != null) ? supplier.get() : null;
}
@Override
@@ -759,7 +806,7 @@ public class JdbcOperationsSessionRepository implements
? oldDeltaValue
: DeltaValue.UPDATED);
}
this.delegate.setAttribute(attributeName, attributeValue);
this.delegate.setAttribute(attributeName, value(attributeValue));
if (PRINCIPAL_NAME_INDEX_NAME.equals(attributeName) ||
SPRING_SECURITY_CONTEXT.equals(attributeName)) {
this.changed = true;
@@ -851,7 +898,8 @@ public class JdbcOperationsSessionRepository implements
}
String attributeName = rs.getString("ATTRIBUTE_NAME");
if (attributeName != null) {
session.delegate.setAttribute(attributeName, deserialize(rs, "ATTRIBUTE_BYTES"));
Object attributeValue = getBlobAsObject(rs, "ATTRIBUTE_BYTES");
session.delegate.setAttribute(attributeName, lazily(() -> attributeValue));
}
sessions.add(session);
}

View File

@@ -42,7 +42,10 @@ import org.springframework.transaction.TransactionDefinition;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
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;
@@ -88,7 +91,7 @@ public class JdbcOperationsSessionRepositoryTests {
assertThatThrownBy(
() -> new JdbcOperationsSessionRepository(this.jdbcOperations, null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Property 'transactionManager' is required");
.hasMessage("TransactionManager must not be null");
}
@Test
@@ -688,6 +691,89 @@ public class JdbcOperationsSessionRepositoryTests {
assertThat(session.getAttributeNames()).isEmpty();
}
@Test
public 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
public void saveUpdatedWithoutTransaction() {
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(
"primaryKey", new MapSession());
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")
public 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
public 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")
public 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
public 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);
}
private void assertPropagationRequiresNew() {
ArgumentCaptor<TransactionDefinition> argument =
ArgumentCaptor.forClass(TransactionDefinition.class);