Compare commits

..

54 Commits

Author SHA1 Message Date
Eleftheria Stein
bb28af9934 Release 2.5.0-RC1 2021-04-13 12:28:27 +02:00
Eleftheria Stein
dee8402473 Upgrade test dependencies 2021-04-13 12:12:17 +02:00
Eleftheria Stein
7bd0b45f29 Upgrade samples to Spring Boot 2.4.4
Closes gh-1822
2021-04-13 12:01:37 +02:00
Eleftheria Stein
b42b01af9b Upgrade Hazelcast 4 to 4.2
Closes gh-1821
2021-04-13 11:31:58 +02:00
Eleftheria Stein
6744fee3cb Upgrade Hazelcast to 3.12.12
Closes gh-1820
2021-04-13 11:22:00 +02:00
Eleftheria Stein
6811f25565 Upgrade Spring Security to 5.5.0-RC1
Closes gh-1819
2021-04-13 11:21:10 +02:00
Eleftheria Stein
47817c46e1 Upgrade Spring Data to 2021.0.0-RC1
Closes gh-1818
2021-04-13 11:20:20 +02:00
Eleftheria Stein
4f7c6406ad Upgrade Spring Framework to 5.3.5
Closes gh-1817
2021-04-13 11:19:27 +02:00
Eleftheria Stein
34cc1d1171 Upgrade Reactor to 2020.0.6
Closes gh-1816
2021-04-13 11:18:29 +02:00
Josh Cummings
9e7d9912e5 Change to GPG_PRIVATE_KEY_NO_HEADER
Closes gh-1812
2021-03-30 13:32:28 -06:00
Eleftheria Stein
d2960b570f Polish gh-1798
- Should throw IllegalStateException
2021-03-25 09:55:32 +02:00
Eleftheria Stein
8bd4374909 Throw exception if session created after response
Closes gh-1798
2021-03-25 09:27:27 +02:00
Eleftheria Stein
15f29f8adf Fix README formatting 2021-03-22 12:48:34 +02:00
Eleftheria Stein
74d53e8bfc Add additional information to README 2021-03-22 12:45:51 +02:00
Eleftheria Stein
77deb63373 Fix SessionRepositoryFilter Javadoc 2021-03-17 16:39:45 +01:00
Stefan Wurzinger
69285f2a9a Fix Redis session expiration entry deletion
Closes gh-585
2021-03-12 16:24:48 +01:00
Eleftheria Stein
c93513f18f Fix typo in reference docs 2021-02-09 16:12:51 +01:00
Eleftheria Stein
27044c8766 Make Hazelcast tests independent of ordering
Closes gh-1787
2021-02-02 13:24:24 +01:00
Eleftheria Stein
b198844671 Use io.spring.gradle-enterprise-conventions
Adds support for build cache and build scans

Closes gh-1713
2021-02-01 18:14:28 +01:00
Eleftheria Stein
0f27bbaff7 Update to gradle 6.8.1
Closes gh-1785
2021-02-01 17:32:09 +01:00
Eleftheria Stein
62ad3e1bab Replace deprecated verifyZeroInteractions in tests 2021-01-29 17:00:03 +01:00
Eleftheria Stein
7eed8427a5 Use spring-build-conventions:0.0.37 2021-01-27 11:09:43 +01:00
Eleftheria Stein
4cbb253c11 Resolve artifacts from Maven Central first
- Use spring-build-conventions:0.0.36
- Add https://repo.spring.io/release to reference

Closes gh-1778
2021-01-22 15:27:06 +01:00
Eleftheria Stein
3fe03c60f3 Add manual trigger to CI workflow
Closes gh-1777
2021-01-19 17:37:18 +01:00
Eleftheria Stein
d95652dcb3 Next development version 2021-01-19 14:19:49 +01:00
Eleftheria Stein
cfc1a1e7ce Release 2.5.0-M1 2021-01-19 13:55:51 +01:00
Eleftheria Stein
e17d0cc1d9 Upgrade test dependencies 2021-01-18 12:54:54 +01:00
Eleftheria Stein
0a0766e4a8 Upgrade Hazelcast 4 to 4.1.1
Closes gh-1761
2021-01-18 12:43:34 +01:00
Eleftheria Stein
4108c77797 Upgrade Hazelcast to 3.12.11
Closes gh-1760
2021-01-18 12:41:02 +01:00
Eleftheria Stein
c015a69a4a Upgrade Spring Data to 2021.0.0-M2
Closes gh-1759
2021-01-18 12:39:30 +01:00
Eleftheria Stein
293cf3f730 Upgrade Spring Security to 5.5.0-M1
Closes gh-1758
2021-01-18 12:37:49 +01:00
Eleftheria Stein
6f79e87c8f Upgrade Spring Framework to 5.3.3
Closes gh-1757
2021-01-18 12:35:29 +01:00
Eleftheria Stein
d74c5b1445 Upgrade Reactor to 2020.0.3
Closes gh-1755
2021-01-18 12:34:01 +01:00
Eleftheria Stein
6075089691 Upgrade samples to Spring Boot 2.4.2
Closes gh-1756
2021-01-18 12:31:54 +01:00
Eleftheria Stein
e7a0924904 Upgrade test dependencies 2021-01-12 13:34:47 +01:00
daisuzz
319f0a97ad Fix example in RedisIndexedSessionRepository and Storage Details section
* Fix example in JavaDocs of RedisIndexedSessionRepository

* Fix example in Storage Details section of documentation
2021-01-04 09:32:44 +01:00
Eleftheria Stein
95de199aa4 Upgrade samples to Spring Boot 2.4.1
Closes gh-1744
2020-12-14 09:56:17 +01:00
Eleftheria Stein
db589b7c29 Update community extensions section of reference manual
Closes gh-1736
2020-12-09 13:02:30 +01:00
Vedran Pavic
2aae51b1a1 Rationalize JDBC integration tests
This commit reduces the JDBC integration tests to only single (latest) version per RDBMS vendor, due to a growing number of integration tests. Additionally, the configuration of most containers is simplified due to improved defaults within the Testcontainers library.
2020-11-28 00:26:17 +01:00
Vedran Pavic
e721efeb85 Optimize insert attribute statement in JdbcIndexedSessionRepository
At present, the SQL statement used to insert a session attribute record contains a nested select statement that verifies the existence of parent record in the session table. Such approach can be susceptible to deadlocks on certain RDMBSs.

This commit optimizes the SQL statement used to insert session attribute so that it doesn't perform a nested select statement.

Closes: #1550
2020-11-28 00:26:17 +01:00
Vedran Pavic
0111c6e686 Provide database specific JdbcIndexedSessionRepository customizers
This commit provides JdbcIndexedSessionRepository customizers for the following SQL dialects:

- PostgreSQL
- MySQL (also used by MariaDB)
- SQL Server
- IBM DB2
- Oracle

These customizers are intended to address the concurrency issues occurring on insert of new session attribute by applying SQL dialect specific SQL upsert/merge statement instead of a generic insert.

Closes: #1213
2020-11-27 23:54:03 +01:00
Eleftheria Stein
07058c0cdf Add artifactory credentials to build 2020-11-17 13:59:06 +01:00
Thomas Heigl
5f5168814d Delay allocating Strings for message channel and body 2020-11-10 08:03:57 -05:00
Vedran Pavic
55502f336d Harmonize Redis key namespace configurations
At present, the RedisSessionRepository#setKeyNamespace expects users to provide the trailing colon (:) character that is used as separator between namespace segments. This is unlike RedisIndexedSessionRepository and ReactiveRedisSessionRepository that apply the separator implicitly in their respective #setRedisKeyNamespace methods.

This commit harmonizes the Redis key namespaces configurations across all Redis-backed session repository implementations.
2020-11-10 06:55:19 -05:00
Eleftheria Stein
0e83e3f1e0 Next development version 2020-11-10 11:48:27 +01:00
Eleftheria Stein
34876397a0 Next development version 2020-11-09 16:49:29 +01:00
Eleftheria Stein
faee8f1bdb Release 2.4.1 2020-11-09 15:40:43 +01:00
Eleftheria Stein
859784fe9e Use secrets from GitHub Actions workflow 2020-11-09 15:39:54 +01:00
Eleftheria Stein
4dd2db32d2 Revert "Release 2.4.1"
This reverts commit ae86831821.
2020-11-09 15:39:20 +01:00
Eleftheria Stein
ae86831821 Release 2.4.1 2020-11-04 17:36:47 +01:00
Eleftheria Stein
b722b12327 Fix formatting
Issue gh-1654
2020-10-30 14:34:37 +01:00
Kohei Tamura
29ff2e47fb Add try-with-resources to methods to insert BLOB 2020-10-30 08:45:52 -04:00
Eleftheria Stein
dc9da1d5bf Use OSSRH token credentials in workflow
Closes gh-1725
2020-10-30 13:42:36 +01:00
Eleftheria Stein
5a52df37ba Next development version 2020-10-28 23:36:40 +01:00
77 changed files with 1115 additions and 867 deletions

View File

