Compare commits

..

64 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
69 changed files with 1064 additions and 608 deletions

42
Jenkinsfile vendored
View File

@@ -11,8 +11,8 @@ currentBuild.result = SUCCESS
try {
parallel check: {
stage('Check') {
timeout(time: 30, unit: 'MINUTES') {
node {
timeout(time: 45, unit: 'MINUTES') {
node('ubuntu1804') {
checkout scm
try {
sh './gradlew clean check --no-daemon --refresh-dependencies'
@@ -22,7 +22,43 @@ try {
throw e
}
finally {
junit '**/build/*-results/*.xml'
junit '**/build/test-results/*/*.xml'
}
}
}
}
},
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
}
}
}
}
},
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.17.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

@@ -24,7 +24,6 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices;
@@ -54,7 +53,7 @@ public class RememberMeSecurityConfiguration extends WebSecurityConfigurerAdapte
// tag::rememberme-bean[]
@Bean
RememberMeServices rememberMeServices() {
public SpringSessionRememberMeServices rememberMeServices() {
SpringSessionRememberMeServices rememberMeServices =
new SpringSessionRememberMeServices();
// optionally customize

View File

@@ -4,7 +4,7 @@
<module name="Checker">
<!-- Supressions -->
<module name="SuppressionFilter">
<property name="file" value="${configDir}/suppressions.xml"/>
<property name="file" value="${config_loc}/suppressions.xml"/>
</module>
<!-- Root Checks -->

View File

@@ -1,16 +0,0 @@
^\Q/*\E$
^\Q * Copyright 2014-\E20\d\d\Q the original author or authors.\E$
^\Q *\E$
^\Q * Licensed under the Apache License, Version 2.0 (the "License");\E$
^\Q * you may not use this file except in compliance with the License.\E$
^\Q * You may obtain a copy of the License at\E$
^\Q *\E$
^\Q * http://www.apache.org/licenses/LICENSE-2.0\E$
^\Q *\E$
^\Q * Unless required by applicable law or agreed to in writing, software\E$
^\Q * distributed under the License is distributed on an "AS IS" BASIS,\E$
^\Q * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\E$
^\Q * See the License for the specific language governing permissions and\E$
^\Q * limitations under the License.\E$
^\Q */\E$
^.*$

View File

@@ -7,7 +7,6 @@
<!-- docs -->
<suppress files="[\\/]docs[\\/]" checks="Javadoc*"/>
<suppress files="[\\/]docs[\\/]" checks="AvoidStaticImport"/>
<suppress files="[\\/]docs[\\/]" checks="InnerTypeLast"/>
<!-- samples -->

View File

@@ -1,2 +1 @@
springBootVersion=2.1.0.M1
version=2.1.0.M2
version=2.1.0.RELEASE

View File

@@ -1,32 +1,33 @@
dependencyManagement {
imports {
mavenBom 'com.fasterxml.jackson:jackson-bom:2.9.6'
mavenBom 'io.projectreactor:reactor-bom:Californium-M2'
mavenBom 'org.springframework:spring-framework-bom:5.1.0.RC2'
mavenBom 'org.springframework.data:spring-data-releasetrain:Lovelace-RC2'
mavenBom 'org.springframework.security:spring-security-bom:5.1.0.RC1'
mavenBom 'org.testcontainers:testcontainers-bom:1.8.3'
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.10.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.1.0.M1'
dependency 'io.lettuce:lettuce-core:5.1.1.RELEASE'
dependency 'javax.annotation:javax.annotation-api:1.3.2'
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'
dependency 'org.assertj:assertj-core:3.11.0'
dependency 'org.assertj:assertj-core:3.11.1'
dependency 'org.hsqldb:hsqldb:2.4.1'
dependency 'org.mariadb.jdbc:mariadb-java-client:2.2.6'
dependency 'org.mockito:mockito-core:2.21.0'
dependency 'org.postgresql:postgresql:42.2.4'
dependency 'org.mariadb.jdbc:mariadb-java-client:2.3.0'
dependency 'org.mockito:mockito-core:2.23.0'
dependency 'org.postgresql:postgresql:42.2.5'
}
}

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.9-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

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

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

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

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.
@@ -42,7 +42,8 @@ public class SessionConfig {
int port = SocketUtils.findAvailableTcpPort();
config.getNetworkConfig()
.setPort(port);
.setPort(port)
.getJoin().getMulticastConfig().setEnabled(false);
System.out.println("Hazelcast port #: " + port);

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

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

View File

@@ -64,7 +64,9 @@ public class Initializer implements ServletContextListener {
private HazelcastInstance createHazelcastInstance() {
Config config = new Config();
config.getNetworkConfig().setPort(getAvailablePort());
config.getNetworkConfig()
.setPort(getAvailablePort())
.getJoin().getMulticastConfig().setEnabled(false);
config.getMapConfig(SESSION_MAP_NAME)
.setTimeToLiveSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);

View File

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

View File

@@ -2,15 +2,18 @@ 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, '').replaceAll(File.separator, ':')
String projectPath = buildFilePath.replace(rootDirPath, '').replace(File.separator, ':')
include projectPath
}
else {

View File

@@ -81,7 +81,7 @@ public interface Session {
@SuppressWarnings("unchecked")
default <T> T getAttributeOrDefault(String name, T defaultValue) {
T result = getAttribute(name);
return (result != null ? result : defaultValue);
return (result != null) ? result : defaultValue;
}
/**

View File

@@ -110,8 +110,9 @@ public class SpringHttpSessionConfiguration implements ApplicationContextAware {
@PostConstruct
public void init() {
CookieSerializer cookieSerializer = (this.cookieSerializer != null
? this.cookieSerializer : createDefaultCookieSerializer());
CookieSerializer cookieSerializer = (this.cookieSerializer != null)
? this.cookieSerializer
: createDefaultCookieSerializer();
this.defaultHttpSessionIdResolver.setCookieSerializer(cookieSerializer);
}

View File

@@ -135,9 +135,9 @@ public class DefaultCookieSerializer implements CookieSerializer {
int maxAge = getMaxAge(cookieValue);
if (maxAge > -1) {
sb.append("; Max-Age=").append(cookieValue.getCookieMaxAge());
OffsetDateTime expires = (maxAge != 0
OffsetDateTime expires = (maxAge != 0)
? OffsetDateTime.now().plusSeconds(maxAge)
: Instant.EPOCH.atOffset(ZoneOffset.UTC));
: Instant.EPOCH.atOffset(ZoneOffset.UTC);
sb.append("; Expires=")
.append(expires.format(DateTimeFormatter.RFC_1123_DATE_TIME));
}

View File

@@ -98,8 +98,8 @@ public class HeaderHttpSessionIdResolver implements HttpSessionIdResolver {
@Override
public List<String> resolveSessionIds(HttpServletRequest request) {
String headerValue = request.getHeader(this.headerName);
return (headerValue != null ? Collections.singletonList(headerValue)
: Collections.emptyList());
return (headerValue != null) ? Collections.singletonList(headerValue)
: Collections.emptyList();
}
@Override

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.
@@ -28,7 +28,7 @@ import javax.servlet.http.HttpServletResponse;
*
* @author Rob Winch
* @author Vedran Pavic
* @since 1.0
* @since 2.0.0
*/
public interface HttpSessionIdResolver {

View File

@@ -174,11 +174,11 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
}
private void trackContentLength(byte[] content) {
checkContentLength(content != null ? content.length : 0);
checkContentLength((content != null) ? content.length : 0);
}
private void trackContentLength(char[] content) {
checkContentLength(content != null ? content.length : 0);
checkContentLength((content != null) ? content.length : 0);
}
private void trackContentLength(int content) {
@@ -257,13 +257,13 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
}
@Override
public int hashCode() {
return this.delegate.hashCode();
public boolean equals(Object obj) {
return this.delegate.equals(obj);
}
@Override
public boolean equals(Object obj) {
return this.delegate.equals(obj);
public int hashCode() {
return this.delegate.hashCode();
}
@Override
@@ -502,13 +502,13 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
}
@Override
public int hashCode() {
return this.delegate.hashCode();
public boolean equals(Object obj) {
return this.delegate.equals(obj);
}
@Override
public boolean equals(Object obj) {
return this.delegate.equals(obj);
public int hashCode() {
return this.delegate.hashCode();
}
@Override

View File

@@ -352,7 +352,7 @@ public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFi
@Override
public String getRequestedSessionId() {
S requestedSession = getRequestedSession();
return (requestedSession != null ? requestedSession.getId() : null);
return (requestedSession != null) ? requestedSession.getId() : null;
}
private S getRequestedSession() {

View File

@@ -71,9 +71,9 @@ public final class WebSocketRegistryListener
SessionDisconnectEvent e = (SessionDisconnectEvent) event;
Map<String, Object> sessionAttributes = SimpMessageHeaderAccessor
.getSessionAttributes(e.getMessage().getHeaders());
String httpSessionId = (sessionAttributes != null
String httpSessionId = (sessionAttributes != null)
? SessionRepositoryMessageInterceptor.getSessionId(sessionAttributes)
: null);
: null;
afterConnectionClosed(httpSessionId, e.getSessionId());
}
}

View File

@@ -117,8 +117,9 @@ public final class SessionRepositoryMessageInterceptor<S extends Session>
}
Map<String, Object> sessionHeaders = SimpMessageHeaderAccessor
.getSessionAttributes(message.getHeaders());
String sessionId = (sessionHeaders != null
? (String) sessionHeaders.get(SPRING_SESSION_ID_ATTR_NAME) : null);
String sessionId = (sessionHeaders != null)
? (String) sessionHeaders.get(SPRING_SESSION_ID_ATTR_NAME)
: null;
if (sessionId != null) {
S session = this.sessionRepository.findById(sessionId);
if (session != null) {

View File

@@ -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();
}

View File

@@ -16,6 +16,8 @@
package org.springframework.session.data.redis;
import java.time.Instant;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -28,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}.
@@ -191,6 +194,31 @@ public class ReactiveRedisOperationsSessionRepositoryITests extends AbstractRedi
assertThat(this.repository.findById(originalId).block()).isNull();
}
// gh-1111
@Test
public void changeSessionSaveOldSessionInstance() {
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
.createSession().block();
String sessionId = toSave.getId();
this.repository.save(toSave).block();
ReactiveRedisOperationsSessionRepository.RedisSession session = this.repository
.findById(sessionId).block();
session.changeSessionId();
session.setLastAccessedTime(Instant.now());
this.repository.save(session).block();
toSave.setLastAccessedTime(Instant.now());
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();
}
@Configuration
@EnableRedisWebSession
static class Config extends BaseConfig {

View File

@@ -16,6 +16,7 @@
package org.springframework.session.data.redis;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.UUID;
@@ -190,9 +191,10 @@ public class RedisOperationsSessionRepositoryITests extends AbstractRedisITests
String body = "RedisOperationsSessionRepositoryITests:sessions:expires:"
+ toSave.getId();
String channel = ":expired";
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"),
body.getBytes("UTF-8"));
String channel = "__keyevent@0__:expired";
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
byte[] pattern = new byte[] {};
this.repository.onMessage(message, pattern);
@@ -358,9 +360,10 @@ public class RedisOperationsSessionRepositoryITests extends AbstractRedisITests
String body = "RedisOperationsSessionRepositoryITests:sessions:expires:"
+ toSave.getId();
String channel = ":expired";
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"),
body.getBytes("UTF-8"));
String channel = "__keyevent@0__:expired";
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
byte[] pattern = new byte[] {};
this.repository.onMessage(message, pattern);
@@ -581,6 +584,22 @@ public class RedisOperationsSessionRepositoryITests extends AbstractRedisITests
assertThat(this.repository.findById(originalId)).isNull();
}
// gh-1137
@Test
public void changeSessionIdWhenSessionIsDeleted() {
RedisSession toSave = this.repository.createSession();
String sessionId = toSave.getId();
this.repository.save(toSave);
this.repository.deleteById(sessionId);
toSave.changeSessionId();
this.repository.save(toSave);
assertThat(this.repository.findById(toSave.getId())).isNull();
assertThat(this.repository.findById(sessionId)).isNull();
}
private String getSecurityName() {
return this.context.getAuthentication().getName();
}

View File

@@ -95,7 +95,7 @@ public class RedisListenerContainerTaskExecutorITests extends AbstractRedisITest
synchronized (this.lock) {
this.lock.wait(TimeUnit.SECONDS.toMillis(1));
}
return (this.taskDispatched != null ? this.taskDispatched : Boolean.FALSE);
return (this.taskDispatched != null) ? this.taskDispatched : Boolean.FALSE;
}
}