@@ -6,11 +6,14 @@ on:
- master
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch: # Manual trigger
env:
GRADLE_ENTERPRISE_CACHE_USER: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
GRADLE_ENTERPRISE_SECRET_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
jobs:
build:
@@ -37,7 +40,7 @@ jobs:
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
./gradlew clean build --no-daemon --stacktrace
./gradlew clean build -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" --no-daemon --stacktrace
artifacts:
name: Deploy Artifacts
needs: [build]
@@ -54,15 +57,15 @@ jobs:
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
export VERSION_HEADER=$'Version: GnuPG v2\n\n'
export ORG_GRADLE_PROJECT_signingKey=${GPG_PRIVATE_KEY#"$VERSION_HEADER"}
export ORG_GRADLE_PROJECT_signingKey=${GPG_PRIVATE_KEY_NO_HEADER#"$VERSION_HEADER"}
export ORG_GRADLE_PROJECT_signingPassword="$GPG_PASSPHRASE"
./gradlew deployArtifacts -PossrhUsername="$OSSRH_USERNAME" -PossrhPassword="$OSSRH_PASSWORD" -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" --stacktrace --no-parallel
./gradlew finalizeDeployArtifacts -PossrhUsername="$OSSRH_USERNAME" -PossrhPassword="$OSSRH_PASSWORD" -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" --stacktrace --no-parallel
./gradlew deployArtifacts -PossrhUsername="$OSSRH_TOKEN_USERNAME" -PossrhPassword="$OSSRH_TOKEN_PASSWORD" -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" --stacktrace --no-parallel
./gradlew finalizeDeployArtifacts -PossrhUsername="$OSSRH_TOKEN_USERNAME" -PossrhPassword="$OSSRH_TOKEN_PASSWORD" -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" --stacktrace --no-parallel
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
OSSRH_TOKEN_USERNAME: ${{ secrets.OSSRH_TOKEN_USERNAME }}
OSSRH_TOKEN_PASSWORD: ${{ secrets.OSSRH_TOKEN_PASSWORD }}
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
docs:

View File

@@ -6,7 +6,7 @@ something, or simply want to hack on the code this document should help you get
== Code of Conduct
Please see our https://github.com/spring-projects/.github/blob/master/CODE_OF_CONDUCT.md[code of conduct]
Please see our https://github.com/spring-projects/.github/blob/master/CODE_OF_CONDUCT.md[code of conduct].
== Reporting Security Vulnerabilities
@@ -26,7 +26,6 @@ information as possible. Ideally, that would include a small sample project that
reproduces the problem.
== Sign the Contributor License Agreement
If you have not previously done so, please fill out and
submit the https://cla.pivotal.io/sign/spring[Contributor License Agreement].

143
Jenkinsfile vendored
View File

@@ -1,143 +0,0 @@
properties([
buildDiscarder(logRotator(numToKeepStr: '10')),
pipelineTriggers([
cron('@daily')
]),
])
def SUCCESS = hudson.model.Result.SUCCESS.toString()
currentBuild.result = SUCCESS
try {
parallel check: {
stage('Check') {
timeout(time: 45, unit: 'MINUTES') {
node('linux') {
label 'spring-session'
checkout scm
sh "git clean -dfx"
try {
withEnv(["JAVA_HOME=${tool 'jdk8'}"]) {
sh './gradlew clean check --no-daemon --stacktrace'
}
}
catch (e) {
currentBuild.result = 'FAILED: check'
throw e
}
finally {
junit '**/build/test-results/*/*.xml'
}
}
}
}
},
jdk11: {
stage('JDK 11') {
timeout(time: 45, unit: 'MINUTES') {
node('linux') {
checkout scm
sh "git clean -dfx"
try {
withEnv(["JAVA_HOME=${tool 'jdk11'}"]) {
sh './gradlew clean test integrationTest --no-daemon --stacktrace'
}
}
catch (e) {
currentBuild.result = 'FAILED: jdk11'
throw e
}
}
}
}
},
jdk12: {
stage('JDK 12') {
timeout(time: 45, unit: 'MINUTES') {
node('linux') {
checkout scm
try {
withEnv(["JAVA_HOME=${tool 'openjdk12'}"]) {
sh './gradlew clean test integrationTest --no-daemon --stacktrace'
}
}
catch (e) {
currentBuild.result = 'FAILED: jdk12'
throw e
}
}
}
}
}
if (currentBuild.result == 'SUCCESS') {
parallel artifacts: {
stage('Deploy Artifacts') {
node('linux') {
checkout scm
sh "git clean -dfx"
try {
withCredentials([file(credentialsId: 'spring-signing-secring.gpg', variable: 'SIGNING_KEYRING_FILE')]) {
withCredentials([string(credentialsId: 'spring-gpg-passphrase', variable: 'SIGNING_PASSWORD')]) {
withCredentials([usernamePassword(credentialsId: 'oss-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) {
withCredentials([usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD')]) {
withEnv(["JAVA_HOME=${tool 'jdk8'}"]) {
sh './gradlew deployArtifacts --no-daemon --stacktrace -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password=$SIGNING_PASSWORD -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD'
sh './gradlew finalizeDeployArtifacts --no-daemon --stacktrace -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password=$SIGNING_PASSWORD -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD'
}
}
}
}
}
}
catch (e) {
currentBuild.result = 'FAILED: artifacts'
throw e
}
}
}
},
docs: {
stage('Deploy Docs') {
node('linux') {
checkout scm
sh "git clean -dfx"
try {
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
withEnv(["JAVA_HOME=${tool 'jdk8'}"]) {
sh './gradlew deployDocs --no-daemon --stacktrace -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME'
}
}
}
catch (e) {
currentBuild.result = 'FAILED: docs'
throw e
}
}
}
}
}
}
finally {
def buildStatus = currentBuild.result
def buildNotSuccess = !SUCCESS.equals(buildStatus)
def lastBuildNotSuccess = !SUCCESS.equals(currentBuild.previousBuild?.result)
if (buildNotSuccess || lastBuildNotSuccess) {
stage('Notify') {
node {
final def RECIPIENTS = [[$class: 'DevelopersRecipientProvider'], [$class: 'RequesterRecipientProvider']]
def subject = "${buildStatus}: Build ${env.JOB_NAME} ${env.BUILD_NUMBER} status is now ${buildStatus}"
def details = "The build status changed to ${buildStatus}. For details see ${env.BUILD_URL}"
emailext(
subject: subject,
body: details,
recipientProviders: RECIPIENTS,
to: "$SPRING_SESSION_TEAM_EMAILS"
)
}
}
}
}

View File

@@ -13,27 +13,61 @@ It also provides transparent integration with:
== Modules
Spring Session consists of the following modules:
This Spring Session repository consists of the following modules:
* Spring Session Core - provides core Spring Session functionalities and APIs
* Spring Session Data Redis - provides `SessionRepository` and `ReactiveSessionRepository` implementation backed by Redis and configuration support
* Spring Session JDBC - provides `SessionRepository` implementation backed by a relational database and configuration support
* Spring Session Hazelcast - provides `SessionRepository` implementation backed by Hazelcast and configuration support
Additional Spring Session modules can be found in the https://github.com/spring-projects/spring-session-data-mongodb[spring-session-data-mongodb] repository
and https://github.com/spring-projects/spring-session-data-geode[spring-session-data-geode] repository.
== Getting Started
We recommend you visit the https://docs.spring.io/spring-session/docs/current/reference/html5/#samples[Spring Session Reference] and look through the "Samples and Guides" section to see which one best suits your needs.
== Samples
Spring Session samples are available in the https://github.com/spring-projects/spring-session/tree/master/spring-session-samples[spring-session-samples] directory.
== Contributing
Please see our https://github.com/spring-projects/spring-session/blob/master/CONTRIBUTING.adoc[Contributing guidelines]
for information on how to report issues, enhancements or security vulnerabilities.
== Building from Source
Spring Session uses a https://gradle.org[Gradle]-based build system.
In the instructions below, `./gradlew` is invoked from the root of the source tree and serves as
a cross-platform, self-contained bootstrap mechanism for the build.
Check out sources
----
git clone git@github.com:spring-projects/spring-session.git
----
Install all spring-\* jars into your local Maven cache
----
./gradlew install
----
Compile and test; build all jars, distribution zips, and docs
----
./gradlew build
----
== Documentation
You can find the documentation, samples, and guides for using Spring Session on the https://projects.spring.io/spring-session/[Spring Session project site].
For more in depth information, visit the https://docs.spring.io/spring-session/docs/current/reference/html5/[Spring Session Reference].
== Code of Conduct
Please see our https://github.com/spring-projects/.github/blob/master/CODE_OF_CONDUCT.md[code of conduct]
== Reporting Security Vulnerabilities
Please see our https://github.com/spring-projects/spring-session/security/policy[Security policy].
== Spring Session Project Site
You can find the documentation, issue management, support, samples, and guides for using Spring Session at https://projects.spring.io/spring-session/
Please see our https://github.com/spring-projects/.github/blob/master/CODE_OF_CONDUCT.md[code of conduct].
== License

View File

@@ -4,17 +4,25 @@ buildscript {
snapshotBuild = version.endsWith('SNAPSHOT')
milestoneBuild = !(releaseBuild || snapshotBuild)
springBootVersion = '2.4.0-M4'
springBootVersion = '2.4.4'
}
repositories {
gradlePluginPortal()
maven { url 'https://repo.spring.io/plugins-release/' }
maven { url 'https://repo.spring.io/plugins-snapshot' }
maven {
url = 'https://repo.spring.io/plugins-snapshot'
if (project.hasProperty('artifactoryUsername')) {
credentials {
username "$artifactoryUsername"
password "$artifactoryPassword"
}
}
}
}
dependencies {
classpath 'io.spring.gradle:spring-build-conventions:0.0.34.RELEASE'
classpath 'io.spring.gradle:spring-build-conventions:0.0.37'
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
}
}

View File

@@ -1,3 +1,3 @@
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.parallel=true
version=2.4.0
version=2.5.0-RC1

View File

@@ -1,36 +1,36 @@
dependencyManagement {
imports {
mavenBom 'io.projectreactor:reactor-bom:2020.0.0'
mavenBom 'org.junit:junit-bom:5.7.0'
mavenBom 'org.springframework:spring-framework-bom:5.3.0'
mavenBom 'org.springframework.data:spring-data-bom:2020.0.0'
mavenBom 'org.springframework.security:spring-security-bom:5.4.1'
mavenBom 'org.testcontainers:testcontainers-bom:1.14.3'
mavenBom 'io.projectreactor:reactor-bom:2020.0.6'
mavenBom 'org.junit:junit-bom:5.7.1'
mavenBom 'org.springframework:spring-framework-bom:5.3.5'
mavenBom 'org.springframework.data:spring-data-bom:2021.0.0-RC1'
mavenBom 'org.springframework.security:spring-security-bom:5.5.0-RC1'
mavenBom 'org.testcontainers:testcontainers-bom:1.15.2'
}
dependencies {
dependencySet(group: 'com.hazelcast', version: '3.12.10') {
dependencySet(group: 'com.hazelcast', version: '3.12.12') {
entry 'hazelcast'
entry 'hazelcast-client'
}
dependency 'org.aspectj:aspectjweaver:1.9.6'
dependency 'com.h2database:h2:1.4.200'
dependency 'com.ibm.db2:jcc:11.5.0.0'
dependency 'com.ibm.db2:jcc:11.5.5.0'
dependency 'com.microsoft.sqlserver:mssql-jdbc:7.4.1.jre8'
dependency 'com.oracle.database.jdbc:ojdbc8:19.8.0.0'
dependency 'com.oracle.database.jdbc:ojdbc8:19.10.0.0'
dependency 'com.zaxxer:HikariCP:3.4.5'
dependency 'edu.umd.cs.mtc:multithreadedtc:1.01'
dependency 'io.lettuce:lettuce-core:6.0.1.RELEASE'
dependency 'io.lettuce:lettuce-core:6.1.0.RELEASE'
dependency 'javax.annotation:javax.annotation-api:1.3.2'
dependency 'javax.servlet:javax.servlet-api:4.0.1'
dependency 'junit:junit:4.13.1'
dependency 'mysql:mysql-connector-java:8.0.22'
dependency 'junit:junit:4.13.2'
dependency 'mysql:mysql-connector-java:8.0.23'
dependency 'org.apache.derby:derby:10.14.2.0'
dependency 'org.assertj:assertj-core:3.18.0'
dependency 'org.assertj:assertj-core:3.18.1'
dependency 'org.hsqldb:hsqldb:2.5.1'
dependency 'org.mariadb.jdbc:mariadb-java-client:2.7.0'
dependency 'org.mockito:mockito-core:3.5.15'
dependency 'org.mariadb.jdbc:mariadb-java-client:2.7.2'
dependency 'org.mockito:mockito-core:3.9.0'
dependency 'org.postgresql:postgresql:42.2.18'
}
}

Binary file not shown.

View File

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

31
gradlew vendored
View File

@@ -82,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -129,6 +130,7 @@ fi
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
@@ -154,19 +156,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
i=`expr $i + 1`
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
@@ -175,14 +177,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

25
gradlew.bat vendored
View File

@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -51,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -61,28 +64,14 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell

View File

@@ -1,3 +1,15 @@
pluginManagement {
repositories {
gradlePluginPortal()
maven { url 'https://repo.spring.io/plugins-release' }
}
}
plugins {
id "com.gradle.enterprise" version "3.5.1"
id "io.spring.ge.conventions" version "0.0.7"
}
rootProject.name = 'spring-session-build'
include 'spring-session-core'

View File

@@ -59,6 +59,7 @@ import org.springframework.session.SessionRepository;
* . The default is to look in a cookie named SESSION.</li>
* <li>The session id of newly created {@link org.springframework.session.Session} is sent
* to the client using
* {@link HttpSessionIdResolver#setSessionId(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, String)}
* <li>The client is notified that the session id is no longer valid with
* {@link HttpSessionIdResolver#expireSession(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
* </li>
@@ -309,6 +310,10 @@ public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFi
if (!create) {
return null;
}
if (SessionRepositoryFilter.this.httpSessionIdResolver instanceof CookieHttpSessionIdResolver
&& this.response.isCommitted()) {
throw new IllegalStateException("Cannot create a session after the response has been committed");
}
if (SESSION_LOGGER.isDebugEnabled()) {
SESSION_LOGGER.debug(
"A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "

View File

@@ -33,7 +33,7 @@ import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Tests for {@link SpringSessionRememberMeServices}.
@@ -88,7 +88,7 @@ class SpringSessionRememberMeServicesTests {
HttpServletResponse response = mock(HttpServletResponse.class);
this.rememberMeServices = new SpringSessionRememberMeServices();
this.rememberMeServices.autoLogin(request, response);
verifyZeroInteractions(request, response);
verifyNoMoreInteractions(request, response);
}
// gh-752
@@ -102,7 +102,7 @@ class SpringSessionRememberMeServicesTests {
this.rememberMeServices.loginFail(request, response);
verify(request, times(1)).getSession(eq(false));
verify(session, times(1)).removeAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
verifyZeroInteractions(request, response, session);
verifyNoMoreInteractions(request, response, session);
}
@Test
@@ -119,7 +119,7 @@ class SpringSessionRememberMeServicesTests {
verify(request, times(1)).getSession();
verify(request, times(1)).setAttribute(eq(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR), eq(true));
verify(session, times(1)).setMaxInactiveInterval(eq(2592000));
verifyZeroInteractions(request, response, session, authentication);
verifyNoMoreInteractions(request, response, session, authentication);
}
@Test
@@ -137,7 +137,7 @@ class SpringSessionRememberMeServicesTests {
verify(request, times(1)).getSession();
verify(request, times(1)).setAttribute(eq(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR), eq(true));
verify(session, times(1)).setMaxInactiveInterval(eq(2592000));
verifyZeroInteractions(request, response, session, authentication);
verifyNoMoreInteractions(request, response, session, authentication);
}
@Test
@@ -153,7 +153,7 @@ class SpringSessionRememberMeServicesTests {
verify(request, times(1)).getSession();
verify(request, times(1)).setAttribute(eq(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR), eq(true));
verify(session, times(1)).setMaxInactiveInterval(eq(2592000));
verifyZeroInteractions(request, response, session, authentication);
verifyNoMoreInteractions(request, response, session, authentication);
}
@Test
@@ -171,7 +171,7 @@ class SpringSessionRememberMeServicesTests {
verify(request, times(1)).getSession();
verify(request, times(1)).setAttribute(eq(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR), eq(true));
verify(session, times(1)).setMaxInactiveInterval(eq(100000));
verifyZeroInteractions(request, response, session, authentication);
verifyNoMoreInteractions(request, response, session, authentication);
}
}

View File

@@ -39,7 +39,7 @@ import org.springframework.session.events.SessionDestroyedEvent;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Tests for {@link SessionEventHttpSessionListenerAdapter}.
@@ -97,7 +97,7 @@ class SessionEventHttpSessionListenerAdapterTests {
this.listener.onApplicationEvent(this.destroyed);
verifyZeroInteractions(this.destroyed, this.listener1, this.listener2);
verifyNoMoreInteractions(this.destroyed, this.listener1, this.listener2);
}
@Test

View File

@@ -62,6 +62,7 @@ import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
@@ -72,7 +73,7 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Tests for {@link SessionRepositoryFilter}.
@@ -423,6 +424,18 @@ class SessionRepositoryFilterTests {
assertThat(this.response.getCookie("SESSION")).isNotNull();
}
@Test
void doFilterGetSessionNewWhenResponseCommittedThenException() {
assertThatIllegalStateException().isThrownBy(() -> doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest, HttpServletResponse wrappedResponse)
throws IOException {
wrappedResponse.getWriter().flush();
wrappedRequest.getSession();
}
}));
}
@Test
void doFilterGetSessionNew() throws Exception {
doFilter(new DoInFilter() {
@@ -1260,7 +1273,7 @@ class SessionRepositoryFilterTests {
}
});
verifyZeroInteractions(sessionRepository);
verifyNoMoreInteractions(sessionRepository);
}
@Test
@@ -1275,7 +1288,7 @@ class SessionRepositoryFilterTests {
}
});
verifyZeroInteractions(sessionRepository);
verifyNoMoreInteractions(sessionRepository);
}
@Test
@@ -1293,7 +1306,7 @@ class SessionRepositoryFilterTests {
}
});
verifyZeroInteractions(sessionRepository);
verifyNoMoreInteractions(sessionRepository);
}
@Test
@@ -1318,7 +1331,7 @@ class SessionRepositoryFilterTests {
verify(sessionRepository).deleteById(eq(session.getId()));
verify(sessionRepository).createSession();
verify(sessionRepository).save(any());
verifyZeroInteractions(sessionRepository);
verifyNoMoreInteractions(sessionRepository);
}
// --- order

View File

@@ -49,7 +49,7 @@ import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
class SessionRepositoryMessageInterceptorTests {
@@ -98,7 +98,7 @@ class SessionRepositoryMessageInterceptorTests {
assertThat(this.interceptor.preSend(createMessage(), this.channel)).isSameAs(this.createMessage);
verifyZeroInteractions(this.sessionRepository);
verifyNoMoreInteractions(this.sessionRepository);
}
@Test
@@ -107,7 +107,7 @@ class SessionRepositoryMessageInterceptorTests {
assertThat(this.interceptor.preSend(createMessage(), this.channel)).isSameAs(this.createMessage);
verifyZeroInteractions(this.sessionRepository);
verifyNoMoreInteractions(this.sessionRepository);
}
@Test
@@ -116,7 +116,7 @@ class SessionRepositoryMessageInterceptorTests {
assertThat(this.interceptor.preSend(createMessage(), this.channel)).isSameAs(this.createMessage);
verifyZeroInteractions(this.sessionRepository);
verifyNoMoreInteractions(this.sessionRepository);
}
@Test
@@ -125,7 +125,7 @@ class SessionRepositoryMessageInterceptorTests {
assertThat(this.interceptor.preSend(createMessage(), this.channel)).isSameAs(this.createMessage);
verifyZeroInteractions(this.sessionRepository);
verifyNoMoreInteractions(this.sessionRepository);
}
@Test
@@ -209,7 +209,7 @@ class SessionRepositoryMessageInterceptorTests {
assertThat(this.interceptor.preSend(createMessage(), this.channel)).isSameAs(this.createMessage);
verifyZeroInteractions(this.sessionRepository);
verifyNoMoreInteractions(this.sessionRepository);
}
@Test
@@ -218,14 +218,14 @@ class SessionRepositoryMessageInterceptorTests {
assertThat(this.interceptor.preSend(createMessage(), this.channel)).isSameAs(this.createMessage);
verifyZeroInteractions(this.sessionRepository);
verifyNoMoreInteractions(this.sessionRepository);
}
@Test
void beforeHandshakeNotServletServerHttpRequest() {
assertThat(this.interceptor.beforeHandshake(null, null, null, null)).isTrue();
verifyZeroInteractions(this.sessionRepository);
verifyNoMoreInteractions(this.sessionRepository);
}
@Test
@@ -233,7 +233,7 @@ class SessionRepositoryMessageInterceptorTests {
ServletServerHttpRequest request = new ServletServerHttpRequest(new MockHttpServletRequest());
assertThat(this.interceptor.beforeHandshake(request, null, null, null)).isTrue();
verifyZeroInteractions(this.sessionRepository);
verifyNoMoreInteractions(this.sessionRepository);
}
@Test
@@ -256,7 +256,7 @@ class SessionRepositoryMessageInterceptorTests {
void afterHandshakeDoesNothing() {
this.interceptor.afterHandshake(null, null, null, null);
verifyZeroInteractions(this.sessionRepository);
verifyNoMoreInteractions(this.sessionRepository);
}
private void setSessionId(String id) {

View File

@@ -29,7 +29,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
*/
public abstract class AbstractRedisITests {
private static final String DOCKER_IMAGE = "redis:5.0.6";
private static final String DOCKER_IMAGE = "redis:5.0.10";
protected static class BaseConfig {

View File

@@ -18,6 +18,7 @@ package org.springframework.session.data.redis;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -36,6 +37,7 @@ import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.util.ByteUtils;
import org.springframework.session.DelegatingIndexResolver;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.FlushMode;
@@ -86,7 +88,7 @@ import org.springframework.util.Assert;
* details.
*
* <pre>
* HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 maxInactiveInterval 1800 lastAccessedTime 1404360000000 sessionAttr:attrName someAttrValue sessionAttr2:attrName someAttrValue2
* HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 maxInactiveInterval 1800 lastAccessedTime 1404360000000 sessionAttr:attrName someAttrValue sessionAttr:attrName2 someAttrValue2
* EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
* APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
* EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
@@ -129,8 +131,8 @@ import org.springframework.util.Assert;
* The {@link RedisIndexedSessionRepository.RedisSession} keeps track of the properties
* that have changed and only updates those. This means if an attribute is written once
* and read many times we only need to write that attribute once. For example, assume the
* session attribute "sessionAttr2" from earlier was updated. The following would be
* executed upon saving:
* session attribute "attrName2" from earlier was updated. The following would be executed
* upon saving:
* </p>
*
* <pre>
@@ -272,10 +274,20 @@ public class RedisIndexedSessionRepository
private String sessionCreatedChannelPrefix;
private byte[] sessionCreatedChannelPrefixBytes;
private String sessionDeletedChannel;
private byte[] sessionDeletedChannelBytes;
private String sessionExpiredChannel;
private byte[] sessionExpiredChannelBytes;
private String expiredKeyPrefix;
private byte[] expiredKeyPrefixBytes;
private final RedisOperations<Object, Object> sessionRedisOperations;
private final RedisSessionExpirationPolicy expirationPolicy;
@@ -381,8 +393,13 @@ public class RedisIndexedSessionRepository
private void configureSessionChannels() {
this.sessionCreatedChannelPrefix = this.namespace + "event:" + this.database + ":created:";
this.sessionCreatedChannelPrefixBytes = this.sessionCreatedChannelPrefix.getBytes();
this.sessionDeletedChannel = "__keyevent@" + this.database + "__:del";
this.sessionDeletedChannelBytes = this.sessionDeletedChannel.getBytes();
this.sessionExpiredChannel = "__keyevent@" + this.database + "__:expired";
this.sessionExpiredChannelBytes = this.sessionExpiredChannel.getBytes();
this.expiredKeyPrefix = this.namespace + "sessions:expires:";
this.expiredKeyPrefixBytes = this.expiredKeyPrefix.getBytes();
}
/**
@@ -501,25 +518,24 @@ public class RedisIndexedSessionRepository
@Override
public void onMessage(Message message, byte[] pattern) {
byte[] messageChannel = message.getChannel();
byte[] messageBody = message.getBody();
String channel = new String(messageChannel);
if (channel.startsWith(this.sessionCreatedChannelPrefix)) {
if (ByteUtils.startsWith(messageChannel, this.sessionCreatedChannelPrefixBytes)) {
// TODO: is this thread safe?
@SuppressWarnings("unchecked")
Map<Object, Object> loaded = (Map<Object, Object>) this.defaultSerializer.deserialize(message.getBody());
handleCreated(loaded, channel);
handleCreated(loaded, new String(messageChannel));
return;
}
String body = new String(messageBody);
if (!body.startsWith(getExpiredKeyPrefix())) {
byte[] messageBody = message.getBody();
if (!ByteUtils.startsWith(messageBody, this.expiredKeyPrefixBytes)) {
return;
}
boolean isDeleted = channel.equals(this.sessionDeletedChannel);
if (isDeleted || channel.equals(this.sessionExpiredChannel)) {
boolean isDeleted = Arrays.equals(messageChannel, this.sessionDeletedChannelBytes);
if (isDeleted || Arrays.equals(messageChannel, this.sessionExpiredChannelBytes)) {
String body = new String(messageBody);
int beginIndex = body.lastIndexOf(":") + 1;
int endIndex = body.length();
String sessionId = body.substring(beginIndex, endIndex);
@@ -611,7 +627,7 @@ public class RedisIndexedSessionRepository
}
private String getExpiredKeyPrefix() {
return this.namespace + "sessions:expires:";
return this.expiredKeyPrefix;
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2021 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.
@@ -50,6 +50,8 @@ final class RedisSessionExpirationPolicy {
private static final Log logger = LogFactory.getLog(RedisSessionExpirationPolicy.class);
private static final String SESSION_EXPIRES_PREFIX = "expires:";
private final RedisOperations<Object, Object> redis;
private final Function<Long, String> lookupExpirationKey;
@@ -67,11 +69,12 @@ final class RedisSessionExpirationPolicy {
void onDelete(Session session) {
long toExpire = roundUpToNextMinute(expiresInMillis(session));
String expireKey = getExpirationKey(toExpire);
this.redis.boundSetOps(expireKey).remove(session.getId());
String entryToRemove = SESSION_EXPIRES_PREFIX + session.getId();
this.redis.boundSetOps(expireKey).remove(entryToRemove);
}
void onExpirationUpdated(Long originalExpirationTimeInMilli, Session session) {
String keyToExpire = "expires:" + session.getId();
String keyToExpire = SESSION_EXPIRES_PREFIX + session.getId();
long toExpire = roundUpToNextMinute(expiresInMillis(session));
if (originalExpirationTimeInMilli != null) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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,13 +42,13 @@ import org.springframework.util.Assert;
*/
public class RedisSessionRepository implements SessionRepository<RedisSessionRepository.RedisSession> {
private static final String DEFAULT_KEY_NAMESPACE = "spring:session:";
private static final String DEFAULT_KEY_NAMESPACE = "spring:session";
private final RedisOperations<String, Object> sessionRedisOperations;
private Duration defaultMaxInactiveInterval = Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
private String keyNamespace = DEFAULT_KEY_NAMESPACE;
private String keyNamespace = DEFAULT_KEY_NAMESPACE + ":";
private FlushMode flushMode = FlushMode.ON_SAVE;
@@ -76,12 +76,23 @@ public class RedisSessionRepository implements SessionRepository<RedisSessionRep
/**
* Set the key namespace.
* @param keyNamespace the key namespace
* @deprecated since 2.4.0 in favor of {@link #setRedisKeyNamespace(String)}
*/
@Deprecated
public void setKeyNamespace(String keyNamespace) {
Assert.hasText(keyNamespace, "keyNamespace must not be empty");
this.keyNamespace = keyNamespace;
}
/**
* Set the Redis key namespace.
* @param namespace the Redis key namespace
*/
public void setRedisKeyNamespace(String namespace) {
Assert.hasText(namespace, "namespace must not be empty");
this.keyNamespace = namespace.trim() + ":";
}
/**
* Set the flush mode.
* @param flushMode the flush mode

View File

@@ -43,7 +43,7 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Tests for {@link ReactiveRedisSessionRepository}.
@@ -137,8 +137,8 @@ class ReactiveRedisSessionRepositoryTests {
verify(this.redisOperations).opsForHash();
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
verify(this.redisOperations).expire(anyString(), any());
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.hashOperations);
verifyNoMoreInteractions(this.redisOperations);
verifyNoMoreInteractions(this.hashOperations);
Map<String, Object> delta = this.delta.getAllValues().get(0);
assertThat(delta.size()).isEqualTo(3);
@@ -160,8 +160,8 @@ class ReactiveRedisSessionRepositoryTests {
StepVerifier.create(this.repository.save(session)).verifyComplete();
verify(this.redisOperations).hasKey(anyString());
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.hashOperations);
verifyNoMoreInteractions(this.redisOperations);
verifyNoMoreInteractions(this.hashOperations);
}
@Test
@@ -179,8 +179,8 @@ class ReactiveRedisSessionRepositoryTests {
verify(this.redisOperations).opsForHash();
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
verify(this.redisOperations).expire(anyString(), any());
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.hashOperations);
verifyNoMoreInteractions(this.redisOperations);
verifyNoMoreInteractions(this.hashOperations);
assertThat(this.delta.getAllValues().get(0)).isEqualTo(
map(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, session.getLastAccessedTime().toEpochMilli()));
@@ -202,8 +202,8 @@ class ReactiveRedisSessionRepositoryTests {
verify(this.redisOperations).opsForHash();
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
verify(this.redisOperations).expire(anyString(), any());
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.hashOperations);
verifyNoMoreInteractions(this.redisOperations);
verifyNoMoreInteractions(this.hashOperations);
assertThat(this.delta.getAllValues().get(0)).isEqualTo(
map(RedisIndexedSessionRepository.getSessionAttrNameKey(attrName), session.getAttribute(attrName)));
@@ -225,8 +225,8 @@ class ReactiveRedisSessionRepositoryTests {
verify(this.redisOperations).opsForHash();
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
verify(this.redisOperations).expire(anyString(), any());
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.hashOperations);
verifyNoMoreInteractions(this.redisOperations);
verifyNoMoreInteractions(this.hashOperations);
assertThat(this.delta.getAllValues().get(0))
.isEqualTo(map(RedisIndexedSessionRepository.getSessionAttrNameKey(attrName), null));
@@ -252,8 +252,8 @@ class ReactiveRedisSessionRepositoryTests {
StepVerifier.create(this.repository.deleteById("test")).verifyComplete();
verify(this.redisOperations).delete(anyString());
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.hashOperations);
verifyNoMoreInteractions(this.redisOperations);
verifyNoMoreInteractions(this.hashOperations);
}
@Test
@@ -267,8 +267,8 @@ class ReactiveRedisSessionRepositoryTests {
verify(this.redisOperations).opsForHash();
verify(this.hashOperations).entries(anyString());
verify(this.redisOperations).delete(anyString());
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.hashOperations);
verifyNoMoreInteractions(this.redisOperations);
verifyNoMoreInteractions(this.hashOperations);
}
@Test
@@ -291,8 +291,8 @@ class ReactiveRedisSessionRepositoryTests {
StepVerifier.create(this.repository.findById("test")).consumeNextWith((session) -> {
verify(this.redisOperations).opsForHash();
verify(this.hashOperations).entries(anyString());
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.hashOperations);
verifyNoMoreInteractions(this.redisOperations);
verifyNoMoreInteractions(this.hashOperations);
assertThat(session.getId()).isEqualTo(expected.getId());
assertThat(session.getAttributeNames()).isEqualTo(expected.getAttributeNames());
@@ -320,8 +320,8 @@ class ReactiveRedisSessionRepositoryTests {
verify(this.redisOperations).opsForHash();
verify(this.hashOperations).entries(anyString());
verify(this.redisOperations).delete(anyString());
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.hashOperations);
verifyNoMoreInteractions(this.redisOperations);
verifyNoMoreInteractions(this.hashOperations);
}
@Test // gh-1120
@@ -357,8 +357,8 @@ class ReactiveRedisSessionRepositoryTests {
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
assertThat(this.delta.getValue()).hasSize(1);
verify(this.redisOperations).expire(anyString(), any());
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.hashOperations);
verifyNoMoreInteractions(this.redisOperations);
verifyNoMoreInteractions(this.hashOperations);
}
@Test
@@ -381,8 +381,8 @@ class ReactiveRedisSessionRepositoryTests {
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
assertThat(this.delta.getValue()).hasSize(2);
verify(this.redisOperations).expire(anyString(), any());
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.hashOperations);
verifyNoMoreInteractions(this.redisOperations);
verifyNoMoreInteractions(this.hashOperations);
}
@Test
@@ -405,8 +405,8 @@ class ReactiveRedisSessionRepositoryTests {
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
assertThat(this.delta.getValue()).hasSize(3);
verify(this.redisOperations).expire(anyString(), any());
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.hashOperations);
verifyNoMoreInteractions(this.redisOperations);
verifyNoMoreInteractions(this.hashOperations);
}
private Map<String, Object> map(Object... objects) {

View File

@@ -63,7 +63,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
class RedisIndexedSessionRepositoryTests {
@@ -188,7 +188,7 @@ class RedisIndexedSessionRepositoryTests {
this.redisRepository.save(session);
verifyZeroInteractions(this.redisOperations);
verifyNoMoreInteractions(this.redisOperations);
}
@Test
@@ -511,10 +511,10 @@ class RedisIndexedSessionRepositoryTests {
verify(this.boundHashOperations).entries();
verify(this.publisher).publishEvent(this.event.capture());
assertThat(this.event.getValue().getSessionId()).isEqualTo(deletedId);
verifyZeroInteractions(this.defaultSerializer);
verifyZeroInteractions(this.publisher);
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.boundHashOperations);
verifyNoMoreInteractions(this.defaultSerializer);
verifyNoMoreInteractions(this.publisher);
verifyNoMoreInteractions(this.redisOperations);
verifyNoMoreInteractions(this.boundHashOperations);
}
@Test
@@ -534,10 +534,10 @@ class RedisIndexedSessionRepositoryTests {
verify(this.redisOperations).boundHashOps(eq(getKey(deletedId)));
verify(this.boundHashOperations).entries();
verifyZeroInteractions(this.defaultSerializer);
verifyZeroInteractions(this.publisher);
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.boundHashOperations);
verifyNoMoreInteractions(this.defaultSerializer);
verifyNoMoreInteractions(this.publisher);
verifyNoMoreInteractions(this.redisOperations);
verifyNoMoreInteractions(this.boundHashOperations);
}
@Test
@@ -561,10 +561,10 @@ class RedisIndexedSessionRepositoryTests {
verify(this.boundHashOperations).entries();
verify(this.publisher).publishEvent(this.event.capture());
assertThat(this.event.getValue().getSessionId()).isEqualTo(expiredId);
verifyZeroInteractions(this.defaultSerializer);
verifyZeroInteractions(this.publisher);
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.boundHashOperations);
verifyNoMoreInteractions(this.defaultSerializer);
verifyNoMoreInteractions(this.publisher);
verifyNoMoreInteractions(this.redisOperations);
verifyNoMoreInteractions(this.boundHashOperations);
}
@Test
@@ -584,17 +584,17 @@ class RedisIndexedSessionRepositoryTests {
verify(this.redisOperations).boundHashOps(eq(getKey(expiredId)));
verify(this.boundHashOperations).entries();
verifyZeroInteractions(this.defaultSerializer);
verifyZeroInteractions(this.publisher);
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.boundHashOperations);
verifyNoMoreInteractions(this.defaultSerializer);
verifyNoMoreInteractions(this.publisher);
verifyNoMoreInteractions(this.redisOperations);
verifyNoMoreInteractions(this.boundHashOperations);
}
@Test
void flushModeOnSaveCreate() {
this.redisRepository.createSession();
verifyZeroInteractions(this.boundHashOperations);
verifyNoMoreInteractions(this.boundHashOperations);
}
@Test
@@ -602,7 +602,7 @@ class RedisIndexedSessionRepositoryTests {
RedisSession session = this.redisRepository.createSession();
session.setAttribute("something", "here");
verifyZeroInteractions(this.boundHashOperations);
verifyNoMoreInteractions(this.boundHashOperations);
}
@Test
@@ -610,7 +610,7 @@ class RedisIndexedSessionRepositoryTests {
RedisSession session = this.redisRepository.createSession();
session.removeAttribute("remove");
verifyZeroInteractions(this.boundHashOperations);
verifyNoMoreInteractions(this.boundHashOperations);
}
@Test
@@ -618,7 +618,7 @@ class RedisIndexedSessionRepositoryTests {
RedisSession session = this.redisRepository.createSession();
session.setLastAccessedTime(Instant.ofEpochMilli(1L));
verifyZeroInteractions(this.boundHashOperations);
verifyNoMoreInteractions(this.boundHashOperations);
}
@Test
@@ -626,7 +626,7 @@ class RedisIndexedSessionRepositoryTests {
RedisSession session = this.redisRepository.createSession();
session.setMaxInactiveInterval(Duration.ofSeconds(1));
verifyZeroInteractions(this.boundHashOperations);
verifyNoMoreInteractions(this.boundHashOperations);
}
@Test
@@ -790,7 +790,7 @@ class RedisIndexedSessionRepositoryTests {
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
assertThat(this.event.getAllValues()).isEmpty();
verifyZeroInteractions(this.publisher);
verifyNoMoreInteractions(this.publisher);
}
@Test
@@ -808,7 +808,7 @@ class RedisIndexedSessionRepositoryTests {
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
assertThat(this.event.getAllValues()).isEmpty();
verifyZeroInteractions(this.publisher);
verifyNoMoreInteractions(this.publisher);
}
@Test
@@ -826,7 +826,7 @@ class RedisIndexedSessionRepositoryTests {
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
assertThat(this.event.getAllValues()).isEmpty();
verifyZeroInteractions(this.publisher);
verifyNoMoreInteractions(this.publisher);
}
@Test

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2021 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.
@@ -164,4 +164,11 @@ class RedisSessionExpirationPolicyTests {
verify(this.hashOperations).persist();
}
@Test
void onDeleteRemoveExpirationEntry() {
this.policy.onDelete(this.session);
verify(this.setOperations).remove("expires:" + this.session.getId());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -102,18 +102,36 @@ class RedisSessionRepositoryTests {
assertThat(ReflectionTestUtils.getField(this.sessionRepository, "keyNamespace")).isEqualTo("test:");
}
@Test
void setRedisKeyNamespace_ValidNamespace_ShouldSetNamespace() {
this.sessionRepository.setRedisKeyNamespace("test");
assertThat(ReflectionTestUtils.getField(this.sessionRepository, "keyNamespace")).isEqualTo("test:");
}
@Test
void setKeyNamespace_NullNamespace_ShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.sessionRepository.setKeyNamespace(null))
.withMessage("keyNamespace must not be empty");
}
@Test
void setRedisKeyNamespace_NullNamespace_ShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.sessionRepository.setRedisKeyNamespace(null))
.withMessage("namespace must not be empty");
}
@Test
void setKeyNamespace_EmptyNamespace_ShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.sessionRepository.setKeyNamespace(" "))
.withMessage("keyNamespace must not be empty");
}
@Test
void setRedisKeyNamespace_EmptyNamespace_ShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.sessionRepository.setRedisKeyNamespace(" "))
.withMessage("namespace must not be empty");
}
@Test
void setFlushMode_ValidFlushMode_ShouldSetFlushMode() {
this.sessionRepository.setFlushMode(FlushMode.IMMEDIATE);
@@ -185,7 +203,7 @@ class RedisSessionRepositoryTests {
@Test
void save_NewSessionAndCustomKeyNamespace_ShouldSaveSession() {
this.sessionRepository.setKeyNamespace("custom:");
this.sessionRepository.setRedisKeyNamespace("custom");
RedisSession session = this.sessionRepository.createSession();
this.sessionRepository.save(session);
String key = "custom:sessions:" + session.getId();

View File

@@ -30,7 +30,7 @@ import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
class RedisHttpSessionConfigurationMockTests {
@@ -53,7 +53,7 @@ class RedisHttpSessionConfigurationMockTests {
init.afterPropertiesSet();
verifyZeroInteractions(this.factory);
verifyNoMoreInteractions(this.factory);
}
@Test

View File

@@ -65,3 +65,7 @@ asciidoctorj {
'highlightjsdir@': "js/highlight",
'docinfodir@': "."
}
repositories {
maven { url "https://repo.spring.io/release" }
}

View File

@@ -791,7 +791,7 @@ HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime
maxInactiveInterval 1800 \
lastAccessedTime 1404360000000 \
sessionAttr:attrName someAttrValue \
sessionAttr2:attrName someAttrValue2
sessionAttr:attrName2 someAttrValue2
EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
@@ -814,7 +814,7 @@ HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime
maxInactiveInterval 1800 \
lastAccessedTime 1404360000000 \
sessionAttr:attrName someAttrValue \
sessionAttr2:attrName someAttrValue2
sessionAttr:attrName2 someAttrValue2
----
====
@@ -833,7 +833,7 @@ The second session attribute is named `attrName2`, with a value of `someAttrValu
The `Session` instances managed by `RedisIndexedSessionRepository` keeps track of the properties that have changed and updates only those.
This means that, if an attribute is written once and read many times, we need to write that attribute only once.
For example, assume the `sessionAttr2` session attribute from the lsiting in the preceding section was updated.
For example, assume the `attrName2` session attribute from the lsiting in the preceding section was updated.
The following command would be run upon saving:
====
@@ -1302,7 +1302,7 @@ WARNING: You should only match on valid domain characters, since the domain name
Doing so prevents a malicious user from performing such attacks as https://en.wikipedia.org/wiki/HTTP_response_splitting[HTTP Response Splitting].
[[custom-sessionrepository]]
== Customing `SessionRepository`
== Customizing `SessionRepository`
Implementing a custom <<api-sessionrepository,`SessionRepository`>> API should be a fairly straightforward task.
Coupling the custom implementation with <<api-enablespringhttpsession,`@EnableSpringHttpSession`>> support lets you reuse existing Spring Session configuration facilities and infrastructure.
@@ -1406,11 +1406,8 @@ Spring Session is Open Source software released under the https://www.apache.org
|===
| Name | Location
| Spring Session OrientDB
| https://github.com/maseev/spring-session-orientdb
| Spring Session Infinispan
| https://infinispan.org/docs/dev/user_guide/user_guide.html#externalizing_session_using_spring_session
| https://infinispan.org/infinispan-spring-boot/master/spring_boot_starter.html#_enabling_spring_session_support
|===

View File

@@ -31,7 +31,7 @@ artifacts {
dependencies {
compile project(':spring-session-core')
optional "com.hazelcast:hazelcast:4.0.3"
optional "com.hazelcast:hazelcast:4.2"
compile "org.springframework:spring-context"
compile "javax.annotation:javax.annotation-api"
@@ -42,7 +42,7 @@ dependencies {
testRuntime "org.junit.jupiter:junit-jupiter-engine"
integrationTestCompile "org.testcontainers:testcontainers"
integrationTestCompile "com.hazelcast:hazelcast:4.0.3"
integrationTestCompile "com.hazelcast:hazelcast:4.2"
integrationTestCompile project(":spring-session-hazelcast")
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2020 the original author or authors.
* Copyright 2014-2021 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.
@@ -54,16 +54,13 @@ abstract class AbstractHazelcast4IndexedSessionRepositoryITests {
IMap<String, MapSession> hazelcastMap = this.hazelcastInstance
.getMap(Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME);
assertThat(hazelcastMap.size()).isEqualTo(0);
this.repository.save(sessionToSave);
assertThat(hazelcastMap.size()).isEqualTo(1);
assertThat(hazelcastMap.get(sessionId)).isEqualTo(sessionToSave);
this.repository.deleteById(sessionId);
assertThat(hazelcastMap.size()).isEqualTo(0);
assertThat(hazelcastMap.get(sessionId)).isNull();
}
@Test
@@ -179,6 +176,8 @@ abstract class AbstractHazelcast4IndexedSessionRepositoryITests {
this.repository.save(session);
assertThat(this.repository.findById(sessionId)).isNotNull();
this.repository.deleteById(sessionId);
}
@Test
@@ -195,6 +194,8 @@ abstract class AbstractHazelcast4IndexedSessionRepositoryITests {
this.repository.save(session);
assertThat(this.repository.findById(sessionId)).isNotNull();
this.repository.deleteById(sessionId);
}
}

View File

@@ -45,8 +45,8 @@ import org.springframework.test.context.web.WebAppConfiguration;
@WebAppConfiguration
class ClientServerHazelcast4IndexedSessionRepositoryITests extends AbstractHazelcast4IndexedSessionRepositoryITests {
private static GenericContainer container = new GenericContainer<>("hazelcast/hazelcast:4.0.2")
.withExposedPorts(5701).withCopyFileToContainer(MountableFile.forClasspathResource("/hazelcast-server.xml"),
private static GenericContainer container = new GenericContainer<>("hazelcast/hazelcast:4.2").withExposedPorts(5701)
.withCopyFileToContainer(MountableFile.forClasspathResource("/hazelcast-server.xml"),
"/opt/hazelcast/hazelcast.xml");
@BeforeAll

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-4.0.xsd">
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-4.1.xsd">
<network>
<join>

View File

@@ -54,7 +54,7 @@ import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Tests for {@link Hazelcast4IndexedSessionRepository}.
@@ -98,7 +98,7 @@ class Hazelcast4IndexedSessionRepositoryTests {
HazelcastSession session = this.repository.createSession();
assertThat(session.getMaxInactiveInterval()).isEqualTo(new MapSession().getMaxInactiveInterval());
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -111,7 +111,7 @@ class Hazelcast4IndexedSessionRepositoryTests {
HazelcastSession session = this.repository.createSession();
assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofSeconds(interval));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -119,12 +119,12 @@ class Hazelcast4IndexedSessionRepositoryTests {
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
HazelcastSession session = this.repository.createSession();
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
this.repository.save(session);
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
eq(TimeUnit.SECONDS));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -136,7 +136,7 @@ class Hazelcast4IndexedSessionRepositoryTests {
HazelcastSession session = this.repository.createSession();
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
eq(TimeUnit.SECONDS));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -145,12 +145,12 @@ class Hazelcast4IndexedSessionRepositoryTests {
HazelcastSession session = this.repository.createSession();
session.setAttribute("testName", "testValue");
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
this.repository.save(session);
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
eq(TimeUnit.SECONDS));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -166,7 +166,7 @@ class Hazelcast4IndexedSessionRepositoryTests {
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
this.repository.save(session);
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -175,12 +175,12 @@ class Hazelcast4IndexedSessionRepositoryTests {
HazelcastSession session = this.repository.createSession();
session.removeAttribute("testName");
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
this.repository.save(session);
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
eq(TimeUnit.SECONDS));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -196,7 +196,7 @@ class Hazelcast4IndexedSessionRepositoryTests {
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
this.repository.save(session);
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -205,12 +205,12 @@ class Hazelcast4IndexedSessionRepositoryTests {
HazelcastSession session = this.repository.createSession();
session.setLastAccessedTime(Instant.now());
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
this.repository.save(session);
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
eq(TimeUnit.SECONDS));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -226,7 +226,7 @@ class Hazelcast4IndexedSessionRepositoryTests {
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
this.repository.save(session);
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -235,12 +235,12 @@ class Hazelcast4IndexedSessionRepositoryTests {
HazelcastSession session = this.repository.createSession();
session.setMaxInactiveInterval(Duration.ofSeconds(1));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
this.repository.save(session);
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
eq(TimeUnit.SECONDS));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -258,7 +258,7 @@ class Hazelcast4IndexedSessionRepositoryTests {
verify(this.sessions, times(1)).executeOnKey(eq(sessionId), any(EntryProcessor.class));
this.repository.save(session);
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -271,7 +271,7 @@ class Hazelcast4IndexedSessionRepositoryTests {
eq(TimeUnit.SECONDS));
this.repository.save(session);
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -285,7 +285,7 @@ class Hazelcast4IndexedSessionRepositoryTests {
eq(TimeUnit.SECONDS));
this.repository.save(session);
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -298,7 +298,7 @@ class Hazelcast4IndexedSessionRepositoryTests {
assertThat(session).isNull();
verify(this.sessions, times(1)).get(eq(sessionId));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -314,7 +314,7 @@ class Hazelcast4IndexedSessionRepositoryTests {
assertThat(session).isNull();
verify(this.sessions, times(1)).get(eq(expired.getId()));
verify(this.sessions, times(1)).remove(eq(expired.getId()));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -330,7 +330,7 @@ class Hazelcast4IndexedSessionRepositoryTests {
assertThat(session.getId()).isEqualTo(saved.getId());
assertThat(session.<String>getAttribute("savedName")).isEqualTo("savedValue");
verify(this.sessions, times(1)).get(eq(saved.getId()));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -342,7 +342,7 @@ class Hazelcast4IndexedSessionRepositoryTests {
this.repository.deleteById(sessionId);
verify(this.sessions, times(1)).remove(eq(sessionId));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -355,7 +355,7 @@ class Hazelcast4IndexedSessionRepositoryTests {
indexValue);
assertThat(sessions).isEmpty();
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -369,7 +369,7 @@ class Hazelcast4IndexedSessionRepositoryTests {
assertThat(sessions).isEmpty();
verify(this.sessions, times(1)).values(isA(EqualPredicate.class));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -393,7 +393,7 @@ class Hazelcast4IndexedSessionRepositoryTests {
assertThat(sessions).hasSize(2);
verify(this.sessions, times(1)).values(isA(EqualPredicate.class));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test // gh-1120
@@ -426,7 +426,7 @@ class Hazelcast4IndexedSessionRepositoryTests {
.forClass(Hazelcast4SessionUpdateEntryProcessor.class);
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(1);
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -446,7 +446,7 @@ class Hazelcast4IndexedSessionRepositoryTests {
.forClass(Hazelcast4SessionUpdateEntryProcessor.class);
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(2);
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -466,7 +466,7 @@ class Hazelcast4IndexedSessionRepositoryTests {
.forClass(Hazelcast4SessionUpdateEntryProcessor.class);
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(3);
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2021 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.
@@ -58,16 +58,13 @@ abstract class AbstractHazelcastIndexedSessionRepositoryITests {
IMap<String, MapSession> hazelcastMap = this.hazelcastInstance
.getMap(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME);
assertThat(hazelcastMap.size()).isEqualTo(0);
this.repository.save(sessionToSave);
assertThat(hazelcastMap.size()).isEqualTo(1);
assertThat(hazelcastMap.get(sessionId)).isEqualTo(sessionToSave);
this.repository.deleteById(sessionId);
assertThat(hazelcastMap.size()).isEqualTo(0);
assertThat(hazelcastMap.get(sessionId)).isNull();
}
@Test
@@ -183,6 +180,8 @@ abstract class AbstractHazelcastIndexedSessionRepositoryITests {
this.repository.save(session);
assertThat(this.repository.findById(sessionId)).isNotNull();
this.repository.deleteById(sessionId);
}
@Test
@@ -199,6 +198,8 @@ abstract class AbstractHazelcastIndexedSessionRepositoryITests {
this.repository.save(session);
assertThat(this.repository.findById(sessionId)).isNotNull();
this.repository.deleteById(sessionId);
}
@Test
@@ -220,6 +221,8 @@ abstract class AbstractHazelcastIndexedSessionRepositoryITests {
assertThat(this.repository
.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username))
.hasSize(1);
this.repository.deleteById(session.getId());
}
}

View File

@@ -46,7 +46,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
@WebAppConfiguration
class ClientServerHazelcastIndexedSessionRepositoryITests extends AbstractHazelcastIndexedSessionRepositoryITests {
private static GenericContainer container = new GenericContainer<>("hazelcast/hazelcast:3.12.3")
private static GenericContainer container = new GenericContainer<>("hazelcast/hazelcast:3.12.12")
.withExposedPorts(5701).withCopyFileToContainer(MountableFile.forClasspathResource("/hazelcast-server.xml"),
"/opt/hazelcast/hazelcast.xml");

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-3.12.xsd">
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-3.13.xsd">
<network>
<join>

View File

@@ -54,7 +54,7 @@ import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Tests for {@link HazelcastIndexedSessionRepository}.
@@ -99,7 +99,7 @@ class HazelcastIndexedSessionRepositoryTests {
HazelcastSession session = this.repository.createSession();
assertThat(session.getMaxInactiveInterval()).isEqualTo(new MapSession().getMaxInactiveInterval());
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -112,7 +112,7 @@ class HazelcastIndexedSessionRepositoryTests {
HazelcastSession session = this.repository.createSession();
assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofSeconds(interval));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -120,12 +120,12 @@ class HazelcastIndexedSessionRepositoryTests {
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
HazelcastSession session = this.repository.createSession();
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
this.repository.save(session);
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
eq(TimeUnit.SECONDS));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -137,7 +137,7 @@ class HazelcastIndexedSessionRepositoryTests {
HazelcastSession session = this.repository.createSession();
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
eq(TimeUnit.SECONDS));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -146,12 +146,12 @@ class HazelcastIndexedSessionRepositoryTests {
HazelcastSession session = this.repository.createSession();
session.setAttribute("testName", "testValue");
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
this.repository.save(session);
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
eq(TimeUnit.SECONDS));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -167,7 +167,7 @@ class HazelcastIndexedSessionRepositoryTests {
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
this.repository.save(session);
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -176,12 +176,12 @@ class HazelcastIndexedSessionRepositoryTests {
HazelcastSession session = this.repository.createSession();
session.removeAttribute("testName");
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
this.repository.save(session);
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
eq(TimeUnit.SECONDS));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -197,7 +197,7 @@ class HazelcastIndexedSessionRepositoryTests {
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
this.repository.save(session);
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -206,12 +206,12 @@ class HazelcastIndexedSessionRepositoryTests {
HazelcastSession session = this.repository.createSession();
session.setLastAccessedTime(Instant.now());
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
this.repository.save(session);
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
eq(TimeUnit.SECONDS));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -227,7 +227,7 @@ class HazelcastIndexedSessionRepositoryTests {
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
this.repository.save(session);
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -236,12 +236,12 @@ class HazelcastIndexedSessionRepositoryTests {
HazelcastSession session = this.repository.createSession();
session.setMaxInactiveInterval(Duration.ofSeconds(1));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
this.repository.save(session);
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
eq(TimeUnit.SECONDS));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -259,7 +259,7 @@ class HazelcastIndexedSessionRepositoryTests {
verify(this.sessions, times(1)).executeOnKey(eq(sessionId), any(EntryProcessor.class));
this.repository.save(session);
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -272,7 +272,7 @@ class HazelcastIndexedSessionRepositoryTests {
eq(TimeUnit.SECONDS));
this.repository.save(session);
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -286,7 +286,7 @@ class HazelcastIndexedSessionRepositoryTests {
eq(TimeUnit.SECONDS));
this.repository.save(session);
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -299,7 +299,7 @@ class HazelcastIndexedSessionRepositoryTests {
assertThat(session).isNull();
verify(this.sessions, times(1)).get(eq(sessionId));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -315,7 +315,7 @@ class HazelcastIndexedSessionRepositoryTests {
assertThat(session).isNull();
verify(this.sessions, times(1)).get(eq(expired.getId()));
verify(this.sessions, times(1)).remove(eq(expired.getId()));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -331,7 +331,7 @@ class HazelcastIndexedSessionRepositoryTests {
assertThat(session.getId()).isEqualTo(saved.getId());
assertThat(session.<String>getAttribute("savedName")).isEqualTo("savedValue");
verify(this.sessions, times(1)).get(eq(saved.getId()));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -343,7 +343,7 @@ class HazelcastIndexedSessionRepositoryTests {
this.repository.deleteById(sessionId);
verify(this.sessions, times(1)).remove(eq(sessionId));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -356,7 +356,7 @@ class HazelcastIndexedSessionRepositoryTests {
indexValue);
assertThat(sessions).isEmpty();
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -370,7 +370,7 @@ class HazelcastIndexedSessionRepositoryTests {
assertThat(sessions).isEmpty();
verify(this.sessions, times(1)).values(isA(EqualPredicate.class));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -394,7 +394,7 @@ class HazelcastIndexedSessionRepositoryTests {
assertThat(sessions).hasSize(2);
verify(this.sessions, times(1)).values(isA(EqualPredicate.class));
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test // gh-1120
@@ -426,7 +426,7 @@ class HazelcastIndexedSessionRepositoryTests {
ArgumentCaptor<SessionUpdateEntryProcessor> captor = ArgumentCaptor.forClass(SessionUpdateEntryProcessor.class);
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(1);
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -445,7 +445,7 @@ class HazelcastIndexedSessionRepositoryTests {
ArgumentCaptor<SessionUpdateEntryProcessor> captor = ArgumentCaptor.forClass(SessionUpdateEntryProcessor.class);
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(2);
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
@Test
@@ -464,7 +464,7 @@ class HazelcastIndexedSessionRepositoryTests {
ArgumentCaptor<SessionUpdateEntryProcessor> captor = ArgumentCaptor.forClass(SessionUpdateEntryProcessor.class);
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(3);
verifyZeroInteractions(this.sessions);
verifyNoMoreInteractions(this.sessions);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -29,8 +29,15 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobCreator;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
@@ -38,6 +45,7 @@ import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.jdbc.JdbcIndexedSessionRepository.JdbcSession;
import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession;
import org.springframework.test.util.ReflectionTestUtils;
@@ -45,6 +53,8 @@ import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Base class for {@link JdbcIndexedSessionRepository} integration tests.
@@ -57,15 +67,27 @@ abstract class AbstractJdbcIndexedSessionRepositoryITests {
private static final String INDEX_NAME = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
@Autowired
private ApplicationContext applicationContext;
@Autowired
private DataSource dataSource;
@Autowired
private JdbcIndexedSessionRepository repository;
private JdbcOperations jdbcOperations;
private LobHandler lobHandler;
private SecurityContext context;
private SecurityContext changedContext;
@BeforeEach
void setUp() {
this.jdbcOperations = new JdbcTemplate(this.dataSource);
this.lobHandler = new DefaultLobHandler();
this.context = SecurityContextHolder.createEmptyContext();
this.context.setAuthentication(new UsernamePasswordAuthenticationToken("username-" + UUID.randomUUID(), "na",
AuthorityUtils.createAuthorityList("ROLE_USER")));
@@ -759,6 +781,32 @@ abstract class AbstractJdbcIndexedSessionRepositoryITests {
assertThat((byte[]) session.getAttribute(attributeName)).hasSize(arraySize);
}
@Test // gh-1213
void saveNewSessionAttributeConcurrently() {
JdbcSession session = this.repository.createSession();
this.repository.save(session);
String attributeName = "attribute1";
String attributeValue = "value1";
try (LobCreator lobCreator = this.lobHandler.getLobCreator()) {
this.jdbcOperations.update("INSERT INTO SPRING_SESSION_ATTRIBUTES VALUES (?, ?, ?)", (ps) -> {
ps.setString(1, (String) ReflectionTestUtils.getField(session, "primaryKey"));
ps.setString(2, attributeName);
lobCreator.setBlobAsBytes(ps, 3, "value2".getBytes());
});
}
session.setAttribute(attributeName, attributeValue);
if (this.applicationContext.getBeansOfType(SessionRepositoryCustomizer.class).isEmpty()) {
// without DB specific upsert configured we're seeing duplicate key error
assertThatExceptionOfType(DuplicateKeyException.class).isThrownBy(() -> this.repository.save(session));
}
else {
// with DB specific upsert configured we're fine
assertThatCode(() -> this.repository.save(session)).doesNotThrowAnyException();
assertThat((String) this.repository.findById(session.getId()).getAttribute(attributeName))
.isEqualTo(attributeValue);
}
}
private String getSecurityName() {
return this.context.getAuthentication().getName();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package org.springframework.session.jdbc;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.testcontainers.containers.Db2Container;
import org.testcontainers.containers.JdbcDatabaseContainer;
@@ -38,169 +37,42 @@ final class DatabaseContainers {
private DatabaseContainers() {
}
static Db211Container db211() {
return new Db211Container();
static Db2Container db2() {
return new Db2Container("ibmcom/db2:11.5.4.0");
}
static MariaDBContainer mariaDb5() {
return new MariaDb5Container();
static MariaDBContainer<?> mariaDb() {
return new MariaDBContainer<>("mariadb:10.5.7");
}
static MariaDBContainer mariaDb10() {
return new MariaDb10Container();
static MySQLContainer<?> mySql() {
return new MySQLContainer<>("mysql:8.0.22");
}
static MySQLContainer mySql5() {
return new MySql5Container();
static OracleContainer oracle() {
return new OracleContainer() {
@Override
protected void configure() {
this.waitStrategy = new LogMessageWaitStrategy().withRegEx(".*DATABASE IS READY TO USE!.*\\s")
.withStartupTimeout(Duration.ofMinutes(10));
addEnv("ORACLE_PWD", getPassword());
}
@Override
protected void waitUntilContainerStarted() {
getWaitStrategy().waitUntilReady(this);
}
};
}
static MySQLContainer mySql8() {
return new MySql8Container();
static PostgreSQLContainer<?> postgreSql() {
return new PostgreSQLContainer<>("postgres:13.0");
}
static OracleXeContainer oracleXe() {
return new OracleXeContainer();
}
static PostgreSQLContainer postgreSql9() {
return new PostgreSql9Container();
}
static PostgreSQLContainer postgreSql10() {
return new PostgreSql10Container();
}
static PostgreSQLContainer postgreSql11() {
return new PostgreSql11Container();
}
static MSSQLServerContainer sqlServer2017() {
return new SqlServer2017Container();
}
private static class Db211Container extends Db2Container {
Db211Container() {
super("ibmcom/db2:11.5.0.0a");
}
}
private static class MariaDb5Container extends MariaDBContainer<MariaDb5Container> {
MariaDb5Container() {
super("mariadb:5.5.64");
}
@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.4.8");
}
@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.27");
}
@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.17");
}
@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 OracleXeContainer extends OracleContainer {
@Override
protected void configure() {
super.configure();
this.waitStrategy = new LogMessageWaitStrategy().withRegEx(".*DATABASE IS READY TO USE!.*\\s")
.withStartupTimeout(Duration.of(10, ChronoUnit.MINUTES));
setShmSize(1024L * 1024L * 1024L);
addEnv("ORACLE_PWD", getPassword());
}
@Override
protected void waitUntilContainerStarted() {
getWaitStrategy().waitUntilReady(this);
}
}
private static class PostgreSql9Container extends PostgreSQLContainer<PostgreSql9Container> {
PostgreSql9Container() {
super("postgres:9.6.15");
}
}
private static class PostgreSql10Container extends PostgreSQLContainer<PostgreSql10Container> {
PostgreSql10Container() {
super("postgres:10.10");
}
}
private static class PostgreSql11Container extends PostgreSQLContainer<PostgreSql11Container> {
PostgreSql11Container() {
super("postgres:11.5");
}
}
private static class SqlServer2017Container extends MSSQLServerContainer<SqlServer2017Container> {
SqlServer2017Container() {
super("mcr.microsoft.com/mssql/server:2017-CU16");
}
static MSSQLServerContainer<?> sqlServer() {
return new MSSQLServerContainer<>("mcr.microsoft.com/mssql/server:2019-CU8-ubuntu-16.04");
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.jdbc;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using IBM DB2 database with
* {@link Db2JdbcIndexedSessionRepositoryCustomizer}.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class Db2JdbcIndexedSessionRepositoryCustomizerITests extends Db2JdbcIndexedSessionRepositoryITests {
@Configuration
static class CustomizerConfig extends Config {
@Bean
Db2JdbcIndexedSessionRepositoryCustomizer db2JdbcIndexedSessionRepositoryCustomizer() {
return new Db2JdbcIndexedSessionRepositoryCustomizer();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -27,21 +27,21 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using IBM DB2 11.x database.
* Integration tests for {@link JdbcIndexedSessionRepository} using IBM DB2 database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class Db211JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
class Db2JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {
@Bean
Db2Container databaseContainer() {
Db2Container databaseContainer = DatabaseContainers.db211();
Db2Container databaseContainer = DatabaseContainers.db2();
databaseContainer.start();
return databaseContainer;
}

View File

@@ -1,56 +0,0 @@
/*
* Copyright 2014-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.jdbc;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testcontainers.containers.MariaDBContainer;
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.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using MariaDB 10.x database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class MariaDb10JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {
@Bean
MariaDBContainer databaseContainer() {
MariaDBContainer databaseContainer = DatabaseContainers.mariaDb10();
databaseContainer.start();
return databaseContainer;
}
@Bean
ResourceDatabasePopulator databasePopulator() {
return DatabasePopulators.mySql();
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.jdbc;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using MariaDB database with
* {@link MySqlJdbcIndexedSessionRepositoryCustomizer}.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class MariaDbJdbcIndexedSessionRepositoryCustomizerITests extends MariaDbJdbcIndexedSessionRepositoryITests {
@Configuration
static class CustomizerConfig extends Config {
@Bean
MySqlJdbcIndexedSessionRepositoryCustomizer mySqlJdbcIndexedSessionRepositoryCustomizer() {
return new MySqlJdbcIndexedSessionRepositoryCustomizer();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -27,21 +27,21 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using MariaDB 5.x database.
* Integration tests for {@link JdbcIndexedSessionRepository} using MariaDB database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class MariaDb5JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
class MariaDbJdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {
@Bean
MariaDBContainer databaseContainer() {
MariaDBContainer databaseContainer = DatabaseContainers.mariaDb5();
MariaDBContainer<?> databaseContainer() {
MariaDBContainer<?> databaseContainer = DatabaseContainers.mariaDb();
databaseContainer.start();
return databaseContainer;
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.jdbc;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using MySQL database with
* {@link MySqlJdbcIndexedSessionRepositoryCustomizer}.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class MySqlJdbcIndexedSessionRepositoryCustomizerITests extends MySqlJdbcIndexedSessionRepositoryITests {
@Configuration
static class CustomizerConfig extends Config {
@Bean
MySqlJdbcIndexedSessionRepositoryCustomizer mySqlJdbcIndexedSessionRepositoryCustomizer() {
return new MySqlJdbcIndexedSessionRepositoryCustomizer();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -27,21 +27,21 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using MySQL 5.x database.
* Integration tests for {@link JdbcIndexedSessionRepository} using MySQL database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class MySql5JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
class MySqlJdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {
@Bean
MySQLContainer databaseContainer() {
MySQLContainer databaseContainer = DatabaseContainers.mySql5();
MySQLContainer<?> databaseContainer() {
MySQLContainer<?> databaseContainer = DatabaseContainers.mySql();
databaseContainer.start();
return databaseContainer;
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.jdbc;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using Oracle database with
* {@link OracleJdbcIndexedSessionRepositoryCustomizer}.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class OracleJdbcIndexedSessionRepositoryCustomizerITests extends OracleJdbcIndexedSessionRepositoryITests {
@Configuration
static class CustomizerConfig extends Config {
@Bean
OracleJdbcIndexedSessionRepositoryCustomizer oracleJdbcIndexedSessionRepositoryCustomizer() {
return new OracleJdbcIndexedSessionRepositoryCustomizer();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -54,7 +54,7 @@ class OracleJdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcInde
@Bean
OracleContainer databaseContainer() {
OracleContainer databaseContainer = DatabaseContainers.oracleXe();
OracleContainer databaseContainer = DatabaseContainers.oracle();
databaseContainer.start();
return databaseContainer;
}

View File

@@ -1,57 +0,0 @@
/*
* Copyright 2014-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.jdbc;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testcontainers.containers.PostgreSQLContainer;
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.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL 10.x
* database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class PostgreSql10JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {
@Bean
PostgreSQLContainer databaseContainer() {
PostgreSQLContainer databaseContainer = DatabaseContainers.postgreSql10();
databaseContainer.start();
return databaseContainer;
}
@Bean
ResourceDatabasePopulator databasePopulator() {
return DatabasePopulators.postgreSql();
}
}
}

View File

@@ -1,57 +0,0 @@
/*
* Copyright 2014-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.jdbc;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testcontainers.containers.PostgreSQLContainer;
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.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL 11.x
* database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class PostgreSql11JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {
@Bean
PostgreSQLContainer databaseContainer() {
PostgreSQLContainer databaseContainer = DatabaseContainers.postgreSql11();
databaseContainer.start();
return databaseContainer;
}
@Bean
ResourceDatabasePopulator databasePopulator() {
return DatabasePopulators.postgreSql();
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.jdbc;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL database
* with {@link PostgreSqlJdbcIndexedSessionRepositoryCustomizer}.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class PostgreSqlJdbcIndexedSessionRepositoryCustomizerITests extends PostgreSqlJdbcIndexedSessionRepositoryITests {
@Configuration
static class CustomizerConfig extends Config {
@Bean
PostgreSqlJdbcIndexedSessionRepositoryCustomizer postgreSqlJdbcIndexedSessionRepositoryCustomizer() {
return new PostgreSqlJdbcIndexedSessionRepositoryCustomizer();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -27,22 +27,21 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL 9.x
* database.
* Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class PostgreSql9JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
class PostgreSqlJdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {
@Bean
PostgreSQLContainer databaseContainer() {
PostgreSQLContainer databaseContainer = DatabaseContainers.postgreSql9();
PostgreSQLContainer<?> databaseContainer() {
PostgreSQLContainer<?> databaseContainer = DatabaseContainers.postgreSql();
databaseContainer.start();
return databaseContainer;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,38 +17,30 @@
package org.springframework.session.jdbc;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testcontainers.containers.MySQLContainer;
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.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using MySQL 8.x database.
* Integration tests for {@link JdbcIndexedSessionRepository} using SQL Server database
* with {@link SqlServerJdbcIndexedSessionRepositoryCustomizer}.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class MySql8JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
class SqlServerJdbcIndexedSessionRepositoryCustomizerITests extends SqlServerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {
static class CustomizerConfig extends Config {
@Bean
MySQLContainer databaseContainer() {
MySQLContainer databaseContainer = DatabaseContainers.mySql8();
databaseContainer.start();
return databaseContainer;
}
@Bean
ResourceDatabasePopulator databasePopulator() {
return DatabasePopulators.mySql();
SqlServerJdbcIndexedSessionRepositoryCustomizer sqlServerJdbcIndexedSessionRepositoryCustomizer() {
return new SqlServerJdbcIndexedSessionRepositoryCustomizer();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -41,8 +41,8 @@ class SqlServerJdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcI
static class Config extends BaseContainerConfig {
@Bean
MSSQLServerContainer databaseContainer() {
MSSQLServerContainer databaseContainer = DatabaseContainers.sqlServer2017();
MSSQLServerContainer<?> databaseContainer() {
MSSQLServerContainer<?> databaseContainer = DatabaseContainers.sqlServer();
databaseContainer.start();
return databaseContainer;
}

View File

@@ -1,2 +1,2 @@
ibmcom/db2:11.5.0.0a
mcr.microsoft.com/mssql/server:2017-CU16
ibmcom/db2:11.5.4.0
mcr.microsoft.com/mssql/server:2019-CU8-ubuntu-16.04

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.jdbc;
import org.springframework.session.config.SessionRepositoryCustomizer;
/**
* A {@link SessionRepositoryCustomizer} implementation that applies IBM DB2 specific
* optimized SQL statements to {@link JdbcIndexedSessionRepository}.
*
* @author Vedran Pavic
* @since 2.5.0
*/
public class Db2JdbcIndexedSessionRepositoryCustomizer
implements SessionRepositoryCustomizer<JdbcIndexedSessionRepository> {
// @formatter:off
private static final String CREATE_SESSION_ATTRIBUTE_QUERY = ""
+ "MERGE INTO %TABLE_NAME%_ATTRIBUTES SA "
+ "USING ( "
+ " VALUES (?, ?, ?) "
+ ") A (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) "
+ "ON (SA.SESSION_PRIMARY_ID = A.SESSION_PRIMARY_ID and SA.ATTRIBUTE_NAME = A.ATTRIBUTE_NAME) "
+ "WHEN MATCHED THEN "
+ " UPDATE SET ATTRIBUTE_BYTES = A.ATTRIBUTE_BYTES "
+ "WHEN NOT MATCHED THEN "
+ " INSERT (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) "
+ " VALUES (A.SESSION_PRIMARY_ID, A.ATTRIBUTE_NAME, A.ATTRIBUTE_BYTES)";
// @formatter:on
@Override
public void customize(JdbcIndexedSessionRepository sessionRepository) {
sessionRepository.setCreateSessionAttributeQuery(CREATE_SESSION_ATTRIBUTE_QUERY);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -40,10 +40,13 @@ import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobCreator;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.session.DelegatingIndexResolver;
import org.springframework.session.FindByIndexNameSessionRepository;
@@ -138,55 +141,64 @@ public class JdbcIndexedSessionRepository
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
// @formatter:off
private static final String CREATE_SESSION_QUERY = "INSERT INTO %TABLE_NAME%(PRIMARY_ID, SESSION_ID, CREATION_TIME, LAST_ACCESS_TIME, MAX_INACTIVE_INTERVAL, EXPIRY_TIME, PRINCIPAL_NAME) "
private static final String CREATE_SESSION_QUERY = ""
+ "INSERT INTO %TABLE_NAME% (PRIMARY_ID, SESSION_ID, CREATION_TIME, LAST_ACCESS_TIME, MAX_INACTIVE_INTERVAL, EXPIRY_TIME, PRINCIPAL_NAME) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?)";
// @formatter:on
// @formatter:off
private static final String CREATE_SESSION_ATTRIBUTE_QUERY = "INSERT INTO %TABLE_NAME%_ATTRIBUTES(SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) "
+ "SELECT PRIMARY_ID, ?, ? "
+ "FROM %TABLE_NAME% "
+ "WHERE SESSION_ID = ?";
private static final String CREATE_SESSION_ATTRIBUTE_QUERY = ""
+ "INSERT INTO %TABLE_NAME%_ATTRIBUTES (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) "
+ "VALUES (?, ?, ?)";
// @formatter:on
// @formatter:off
private static final String GET_SESSION_QUERY = "SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES "
private static final String GET_SESSION_QUERY = ""
+ "SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES "
+ "FROM %TABLE_NAME% S "
+ "LEFT OUTER JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID "
+ "LEFT JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID "
+ "WHERE S.SESSION_ID = ?";
// @formatter:on
// @formatter:off
private static final String UPDATE_SESSION_QUERY = "UPDATE %TABLE_NAME% SET SESSION_ID = ?, LAST_ACCESS_TIME = ?, MAX_INACTIVE_INTERVAL = ?, EXPIRY_TIME = ?, PRINCIPAL_NAME = ? "
private static final String UPDATE_SESSION_QUERY = ""
+ "UPDATE %TABLE_NAME% "
+ "SET SESSION_ID = ?, LAST_ACCESS_TIME = ?, MAX_INACTIVE_INTERVAL = ?, EXPIRY_TIME = ?, PRINCIPAL_NAME = ? "
+ "WHERE PRIMARY_ID = ?";
// @formatter:on
// @formatter:off
private static final String UPDATE_SESSION_ATTRIBUTE_QUERY = "UPDATE %TABLE_NAME%_ATTRIBUTES SET ATTRIBUTE_BYTES = ? "
private static final String UPDATE_SESSION_ATTRIBUTE_QUERY = ""
+ "UPDATE %TABLE_NAME%_ATTRIBUTES "
+ "SET ATTRIBUTE_BYTES = ? "
+ "WHERE SESSION_PRIMARY_ID = ? "
+ "AND ATTRIBUTE_NAME = ?";
// @formatter:on
// @formatter:off
private static final String DELETE_SESSION_ATTRIBUTE_QUERY = "DELETE FROM %TABLE_NAME%_ATTRIBUTES "
private static final String DELETE_SESSION_ATTRIBUTE_QUERY = ""
+ "DELETE FROM %TABLE_NAME%_ATTRIBUTES "
+ "WHERE SESSION_PRIMARY_ID = ? "
+ "AND ATTRIBUTE_NAME = ?";
// @formatter:on
// @formatter:off
private static final String DELETE_SESSION_QUERY = "DELETE FROM %TABLE_NAME% "
private static final String DELETE_SESSION_QUERY = ""
+ "DELETE FROM %TABLE_NAME% "
+ "WHERE SESSION_ID = ?";
// @formatter:on
// @formatter:off
private static final String LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY = "SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES "
private static final String LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY = ""
+ "SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES "
+ "FROM %TABLE_NAME% S "
+ "LEFT OUTER JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID "
+ "LEFT JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID "
+ "WHERE S.PRINCIPAL_NAME = ?";
// @formatter:on
// @formatter:off
private static final String DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY = "DELETE FROM %TABLE_NAME% "
private static final String DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY = ""
+ "DELETE FROM %TABLE_NAME% "
+ "WHERE EXPIRY_TIME < ?";
// @formatter:on
@@ -267,7 +279,7 @@ public class JdbcIndexedSessionRepository
*/
public void setCreateSessionQuery(String createSessionQuery) {
Assert.hasText(createSessionQuery, "Query must not be empty");
this.createSessionQuery = createSessionQuery;
this.createSessionQuery = getQuery(createSessionQuery);
}
/**
@@ -276,7 +288,7 @@ public class JdbcIndexedSessionRepository
*/
public void setCreateSessionAttributeQuery(String createSessionAttributeQuery) {
Assert.hasText(createSessionAttributeQuery, "Query must not be empty");
this.createSessionAttributeQuery = createSessionAttributeQuery;
this.createSessionAttributeQuery = getQuery(createSessionAttributeQuery);
}
/**
@@ -285,7 +297,7 @@ public class JdbcIndexedSessionRepository
*/
public void setGetSessionQuery(String getSessionQuery) {
Assert.hasText(getSessionQuery, "Query must not be empty");
this.getSessionQuery = getSessionQuery;
this.getSessionQuery = getQuery(getSessionQuery);
}
/**
@@ -294,7 +306,7 @@ public class JdbcIndexedSessionRepository
*/
public void setUpdateSessionQuery(String updateSessionQuery) {
Assert.hasText(updateSessionQuery, "Query must not be empty");
this.updateSessionQuery = updateSessionQuery;
this.updateSessionQuery = getQuery(updateSessionQuery);
}
/**
@@ -303,7 +315,7 @@ public class JdbcIndexedSessionRepository
*/
public void setUpdateSessionAttributeQuery(String updateSessionAttributeQuery) {
Assert.hasText(updateSessionAttributeQuery, "Query must not be empty");
this.updateSessionAttributeQuery = updateSessionAttributeQuery;
this.updateSessionAttributeQuery = getQuery(updateSessionAttributeQuery);
}
/**
@@ -312,7 +324,7 @@ public class JdbcIndexedSessionRepository
*/
public void setDeleteSessionAttributeQuery(String deleteSessionAttributeQuery) {
Assert.hasText(deleteSessionAttributeQuery, "Query must not be empty");
this.deleteSessionAttributeQuery = deleteSessionAttributeQuery;
this.deleteSessionAttributeQuery = getQuery(deleteSessionAttributeQuery);
}
/**
@@ -321,7 +333,7 @@ public class JdbcIndexedSessionRepository
*/
public void setDeleteSessionQuery(String deleteSessionQuery) {
Assert.hasText(deleteSessionQuery, "Query must not be empty");
this.deleteSessionQuery = deleteSessionQuery;
this.deleteSessionQuery = getQuery(deleteSessionQuery);
}
/**
@@ -330,7 +342,7 @@ public class JdbcIndexedSessionRepository
*/
public void setListSessionsByPrincipalNameQuery(String listSessionsByPrincipalNameQuery) {
Assert.hasText(listSessionsByPrincipalNameQuery, "Query must not be empty");
this.listSessionsByPrincipalNameQuery = listSessionsByPrincipalNameQuery;
this.listSessionsByPrincipalNameQuery = getQuery(listSessionsByPrincipalNameQuery);
}
/**
@@ -339,7 +351,7 @@ public class JdbcIndexedSessionRepository
*/
public void setDeleteSessionsByExpiryTimeQuery(String deleteSessionsByExpiryTimeQuery) {
Assert.hasText(deleteSessionsByExpiryTimeQuery, "Query must not be empty");
this.deleteSessionsByExpiryTimeQuery = deleteSessionsByExpiryTimeQuery;
this.deleteSessionsByExpiryTimeQuery = getQuery(deleteSessionsByExpiryTimeQuery);
}
/**
@@ -460,63 +472,84 @@ public class JdbcIndexedSessionRepository
private void insertSessionAttributes(JdbcSession session, List<String> attributeNames) {
Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
if (attributeNames.size() > 1) {
this.jdbcOperations.batchUpdate(this.createSessionAttributeQuery, new BatchPreparedStatementSetter() {
try (LobCreator lobCreator = this.lobHandler.getLobCreator()) {
if (attributeNames.size() > 1) {
try {
this.jdbcOperations.batchUpdate(this.createSessionAttributeQuery,
new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
ps.setString(1, attributeName);
getLobHandler().getLobCreator().setBlobAsBytes(ps, 2,
serialize(session.getAttribute(attributeName)));
ps.setString(3, session.getId());
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
ps.setString(1, session.primaryKey);
ps.setString(2, attributeName);
lobCreator.setBlobAsBytes(ps, 3, serialize(session.getAttribute(attributeName)));
}
@Override
public int getBatchSize() {
return attributeNames.size();
}
});
}
@Override
public int getBatchSize() {
return attributeNames.size();
catch (DuplicateKeyException ex) {
throw ex;
}
});
}
else {
this.jdbcOperations.update(this.createSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
ps.setString(1, attributeName);
getLobHandler().getLobCreator().setBlobAsBytes(ps, 2, serialize(session.getAttribute(attributeName)));
ps.setString(3, session.getId());
});
catch (DataIntegrityViolationException ex) {
// parent record not found - we are ignoring this error because we
// assume that a concurrent request has removed the session
}
}
else {
try {
this.jdbcOperations.update(this.createSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
ps.setString(1, session.primaryKey);
ps.setString(2, attributeName);
lobCreator.setBlobAsBytes(ps, 3, serialize(session.getAttribute(attributeName)));
});
}
catch (DuplicateKeyException ex) {
throw ex;
}
catch (DataIntegrityViolationException ex) {
// parent record not found - we are ignoring this error because we
// assume that a concurrent request has removed the session
}
}
}
}
private void updateSessionAttributes(JdbcSession session, List<String> attributeNames) {
Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
if (attributeNames.size() > 1) {
this.jdbcOperations.batchUpdate(this.updateSessionAttributeQuery, new BatchPreparedStatementSetter() {
try (LobCreator lobCreator = this.lobHandler.getLobCreator()) {
if (attributeNames.size() > 1) {
this.jdbcOperations.batchUpdate(this.updateSessionAttributeQuery, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
getLobHandler().getLobCreator().setBlobAsBytes(ps, 1,
serialize(session.getAttribute(attributeName)));
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
lobCreator.setBlobAsBytes(ps, 1, serialize(session.getAttribute(attributeName)));
ps.setString(2, session.primaryKey);
ps.setString(3, attributeName);
}
@Override
public int getBatchSize() {
return attributeNames.size();
}
});
}
else {
this.jdbcOperations.update(this.updateSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
lobCreator.setBlobAsBytes(ps, 1, serialize(session.getAttribute(attributeName)));
ps.setString(2, session.primaryKey);
ps.setString(3, attributeName);
}
@Override
public int getBatchSize() {
return attributeNames.size();
}
});
}
else {
this.jdbcOperations.update(this.updateSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
getLobHandler().getLobCreator().setBlobAsBytes(ps, 1, serialize(session.getAttribute(attributeName)));
ps.setString(2, session.primaryKey);
ps.setString(3, attributeName);
});
});
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.jdbc;
import org.springframework.session.config.SessionRepositoryCustomizer;
/**
* A {@link SessionRepositoryCustomizer} implementation that applies MySQL specific
* optimized SQL statements to {@link JdbcIndexedSessionRepository}.
*
* @author Vedran Pavic
* @since 2.5.0
*/
public class MySqlJdbcIndexedSessionRepositoryCustomizer
implements SessionRepositoryCustomizer<JdbcIndexedSessionRepository> {
// @formatter:off
private static final String CREATE_SESSION_ATTRIBUTE_QUERY = ""
+ "INSERT INTO %TABLE_NAME%_ATTRIBUTES (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) "
+ "VALUES (?, ?, ?) "
+ "ON DUPLICATE KEY UPDATE ATTRIBUTE_BYTES = VALUES(ATTRIBUTE_BYTES)";
// @formatter:on
@Override
public void customize(JdbcIndexedSessionRepository sessionRepository) {
sessionRepository.setCreateSessionAttributeQuery(CREATE_SESSION_ATTRIBUTE_QUERY);
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.jdbc;
import org.springframework.session.config.SessionRepositoryCustomizer;
/**
* A {@link SessionRepositoryCustomizer} implementation that applies Oracle specific
* optimized SQL statements to {@link JdbcIndexedSessionRepository}.
*
* @author Vedran Pavic
* @since 2.5.0
*/
public class OracleJdbcIndexedSessionRepositoryCustomizer
implements SessionRepositoryCustomizer<JdbcIndexedSessionRepository> {
// @formatter:off
private static final String CREATE_SESSION_ATTRIBUTE_QUERY = ""
+ "MERGE INTO %TABLE_NAME%_ATTRIBUTES SA "
+ "USING ( "
+ " SELECT ? AS SESSION_PRIMARY_ID, ? AS ATTRIBUTE_NAME, ? AS ATTRIBUTE_BYTES "
+ " FROM DUAL "
+ ") A "
+ "ON (SA.SESSION_PRIMARY_ID = A.SESSION_PRIMARY_ID and SA.ATTRIBUTE_NAME = A.ATTRIBUTE_NAME) "
+ "WHEN MATCHED THEN "
+ " UPDATE SET ATTRIBUTE_BYTES = A.ATTRIBUTE_BYTES "
+ "WHEN NOT MATCHED THEN "
+ " INSERT (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) "
+ " VALUES (A.SESSION_PRIMARY_ID, A.ATTRIBUTE_NAME, A.ATTRIBUTE_BYTES)";
// @formatter:on
@Override
public void customize(JdbcIndexedSessionRepository sessionRepository) {
sessionRepository.setCreateSessionAttributeQuery(CREATE_SESSION_ATTRIBUTE_QUERY);
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.jdbc;
import org.springframework.session.config.SessionRepositoryCustomizer;
/**
* A {@link SessionRepositoryCustomizer} implementation that applies PostgreSQL specific
* optimized SQL statements to {@link JdbcIndexedSessionRepository}.
*
* @author Vedran Pavic
* @since 2.5.0
*/
public class PostgreSqlJdbcIndexedSessionRepositoryCustomizer
implements SessionRepositoryCustomizer<JdbcIndexedSessionRepository> {
// @formatter:off
private static final String CREATE_SESSION_ATTRIBUTE_QUERY = ""
+ "INSERT INTO %TABLE_NAME%_ATTRIBUTES (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) "
+ "VALUES (?, ?, ?) "
+ "ON CONFLICT (SESSION_PRIMARY_ID, ATTRIBUTE_NAME) "
+ "DO UPDATE SET ATTRIBUTE_BYTES = EXCLUDED.ATTRIBUTE_BYTES";
// @formatter:on
@Override
public void customize(JdbcIndexedSessionRepository sessionRepository) {
sessionRepository.setCreateSessionAttributeQuery(CREATE_SESSION_ATTRIBUTE_QUERY);
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.jdbc;
import org.springframework.session.config.SessionRepositoryCustomizer;
/**
* A {@link SessionRepositoryCustomizer} implementation that applies SQL Server specific
* optimized SQL statements to {@link JdbcIndexedSessionRepository}.
*
* @author Vedran Pavic
* @since 2.5.0
*/
public class SqlServerJdbcIndexedSessionRepositoryCustomizer
implements SessionRepositoryCustomizer<JdbcIndexedSessionRepository> {
// @formatter:off
private static final String CREATE_SESSION_ATTRIBUTE_QUERY = ""
+ "MERGE INTO %TABLE_NAME%_ATTRIBUTES SA "
+ "USING ( "
+ " VALUES (?, ?, ?) "
+ ") A (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) "
+ "ON (SA.SESSION_PRIMARY_ID = A.SESSION_PRIMARY_ID and SA.ATTRIBUTE_NAME = A.ATTRIBUTE_NAME) "
+ "WHEN MATCHED THEN "
+ " UPDATE SET ATTRIBUTE_BYTES = A.ATTRIBUTE_BYTES "
+ "WHEN NOT MATCHED THEN "
+ " INSERT (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) "
+ " VALUES (A.SESSION_PRIMARY_ID, A.ATTRIBUTE_NAME, A.ATTRIBUTE_BYTES);";
// @formatter:on
@Override
public void customize(JdbcIndexedSessionRepository sessionRepository) {
sessionRepository.setCreateSessionAttributeQuery(CREATE_SESSION_ATTRIBUTE_QUERY);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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,8 @@ import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.TemporaryLobCreator;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
@@ -53,6 +55,8 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -286,9 +290,9 @@ class JdbcIndexedSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION("),
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION ("),
isA(PreparedStatementSetter.class));
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES ("),
isA(PreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
}
@@ -302,9 +306,9 @@ class JdbcIndexedSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION("),
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION ("),
isA(PreparedStatementSetter.class));
verify(this.jdbcOperations, times(1)).batchUpdate(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
verify(this.jdbcOperations, times(1)).batchUpdate(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES ("),
isA(BatchPreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
}
@@ -317,7 +321,7 @@ class JdbcIndexedSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES ("),
isA(PreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
}
@@ -331,7 +335,7 @@ class JdbcIndexedSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
verify(this.jdbcOperations, times(1)).batchUpdate(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
verify(this.jdbcOperations, times(1)).batchUpdate(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES ("),
isA(BatchPreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
}
@@ -420,7 +424,7 @@ class JdbcIndexedSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
verify(this.jdbcOperations).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
verify(this.jdbcOperations).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES ("),
isA(PreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
}
@@ -675,7 +679,7 @@ class JdbcIndexedSessionRepositoryTests {
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
String attrName = "someAttribute";
session.setAttribute(attrName, "someValue");
verify(this.jdbcOperations).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
verify(this.jdbcOperations).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES ("),
isA(PreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
}
@@ -710,4 +714,19 @@ class JdbcIndexedSessionRepositoryTests {
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
void saveAndFreeTemporaryLob() {
DefaultLobHandler lobHandler = mock(DefaultLobHandler.class);
TemporaryLobCreator lobCreator = mock(TemporaryLobCreator.class);
given(lobHandler.getLobCreator()).willReturn(lobCreator);
lobHandler.setCreateTemporaryLob(true);
this.repository.setLobHandler(lobHandler);
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
session.setAttribute("testName", "testValue");
this.repository.save(session);
verify(lobCreator, atLeastOnce()).close();
}
}

View File

@@ -1,6 +1,6 @@
dependencyManagement {
imports {
mavenBom 'com.fasterxml.jackson:jackson-bom:2.11.3'
mavenBom 'com.fasterxml.jackson:jackson-bom:2.12.3'
}
dependencies {
@@ -9,7 +9,7 @@ dependencyManagement {
dependency 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.2'
dependency 'javax.servlet.jsp:javax.servlet.jsp-api:2.3.3'
dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5'
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.44.0'
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.49.1'
dependency 'org.slf4j:jcl-over-slf4j:1.7.30'
dependency 'org.slf4j:log4j-over-slf4j:1.7.30'
dependency 'org.webjars:bootstrap:2.3.2'
@@ -17,7 +17,7 @@ dependencyManagement {
dependency 'org.webjars:jquery:1.12.4'
dependency 'org.webjars:knockout:2.3.0'
dependency 'org.webjars:sockjs-client:0.3.4'
dependency 'org.webjars:stomp-websocket:2.3.3'
dependency 'org.webjars:stomp-websocket:2.3.4'
dependency 'org.webjars:webjars-taglib:0.3'
}
}

View File

@@ -46,7 +46,7 @@ import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDr
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
class FindByUsernameTests {
private static final String DOCKER_IMAGE = "redis:5.0.6";
private static final String DOCKER_IMAGE = "redis:5.0.10";
@Autowired
private MockMvc mockMvc;

View File

@@ -6,7 +6,7 @@ dependencies {
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-thymeleaf"
compile "org.springframework.boot:spring-boot-starter-security"
compile "com.hazelcast:hazelcast:4.0.3"
compile "com.hazelcast:hazelcast:4.2"
compile "nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect"
compile "org.webjars:bootstrap"
compile "org.webjars:html5shiv"

View File

@@ -50,7 +50,7 @@ import static org.assertj.core.api.Assertions.assertThat;
@AutoConfigureMockMvc
class HttpRedisJsonTest {
private static final String DOCKER_IMAGE = "redis:5.0.6";
private static final String DOCKER_IMAGE = "redis:5.0.10";
@Autowired
private MockMvc mockMvc;

View File

@@ -39,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
class RedisSerializerTest {
private static final String DOCKER_IMAGE = "redis:5.0.6";
private static final String DOCKER_IMAGE = "redis:5.0.10";
@SpringSessionRedisOperations
private RedisTemplate<Object, Object> sessionRedisTemplate;

View File

@@ -41,7 +41,7 @@ import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDr
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
class BootTests {
private static final String DOCKER_IMAGE = "redis:5.0.6";
private static final String DOCKER_IMAGE = "redis:5.0.10";
@Autowired
private MockMvc mockMvc;

View File

@@ -45,7 +45,7 @@ import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDr
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
class BootTests {
private static final String DOCKER_IMAGE = "redis:5.0.6";
private static final String DOCKER_IMAGE = "redis:5.0.10";
@Autowired
private MockMvc mockMvc;

View File

@@ -47,7 +47,7 @@ import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class AttributeTests {
private static final String DOCKER_IMAGE = "redis:5.0.6";
private static final String DOCKER_IMAGE = "redis:5.0.10";
@LocalServerPort
private int port;

View File

@@ -52,7 +52,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class ApplicationTests {
private static final String DOCKER_IMAGE = "redis:5.0.6";
private static final String DOCKER_IMAGE = "redis:5.0.10";
@Value("${local.server.port}")
private String port;

View File

@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
@Profile("embedded-redis")
public class EmbeddedRedisConfig {
private static final String DOCKER_IMAGE = "redis:5.0.6";
private static final String DOCKER_IMAGE = "redis:5.0.10";
@Bean
public GenericContainer redisContainer() {

View File

@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
@Profile("embedded-redis")
public class EmbeddedRedisConfig {
private static final String DOCKER_IMAGE = "redis:5.0.6";
private static final String DOCKER_IMAGE = "redis:5.0.10";
@Bean
public GenericContainer redisContainer() {

View File

@@ -53,7 +53,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@WebAppConfiguration
class RestMockMvcTests {
private static final String DOCKER_IMAGE = "redis:5.0.6";
private static final String DOCKER_IMAGE = "redis:5.0.10";
@Autowired
private SessionRepositoryFilter<? extends Session> sessionRepositoryFilter;

View File

@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
@Profile("embedded-redis")
public class EmbeddedRedisConfig {
private static final String DOCKER_IMAGE = "redis:5.0.6";
private static final String DOCKER_IMAGE = "redis:5.0.10";
@Bean
public GenericContainer redisContainer() {

View File

@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
@Profile("embedded-redis")
public class EmbeddedRedisConfig {
private static final String DOCKER_IMAGE = "redis:5.0.6";
private static final String DOCKER_IMAGE = "redis:5.0.10";
@Bean
public GenericContainer redisContainer() {

View File

@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
@Profile("embedded-redis")
public class EmbeddedRedisConfig {
private static final String DOCKER_IMAGE = "redis:5.0.6";
private static final String DOCKER_IMAGE = "redis:5.0.10";
@Bean
public GenericContainer redisContainer() {