View File

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

View File

@@ -118,6 +118,11 @@ 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;
}
@@ -138,24 +143,38 @@ public class ReactiveRedisOperationsSessionRepository implements
@Override
public Mono<Void> save(RedisSession session) {
return session.saveDelta().and((s) -> {
if (session.isNew) {
session.setNew(false);
}
s.onComplete();
});
Mono<Void> result = session.saveChangeSessionId().and(session.saveDelta())
.and((s) -> {
session.isNew = false;
s.onComplete();
});
if (session.isNew) {
return result;
}
else {
String sessionKey = getSessionKey(
session.hasChangedSessionId() ? session.originalSessionId
: session.getId());
return this.sessionRedisOperations.hasKey(sessionKey)
.flatMap((exists) -> exists ? result
: Mono.error(new IllegalStateException(
"Session was invalidated")));
}
}
@Override
public Mono<RedisSession> findById(String id) {
String sessionKey = getSessionKey(id);
// @formatter:off
return this.sessionRedisOperations.opsForHash().entries(sessionKey)
.collectMap((e) -> e.getKey().toString(), Map.Entry::getValue)
.filter((map) -> !map.isEmpty()).map(new SessionMapper(id))
.filter((session) -> !session.isExpired()).map(RedisSession::new)
.filter((map) -> !map.isEmpty())
.map(new SessionMapper(id))
.filter((session) -> !session.isExpired())
.map(RedisSession::new)
.switchIfEmpty(Mono.defer(() -> deleteById(id).then(Mono.empty())));
// @formatter:on
}
@Override
@@ -280,12 +299,8 @@ public class ReactiveRedisOperationsSessionRepository implements
return this.cached.isExpired();
}
public void setNew(boolean isNew) {
this.isNew = isNew;
}
public boolean isNew() {
return this.isNew;
private boolean hasChangedSessionId() {
return !getId().equals(this.originalSessionId);
}
private void flushImmediateIfNecessary() {
@@ -300,38 +315,35 @@ public class ReactiveRedisOperationsSessionRepository implements
}
private Mono<Void> saveDelta() {
String sessionId = getId();
Mono<Void> changeSessionId = saveChangeSessionId(sessionId);
if (this.delta.isEmpty()) {
return changeSessionId.and(Mono.empty());
return Mono.empty();
}
String sessionKey = getSessionKey(sessionId);
String sessionKey = getSessionKey(getId());
Mono<Boolean> update = ReactiveRedisOperationsSessionRepository.this.sessionRedisOperations
.opsForHash().putAll(sessionKey, this.delta);
Mono<Boolean> setTtl = ReactiveRedisOperationsSessionRepository.this.sessionRedisOperations
.expire(sessionKey, getMaxInactiveInterval());
return changeSessionId.and(update).and(setTtl).and((s) -> {
return update.and(setTtl).and((s) -> {
this.delta.clear();
s.onComplete();
}).then();
}
private Mono<Void> saveChangeSessionId(String sessionId) {
if (sessionId.equals(this.originalSessionId)) {
private Mono<Void> saveChangeSessionId() {
if (!hasChangedSessionId()) {
return Mono.empty();
}
String sessionId = getId();
Publisher<Void> replaceSessionId = (s) -> {
this.originalSessionId = sessionId;
s.onComplete();
};
if (isNew()) {
if (this.isNew) {
return Mono.from(replaceSessionId);
}
else {

View File

@@ -28,6 +28,8 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.BoundHashOperations;
@@ -252,6 +254,11 @@ public class RedisOperationsSessionRepository implements
static PrincipalNameResolver PRINCIPAL_NAME_RESOLVER = new PrincipalNameResolver();
/**
* The default Redis database used by Spring Session.
*/
public static final int DEFAULT_DATABASE = 0;
/**
* The default namespace for each key and channel in Redis used by Spring Session.
*/
@@ -284,11 +291,19 @@ public class RedisOperationsSessionRepository implements
*/
static final String SESSION_ATTR_PREFIX = "sessionAttr:";
private int database = RedisOperationsSessionRepository.DEFAULT_DATABASE;
/**
* The namespace for every key used by Spring Session in Redis.
*/
private String namespace = DEFAULT_NAMESPACE + ":";
private String sessionCreatedChannelPrefix;
private String sessionDeletedChannel;
private String sessionExpiredChannel;
private final RedisOperations<Object, Object> sessionRedisOperations;
private final RedisSessionExpirationPolicy expirationPolicy;
@@ -325,6 +340,7 @@ public class RedisOperationsSessionRepository implements
this.sessionRedisOperations = sessionRedisOperations;
this.expirationPolicy = new RedisSessionExpirationPolicy(sessionRedisOperations,
this::getExpirationsKey, this::getSessionKey);
configureSessionChannels();
}
/**
@@ -375,6 +391,27 @@ public class RedisOperationsSessionRepository implements
this.redisFlushMode = redisFlushMode;
}
/**
* Sets the database index to use. Defaults to {@link #DEFAULT_DATABASE}.
* @param database the database index to use
*/
public void setDatabase(int database) {
this.database = database;
configureSessionChannels();
}
private void configureSessionChannels() {
this.sessionCreatedChannelPrefix = this.namespace + "event:" + this.database
+ ":created:";
this.sessionDeletedChannel = "__keyevent@" + this.database + "__:del";
this.sessionExpiredChannel = "__keyevent@" + this.database + "__:expired";
}
/**
* Returns the {@link RedisOperations} used for sessions.
* @return the {@link RedisOperations} used for sessions
* @since 2.0.0
*/
public RedisOperations<Object, Object> getSessionRedisOperations() {
return this.sessionRedisOperations;
}
@@ -495,7 +532,7 @@ public class RedisOperationsSessionRepository implements
String channel = new String(messageChannel);
if (channel.startsWith(getSessionCreatedChannelPrefix())) {
if (channel.startsWith(this.sessionCreatedChannelPrefix)) {
// TODO: is this thread safe?
Map<Object, Object> loaded = (Map<Object, Object>) this.defaultSerializer
.deserialize(message.getBody());
@@ -508,8 +545,8 @@ public class RedisOperationsSessionRepository implements
return;
}
boolean isDeleted = channel.endsWith(":del");
if (isDeleted || channel.endsWith(":expired")) {
boolean isDeleted = channel.equals(this.sessionDeletedChannel);
if (isDeleted || channel.equals(this.sessionExpiredChannel)) {
int beginIndex = body.lastIndexOf(":") + 1;
int endIndex = body.length();
String sessionId = body.substring(beginIndex, endIndex);
@@ -572,6 +609,7 @@ public class RedisOperationsSessionRepository implements
public void setRedisKeyNamespace(String namespace) {
Assert.hasText(namespace, "namespace cannot be null or empty");
this.namespace = namespace.trim() + ":";
configureSessionChannels();
}
/**
@@ -603,17 +641,33 @@ public class RedisOperationsSessionRepository implements
}
private String getExpiredKeyPrefix() {
return this.namespace + "sessions:" + "expires:";
return this.namespace + "sessions:expires:";
}
/**
* Gets the prefix for the channel that SessionCreatedEvent are published to. The
* suffix is the session id of the session that was created.
*
* @return the prefix for the channel that SessionCreatedEvent are published to
* Gets the prefix for the channel that {@link SessionCreatedEvent}s are published to.
* The suffix is the session id of the session that was created.
* @return the prefix for the channel that {@link SessionCreatedEvent}s are published
* to
*/
public String getSessionCreatedChannelPrefix() {
return this.namespace + "event:created:";
return this.sessionCreatedChannelPrefix;
}
/**
* Gets the name of the channel that {@link SessionDeletedEvent}s are published to.
* @return the name for the channel that {@link SessionDeletedEvent}s are published to
*/
public String getSessionDeletedChannel() {
return this.sessionDeletedChannel;
}
/**
* Gets the name of the channel that {@link SessionExpiredEvent}s are published to.
* @return the name for the channel that {@link SessionExpiredEvent}s are published to
*/
public String getSessionExpiredChannel() {
return this.sessionExpiredChannel;
}
/**
@@ -797,9 +851,10 @@ public class RedisOperationsSessionRepository implements
this.delta = new HashMap<>(this.delta.size());
Long originalExpiration = (this.originalLastAccessTime != null
? this.originalLastAccessTime.plus(getMaxInactiveInterval()).toEpochMilli()
: null);
Long originalExpiration = (this.originalLastAccessTime != null)
? this.originalLastAccessTime.plus(getMaxInactiveInterval())
.toEpochMilli()
: null;
RedisOperationsSessionRepository.this.expirationPolicy
.onExpirationUpdated(originalExpiration, this);
}
@@ -813,8 +868,16 @@ public class RedisOperationsSessionRepository implements
originalSessionIdKey, sessionIdKey);
String originalExpiredKey = getExpiredKey(this.originalSessionId);
String expiredKey = getExpiredKey(sessionId);
RedisOperationsSessionRepository.this.sessionRedisOperations.rename(
originalExpiredKey, expiredKey);
try {
RedisOperationsSessionRepository.this.sessionRedisOperations.rename(
originalExpiredKey, expiredKey);
}
catch (NonTransientDataAccessException ex) {
if (!"ERR no such key".equals(NestedExceptionUtils
.getMostSpecificCause(ex).getMessage())) {
throw ex;
}
}
}
this.originalSessionId = 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.
@@ -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

@@ -37,7 +37,10 @@ import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.RedisSerializer;
@@ -54,6 +57,7 @@ import org.springframework.session.data.redis.config.ConfigureRedisAction;
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
@@ -115,6 +119,8 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
sessionRepository.setRedisKeyNamespace(this.redisNamespace);
}
sessionRepository.setRedisFlushMode(this.redisFlushMode);
int database = resolveDatabase();
sessionRepository.setDatabase(database);
return sessionRepository;
}
@@ -128,9 +134,9 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
if (this.redisSubscriptionExecutor != null) {
container.setSubscriptionExecutor(this.redisSubscriptionExecutor);
}
container.addMessageListener(sessionRepository(),
Arrays.asList(new PatternTopic("__keyevent@*:del"),
new PatternTopic("__keyevent@*:expired")));
container.addMessageListener(sessionRepository(), Arrays.asList(
new ChannelTopic(sessionRepository().getSessionDeletedChannel()),
new ChannelTopic(sessionRepository().getSessionExpiredChannel())));
container.addMessageListener(sessionRepository(),
Collections.singletonList(new PatternTopic(
sessionRepository().getSessionCreatedChannelPrefix() + "*")));
@@ -256,6 +262,18 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
return redisTemplate;
}
private int resolveDatabase() {
if (ClassUtils.isPresent("io.lettuce.core.RedisClient", null)
&& this.redisConnectionFactory instanceof LettuceConnectionFactory) {
return ((LettuceConnectionFactory) this.redisConnectionFactory).getDatabase();
}
if (ClassUtils.isPresent("redis.clients.jedis.Jedis", null)
&& this.redisConnectionFactory instanceof JedisConnectionFactory) {
return ((JedisConnectionFactory) this.redisConnectionFactory).getDatabase();
}
return RedisOperationsSessionRepository.DEFAULT_DATABASE;
}
/**
* Ensures that Redis is configured to send keyspace notifications. This is important
* to ensure that expiration and deletion of sessions trigger SessionDestroyedEvents.

View File

@@ -144,9 +144,9 @@ public class RedisWebSessionConfiguration extends SpringWebSessionConfiguration
private ReactiveRedisTemplate<String, Object> createReactiveRedisTemplate() {
RedisSerializer<String> keySerializer = new StringRedisSerializer();
RedisSerializer<Object> defaultSerializer = (this.defaultRedisSerializer != null
RedisSerializer<Object> defaultSerializer = (this.defaultRedisSerializer != null)
? this.defaultRedisSerializer
: new JdkSerializationRedisSerializer(this.classLoader));
: new JdkSerializationRedisSerializer(this.classLoader);
RedisSerializationContext<String, Object> serializationContext = RedisSerializationContext
.<String, Object>newSerializationContext(defaultSerializer)
.key(keySerializer).hashKey(keySerializer).build();

View File

@@ -183,6 +183,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
@Test
public void saveSessionNothingChanged() {
given(this.redisOperations.hasKey(anyString())).willReturn(Mono.just(true));
given(this.redisOperations.expire(anyString(), any()))
.willReturn(Mono.just(true));
@@ -191,12 +192,14 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
StepVerifier.create(this.repository.save(session)).verifyComplete();
verify(this.redisOperations).hasKey(anyString());
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.hashOperations);
}
@Test
public void saveLastAccessChanged() {
given(this.redisOperations.hasKey(anyString())).willReturn(Mono.just(true));
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
given(this.hashOperations.putAll(anyString(), any())).willReturn(Mono.just(true));
given(this.redisOperations.expire(anyString(), any()))
@@ -206,6 +209,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
session.setLastAccessedTime(Instant.ofEpochMilli(12345678L));
Mono.just(session).subscribe(this.repository::save);
verify(this.redisOperations).hasKey(anyString());
verify(this.redisOperations).opsForHash();
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
verify(this.redisOperations).expire(anyString(), any());
@@ -219,6 +223,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
@Test
public void saveSetAttribute() {
given(this.redisOperations.hasKey(anyString())).willReturn(Mono.just(true));
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
given(this.hashOperations.putAll(anyString(), any())).willReturn(Mono.just(true));
given(this.redisOperations.expire(anyString(), any()))
@@ -229,6 +234,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
session.setAttribute(attrName, "attrValue");
Mono.just(session).subscribe(this.repository::save);
verify(this.redisOperations).hasKey(anyString());
verify(this.redisOperations).opsForHash();
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
verify(this.redisOperations).expire(anyString(), any());
@@ -242,6 +248,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
@Test
public void saveRemoveAttribute() {
given(this.redisOperations.hasKey(anyString())).willReturn(Mono.just(true));
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
given(this.hashOperations.putAll(anyString(), any())).willReturn(Mono.just(true));
given(this.redisOperations.expire(anyString(), any()))
@@ -252,6 +259,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
session.removeAttribute(attrName);
Mono.just(session).subscribe(this.repository::save);
verify(this.redisOperations).hasKey(anyString());
verify(this.redisOperations).opsForHash();
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
verify(this.redisOperations).expire(anyString(), any());
@@ -338,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

@@ -16,6 +16,7 @@
package org.springframework.session.data.redis;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
@@ -431,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
@@ -497,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
@@ -522,14 +525,15 @@ public class RedisOperationsSessionRepositoryTests {
}
@Test
public void onMessageCreated() throws Exception {
public void onMessageCreated() {
MapSession session = this.cached;
byte[] pattern = "".getBytes("UTF-8");
String channel = "spring:session:event:created:" + session.getId();
byte[] pattern = "".getBytes(StandardCharsets.UTF_8);
String channel = "spring:session:event:0:created:" + session.getId();
JdkSerializationRedisSerializer defaultSerailizer = new JdkSerializationRedisSerializer();
this.redisRepository.setDefaultSerializer(defaultSerailizer);
byte[] body = defaultSerailizer.serialize(new HashMap());
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body);
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8), body);
this.redisRepository.setApplicationEventPublisher(this.publisher);
@@ -539,16 +543,16 @@ public class RedisOperationsSessionRepositoryTests {
assertThat(this.event.getValue().getSessionId()).isEqualTo(session.getId());
}
// gh-309
@Test
public void onMessageCreatedCustomSerializer() throws Exception {
@Test // gh-309
public void onMessageCreatedCustomSerializer() {
MapSession session = this.cached;
byte[] pattern = "".getBytes("UTF-8");
byte[] pattern = "".getBytes(StandardCharsets.UTF_8);
byte[] body = new byte[0];
String channel = "spring:session:event:created:" + session.getId();
String channel = "spring:session:event:0:created:" + session.getId();
given(this.defaultSerializer.deserialize(body))
.willReturn(new HashMap<String, Object>());
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body);
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8), body);
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, pattern);
@@ -559,7 +563,7 @@ public class RedisOperationsSessionRepositoryTests {
}
@Test
public void onMessageDeletedSessionFound() throws Exception {
public void onMessageDeletedSessionFound() {
String deletedId = "deleted-id";
given(this.redisOperations.boundHashOps(getKey(deletedId)))
.willReturn(this.boundHashOperations);
@@ -570,10 +574,12 @@ public class RedisOperationsSessionRepositoryTests {
String channel = "__keyevent@0__:del";
String body = "spring:session:sessions:expires:" + deletedId;
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body.getBytes("UTF-8"));
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, "".getBytes("UTF-8"));
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
verify(this.redisOperations).boundHashOps(eq(getKey(deletedId)));
verify(this.boundHashOperations).entries();
@@ -586,7 +592,7 @@ public class RedisOperationsSessionRepositoryTests {
}
@Test
public void onMessageDeletedSessionNotFound() throws Exception {
public void onMessageDeletedSessionNotFound() {
String deletedId = "deleted-id";
given(this.redisOperations.boundHashOps(getKey(deletedId)))
.willReturn(this.boundHashOperations);
@@ -594,10 +600,12 @@ public class RedisOperationsSessionRepositoryTests {
String channel = "__keyevent@0__:del";
String body = "spring:session:sessions:expires:" + deletedId;
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body.getBytes("UTF-8"));
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, "".getBytes("UTF-8"));
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
verify(this.redisOperations).boundHashOps(eq(getKey(deletedId)));
verify(this.boundHashOperations).entries();
@@ -608,7 +616,7 @@ public class RedisOperationsSessionRepositoryTests {
}
@Test
public void onMessageExpiredSessionFound() throws Exception {
public void onMessageExpiredSessionFound() {
String expiredId = "expired-id";
given(this.redisOperations.boundHashOps(getKey(expiredId)))
.willReturn(this.boundHashOperations);
@@ -619,10 +627,12 @@ public class RedisOperationsSessionRepositoryTests {
String channel = "__keyevent@0__:expired";
String body = "spring:session:sessions:expires:" + expiredId;
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body.getBytes("UTF-8"));
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, "".getBytes("UTF-8"));
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
verify(this.redisOperations).boundHashOps(eq(getKey(expiredId)));
verify(this.boundHashOperations).entries();
@@ -635,7 +645,7 @@ public class RedisOperationsSessionRepositoryTests {
}
@Test
public void onMessageExpiredSessionNotFound() throws Exception {
public void onMessageExpiredSessionNotFound() {
String expiredId = "expired-id";
given(this.redisOperations.boundHashOps(getKey(expiredId)))
.willReturn(this.boundHashOperations);
@@ -643,10 +653,12 @@ public class RedisOperationsSessionRepositoryTests {
String channel = "__keyevent@0__:expired";
String body = "spring:session:sessions:expires:" + expiredId;
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body.getBytes("UTF-8"));
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, "".getBytes("UTF-8"));
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
verify(this.redisOperations).boundHashOps(eq(getKey(expiredId)));
verify(this.boundHashOperations).entries();
@@ -881,6 +893,62 @@ public class RedisOperationsSessionRepositoryTests {
assertThat(session.getAttributeNames()).isEmpty();
}
@Test
public void onMessageCreatedInOtherDatabase() {
JdkSerializationRedisSerializer serializer = new JdkSerializationRedisSerializer();
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.setDefaultSerializer(serializer);
MapSession session = this.cached;
String channel = "spring:session:event:created:1:" + session.getId();
byte[] body = serializer.serialize(new HashMap());
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8), body);
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
assertThat(this.event.getAllValues()).isEmpty();
verifyZeroInteractions(this.publisher);
}
@Test
public void onMessageDeletedInOtherDatabase() {
JdkSerializationRedisSerializer serializer = new JdkSerializationRedisSerializer();
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.setDefaultSerializer(serializer);
MapSession session = this.cached;
String channel = "__keyevent@1__:del";
String body = "spring:session:sessions:expires:" + session.getId();
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
assertThat(this.event.getAllValues()).isEmpty();
verifyZeroInteractions(this.publisher);
}
@Test
public void onMessageExpiredInOtherDatabase() {
JdkSerializationRedisSerializer serializer = new JdkSerializationRedisSerializer();
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.setDefaultSerializer(serializer);
MapSession session = this.cached;
String channel = "__keyevent@1__:expired";
String body = "spring:session:sessions:expires:" + session.getId();
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
assertThat(this.event.getAllValues()).isEmpty();
verifyZeroInteractions(this.publisher);
}
private String getKey(String id) {
return "spring:session:sessions:" + id;
}

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.10.3")
"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());
@@ -745,7 +761,8 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
@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());
@@ -757,7 +774,8 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
@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,46 @@ 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";
int arraySize = 4000;
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.createSession();
session.setAttribute(attributeName, new byte[arraySize]);
this.repository.save(session);
session = this.repository.findById(session.getId());
assertThat(session).isNotNull();
assertThat((byte[]) session.getAttribute(attributeName)).hasSize(arraySize);
}
private String getSecurityName() {
return this.context.getAuthentication().getName();
}
@@ -777,7 +835,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
@EnableJdbcHttpSession
protected static class BaseConfig {
static class BaseConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
@@ -785,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.9");
}
@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-CU9");
withStartupTimeoutSeconds(240);
withConnectTimeoutSeconds(240);
public ResourceDatabasePopulator databasePopulator() {
return DatabasePopulators.sqlServer();
}
}

View File

@@ -1 +1 @@
microsoft/mssql-server-linux:2017-CU9
microsoft/mssql-server-linux:2017-CU11

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;
@@ -128,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
@@ -530,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());
}
@@ -545,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());
});
}
@@ -559,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);
}
@@ -574,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);
});
@@ -657,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));
}
@@ -679,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}.
*
@@ -748,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
@@ -765,25 +788,25 @@ public class JdbcOperationsSessionRepository implements
}
if (attributeExists) {
if (attributeRemoved) {
this.delta.merge(attributeName, DeltaValue.REMOVED,
(oldDeltaValue, deltaValue) -> (oldDeltaValue == DeltaValue.ADDED
? null
: deltaValue));
this.delta.merge(attributeName, DeltaValue.REMOVED, (oldDeltaValue,
deltaValue) -> (oldDeltaValue == DeltaValue.ADDED) ? null
: deltaValue);
}
else {
this.delta.merge(attributeName, DeltaValue.UPDATED,
(oldDeltaValue, deltaValue) -> (oldDeltaValue == DeltaValue.ADDED
? oldDeltaValue
: deltaValue));
(oldDeltaValue,
deltaValue) -> (oldDeltaValue == DeltaValue.ADDED)
? oldDeltaValue
: deltaValue);
}
}
else {
this.delta.merge(attributeName, DeltaValue.ADDED,
(oldDeltaValue, deltaValue) -> (oldDeltaValue == DeltaValue.ADDED
(oldDeltaValue, deltaValue) -> (oldDeltaValue == DeltaValue.ADDED)
? oldDeltaValue
: DeltaValue.UPDATED));
: 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;
@@ -875,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

@@ -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.
@@ -35,6 +35,9 @@ import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.MetaDataAccessException;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
@@ -102,6 +105,11 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
if (this.lobHandler != null) {
sessionRepository.setLobHandler(this.lobHandler);
}
else if (requiresTemporaryLob(this.dataSource)) {
DefaultLobHandler lobHandler = new DefaultLobHandler();
lobHandler.setCreateTemporaryLob(true);
sessionRepository.setLobHandler(lobHandler);
}
if (this.springSessionConversionService != null) {
sessionRepository.setConversionService(this.springSessionConversionService);
}
@@ -115,6 +123,17 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
return sessionRepository;
}
private static boolean requiresTemporaryLob(DataSource dataSource) {
try {
String productName = JdbcUtils.extractDatabaseMetaData(dataSource,
"getDatabaseProductName");
return "Oracle".equalsIgnoreCase(JdbcUtils.commonDatabaseName(productName));
}
catch (MetaDataAccessException ex) {
return false;
}
}
public void setMaxInactiveIntervalInSeconds(Integer maxInactiveIntervalInSeconds) {
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
}