Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bc21d5187 | ||
|
|
2effbd19ab | ||
|
|
31275574ee | ||
|
|
e4201aea05 | ||
|
|
3c134778d8 | ||
|
|
bc51d842dc | ||
|
|
17610e7cc2 | ||
|
|
3ce78f6cd0 | ||
|
|
21b0f60721 | ||
|
|
0dcdf5f147 | ||
|
|
a0bf6a0e62 | ||
|
|
d23b81f300 | ||
|
|
f33c5fe19a | ||
|
|
e1c4b25671 | ||
|
|
9bf18059d2 | ||
|
|
342198cdfb | ||
|
|
c151a97227 | ||
|
|
0cb6e0ebc9 | ||
|
|
b4c3cefcf4 | ||
|
|
2a6a9cfb78 | ||
|
|
55f9bc9c37 | ||
|
|
c9cf1eab7b | ||
|
|
003335df73 | ||
|
|
c3b8634fb4 | ||
|
|
28e1ab1d8d | ||
|
|
ff8750e9c1 | ||
|
|
e51dd2d1b0 | ||
|
|
a6f24bc27e | ||
|
|
42580c3a44 | ||
|
|
ea0aef9d97 | ||
|
|
0d458a4a5b | ||
|
|
102027a456 | ||
|
|
e580a97c0c | ||
|
|
7227949afb | ||
|
|
34d59a0ed9 | ||
|
|
8582b9706d | ||
|
|
14ecf21c94 | ||
|
|
6fc4097c2e | ||
|
|
a1cfbcae0c | ||
|
|
004cf6656b | ||
|
|
cde256e1a3 | ||
|
|
63f7f7b0a9 | ||
|
|
140cc75583 | ||
|
|
e157700087 | ||
|
|
1b18d64220 | ||
|
|
14756984fd | ||
|
|
34199baded | ||
|
|
cc5bb1f3a2 | ||
|
|
48cf6849fe | ||
|
|
0940451d50 | ||
|
|
304a6762b6 | ||
|
|
b6417a9c7b | ||
|
|
02885dca6d | ||
|
|
88aa71b3f3 | ||
|
|
499cb75b1b | ||
|
|
104bcefbc1 | ||
|
|
029d2cf3de | ||
|
|
108e108e47 | ||
|
|
1bc3bd2e6f | ||
|
|
24a3203755 | ||
|
|
77230a3318 | ||
|
|
b5cdb193a8 | ||
|
|
88e213d6a9 | ||
|
|
ec9db3fb5e | ||
|
|
91f20ca58c | ||
|
|
dc7a52b350 | ||
|
|
979598e3c3 | ||
|
|
2113c32ed0 | ||
|
|
73149d2130 | ||
|
|
20e1fa10d9 | ||
|
|
43e3c27169 | ||
|
|
f93d0be10e | ||
|
|
d5ebb14f8d | ||
|
|
00ba1b2028 | ||
|
|
cc1fd826ac | ||
|
|
fc5f875036 | ||
|
|
8e8cc7b1f0 | ||
|
|
c2ed6a31be | ||
|
|
a9b9ae347f | ||
|
|
f697850d23 | ||
|
|
182d24219c | ||
|
|
712e7d5a41 | ||
|
|
09ea3e4d39 |
7
.github/workflows/antora-generate.yml
vendored
7
.github/workflows/antora-generate.yml
vendored
@@ -1,7 +1,6 @@
|
||||
name: Generate Antora Files and Request Build
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Manual trigger
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
@@ -15,6 +14,12 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
cache: gradle
|
||||
- name: Generate antora.yml
|
||||
run: ./gradlew :spring-session-docs:generateAntora
|
||||
- name: Extract Branch Name
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
if: github.repository == 'spring-projects/spring-session'
|
||||
strategy:
|
||||
matrix:
|
||||
jdk: [8, 11]
|
||||
jdk: [17]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: '8'
|
||||
java-version: '17'
|
||||
- name: Setup gradle user name
|
||||
run: |
|
||||
mkdir -p ~/.gradle
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: '8'
|
||||
java-version: '17'
|
||||
- name: Setup gradle user name
|
||||
run: |
|
||||
mkdir -p ~/.gradle
|
||||
|
||||
4
.github/workflows/deploy-reference.yml
vendored
4
.github/workflows/deploy-reference.yml
vendored
@@ -13,10 +13,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 11
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
cache: gradle
|
||||
- name: Validate Gradle wrapper
|
||||
|
||||
2
.github/workflows/pr-build-workflow.yml
vendored
2
.github/workflows/pr-build-workflow.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
if: github.repository == 'spring-projects/spring-session'
|
||||
strategy:
|
||||
matrix:
|
||||
jdk: [8, 11]
|
||||
jdk: [17]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# See https://sdkman.io/usage#config
|
||||
# A summary is to add the following to ~/.sdkman/etc/config
|
||||
# sdkman_auto_env=true
|
||||
java=8.0.332-zulu
|
||||
java=17.0.2-tem
|
||||
|
||||
12
RELEASE.adoc
12
RELEASE.adoc
@@ -18,7 +18,7 @@ You can manually check at https://github.com/spring-projects/spring-session/mile
|
||||
|
||||
== 3. Update Release Version
|
||||
|
||||
Update the version number in `gradle.properties` for the release, for example `2.7.0-M1`, `2.7.0-RC1`, `2.7.3`
|
||||
Update the version number in `gradle.properties` for the release, for example `3.0.0-M1`, `3.0.0-RC1`, `3.0.4`
|
||||
|
||||
== 4. Update Antora Version
|
||||
|
||||
@@ -26,9 +26,9 @@ You will need to update the antora.yml version.
|
||||
|
||||
For milestone / release candidate releases you should follow this format:
|
||||
----
|
||||
version: '2.7.0-RC1'
|
||||
version: '3.0.0-RC1'
|
||||
prerelease: 'true'
|
||||
display_version: '2.7.0-RC1'
|
||||
display_version: '3.0.0-RC1'
|
||||
----
|
||||
|
||||
== 5. Build Locally
|
||||
@@ -50,8 +50,8 @@ Wait for the artifact to appear in https://repo1.maven.org/maven2/org/springfram
|
||||
Tag the release and then push the tag
|
||||
|
||||
....
|
||||
git tag 2.7.0-RC1
|
||||
git push origin 2.7.0-RC1
|
||||
git tag 3.0.0-RC1
|
||||
git push origin 3.0.0-RC1
|
||||
....
|
||||
|
||||
== 8. Update to Next Development Version
|
||||
@@ -76,7 +76,7 @@ java -jar github-changelog-generator.jar \
|
||||
$MILESTONE release-notes
|
||||
....
|
||||
|
||||
Note 1: `+$MILESTONE+` is something like `+2.7.1+` or `+2.7.0-M1+`. +
|
||||
Note 1: `+$MILESTONE+` is something like `+3.0.4+` or `+3.0.0-M1+`. +
|
||||
Note 2: This will create a file on your filesystem
|
||||
called `+release-notes+`.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ buildscript {
|
||||
snapshotBuild = version.endsWith('SNAPSHOT')
|
||||
milestoneBuild = !(releaseBuild || snapshotBuild)
|
||||
|
||||
springBootVersion = '2.7.0-M3'
|
||||
springBootVersion = '3.0.0-SNAPSHOT'
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -35,7 +35,7 @@ subprojects {
|
||||
apply plugin: 'io.spring.javaformat'
|
||||
|
||||
plugins.withType(JavaPlugin) {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
tasks.withType(Test) {
|
||||
|
||||
@@ -4,7 +4,7 @@ plugins {
|
||||
id "groovy"
|
||||
}
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
@@ -64,7 +64,7 @@ dependencies {
|
||||
implementation 'com.github.spullara.mustache.java:compiler:0.9.10'
|
||||
implementation 'io.spring.javaformat:spring-javaformat-gradle-plugin:0.0.15'
|
||||
implementation 'io.spring.nohttp:nohttp-gradle:0.0.9'
|
||||
implementation 'net.sourceforge.htmlunit:htmlunit:2.55.0'
|
||||
implementation 'net.sourceforge.htmlunit:htmlunit:2.37.0'
|
||||
implementation 'org.hidetake:gradle-ssh-plugin:2.10.1'
|
||||
implementation 'org.jfrog.buildinfo:build-info-extractor-gradle:4.24.20'
|
||||
implementation 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1'
|
||||
|
||||
BIN
buildSrc/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
buildSrc/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -34,7 +34,7 @@ class JacocoPlugin implements Plugin<Project> {
|
||||
project.tasks.check.dependsOn project.tasks.jacocoTestReport
|
||||
|
||||
project.jacoco {
|
||||
toolVersion = '0.8.2'
|
||||
toolVersion = '0.8.7'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public class SpringSampleWarPlugin extends SpringSamplePlugin {
|
||||
pluginManager.apply("org.gretty");
|
||||
|
||||
project.gretty {
|
||||
servletContainer = 'tomcat85'
|
||||
servletContainer = 'tomcat10'
|
||||
contextPath = '/'
|
||||
fileLogEnabled = false
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public class JavadocApiPluginITest {
|
||||
.build();
|
||||
assertThat(result.task(":api").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
File allClasses = new File(testKit.getRootDir(), "build/api/allclasses-noframe.html");
|
||||
File index = new File(testKit.getRootDir(), "build/api/allclasses.html");
|
||||
File index = new File(testKit.getRootDir(), "build/api/allclasses-index.html");
|
||||
File listing = allClasses.exists() ? allClasses : index;
|
||||
String listingText = FileUtils.readFileToString(listing);
|
||||
assertThat(listingText).contains("sample/Api.html");
|
||||
|
||||
@@ -9,7 +9,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
optional 'jakarta.servlet:jakarta.servlet-api:4.0.4'
|
||||
optional 'jakarta.servlet:jakarta.servlet-api:5.0.0'
|
||||
testImplementation platform('org.junit:junit-bom:5.8.1')
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-engine'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package sample;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
public class TheTest {
|
||||
@Test
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
plugins {
|
||||
id "org.gretty" version "3.0.7"
|
||||
id "org.gretty" version "4.0.0"
|
||||
id "io.spring.convention.spring-sample-war"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
provided 'jakarta.servlet:jakarta.servlet-api:4.0.4'
|
||||
provided 'jakarta.servlet:jakarta.servlet-api:5.0.0'
|
||||
testImplementation 'commons-io:commons-io:2.11.0'
|
||||
testImplementation 'org.assertj:assertj-core:3.21.0'
|
||||
testImplementation platform('org.junit:junit-bom:5.8.1')
|
||||
|
||||
@@ -18,11 +18,11 @@ package sample;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.annotation.WebServlet;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.annotation.WebServlet;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
@WebServlet("/")
|
||||
public class HelloServlet extends HttpServlet {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
org.gradle.parallel=true
|
||||
version=2.7.0
|
||||
version=3.0.0-M2
|
||||
|
||||
@@ -1,49 +1,45 @@
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom 'io.projectreactor:reactor-bom:2020.0.19'
|
||||
mavenBom 'com.fasterxml.jackson:jackson-bom:2.13.2.20220328'
|
||||
mavenBom 'com.fasterxml.jackson:jackson-bom:2.13.3'
|
||||
mavenBom 'org.junit:junit-bom:5.8.2'
|
||||
mavenBom 'org.springframework:spring-framework-bom:5.3.20'
|
||||
mavenBom 'org.springframework.data:spring-data-bom:2021.2.0'
|
||||
mavenBom 'org.springframework.security:spring-security-bom:5.7.0'
|
||||
mavenBom 'org.testcontainers:testcontainers-bom:1.17.1'
|
||||
mavenBom 'org.springframework:spring-framework-bom:6.0.0-M4'
|
||||
mavenBom 'org.springframework.data:spring-data-bom:2022.0.0-M3'
|
||||
mavenBom 'org.springframework.security:spring-security-bom:6.0.0-M5'
|
||||
mavenBom 'org.testcontainers:testcontainers-bom:1.16.2'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
dependencySet(group: 'com.hazelcast', version: '3.12.12') {
|
||||
entry 'hazelcast'
|
||||
entry 'hazelcast-client'
|
||||
}
|
||||
|
||||
dependency 'org.aspectj:aspectjweaver:1.9.9.1'
|
||||
dependency 'ch.qos.logback:logback-core:1.2.11'
|
||||
dependency 'com.hazelcast:hazelcast:5.0.2'
|
||||
dependency 'org.aspectj:aspectjweaver:1.9.7'
|
||||
dependency 'ch.qos.logback:logback-core:1.2.10'
|
||||
dependency 'com.google.code.findbugs:jsr305:3.0.2'
|
||||
dependency 'com.h2database:h2:1.4.200'
|
||||
dependency 'com.ibm.db2:jcc:11.5.7.0'
|
||||
dependency 'com.h2database:h2:2.1.212'
|
||||
dependency 'com.ibm.db2:jcc:11.5.6.0'
|
||||
dependency 'com.microsoft.sqlserver:mssql-jdbc:9.4.1.jre8'
|
||||
dependency 'com.oracle.database.jdbc:ojdbc8:21.5.0.0'
|
||||
dependency 'com.oracle.database.jdbc:ojdbc8:21.4.0.0.1'
|
||||
dependency 'com.zaxxer:HikariCP:3.4.5'
|
||||
dependency 'edu.umd.cs.mtc:multithreadedtc:1.01'
|
||||
dependency 'io.lettuce:lettuce-core:6.1.8.RELEASE'
|
||||
dependency 'jakarta.annotation:jakarta.annotation-api:1.3.5'
|
||||
dependency 'jakarta.servlet:jakarta.servlet-api:4.0.4'
|
||||
dependency 'mysql:mysql-connector-java:8.0.28'
|
||||
dependency 'io.lettuce:lettuce-core:6.1.6.RELEASE'
|
||||
dependency 'jakarta.annotation:jakarta.annotation-api:2.0.0'
|
||||
dependency 'jakarta.servlet:jakarta.servlet-api:5.0.0'
|
||||
dependency 'mysql:mysql-connector-java:8.0.27'
|
||||
dependency 'org.apache.derby:derby:10.14.2.0'
|
||||
dependency 'org.assertj:assertj-core:3.22.0'
|
||||
dependency 'org.hamcrest:hamcrest:2.2'
|
||||
dependency 'org.hsqldb:hsqldb:2.5.2'
|
||||
dependency 'org.mariadb.jdbc:mariadb-java-client:2.7.5'
|
||||
dependencySet(group: 'org.mockito', version: '4.4.0') {
|
||||
dependency 'org.hsqldb:hsqldb:2.6.1'
|
||||
dependency 'org.mariadb.jdbc:mariadb-java-client:2.7.4'
|
||||
dependencySet(group: 'org.mockito', version: '4.2.0') {
|
||||
entry 'mockito-core'
|
||||
entry 'mockito-junit-jupiter'
|
||||
}
|
||||
|
||||
dependencySet(group: 'org.mongodb', version: '4.5.1') {
|
||||
dependencySet(group: 'org.mongodb', version: '4.6.0') {
|
||||
entry 'mongodb-driver-core'
|
||||
entry 'mongodb-driver-sync'
|
||||
entry 'mongodb-driver-reactivestreams'
|
||||
}
|
||||
dependency 'org.postgresql:postgresql:42.3.4'
|
||||
dependency 'org.postgresql:postgresql:42.3.1'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -18,13 +18,11 @@ include 'spring-session-data-redis'
|
||||
include 'spring-session-docs'
|
||||
include 'spring-session-hazelcast'
|
||||
include 'spring-session-jdbc'
|
||||
include 'hazelcast4'
|
||||
project(':hazelcast4').projectDir = file('spring-session-hazelcast/hazelcast4')
|
||||
|
||||
file('spring-session-samples').eachDirMatch(~/spring-session-sample-.*/) { dir ->
|
||||
include dir.name
|
||||
project(":$dir.name").projectDir = dir
|
||||
}
|
||||
//file('spring-session-samples').eachDirMatch(~/spring-session-sample-.*/) { dir ->
|
||||
// include dir.name
|
||||
// project(":$dir.name").projectDir = dir
|
||||
//}
|
||||
|
||||
rootProject.children.each { project ->
|
||||
project.buildFileName = "${project.name}.gradle"
|
||||
|
||||
@@ -19,10 +19,10 @@ package org.springframework.session.config.annotation.web.http;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.SessionCookieConfig;
|
||||
import javax.servlet.http.HttpSessionListener;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.SessionCookieConfig;
|
||||
import jakarta.servlet.http.HttpSessionListener;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
package org.springframework.session.security.web.authentication;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
@@ -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.
|
||||
@@ -19,10 +19,10 @@ package org.springframework.session.web.context;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterRegistration.Dynamic;
|
||||
import javax.servlet.ServletContext;
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterRegistration.Dynamic;
|
||||
import jakarta.servlet.ServletContext;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.Conventions;
|
||||
|
||||
@@ -18,8 +18,8 @@ package org.springframework.session.web.http;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.session.web.http.CookieSerializer.CookieValue;
|
||||
|
||||
@@ -32,8 +32,8 @@ import org.springframework.session.web.http.CookieSerializer.CookieValue;
|
||||
* When a session is created, the HTTP response will have a cookie with the specified
|
||||
* cookie name and the value of the session id. The cookie will be marked as a session
|
||||
* cookie, use the context path for the path of the cookie, marked as HTTPOnly, and if
|
||||
* {@link javax.servlet.http.HttpServletRequest#isSecure()} returns true, the cookie will
|
||||
* be marked as secure. For example:
|
||||
* {@link jakarta.servlet.http.HttpServletRequest#isSecure()} returns true, the cookie
|
||||
* will be marked as secure. For example:
|
||||
*
|
||||
* <pre>
|
||||
* HTTP/1.1 200 OK
|
||||
|
||||
@@ -18,9 +18,9 @@ package org.springframework.session.web.http;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Strategy for reading and writing a cookie value to the {@link HttpServletResponse}.
|
||||
|
||||
@@ -28,9 +28,9 @@ import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -88,7 +88,8 @@ public class DefaultCookieSerializer implements CookieSerializer {
|
||||
private String sameSite = "Lax";
|
||||
|
||||
/*
|
||||
* @see org.springframework.session.web.http.CookieSerializer#readCookieValues(javax.
|
||||
* @see
|
||||
* org.springframework.session.web.http.CookieSerializer#readCookieValues(jakarta.
|
||||
* servlet.http.HttpServletRequest)
|
||||
*/
|
||||
@Override
|
||||
|
||||
@@ -19,8 +19,8 @@ package org.springframework.session.web.http;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* A {@link HttpSessionIdResolver} that uses a header to resolve the session id.
|
||||
|
||||
@@ -22,11 +22,11 @@ import java.util.Enumeration;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.servlet.http.HttpSessionBindingEvent;
|
||||
import javax.servlet.http.HttpSessionBindingListener;
|
||||
import javax.servlet.http.HttpSessionContext;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import jakarta.servlet.http.HttpSessionBindingEvent;
|
||||
import jakarta.servlet.http.HttpSessionBindingListener;
|
||||
import jakarta.servlet.http.HttpSessionContext;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
@@ -18,8 +18,8 @@ package org.springframework.session.web.http;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Contract for session id resolution strategies. Allows for session id resolution through
|
||||
|
||||
@@ -20,14 +20,14 @@ import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.WriteListener;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpServletResponseWrapper;
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.WriteListener;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpServletResponseWrapper;
|
||||
|
||||
/**
|
||||
* Base class for response wrappers which encapsulate the logic for handling an event when
|
||||
* the {@link javax.servlet.http.HttpServletResponse} is committed.
|
||||
* the {@link jakarta.servlet.http.HttpServletResponse} is committed.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.0
|
||||
@@ -84,16 +84,16 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
|
||||
/**
|
||||
* Invoke this method to disable invoking
|
||||
* {@link OnCommittedResponseWrapper#onResponseCommitted()} when the
|
||||
* {@link javax.servlet.http.HttpServletResponse} is committed. This can be useful in
|
||||
* the event that Async Web Requests are made.
|
||||
* {@link jakarta.servlet.http.HttpServletResponse} is committed. This can be useful
|
||||
* in the event that Async Web Requests are made.
|
||||
*/
|
||||
private void disableOnResponseCommitted() {
|
||||
this.disableOnCommitted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement the logic for handling the {@link javax.servlet.http.HttpServletResponse}
|
||||
* being committed.
|
||||
* Implement the logic for handling the
|
||||
* {@link jakarta.servlet.http.HttpServletResponse} being committed.
|
||||
*/
|
||||
protected abstract void onResponseCommitted();
|
||||
|
||||
@@ -474,8 +474,9 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
|
||||
/**
|
||||
* Ensures{@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked before
|
||||
* calling methods that commit the response. We delegate all methods to the original
|
||||
* {@link javax.servlet.ServletOutputStream} to ensure that the behavior is as close
|
||||
* to the original {@link javax.servlet.ServletOutputStream} as possible. See SEC-2039
|
||||
* {@link jakarta.servlet.ServletOutputStream} to ensure that the behavior is as close
|
||||
* to the original {@link jakarta.servlet.ServletOutputStream} as possible. See
|
||||
* SEC-2039
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
|
||||
@@ -18,15 +18,15 @@ package org.springframework.session.web.http;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.FilterConfig;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Allows for easily ensuring that a request is only invoked once per request. This is a
|
||||
|
||||
@@ -18,10 +18,10 @@ package org.springframework.session.web.http;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.servlet.http.HttpSessionEvent;
|
||||
import javax.servlet.http.HttpSessionListener;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import jakarta.servlet.http.HttpSessionEvent;
|
||||
import jakarta.servlet.http.HttpSessionListener;
|
||||
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.session.Session;
|
||||
@@ -79,9 +79,8 @@ public class SessionEventHttpSessionListenerAdapter
|
||||
}
|
||||
|
||||
/*
|
||||
* @see
|
||||
* org.springframework.web.context.ServletContextAware#setServletContext(javax.servlet
|
||||
* .ServletContext)
|
||||
* @see org.springframework.web.context.ServletContextAware#setServletContext(jakarta.
|
||||
* servlet.ServletContext)
|
||||
*/
|
||||
@Override
|
||||
public void setServletContext(ServletContext servletContext) {
|
||||
|
||||
@@ -20,16 +20,16 @@ import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -39,29 +39,29 @@ import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
|
||||
/**
|
||||
* Switches the {@link javax.servlet.http.HttpSession} implementation to be backed by a
|
||||
* Switches the {@link jakarta.servlet.http.HttpSession} implementation to be backed by a
|
||||
* {@link org.springframework.session.Session}.
|
||||
*
|
||||
* The {@link SessionRepositoryFilter} wraps the
|
||||
* {@link javax.servlet.http.HttpServletRequest} and overrides the methods to get an
|
||||
* {@link javax.servlet.http.HttpSession} to be backed by a
|
||||
* {@link jakarta.servlet.http.HttpServletRequest} and overrides the methods to get an
|
||||
* {@link jakarta.servlet.http.HttpSession} to be backed by a
|
||||
* {@link org.springframework.session.Session} returned by the
|
||||
* {@link org.springframework.session.SessionRepository}.
|
||||
*
|
||||
* The {@link SessionRepositoryFilter} uses a {@link HttpSessionIdResolver} (default
|
||||
* {@link CookieHttpSessionIdResolver}) to bridge logic between an
|
||||
* {@link javax.servlet.http.HttpSession} and the
|
||||
* {@link jakarta.servlet.http.HttpSession} and the
|
||||
* {@link org.springframework.session.Session} abstraction. Specifically:
|
||||
*
|
||||
* <ul>
|
||||
* <li>The session id is looked up using
|
||||
* {@link HttpSessionIdResolver#resolveSessionIds(javax.servlet.http.HttpServletRequest)}
|
||||
* {@link HttpSessionIdResolver#resolveSessionIds(jakarta.servlet.http.HttpServletRequest)}
|
||||
* . 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)}
|
||||
* {@link HttpSessionIdResolver#setSessionId(jakarta.servlet.http.HttpServletRequest, jakarta.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)}
|
||||
* {@link HttpSessionIdResolver#expireSession(jakarta.servlet.http.HttpServletRequest, jakarta.servlet.http.HttpServletResponse)}
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
@@ -183,8 +183,8 @@ public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFi
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link javax.servlet.http.HttpServletRequest} that retrieves the
|
||||
* {@link javax.servlet.http.HttpSession} using a
|
||||
* A {@link jakarta.servlet.http.HttpServletRequest} that retrieves the
|
||||
* {@link jakarta.servlet.http.HttpSession} using a
|
||||
* {@link org.springframework.session.SessionRepository}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.util.EnumSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
|
||||
@@ -19,10 +19,10 @@ package org.springframework.session.config.annotation.web.http;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -18,7 +18,7 @@ package org.springframework.session.config.annotation.web.http;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletContext;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
package org.springframework.session.security.web.authentication;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ package org.springframework.session.web.http;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -23,7 +23,7 @@ import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -21,8 +21,8 @@ import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -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.
|
||||
@@ -20,12 +20,12 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -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.
|
||||
@@ -19,9 +19,9 @@ package org.springframework.session.web.http;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpSessionEvent;
|
||||
import javax.servlet.http.HttpSessionListener;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.http.HttpSessionEvent;
|
||||
import jakarta.servlet.http.HttpSessionListener;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -30,17 +30,17 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.servlet.http.HttpSessionBindingEvent;
|
||||
import javax.servlet.http.HttpSessionBindingListener;
|
||||
import javax.servlet.http.HttpSessionContext;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import jakarta.servlet.http.HttpSessionBindingEvent;
|
||||
import jakarta.servlet.http.HttpSessionBindingListener;
|
||||
import jakarta.servlet.http.HttpSessionContext;
|
||||
|
||||
import org.assertj.core.data.Offset;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
@@ -23,7 +23,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -389,7 +389,7 @@ public abstract class AbstractMongoRepositoryITest extends AbstractITest {
|
||||
|
||||
protected static class BaseConfig {
|
||||
|
||||
private static final String DOCKER_IMAGE = "mongo:4.4.1";
|
||||
private static final String DOCKER_IMAGE = "mongo:4.0.10";
|
||||
|
||||
@Bean(initMethod = "start", destroyMethod = "stop")
|
||||
public MongoDBContainer mongoContainer() {
|
||||
|
||||
@@ -175,7 +175,7 @@ public class MongoDbDeleteJacksonSessionVerificationTest {
|
||||
@EnableMongoWebSession
|
||||
static class Config {
|
||||
|
||||
private static final String DOCKER_IMAGE = "mongo:4.4.1";
|
||||
private static final String DOCKER_IMAGE = "mongo:4.0.10";
|
||||
|
||||
@Bean(initMethod = "start", destroyMethod = "stop")
|
||||
MongoDBContainer mongoContainer() {
|
||||
|
||||
@@ -170,7 +170,7 @@ public class MongoDbLogoutVerificationTest {
|
||||
@EnableMongoWebSession
|
||||
static class Config {
|
||||
|
||||
private static final String DOCKER_IMAGE = "mongo:4.4.1";
|
||||
private static final String DOCKER_IMAGE = "mongo:4.0.10";
|
||||
|
||||
@Bean(initMethod = "start", destroyMethod = "stop")
|
||||
MongoDBContainer mongoContainer() {
|
||||
|
||||
@@ -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.14";
|
||||
private static final String DOCKER_IMAGE = "redis:5.0.10";
|
||||
|
||||
protected static class BaseConfig {
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2021 the original author or authors.
|
||||
* Copyright 2014-2022 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.
|
||||
@@ -691,7 +691,7 @@ class RedisIndexedSessionRepositoryITests extends AbstractRedisITests {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(redisNamespace = "RedisIndexedSessionRepositoryITests")
|
||||
@EnableRedisHttpSession(redisNamespace = "RedisIndexedSessionRepositoryITests", enableIndexingAndEvents = true)
|
||||
static class Config extends BaseConfig {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2022 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.
|
||||
@@ -26,13 +26,10 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
|
||||
import org.springframework.session.data.redis.RedisSessionRepository.RedisSession;
|
||||
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
@@ -223,17 +220,9 @@ class RedisSessionRepositoryITests extends AbstractRedisITests {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableSpringHttpSession
|
||||
@EnableRedisHttpSession
|
||||
static class Config extends BaseConfig {
|
||||
|
||||
@Bean
|
||||
RedisSessionRepository sessionRepository(RedisConnectionFactory redisConnectionFactory) {
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
||||
redisTemplate.afterPropertiesSet();
|
||||
return new RedisSessionRepository(redisTemplate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2022 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.
|
||||
@@ -113,7 +113,7 @@ class EnableRedisHttpSessionExpireSessionDestroyedTests<S extends Session> exten
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1)
|
||||
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1, enableIndexingAndEvents = true)
|
||||
static class Config extends BaseConfig {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2022 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.
|
||||
@@ -101,7 +101,7 @@ class RedisListenerContainerTaskExecutorITests extends AbstractRedisITests {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(redisNamespace = "RedisListenerContainerTaskExecutorITests")
|
||||
@EnableRedisHttpSession(redisNamespace = "RedisListenerContainerTaskExecutorITests", enableIndexingAndEvents = true)
|
||||
static class Config extends BaseConfig {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2022 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.
|
||||
@@ -33,6 +33,7 @@ import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
|
||||
import org.springframework.session.data.redis.RedisFlushMode;
|
||||
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
|
||||
import org.springframework.session.data.redis.RedisSessionRepository;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
|
||||
/**
|
||||
@@ -54,7 +55,8 @@ import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* More advanced configurations can extend {@link RedisHttpSessionConfiguration} instead.
|
||||
* More advanced configurations can extend {@link RedisHttpSessionConfiguration} or
|
||||
* {@link RedisIndexedHttpSessionConfiguration} instead.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
@@ -64,7 +66,7 @@ import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
@Import(RedisHttpSessionConfiguration.class)
|
||||
@Import(RedisHttpSessionConfigurationSelector.class)
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public @interface EnableRedisHttpSession {
|
||||
|
||||
@@ -113,13 +115,6 @@ public @interface EnableRedisHttpSession {
|
||||
*/
|
||||
FlushMode flushMode() default FlushMode.ON_SAVE;
|
||||
|
||||
/**
|
||||
* The cron expression for expired session cleanup job. By default runs every minute.
|
||||
* @return the session cleanup cron expression
|
||||
* @since 2.0.0
|
||||
*/
|
||||
String cleanupCron() default RedisHttpSessionConfiguration.DEFAULT_CLEANUP_CRON;
|
||||
|
||||
/**
|
||||
* Save mode for the session. The default is {@link SaveMode#ON_SET_ATTRIBUTE}, which
|
||||
* only saves changes made to session.
|
||||
@@ -128,4 +123,13 @@ public @interface EnableRedisHttpSession {
|
||||
*/
|
||||
SaveMode saveMode() default SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
/**
|
||||
* Indicate whether the {@link SessionRepository} should publish session events and
|
||||
* support fetching sessions by index. If true, a
|
||||
* {@link RedisIndexedSessionRepository} will be used in place of
|
||||
* {@link RedisSessionRepository}. This will result in slower performance.
|
||||
* @return true if indexing and events should be enabled, false otherwise
|
||||
*/
|
||||
boolean enableIndexingAndEvents() default false;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2022 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.
|
||||
@@ -16,55 +16,35 @@
|
||||
|
||||
package org.springframework.session.data.redis.config.annotation.web.http;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.EmbeddedValueResolverAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportAware;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.listener.ChannelTopic;
|
||||
import org.springframework.data.redis.listener.PatternTopic;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.IndexResolver;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.config.SessionRepositoryCustomizer;
|
||||
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
|
||||
import org.springframework.session.data.redis.RedisFlushMode;
|
||||
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
|
||||
import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction;
|
||||
import org.springframework.session.data.redis.config.ConfigureRedisAction;
|
||||
import org.springframework.session.data.redis.RedisSessionRepository;
|
||||
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.StringValueResolver;
|
||||
|
||||
@@ -83,86 +63,39 @@ import org.springframework.util.StringValueResolver;
|
||||
public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
||||
implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware {
|
||||
|
||||
static final String DEFAULT_CLEANUP_CRON = "0 * * * * *";
|
||||
|
||||
private Integer maxInactiveIntervalInSeconds = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
|
||||
|
||||
private String redisNamespace = RedisIndexedSessionRepository.DEFAULT_NAMESPACE;
|
||||
private String redisNamespace = RedisSessionRepository.DEFAULT_KEY_NAMESPACE;
|
||||
|
||||
private FlushMode flushMode = FlushMode.ON_SAVE;
|
||||
|
||||
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
private String cleanupCron = DEFAULT_CLEANUP_CRON;
|
||||
|
||||
private ConfigureRedisAction configureRedisAction = new ConfigureNotifyKeyspaceEventsAction();
|
||||
|
||||
private RedisConnectionFactory redisConnectionFactory;
|
||||
|
||||
private IndexResolver<Session> indexResolver;
|
||||
|
||||
private RedisSerializer<Object> defaultRedisSerializer;
|
||||
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
private Executor redisTaskExecutor;
|
||||
|
||||
private Executor redisSubscriptionExecutor;
|
||||
|
||||
private List<SessionRepositoryCustomizer<RedisIndexedSessionRepository>> sessionRepositoryCustomizers;
|
||||
private List<SessionRepositoryCustomizer<RedisSessionRepository>> sessionRepositoryCustomizers;
|
||||
|
||||
private ClassLoader classLoader;
|
||||
|
||||
private StringValueResolver embeddedValueResolver;
|
||||
|
||||
@Bean
|
||||
public RedisIndexedSessionRepository sessionRepository() {
|
||||
RedisTemplate<Object, Object> redisTemplate = createRedisTemplate();
|
||||
RedisIndexedSessionRepository sessionRepository = new RedisIndexedSessionRepository(redisTemplate);
|
||||
sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
|
||||
if (this.indexResolver != null) {
|
||||
sessionRepository.setIndexResolver(this.indexResolver);
|
||||
}
|
||||
if (this.defaultRedisSerializer != null) {
|
||||
sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
|
||||
}
|
||||
sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
|
||||
public RedisSessionRepository sessionRepository() {
|
||||
RedisTemplate<String, Object> redisTemplate = createRedisTemplate();
|
||||
RedisSessionRepository sessionRepository = new RedisSessionRepository(redisTemplate);
|
||||
sessionRepository.setDefaultMaxInactiveInterval(Duration.ofSeconds(this.maxInactiveIntervalInSeconds));
|
||||
if (StringUtils.hasText(this.redisNamespace)) {
|
||||
sessionRepository.setRedisKeyNamespace(this.redisNamespace);
|
||||
}
|
||||
sessionRepository.setFlushMode(this.flushMode);
|
||||
sessionRepository.setSaveMode(this.saveMode);
|
||||
int database = resolveDatabase();
|
||||
sessionRepository.setDatabase(database);
|
||||
this.sessionRepositoryCustomizers
|
||||
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
|
||||
return sessionRepository;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisMessageListenerContainer springSessionRedisMessageListenerContainer(
|
||||
RedisIndexedSessionRepository sessionRepository) {
|
||||
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
||||
container.setConnectionFactory(this.redisConnectionFactory);
|
||||
if (this.redisTaskExecutor != null) {
|
||||
container.setTaskExecutor(this.redisTaskExecutor);
|
||||
}
|
||||
if (this.redisSubscriptionExecutor != null) {
|
||||
container.setSubscriptionExecutor(this.redisSubscriptionExecutor);
|
||||
}
|
||||
container.addMessageListener(sessionRepository,
|
||||
Arrays.asList(new ChannelTopic(sessionRepository.getSessionDeletedChannel()),
|
||||
new ChannelTopic(sessionRepository.getSessionExpiredChannel())));
|
||||
container.addMessageListener(sessionRepository,
|
||||
Collections.singletonList(new PatternTopic(sessionRepository.getSessionCreatedChannelPrefix() + "*")));
|
||||
return container;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public InitializingBean enableRedisKeyspaceNotificationsInitializer() {
|
||||
return new EnableRedisKeyspaceNotificationsInitializer(this.redisConnectionFactory, this.configureRedisAction);
|
||||
}
|
||||
|
||||
public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
|
||||
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
|
||||
}
|
||||
@@ -186,20 +119,6 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
|
||||
this.saveMode = saveMode;
|
||||
}
|
||||
|
||||
public void setCleanupCron(String cleanupCron) {
|
||||
this.cleanupCron = cleanupCron;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action to perform for configuring Redis.
|
||||
* @param configureRedisAction the configureRedis to set. The default is
|
||||
* {@link ConfigureNotifyKeyspaceEventsAction}.
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
public void setConfigureRedisAction(ConfigureRedisAction configureRedisAction) {
|
||||
this.configureRedisAction = configureRedisAction;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setRedisConnectionFactory(
|
||||
@SpringSessionRedisConnectionFactory ObjectProvider<RedisConnectionFactory> springSessionRedisConnectionFactory,
|
||||
@@ -217,31 +136,9 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
|
||||
this.defaultRedisSerializer = defaultRedisSerializer;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setIndexResolver(IndexResolver<Session> indexResolver) {
|
||||
this.indexResolver = indexResolver;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
@Qualifier("springSessionRedisTaskExecutor")
|
||||
public void setRedisTaskExecutor(Executor redisTaskExecutor) {
|
||||
this.redisTaskExecutor = redisTaskExecutor;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
@Qualifier("springSessionRedisSubscriptionExecutor")
|
||||
public void setRedisSubscriptionExecutor(Executor redisSubscriptionExecutor) {
|
||||
this.redisSubscriptionExecutor = redisSubscriptionExecutor;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setSessionRepositoryCustomizer(
|
||||
ObjectProvider<SessionRepositoryCustomizer<RedisIndexedSessionRepository>> sessionRepositoryCustomizers) {
|
||||
ObjectProvider<SessionRepositoryCustomizer<RedisSessionRepository>> sessionRepositoryCustomizers) {
|
||||
this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -273,14 +170,10 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
|
||||
}
|
||||
this.flushMode = flushMode;
|
||||
this.saveMode = attributes.getEnum("saveMode");
|
||||
String cleanupCron = attributes.getString("cleanupCron");
|
||||
if (StringUtils.hasText(cleanupCron)) {
|
||||
this.cleanupCron = cleanupCron;
|
||||
}
|
||||
}
|
||||
|
||||
private RedisTemplate<Object, Object> createRedisTemplate() {
|
||||
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
|
||||
private RedisTemplate<String, Object> createRedisTemplate() {
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
||||
if (this.defaultRedisSerializer != null) {
|
||||
@@ -292,77 +185,4 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
|
||||
return redisTemplate;
|
||||
}
|
||||
|
||||
private int resolveDatabase() {
|
||||
if (ClassUtils.isPresent("io.lettuce.core.RedisClient", null)
|
||||
&& this.redisConnectionFactory instanceof LettuceConnectionFactory) {
|
||||
return ((LettuceConnectionFactory) this.redisConnectionFactory).getDatabase();
|
||||
}
|
||||
if (ClassUtils.isPresent("redis.clients.jedis.Jedis", null)
|
||||
&& this.redisConnectionFactory instanceof JedisConnectionFactory) {
|
||||
return ((JedisConnectionFactory) this.redisConnectionFactory).getDatabase();
|
||||
}
|
||||
return RedisIndexedSessionRepository.DEFAULT_DATABASE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that Redis is configured to send keyspace notifications. This is important
|
||||
* to ensure that expiration and deletion of sessions trigger SessionDestroyedEvents.
|
||||
* Without the SessionDestroyedEvent resources may not get cleaned up properly. For
|
||||
* example, the mapping of the Session to WebSocket connections may not get cleaned
|
||||
* up.
|
||||
*/
|
||||
static class EnableRedisKeyspaceNotificationsInitializer implements InitializingBean {
|
||||
|
||||
private final RedisConnectionFactory connectionFactory;
|
||||
|
||||
private ConfigureRedisAction configure;
|
||||
|
||||
EnableRedisKeyspaceNotificationsInitializer(RedisConnectionFactory connectionFactory,
|
||||
ConfigureRedisAction configure) {
|
||||
this.connectionFactory = connectionFactory;
|
||||
this.configure = configure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
if (this.configure == ConfigureRedisAction.NO_OP) {
|
||||
return;
|
||||
}
|
||||
RedisConnection connection = this.connectionFactory.getConnection();
|
||||
try {
|
||||
this.configure.configure(connection);
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
connection.close();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
LogFactory.getLog(getClass()).error("Error closing RedisConnection", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration of scheduled job for cleaning up expired sessions.
|
||||
*/
|
||||
@EnableScheduling
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class SessionCleanupConfiguration implements SchedulingConfigurer {
|
||||
|
||||
private final RedisIndexedSessionRepository sessionRepository;
|
||||
|
||||
SessionCleanupConfiguration(RedisIndexedSessionRepository sessionRepository) {
|
||||
this.sessionRepository = sessionRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||
taskRegistrar.addCronTask(this.sessionRepository::cleanupExpiredSessions,
|
||||
RedisHttpSessionConfiguration.this.cleanupCron);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2014-2022 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.data.redis.config.annotation.web.http;
|
||||
|
||||
import org.springframework.context.annotation.ImportSelector;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
|
||||
/**
|
||||
* Dynamically determines which session repository configuration to include using the
|
||||
* {@link EnableRedisHttpSession} annotation.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 3.0
|
||||
*/
|
||||
final class RedisHttpSessionConfigurationSelector implements ImportSelector {
|
||||
|
||||
@Override
|
||||
public String[] selectImports(AnnotationMetadata importMetadata) {
|
||||
if (!importMetadata.hasAnnotation(EnableRedisHttpSession.class.getName())) {
|
||||
return new String[0];
|
||||
}
|
||||
EnableRedisHttpSession annotation = importMetadata.getAnnotations().get(EnableRedisHttpSession.class)
|
||||
.synthesize();
|
||||
if (annotation.enableIndexingAndEvents()) {
|
||||
return new String[] { RedisIndexedHttpSessionConfiguration.class.getName() };
|
||||
}
|
||||
else {
|
||||
return new String[] { RedisHttpSessionConfiguration.class.getName() };
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
* Copyright 2014-2022 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.data.redis.config.annotation.web.http;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.EmbeddedValueResolverAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportAware;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.listener.ChannelTopic;
|
||||
import org.springframework.data.redis.listener.PatternTopic;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.IndexResolver;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.config.SessionRepositoryCustomizer;
|
||||
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
|
||||
import org.springframework.session.data.redis.RedisFlushMode;
|
||||
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
|
||||
import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction;
|
||||
import org.springframework.session.data.redis.config.ConfigureRedisAction;
|
||||
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.StringValueResolver;
|
||||
|
||||
/**
|
||||
* Exposes the {@link SessionRepositoryFilter} as a bean named
|
||||
* {@code springSessionRepositoryFilter}. In order to use this a single
|
||||
* {@link RedisConnectionFactory} must be exposed as a Bean.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 3.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class RedisIndexedHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
||||
implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware {
|
||||
|
||||
static final String DEFAULT_CLEANUP_CRON = "0 * * * * *";
|
||||
|
||||
private Integer maxInactiveIntervalInSeconds = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
|
||||
|
||||
private String redisNamespace = RedisIndexedSessionRepository.DEFAULT_NAMESPACE;
|
||||
|
||||
private FlushMode flushMode = FlushMode.ON_SAVE;
|
||||
|
||||
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
private String cleanupCron = DEFAULT_CLEANUP_CRON;
|
||||
|
||||
private ConfigureRedisAction configureRedisAction = new ConfigureNotifyKeyspaceEventsAction();
|
||||
|
||||
private RedisConnectionFactory redisConnectionFactory;
|
||||
|
||||
private IndexResolver<Session> indexResolver;
|
||||
|
||||
private RedisSerializer<Object> defaultRedisSerializer;
|
||||
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
private Executor redisTaskExecutor;
|
||||
|
||||
private Executor redisSubscriptionExecutor;
|
||||
|
||||
private List<SessionRepositoryCustomizer<RedisIndexedSessionRepository>> sessionRepositoryCustomizers;
|
||||
|
||||
private ClassLoader classLoader;
|
||||
|
||||
private StringValueResolver embeddedValueResolver;
|
||||
|
||||
@Bean
|
||||
public RedisIndexedSessionRepository sessionRepository() {
|
||||
RedisTemplate<Object, Object> redisTemplate = createRedisTemplate();
|
||||
RedisIndexedSessionRepository sessionRepository = new RedisIndexedSessionRepository(redisTemplate);
|
||||
sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
|
||||
if (this.indexResolver != null) {
|
||||
sessionRepository.setIndexResolver(this.indexResolver);
|
||||
}
|
||||
if (this.defaultRedisSerializer != null) {
|
||||
sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
|
||||
}
|
||||
sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
|
||||
if (StringUtils.hasText(this.redisNamespace)) {
|
||||
sessionRepository.setRedisKeyNamespace(this.redisNamespace);
|
||||
}
|
||||
sessionRepository.setFlushMode(this.flushMode);
|
||||
sessionRepository.setSaveMode(this.saveMode);
|
||||
int database = resolveDatabase();
|
||||
sessionRepository.setDatabase(database);
|
||||
this.sessionRepositoryCustomizers
|
||||
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
|
||||
return sessionRepository;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisMessageListenerContainer springSessionRedisMessageListenerContainer(
|
||||
RedisIndexedSessionRepository sessionRepository) {
|
||||
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
||||
container.setConnectionFactory(this.redisConnectionFactory);
|
||||
if (this.redisTaskExecutor != null) {
|
||||
container.setTaskExecutor(this.redisTaskExecutor);
|
||||
}
|
||||
if (this.redisSubscriptionExecutor != null) {
|
||||
container.setSubscriptionExecutor(this.redisSubscriptionExecutor);
|
||||
}
|
||||
container.addMessageListener(sessionRepository,
|
||||
Arrays.asList(new ChannelTopic(sessionRepository.getSessionDeletedChannel()),
|
||||
new ChannelTopic(sessionRepository.getSessionExpiredChannel())));
|
||||
container.addMessageListener(sessionRepository,
|
||||
Collections.singletonList(new PatternTopic(sessionRepository.getSessionCreatedChannelPrefix() + "*")));
|
||||
return container;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public InitializingBean enableRedisKeyspaceNotificationsInitializer() {
|
||||
return new EnableRedisKeyspaceNotificationsInitializer(this.redisConnectionFactory, this.configureRedisAction);
|
||||
}
|
||||
|
||||
public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
|
||||
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
|
||||
}
|
||||
|
||||
public void setRedisNamespace(String namespace) {
|
||||
this.redisNamespace = namespace;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setRedisFlushMode(RedisFlushMode redisFlushMode) {
|
||||
Assert.notNull(redisFlushMode, "redisFlushMode cannot be null");
|
||||
setFlushMode(redisFlushMode.getFlushMode());
|
||||
}
|
||||
|
||||
public void setFlushMode(FlushMode flushMode) {
|
||||
Assert.notNull(flushMode, "flushMode cannot be null");
|
||||
this.flushMode = flushMode;
|
||||
}
|
||||
|
||||
public void setSaveMode(SaveMode saveMode) {
|
||||
this.saveMode = saveMode;
|
||||
}
|
||||
|
||||
public void setCleanupCron(String cleanupCron) {
|
||||
this.cleanupCron = cleanupCron;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action to perform for configuring Redis.
|
||||
* @param configureRedisAction the configureRedis to set. The default is
|
||||
* {@link ConfigureNotifyKeyspaceEventsAction}.
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
public void setConfigureRedisAction(ConfigureRedisAction configureRedisAction) {
|
||||
this.configureRedisAction = configureRedisAction;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setRedisConnectionFactory(
|
||||
@SpringSessionRedisConnectionFactory ObjectProvider<RedisConnectionFactory> springSessionRedisConnectionFactory,
|
||||
ObjectProvider<RedisConnectionFactory> redisConnectionFactory) {
|
||||
RedisConnectionFactory redisConnectionFactoryToUse = springSessionRedisConnectionFactory.getIfAvailable();
|
||||
if (redisConnectionFactoryToUse == null) {
|
||||
redisConnectionFactoryToUse = redisConnectionFactory.getObject();
|
||||
}
|
||||
this.redisConnectionFactory = redisConnectionFactoryToUse;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
@Qualifier("springSessionDefaultRedisSerializer")
|
||||
public void setDefaultRedisSerializer(RedisSerializer<Object> defaultRedisSerializer) {
|
||||
this.defaultRedisSerializer = defaultRedisSerializer;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setIndexResolver(IndexResolver<Session> indexResolver) {
|
||||
this.indexResolver = indexResolver;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
@Qualifier("springSessionRedisTaskExecutor")
|
||||
public void setRedisTaskExecutor(Executor redisTaskExecutor) {
|
||||
this.redisTaskExecutor = redisTaskExecutor;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
@Qualifier("springSessionRedisSubscriptionExecutor")
|
||||
public void setRedisSubscriptionExecutor(Executor redisSubscriptionExecutor) {
|
||||
this.redisSubscriptionExecutor = redisSubscriptionExecutor;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setSessionRepositoryCustomizer(
|
||||
ObjectProvider<SessionRepositoryCustomizer<RedisIndexedSessionRepository>> sessionRepositoryCustomizers) {
|
||||
this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmbeddedValueResolver(StringValueResolver resolver) {
|
||||
this.embeddedValueResolver = resolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
Map<String, Object> attributeMap = importMetadata
|
||||
.getAnnotationAttributes(EnableRedisHttpSession.class.getName());
|
||||
AnnotationAttributes attributes = AnnotationAttributes.fromMap(attributeMap);
|
||||
this.maxInactiveIntervalInSeconds = attributes.getNumber("maxInactiveIntervalInSeconds");
|
||||
String redisNamespaceValue = attributes.getString("redisNamespace");
|
||||
if (StringUtils.hasText(redisNamespaceValue)) {
|
||||
this.redisNamespace = this.embeddedValueResolver.resolveStringValue(redisNamespaceValue);
|
||||
}
|
||||
FlushMode flushMode = attributes.getEnum("flushMode");
|
||||
RedisFlushMode redisFlushMode = attributes.getEnum("redisFlushMode");
|
||||
if (flushMode == FlushMode.ON_SAVE && redisFlushMode != RedisFlushMode.ON_SAVE) {
|
||||
flushMode = redisFlushMode.getFlushMode();
|
||||
}
|
||||
this.flushMode = flushMode;
|
||||
this.saveMode = attributes.getEnum("saveMode");
|
||||
}
|
||||
|
||||
private RedisTemplate<Object, Object> createRedisTemplate() {
|
||||
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
|
||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
||||
if (this.defaultRedisSerializer != null) {
|
||||
redisTemplate.setDefaultSerializer(this.defaultRedisSerializer);
|
||||
}
|
||||
redisTemplate.setConnectionFactory(this.redisConnectionFactory);
|
||||
redisTemplate.setBeanClassLoader(this.classLoader);
|
||||
redisTemplate.afterPropertiesSet();
|
||||
return redisTemplate;
|
||||
}
|
||||
|
||||
private int resolveDatabase() {
|
||||
if (ClassUtils.isPresent("io.lettuce.core.RedisClient", null)
|
||||
&& this.redisConnectionFactory instanceof LettuceConnectionFactory) {
|
||||
return ((LettuceConnectionFactory) this.redisConnectionFactory).getDatabase();
|
||||
}
|
||||
if (ClassUtils.isPresent("redis.clients.jedis.Jedis", null)
|
||||
&& this.redisConnectionFactory instanceof JedisConnectionFactory) {
|
||||
return ((JedisConnectionFactory) this.redisConnectionFactory).getDatabase();
|
||||
}
|
||||
return RedisIndexedSessionRepository.DEFAULT_DATABASE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that Redis is configured to send keyspace notifications. This is important
|
||||
* to ensure that expiration and deletion of sessions trigger SessionDestroyedEvents.
|
||||
* Without the SessionDestroyedEvent resources may not get cleaned up properly. For
|
||||
* example, the mapping of the Session to WebSocket connections may not get cleaned
|
||||
* up.
|
||||
*/
|
||||
static class EnableRedisKeyspaceNotificationsInitializer implements InitializingBean {
|
||||
|
||||
private final RedisConnectionFactory connectionFactory;
|
||||
|
||||
private ConfigureRedisAction configure;
|
||||
|
||||
EnableRedisKeyspaceNotificationsInitializer(RedisConnectionFactory connectionFactory,
|
||||
ConfigureRedisAction configure) {
|
||||
this.connectionFactory = connectionFactory;
|
||||
this.configure = configure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
if (this.configure == ConfigureRedisAction.NO_OP) {
|
||||
return;
|
||||
}
|
||||
RedisConnection connection = this.connectionFactory.getConnection();
|
||||
try {
|
||||
this.configure.configure(connection);
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
connection.close();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
LogFactory.getLog(getClass()).error("Error closing RedisConnection", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration of scheduled job for cleaning up expired sessions.
|
||||
*/
|
||||
@EnableScheduling
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class SessionCleanupConfiguration implements SchedulingConfigurer {
|
||||
|
||||
private final RedisIndexedSessionRepository sessionRepository;
|
||||
|
||||
SessionCleanupConfiguration(RedisIndexedSessionRepository sessionRepository) {
|
||||
this.sessionRepository = sessionRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||
taskRegistrar.addCronTask(this.sessionRepository::cleanupExpiredSessions,
|
||||
RedisIndexedHttpSessionConfiguration.this.cleanupCron);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2022 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.
|
||||
@@ -49,14 +49,14 @@ class EnableRedisKeyspaceNotificationsInitializerTests {
|
||||
@Captor
|
||||
ArgumentCaptor<String> options;
|
||||
|
||||
private RedisHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer initializer;
|
||||
private RedisIndexedHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer initializer;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
given(this.connectionFactory.getConnection()).willReturn(this.connection);
|
||||
|
||||
this.initializer = new RedisHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer(
|
||||
this.initializer = new RedisIndexedHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer(
|
||||
this.connectionFactory, new ConfigureNotifyKeyspaceEventsAction());
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package org.springframework.session.data.redis.config.annotation.web.http;
|
||||
|
||||
import java.util.Map;
|
||||
import java.time.Duration;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@@ -34,15 +34,12 @@ import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.SubscriptionListener;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.IndexResolver;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.config.SessionRepositoryCustomizer;
|
||||
import org.springframework.session.data.redis.RedisFlushMode;
|
||||
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
|
||||
import org.springframework.session.data.redis.RedisSessionRepository;
|
||||
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
@@ -62,9 +59,7 @@ import static org.mockito.Mockito.mock;
|
||||
*/
|
||||
class RedisHttpSessionConfigurationTests {
|
||||
|
||||
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600;
|
||||
|
||||
private static final String CLEANUP_CRON_EXPRESSION = "0 0 * * * *";
|
||||
private static final Duration MAX_INACTIVE_INTERVAL_DURATION = Duration.ofSeconds(600);
|
||||
|
||||
private AnnotationConfigApplicationContext context;
|
||||
|
||||
@@ -100,7 +95,7 @@ class RedisHttpSessionConfigurationTests {
|
||||
@Test
|
||||
void customFlushImmediately() {
|
||||
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelyConfiguration.class);
|
||||
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class);
|
||||
assertThat(sessionRepository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
|
||||
}
|
||||
@@ -108,7 +103,7 @@ class RedisHttpSessionConfigurationTests {
|
||||
@Test
|
||||
void customFlushImmediatelyLegacy() {
|
||||
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelyLegacyConfiguration.class);
|
||||
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class);
|
||||
assertThat(sessionRepository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
|
||||
}
|
||||
@@ -116,7 +111,7 @@ class RedisHttpSessionConfigurationTests {
|
||||
@Test
|
||||
void setCustomFlushImmediately() {
|
||||
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelySetConfiguration.class);
|
||||
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class);
|
||||
assertThat(sessionRepository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
|
||||
}
|
||||
@@ -124,40 +119,22 @@ class RedisHttpSessionConfigurationTests {
|
||||
@Test
|
||||
void setCustomFlushImmediatelyLegacy() {
|
||||
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelySetLegacyConfiguration.class);
|
||||
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class);
|
||||
assertThat(sessionRepository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customCleanupCronAnnotation() {
|
||||
registerAndRefresh(RedisConfig.class, CustomCleanupCronExpressionAnnotationConfiguration.class);
|
||||
|
||||
RedisHttpSessionConfiguration configuration = this.context.getBean(RedisHttpSessionConfiguration.class);
|
||||
assertThat(configuration).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(configuration, "cleanupCron")).isEqualTo(CLEANUP_CRON_EXPRESSION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customCleanupCronSetter() {
|
||||
registerAndRefresh(RedisConfig.class, CustomCleanupCronExpressionSetterConfiguration.class);
|
||||
|
||||
RedisHttpSessionConfiguration configuration = this.context.getBean(RedisHttpSessionConfiguration.class);
|
||||
assertThat(configuration).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(configuration, "cleanupCron")).isEqualTo(CLEANUP_CRON_EXPRESSION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customSaveModeAnnotation() {
|
||||
registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionAnnotationConfiguration.class);
|
||||
assertThat(this.context.getBean(RedisIndexedSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode",
|
||||
assertThat(this.context.getBean(RedisSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode",
|
||||
SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customSaveModeSetter() {
|
||||
registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionSetterConfiguration.class);
|
||||
assertThat(this.context.getBean(RedisIndexedSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode",
|
||||
assertThat(this.context.getBean(RedisSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode",
|
||||
SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
@@ -165,7 +142,7 @@ class RedisHttpSessionConfigurationTests {
|
||||
void qualifiedConnectionFactoryRedisConfig() {
|
||||
registerAndRefresh(RedisConfig.class, QualifiedConnectionFactoryRedisConfig.class);
|
||||
|
||||
RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisSessionRepository repository = this.context.getBean(RedisSessionRepository.class);
|
||||
RedisConnectionFactory redisConnectionFactory = this.context.getBean("qualifiedRedisConnectionFactory",
|
||||
RedisConnectionFactory.class);
|
||||
assertThat(repository).isNotNull();
|
||||
@@ -181,7 +158,7 @@ class RedisHttpSessionConfigurationTests {
|
||||
void primaryConnectionFactoryRedisConfig() {
|
||||
registerAndRefresh(RedisConfig.class, PrimaryConnectionFactoryRedisConfig.class);
|
||||
|
||||
RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisSessionRepository repository = this.context.getBean(RedisSessionRepository.class);
|
||||
RedisConnectionFactory redisConnectionFactory = this.context.getBean("primaryRedisConnectionFactory",
|
||||
RedisConnectionFactory.class);
|
||||
assertThat(repository).isNotNull();
|
||||
@@ -197,7 +174,7 @@ class RedisHttpSessionConfigurationTests {
|
||||
void qualifiedAndPrimaryConnectionFactoryRedisConfig() {
|
||||
registerAndRefresh(RedisConfig.class, QualifiedAndPrimaryConnectionFactoryRedisConfig.class);
|
||||
|
||||
RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisSessionRepository repository = this.context.getBean(RedisSessionRepository.class);
|
||||
RedisConnectionFactory redisConnectionFactory = this.context.getBean("qualifiedRedisConnectionFactory",
|
||||
RedisConnectionFactory.class);
|
||||
assertThat(repository).isNotNull();
|
||||
@@ -213,7 +190,7 @@ class RedisHttpSessionConfigurationTests {
|
||||
void namedConnectionFactoryRedisConfig() {
|
||||
registerAndRefresh(RedisConfig.class, NamedConnectionFactoryRedisConfig.class);
|
||||
|
||||
RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisSessionRepository repository = this.context.getBean(RedisSessionRepository.class);
|
||||
RedisConnectionFactory redisConnectionFactory = this.context.getBean("redisConnectionFactory",
|
||||
RedisConnectionFactory.class);
|
||||
assertThat(repository).isNotNull();
|
||||
@@ -232,32 +209,12 @@ class RedisHttpSessionConfigurationTests {
|
||||
.withMessageContaining("expected single matching bean but found 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customIndexResolverConfiguration() {
|
||||
registerAndRefresh(RedisConfig.class, CustomIndexResolverConfiguration.class);
|
||||
RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
IndexResolver<Session> indexResolver = this.context.getBean(IndexResolver.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(indexResolver).isNotNull();
|
||||
assertThat(repository).hasFieldOrPropertyWithValue("indexResolver", indexResolver);
|
||||
}
|
||||
|
||||
@Test // gh-1252
|
||||
void customRedisMessageListenerContainerConfig() {
|
||||
registerAndRefresh(RedisConfig.class, CustomRedisMessageListenerContainerConfig.class);
|
||||
Map<String, RedisMessageListenerContainer> beans = this.context
|
||||
.getBeansOfType(RedisMessageListenerContainer.class);
|
||||
assertThat(beans).hasSize(2);
|
||||
assertThat(beans).containsKeys("springSessionRedisMessageListenerContainer", "redisMessageListenerContainer");
|
||||
}
|
||||
|
||||
@Test
|
||||
void sessionRepositoryCustomizer() {
|
||||
registerAndRefresh(RedisConfig.class, SessionRepositoryCustomizerConfiguration.class);
|
||||
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class);
|
||||
assertThat(sessionRepository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
|
||||
MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
MAX_INACTIVE_INTERVAL_DURATION);
|
||||
}
|
||||
|
||||
private void registerAndRefresh(Class<?>... annotatedClasses) {
|
||||
@@ -338,20 +295,6 @@ class RedisHttpSessionConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableRedisHttpSession(cleanupCron = CLEANUP_CRON_EXPRESSION)
|
||||
static class CustomCleanupCronExpressionAnnotationConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomCleanupCronExpressionSetterConfiguration extends RedisHttpSessionConfiguration {
|
||||
|
||||
CustomCleanupCronExpressionSetterConfiguration() {
|
||||
setCleanupCron(CLEANUP_CRON_EXPRESSION);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableRedisHttpSession(saveMode = SaveMode.ALWAYS)
|
||||
static class CustomSaveModeExpressionAnnotationConfiguration {
|
||||
|
||||
@@ -442,43 +385,20 @@ class RedisHttpSessionConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession
|
||||
static class CustomIndexResolverConfiguration {
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("unchecked")
|
||||
IndexResolver<Session> indexResolver() {
|
||||
return mock(IndexResolver.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession
|
||||
static class CustomRedisMessageListenerContainerConfig {
|
||||
|
||||
@Bean
|
||||
RedisMessageListenerContainer redisMessageListenerContainer() {
|
||||
return mock(RedisMessageListenerContainer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableRedisHttpSession
|
||||
static class SessionRepositoryCustomizerConfiguration {
|
||||
|
||||
@Bean
|
||||
@Order(0)
|
||||
SessionRepositoryCustomizer<RedisIndexedSessionRepository> sessionRepositoryCustomizerOne() {
|
||||
return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(0);
|
||||
SessionRepositoryCustomizer<RedisSessionRepository> sessionRepositoryCustomizerOne() {
|
||||
return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(Duration.ZERO);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
SessionRepositoryCustomizer<RedisIndexedSessionRepository> sessionRepositoryCustomizerTwo() {
|
||||
SessionRepositoryCustomizer<RedisSessionRepository> sessionRepositoryCustomizerTwo() {
|
||||
return (sessionRepository) -> sessionRepository
|
||||
.setDefaultMaxInactiveInterval(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
.setDefaultMaxInactiveInterval(MAX_INACTIVE_INTERVAL_DURATION);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,7 +24,7 @@ import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.session.data.redis.config.ConfigureRedisAction;
|
||||
import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer;
|
||||
import org.springframework.session.data.redis.config.annotation.web.http.RedisIndexedHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
@@ -33,7 +33,7 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
class RedisHttpSessionConfigurationMockTests {
|
||||
class RedisIndexedHttpSessionConfigurationMockTests {
|
||||
|
||||
@Mock
|
||||
RedisConnectionFactory factory;
|
||||
@@ -48,7 +48,7 @@ import static org.mockito.Mockito.verify;
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
class RedisHttpSessionConfigurationOverrideSessionTaskExecutor {
|
||||
class RedisIndexedHttpSessionConfigurationOverrideSessionTaskExecutor {
|
||||
|
||||
@Autowired
|
||||
RedisMessageListenerContainer redisMessageListenerContainer;
|
||||
@@ -61,7 +61,7 @@ class RedisHttpSessionConfigurationOverrideSessionTaskExecutor {
|
||||
verify(this.springSessionRedisTaskExecutor, times(1)).execute(any(Runnable.class));
|
||||
}
|
||||
|
||||
@EnableRedisHttpSession
|
||||
@EnableRedisHttpSession(enableIndexingAndEvents = true)
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@@ -50,7 +50,7 @@ import static org.mockito.Mockito.verify;
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
class RedisHttpSessionConfigurationOverrideSessionTaskExecutors {
|
||||
class RedisIndexedHttpSessionConfigurationOverrideSessionTaskExecutors {
|
||||
|
||||
@Autowired
|
||||
RedisMessageListenerContainer redisMessageListenerContainer;
|
||||
@@ -67,7 +67,7 @@ class RedisHttpSessionConfigurationOverrideSessionTaskExecutors {
|
||||
verify(this.springSessionRedisTaskExecutor, never()).execute(any(Runnable.class));
|
||||
}
|
||||
|
||||
@EnableRedisHttpSession
|
||||
@EnableRedisHttpSession(enableIndexingAndEvents = true)
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@@ -0,0 +1,471 @@
|
||||
/*
|
||||
* Copyright 2014-2022 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.data.redis.config.annotation.web.http;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.SubscriptionListener;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.IndexResolver;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.config.SessionRepositoryCustomizer;
|
||||
import org.springframework.session.data.redis.RedisFlushMode;
|
||||
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
|
||||
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
|
||||
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.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.willAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link RedisIndexedHttpSessionConfiguration}.
|
||||
*/
|
||||
class RedisIndexedHttpSessionConfigurationTests {
|
||||
|
||||
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600;
|
||||
|
||||
private static final String CLEANUP_CRON_EXPRESSION = "0 0 * * * *";
|
||||
|
||||
private AnnotationConfigApplicationContext context;
|
||||
|
||||
@BeforeEach
|
||||
void before() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void after() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveValue() {
|
||||
registerAndRefresh(RedisConfig.class, CustomRedisHttpSessionConfiguration.class);
|
||||
RedisIndexedHttpSessionConfiguration configuration = this.context
|
||||
.getBean(RedisIndexedHttpSessionConfiguration.class);
|
||||
assertThat(ReflectionTestUtils.getField(configuration, "redisNamespace")).isEqualTo("myRedisNamespace");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveValueByPlaceholder() {
|
||||
this.context
|
||||
.setEnvironment(new MockEnvironment().withProperty("session.redis.namespace", "customRedisNamespace"));
|
||||
registerAndRefresh(RedisConfig.class, PropertySourceConfiguration.class,
|
||||
CustomRedisHttpSessionConfiguration2.class);
|
||||
RedisIndexedHttpSessionConfiguration configuration = this.context
|
||||
.getBean(RedisIndexedHttpSessionConfiguration.class);
|
||||
assertThat(ReflectionTestUtils.getField(configuration, "redisNamespace")).isEqualTo("customRedisNamespace");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customFlushImmediately() {
|
||||
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelyConfiguration.class);
|
||||
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
assertThat(sessionRepository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customFlushImmediatelyLegacy() {
|
||||
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelyLegacyConfiguration.class);
|
||||
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
assertThat(sessionRepository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void setCustomFlushImmediately() {
|
||||
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelySetConfiguration.class);
|
||||
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
assertThat(sessionRepository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void setCustomFlushImmediatelyLegacy() {
|
||||
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelySetLegacyConfiguration.class);
|
||||
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
assertThat(sessionRepository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customCleanupCronSetter() {
|
||||
registerAndRefresh(RedisConfig.class, CustomCleanupCronExpressionSetterConfiguration.class);
|
||||
|
||||
RedisIndexedHttpSessionConfiguration configuration = this.context
|
||||
.getBean(RedisIndexedHttpSessionConfiguration.class);
|
||||
assertThat(configuration).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(configuration, "cleanupCron")).isEqualTo(CLEANUP_CRON_EXPRESSION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customSaveModeAnnotation() {
|
||||
registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionAnnotationConfiguration.class);
|
||||
assertThat(this.context.getBean(RedisIndexedSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode",
|
||||
SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customSaveModeSetter() {
|
||||
registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionSetterConfiguration.class);
|
||||
assertThat(this.context.getBean(RedisIndexedSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode",
|
||||
SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void qualifiedConnectionFactoryRedisConfig() {
|
||||
registerAndRefresh(RedisConfig.class, QualifiedConnectionFactoryRedisConfig.class);
|
||||
|
||||
RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisConnectionFactory redisConnectionFactory = this.context.getBean("qualifiedRedisConnectionFactory",
|
||||
RedisConnectionFactory.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(redisConnectionFactory).isNotNull();
|
||||
RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils.getField(repository,
|
||||
"sessionRedisOperations");
|
||||
assertThat(redisOperations).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory"))
|
||||
.isEqualTo(redisConnectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void primaryConnectionFactoryRedisConfig() {
|
||||
registerAndRefresh(RedisConfig.class, PrimaryConnectionFactoryRedisConfig.class);
|
||||
|
||||
RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisConnectionFactory redisConnectionFactory = this.context.getBean("primaryRedisConnectionFactory",
|
||||
RedisConnectionFactory.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(redisConnectionFactory).isNotNull();
|
||||
RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils.getField(repository,
|
||||
"sessionRedisOperations");
|
||||
assertThat(redisOperations).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory"))
|
||||
.isEqualTo(redisConnectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void qualifiedAndPrimaryConnectionFactoryRedisConfig() {
|
||||
registerAndRefresh(RedisConfig.class, QualifiedAndPrimaryConnectionFactoryRedisConfig.class);
|
||||
|
||||
RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisConnectionFactory redisConnectionFactory = this.context.getBean("qualifiedRedisConnectionFactory",
|
||||
RedisConnectionFactory.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(redisConnectionFactory).isNotNull();
|
||||
RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils.getField(repository,
|
||||
"sessionRedisOperations");
|
||||
assertThat(redisOperations).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory"))
|
||||
.isEqualTo(redisConnectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void namedConnectionFactoryRedisConfig() {
|
||||
registerAndRefresh(RedisConfig.class, NamedConnectionFactoryRedisConfig.class);
|
||||
|
||||
RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisConnectionFactory redisConnectionFactory = this.context.getBean("redisConnectionFactory",
|
||||
RedisConnectionFactory.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(redisConnectionFactory).isNotNull();
|
||||
RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils.getField(repository,
|
||||
"sessionRedisOperations");
|
||||
assertThat(redisOperations).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory"))
|
||||
.isEqualTo(redisConnectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleConnectionFactoryRedisConfig() {
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> registerAndRefresh(RedisConfig.class, MultipleConnectionFactoryRedisConfig.class))
|
||||
.withMessageContaining("expected single matching bean but found 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customIndexResolverConfiguration() {
|
||||
registerAndRefresh(RedisConfig.class, CustomIndexResolverConfiguration.class);
|
||||
RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
IndexResolver<Session> indexResolver = this.context.getBean(IndexResolver.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(indexResolver).isNotNull();
|
||||
assertThat(repository).hasFieldOrPropertyWithValue("indexResolver", indexResolver);
|
||||
}
|
||||
|
||||
@Test // gh-1252
|
||||
void customRedisMessageListenerContainerConfig() {
|
||||
registerAndRefresh(RedisConfig.class, CustomRedisMessageListenerContainerConfig.class);
|
||||
Map<String, RedisMessageListenerContainer> beans = this.context
|
||||
.getBeansOfType(RedisMessageListenerContainer.class);
|
||||
assertThat(beans).hasSize(2);
|
||||
assertThat(beans).containsKeys("springSessionRedisMessageListenerContainer", "redisMessageListenerContainer");
|
||||
}
|
||||
|
||||
@Test
|
||||
void sessionRepositoryCustomizer() {
|
||||
registerAndRefresh(RedisConfig.class, SessionRepositoryCustomizerConfiguration.class);
|
||||
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
assertThat(sessionRepository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
|
||||
MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
}
|
||||
|
||||
private void registerAndRefresh(Class<?>... annotatedClasses) {
|
||||
this.context.register(annotatedClasses);
|
||||
this.context.refresh();
|
||||
}
|
||||
|
||||
private static RedisConnectionFactory mockRedisConnectionFactory() {
|
||||
RedisConnectionFactory connectionFactoryMock = mock(RedisConnectionFactory.class);
|
||||
RedisConnection connectionMock = mock(RedisConnection.class);
|
||||
given(connectionFactoryMock.getConnection()).willReturn(connectionMock);
|
||||
|
||||
Properties keyspaceEventsConfig = new Properties();
|
||||
keyspaceEventsConfig.put("notify-keyspace-events", "KEA");
|
||||
given(connectionMock.getConfig("notify-keyspace-events")).willReturn(keyspaceEventsConfig);
|
||||
|
||||
willAnswer((it) -> {
|
||||
SubscriptionListener listener = it.getArgument(0);
|
||||
listener.onPatternSubscribed(it.getArgument(1), 0);
|
||||
listener.onChannelSubscribed("__keyevent@0__:del".getBytes(), 0);
|
||||
listener.onChannelSubscribed("__keyevent@0__:expired".getBytes(), 0);
|
||||
|
||||
return null;
|
||||
}).given(connectionMock).pSubscribe(any(), any());
|
||||
|
||||
return connectionFactoryMock;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class PropertySourceConfiguration {
|
||||
|
||||
@Bean
|
||||
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
||||
return new PropertySourcesPlaceholderConfigurer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class RedisConfig {
|
||||
|
||||
@Bean
|
||||
RedisConnectionFactory defaultRedisConnectionFactory() {
|
||||
return mockRedisConnectionFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomFlushImmediatelySetConfiguration extends RedisIndexedHttpSessionConfiguration {
|
||||
|
||||
CustomFlushImmediatelySetConfiguration() {
|
||||
setFlushMode(FlushMode.IMMEDIATE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@SuppressWarnings("deprecation")
|
||||
static class CustomFlushImmediatelySetLegacyConfiguration extends RedisIndexedHttpSessionConfiguration {
|
||||
|
||||
CustomFlushImmediatelySetLegacyConfiguration() {
|
||||
setRedisFlushMode(RedisFlushMode.IMMEDIATE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(flushMode = FlushMode.IMMEDIATE, enableIndexingAndEvents = true)
|
||||
static class CustomFlushImmediatelyConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE, enableIndexingAndEvents = true)
|
||||
@SuppressWarnings("deprecation")
|
||||
static class CustomFlushImmediatelyLegacyConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomCleanupCronExpressionSetterConfiguration extends RedisIndexedHttpSessionConfiguration {
|
||||
|
||||
CustomCleanupCronExpressionSetterConfiguration() {
|
||||
setCleanupCron(CLEANUP_CRON_EXPRESSION);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableRedisHttpSession(saveMode = SaveMode.ALWAYS, enableIndexingAndEvents = true)
|
||||
static class CustomSaveModeExpressionAnnotationConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomSaveModeExpressionSetterConfiguration extends RedisIndexedHttpSessionConfiguration {
|
||||
|
||||
CustomSaveModeExpressionSetterConfiguration() {
|
||||
setSaveMode(SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(enableIndexingAndEvents = true)
|
||||
static class QualifiedConnectionFactoryRedisConfig {
|
||||
|
||||
@Bean
|
||||
@SpringSessionRedisConnectionFactory
|
||||
RedisConnectionFactory qualifiedRedisConnectionFactory() {
|
||||
return mockRedisConnectionFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(enableIndexingAndEvents = true)
|
||||
static class PrimaryConnectionFactoryRedisConfig {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
RedisConnectionFactory primaryRedisConnectionFactory() {
|
||||
return mockRedisConnectionFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(enableIndexingAndEvents = true)
|
||||
static class QualifiedAndPrimaryConnectionFactoryRedisConfig {
|
||||
|
||||
@Bean
|
||||
@SpringSessionRedisConnectionFactory
|
||||
RedisConnectionFactory qualifiedRedisConnectionFactory() {
|
||||
return mockRedisConnectionFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
RedisConnectionFactory primaryRedisConnectionFactory() {
|
||||
return mockRedisConnectionFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(enableIndexingAndEvents = true)
|
||||
static class NamedConnectionFactoryRedisConfig {
|
||||
|
||||
@Bean
|
||||
RedisConnectionFactory redisConnectionFactory() {
|
||||
return mockRedisConnectionFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(enableIndexingAndEvents = true)
|
||||
static class MultipleConnectionFactoryRedisConfig {
|
||||
|
||||
@Bean
|
||||
RedisConnectionFactory secondaryRedisConnectionFactory() {
|
||||
return mockRedisConnectionFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(redisNamespace = "myRedisNamespace", enableIndexingAndEvents = true)
|
||||
static class CustomRedisHttpSessionConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(redisNamespace = "${session.redis.namespace}", enableIndexingAndEvents = true)
|
||||
static class CustomRedisHttpSessionConfiguration2 {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(enableIndexingAndEvents = true)
|
||||
static class CustomIndexResolverConfiguration {
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("unchecked")
|
||||
IndexResolver<Session> indexResolver() {
|
||||
return mock(IndexResolver.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(enableIndexingAndEvents = true)
|
||||
static class CustomRedisMessageListenerContainerConfig {
|
||||
|
||||
@Bean
|
||||
RedisMessageListenerContainer redisMessageListenerContainer() {
|
||||
return mock(RedisMessageListenerContainer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableRedisHttpSession(enableIndexingAndEvents = true)
|
||||
static class SessionRepositoryCustomizerConfiguration {
|
||||
|
||||
@Bean
|
||||
@Order(0)
|
||||
SessionRepositoryCustomizer<RedisIndexedSessionRepository> sessionRepositoryCustomizerOne() {
|
||||
return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(0);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
SessionRepositoryCustomizer<RedisIndexedSessionRepository> sessionRepositoryCustomizerTwo() {
|
||||
return (sessionRepository) -> sessionRepository
|
||||
.setDefaultMaxInactiveInterval(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,8 +9,7 @@ content:
|
||||
- url: https://github.com/spring-io/spring-generated-docs
|
||||
branches: [spring-projects/spring-session/*]
|
||||
- url: https://github.com/spring-projects/spring-session
|
||||
branches: [main,3.0.x,2.6.x]
|
||||
tags: [ '3.0.*', '2.7.*', '2.6.*','!2.6.0-M*','!2.6.0-RC*','!2.7.0-M1','!3.0.0-M1']
|
||||
branches: [main]
|
||||
start_path: spring-session-docs
|
||||
urls:
|
||||
latest_version_segment_strategy: redirect:to
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
name: ROOT
|
||||
version: '2.7.0'
|
||||
version: '3.0.0-M2'
|
||||
prerelease: 'true'
|
||||
|
||||
@@ -91,7 +91,6 @@ function createSymbolicVersionAlias (component, version, symbolicVersionSegment,
|
||||
|
||||
function computeOut (src, family, version, htmlUrlExtensionStyle) {
|
||||
let { component, module: module_, basename, extname, relative, stem } = src
|
||||
if (component === 'ROOT') component = ''
|
||||
if (module_ === 'ROOT') module_ = ''
|
||||
let indexifyPathSegment = ''
|
||||
let familyPathSegment = ''
|
||||
@@ -121,11 +120,8 @@ function computePub (src, out, family, version, htmlUrlExtensionStyle) {
|
||||
const pub = {}
|
||||
let url
|
||||
if (family === 'nav') {
|
||||
const component = src.component || 'ROOT'
|
||||
const urlSegments = component === 'ROOT' ? [] : [component]
|
||||
if (version) urlSegments.push(version)
|
||||
const module_ = src.module || 'ROOT'
|
||||
if (module_ !== 'ROOT') urlSegments.push(module_)
|
||||
const urlSegments = version ? [src.component, version] : [src.component]
|
||||
if (src.module && src.module !== 'ROOT') urlSegments.push(src.module)
|
||||
// an artificial URL used for resolving page references in navigation model
|
||||
url = '/' + urlSegments.join('/') + '/'
|
||||
pub.moduleRootPath = '.'
|
||||
|
||||
@@ -19,9 +19,6 @@ package docs;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.hazelcast.config.Config;
|
||||
import com.hazelcast.core.Hazelcast;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
@@ -38,7 +35,7 @@ import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.data.redis.ReactiveRedisSessionRepository;
|
||||
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
|
||||
import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository;
|
||||
import org.springframework.session.data.redis.RedisSessionRepository;
|
||||
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
@@ -117,6 +114,18 @@ class IndexDocTests {
|
||||
}
|
||||
// end::expire-repository-demo[]
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unused")
|
||||
void newRedisSessionRepository() {
|
||||
// tag::new-redissessionrepository[]
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||
|
||||
// ... configure redisTemplate ...
|
||||
|
||||
SessionRepository<? extends Session> repository = new RedisSessionRepository(redisTemplate);
|
||||
// end::new-redissessionrepository[]
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unused")
|
||||
void newRedisIndexedSessionRepository() {
|
||||
@@ -171,20 +180,21 @@ class IndexDocTests {
|
||||
// end::new-jdbcindexedsessionrepository[]
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unused")
|
||||
void newHazelcastIndexedSessionRepository() {
|
||||
// tag::new-hazelcastindexedsessionrepository[]
|
||||
|
||||
Config config = new Config();
|
||||
|
||||
// ... configure Hazelcast ...
|
||||
|
||||
HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config);
|
||||
|
||||
HazelcastIndexedSessionRepository repository = new HazelcastIndexedSessionRepository(hazelcastInstance);
|
||||
// end::new-hazelcastindexedsessionrepository[]
|
||||
}
|
||||
// @Test
|
||||
// @SuppressWarnings("unused")
|
||||
// void newHazelcastIndexedSessionRepository() {
|
||||
// // tag::new-hazelcastindexedsessionrepository[]
|
||||
//
|
||||
// Config config = new Config();
|
||||
//
|
||||
// // ... configure Hazelcast ...
|
||||
//
|
||||
// HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config);
|
||||
//
|
||||
// HazelcastIndexedSessionRepository repository = new
|
||||
// HazelcastIndexedSessionRepository(hazelcastInstance);
|
||||
// // end::new-hazelcastindexedsessionrepository[]
|
||||
// }
|
||||
|
||||
@Test
|
||||
void runSpringHttpSessionConfig() {
|
||||
|
||||
@@ -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.
|
||||
@@ -16,40 +16,44 @@
|
||||
|
||||
package docs.http;
|
||||
|
||||
import com.hazelcast.config.Config;
|
||||
import com.hazelcast.config.MapAttributeConfig;
|
||||
import com.hazelcast.config.MapIndexConfig;
|
||||
import com.hazelcast.config.SerializerConfig;
|
||||
import com.hazelcast.core.Hazelcast;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
// import com.hazelcast.config.AttributeConfig;
|
||||
// import com.hazelcast.config.Config;
|
||||
// import com.hazelcast.config.IndexConfig;
|
||||
// import com.hazelcast.config.IndexType;
|
||||
// import com.hazelcast.config.SerializerConfig;
|
||||
// import com.hazelcast.core.Hazelcast;
|
||||
// import com.hazelcast.core.HazelcastInstance;
|
||||
//
|
||||
// import org.springframework.context.annotation.Bean;
|
||||
// import org.springframework.context.annotation.Configuration;
|
||||
// import org.springframework.session.MapSession;
|
||||
// import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository;
|
||||
// import org.springframework.session.hazelcast.HazelcastSessionSerializer;
|
||||
// import org.springframework.session.hazelcast.PrincipalNameExtractor;
|
||||
// import
|
||||
// org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository;
|
||||
import org.springframework.session.hazelcast.HazelcastSessionSerializer;
|
||||
import org.springframework.session.hazelcast.PrincipalNameExtractor;
|
||||
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||
|
||||
//tag::config[]
|
||||
@EnableHazelcastHttpSession // <1>
|
||||
@Configuration
|
||||
public class HazelcastHttpSessionConfig {
|
||||
|
||||
@Bean
|
||||
public HazelcastInstance hazelcastInstance() {
|
||||
Config config = new Config();
|
||||
MapAttributeConfig attributeConfig = new MapAttributeConfig()
|
||||
.setName(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
|
||||
.setExtractor(PrincipalNameExtractor.class.getName());
|
||||
config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME) // <2>
|
||||
.addMapAttributeConfig(attributeConfig).addMapIndexConfig(
|
||||
new MapIndexConfig(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));
|
||||
SerializerConfig serializerConfig = new SerializerConfig();
|
||||
serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class);
|
||||
config.getSerializationConfig().addSerializerConfig(serializerConfig); // <3>
|
||||
return Hazelcast.newHazelcastInstance(config); // <4>
|
||||
}
|
||||
|
||||
}
|
||||
// tag::config[]
|
||||
// @EnableHazelcastHttpSession // <1>
|
||||
// @Configuration
|
||||
// public class HazelcastHttpSessionConfig {
|
||||
//
|
||||
// @Bean
|
||||
// public HazelcastInstance hazelcastInstance() {
|
||||
// Config config = new Config();
|
||||
// AttributeConfig attributeConfig = new AttributeConfig()
|
||||
// .setName(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
|
||||
// .setExtractorClassName(PrincipalNameExtractor.class.getName());
|
||||
// config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME) // <2>
|
||||
// .addAttributeConfig(attributeConfig).addIndexConfig(
|
||||
// new IndexConfig(IndexType.HASH,
|
||||
// HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE));
|
||||
// SerializerConfig serializerConfig = new SerializerConfig();
|
||||
// serializerConfig.setImplementation(new
|
||||
// HazelcastSessionSerializer()).setTypeClass(MapSession.class);
|
||||
// config.getSerializationConfig().addSerializerConfig(serializerConfig); // <3>
|
||||
// return Hazelcast.newHazelcastInstance(config); // <4>
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// end::config[]
|
||||
|
||||
@@ -19,7 +19,7 @@ package docs.security;
|
||||
import java.time.Duration;
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -19,7 +19,7 @@ package docs.security;
|
||||
import java.time.Duration;
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
|
||||
<context:annotation-config/>
|
||||
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>
|
||||
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisIndexedHttpSessionConfiguration"/>
|
||||
|
||||
<bean class="docs.http.AbstractHttpSessionListenerTests"
|
||||
factory-method="createMockRedisConnection"/>
|
||||
|
||||
@@ -148,6 +148,78 @@ Note that no infrastructure for session expirations is configured for you.
|
||||
This is because things such as session expiration are highly implementation-dependent.
|
||||
This means that, if you require cleaning up expired sessions, you are responsible for cleaning up the expired sessions.
|
||||
|
||||
[[api-redissessionrepository]]
|
||||
== Using `RedisSessionRepository`
|
||||
|
||||
`RedisSessionRepository` is a `SessionRepository` that is implemented by using Spring Data's `RedisOperations`.
|
||||
In a web environment, this is typically used in combination with `SessionRepositoryFilter`.
|
||||
Note that this implementation does not support publishing of session events.
|
||||
|
||||
[[api-redissessionrepository-new]]
|
||||
=== Instantiating a `RedisSessionRepository`
|
||||
|
||||
You can see a typical example of how to create a new instance in the following listing:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{indexdoc-tests}[tags=new-redissessionrepository]
|
||||
----
|
||||
====
|
||||
|
||||
For additional information on how to create a `RedisConnectionFactory`, see the Spring Data Redis Reference.
|
||||
|
||||
[[api-redissessionrepository-config]]
|
||||
=== Using `@EnableRedisHttpSession`
|
||||
|
||||
In a web environment, the simplest way to create a new `RedisSessionRepository` is to use `@EnableRedisHttpSession`.
|
||||
You can find complete example usage in the xref:samples.adoc#samples[Samples and Guides (Start Here)].
|
||||
You can use the following attributes to customize the configuration:
|
||||
|
||||
enableIndexingAndEvents
|
||||
* *enableIndexingAndEvents*: Whether to use a `RedisIndexedSessionRepository` instead of a `RedisSessionRepository`. The default is `false`.
|
||||
* *maxInactiveIntervalInSeconds*: The amount of time before the session expires, in seconds.
|
||||
* *redisNamespace*: Allows configuring an application specific namespace for the sessions. Redis keys and channel IDs start with the prefix of `<redisNamespace>:`.
|
||||
* *flushMode*: Allows specifying when data is written to Redis. The default is only when `save` is invoked on `SessionRepository`.
|
||||
A value of `FlushMode.IMMEDIATE` writes to Redis as soon as possible.
|
||||
|
||||
==== Custom `RedisSerializer`
|
||||
|
||||
You can customize the serialization by creating a bean named `springSessionDefaultRedisSerializer` that implements `RedisSerializer<Object>`.
|
||||
|
||||
[[api-redissessionrepository-cli]]
|
||||
=== Viewing the Session in Redis
|
||||
|
||||
After https://redis.io/topics/quickstart[installing redis-cli], you can inspect the values in Redis https://redis.io/commands#hash[using the redis-cli].
|
||||
For example, you can enter the following command into a terminal window:
|
||||
|
||||
====
|
||||
[source,bash]
|
||||
----
|
||||
$ redis-cli
|
||||
redis 127.0.0.1:6379> keys *
|
||||
1) "spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021" <1>
|
||||
----
|
||||
|
||||
<1> The suffix of this key is the session identifier of the Spring Session.
|
||||
====
|
||||
|
||||
You can also view the attributes of each session by using the `hkeys` command.
|
||||
The following example shows how to do so:
|
||||
|
||||
====
|
||||
[source,bash]
|
||||
----
|
||||
redis 127.0.0.1:6379> hkeys spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021
|
||||
1) "lastAccessedTime"
|
||||
2) "creationTime"
|
||||
3) "maxInactiveInterval"
|
||||
4) "sessionAttr:username"
|
||||
redis 127.0.0.1:6379> hget spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021 sessionAttr:username
|
||||
"\xac\xed\x00\x05t\x00\x03rob"
|
||||
----
|
||||
====
|
||||
|
||||
[[api-redisindexedsessionrepository]]
|
||||
== Using `RedisIndexedSessionRepository`
|
||||
|
||||
@@ -170,12 +242,13 @@ include::{indexdoc-tests}[tags=new-redisindexedsessionrepository]
|
||||
For additional information on how to create a `RedisConnectionFactory`, see the Spring Data Redis Reference.
|
||||
|
||||
[[api-redisindexedsessionrepository-config]]
|
||||
=== Using `@EnableRedisHttpSession`
|
||||
=== Using `@EnableRedisHttpSession(enableIndexingAndEvents = true)`
|
||||
|
||||
In a web environment, the simplest way to create a new `RedisIndexedSessionRepository` is to use `@EnableRedisHttpSession`.
|
||||
In a web environment, the simplest way to create a new `RedisIndexedSessionRepository` is to use `@EnableRedisHttpSession(enableIndexingAndEvents = true)`.
|
||||
You can find complete example usage in the xref:samples.adoc#samples[Samples and Guides (Start Here)].
|
||||
You can use the following attributes to customize the configuration:
|
||||
|
||||
* *enableIndexingAndEvents*: Whether to use a `RedisIndexedSessionRepository` instead of a `RedisSessionRepository`. The default is `false`.
|
||||
* *maxInactiveIntervalInSeconds*: The amount of time before the session expires, in seconds.
|
||||
* *redisNamespace*: Allows configuring an application specific namespace for the sessions. Redis keys and channel IDs start with the prefix of `<redisNamespace>:`.
|
||||
* *flushMode*: Allows specifying when data is written to Redis. The default is only when `save` is invoked on `SessionRepository`.
|
||||
@@ -335,7 +408,7 @@ redis-cli config set notify-keyspace-events Egx
|
||||
----
|
||||
====
|
||||
|
||||
If you use `@EnableRedisHttpSession`, managing the `SessionMessageListener` and enabling the necessary Redis Keyspace events is done automatically.
|
||||
If you use `@EnableRedisHttpSession(enableIndexingAndEvents = true)`, managing the `SessionMessageListener` and enabling the necessary Redis Keyspace events is done automatically.
|
||||
However, in a secured Redis enviornment, the config command is disabled.
|
||||
This means that Spring Session cannot configure Redis Keyspace events for you.
|
||||
To disable the automatic configuration, add `ConfigureRedisAction.NO_OP` as a bean.
|
||||
|
||||
@@ -235,7 +235,7 @@ To use this support, you need to:
|
||||
* Configure `SessionEventHttpSessionListenerAdapter` as a Spring bean.
|
||||
* Inject every `HttpSessionListener` into the `SessionEventHttpSessionListenerAdapter`
|
||||
|
||||
If you use the configuration support documented in <<httpsession-redis,`HttpSession` with Redis>>, all you need to do is register every `HttpSessionListener` as a bean.
|
||||
If you use the Redis support with `enableIndexingAndEvents` set to `true`, `@EnableRedisHttpSession(enableIndexingAndEvents = true)`, all you need to do is register every `HttpSessionListener` as a bean.
|
||||
For example, assume you want to support Spring Security's concurrency control and need to use `HttpSessionEventPublisher`. In that case, you can add `HttpSessionEventPublisher` as a bean.
|
||||
In Java configuration, this might look like the following:
|
||||
|
||||
|
||||
@@ -67,9 +67,9 @@ Spring Session is Open Source software released under the https://www.apache.org
|
||||
|
||||
The minimum requirements for Spring Session are:
|
||||
|
||||
* Java 8+.
|
||||
* Java 17+.
|
||||
* If you run in a Servlet Container (not required), Servlet 3.1+.
|
||||
* If you use other Spring libraries (not required), the minimum required version is Spring 5.0.x.
|
||||
* If you use other Spring libraries (not required), the minimum required version is Spring 6.0.x.
|
||||
* `@EnableRedisHttpSession` requires Redis 2.8+. This is necessary to support xref:api.adoc#api-redisindexedsessionrepository-expiration[Session Expiration]
|
||||
* `@EnableHazelcastHttpSession` requires Hazelcast 3.6+. This is necessary to support xref:api.adoc#api-enablehazelcasthttpsession-storage[`FindByIndexNameSessionRepository`]
|
||||
|
||||
|
||||
@@ -1,46 +1,20 @@
|
||||
[[upgrading-2.0]]
|
||||
= Upgrading to 2.x
|
||||
[[upgrading-3.0]]
|
||||
= Upgrading to 3.x
|
||||
|
||||
With the new major release version, the Spring Session team took the opportunity to make some non-passive changes.
|
||||
The focus of these changes is to improve and harmonize Spring Session's APIs as well as remove the deprecated components.
|
||||
|
||||
== Baseline Update
|
||||
|
||||
Spring Session 2.0 requires Java 8 and Spring Framework 5.0 as a baseline, since its entire codebase is now based on Java 8 source code.
|
||||
Spring Session 3.0 requires Java 17 and Spring Framework 6.0 as a baseline, since its entire codebase is now based on Java 17 source code.
|
||||
See https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-5.x[Upgrading to Spring Framework 5.x] for more on upgrading Spring Framework.
|
||||
|
||||
== Replaced and Removed Modules
|
||||
|
||||
As a part of the project's splitting of the modules, the existing `spring-session` has been replaced with the `spring-session-core` module.
|
||||
The `spring-session-core` module holds only the common set of APIs and components, while other modules contain the implementation of the appropriate `SessionRepository` and functionality related to that data store.
|
||||
This applies to several existing modules that were previously a simple dependency aggregator helper module.
|
||||
With new module arrangement, the following modules actually carry the implementation:
|
||||
|
||||
* Spring Session for MongoDB
|
||||
* Spring Session for Redis
|
||||
* Spring Session JDBC
|
||||
* Spring Session Hazelcast
|
||||
|
||||
Also, the following were removed from the main project repository:
|
||||
|
||||
* Spring Session Data GemFire
|
||||
** https://github.com/spring-projects/spring-session-data-geode[`spring-session-data-geode`]
|
||||
|
||||
== Replaced and Removed Packages, Classes, and Methods
|
||||
|
||||
The following changes were made to packages, classes, and methods:
|
||||
|
||||
* `ExpiringSession` API has been merged into the `Session` API.
|
||||
* The `Session` API has been enhanced to make full use of Java 8.
|
||||
* The `Session` API has been extended with `changeSessionId` support.
|
||||
* The `SessionRepository` API has been updated to better align with Spring Data method naming conventions.
|
||||
* `AbstractSessionEvent` and its subclasses are no longer constructable without an underlying `Session` object.
|
||||
* The Redis namespace used by `RedisOperationsSessionRepository` is now fully configurable, instead of being partially configurable.
|
||||
* Redis configuration support has been updated to avoid registering a Spring Session-specific `RedisTemplate` bean.
|
||||
* JDBC configuration support has been updated to avoid registering a Spring Session-specific `JdbcTemplate` bean.
|
||||
* Previously deprecated classes and methods have been removed across the codebase
|
||||
|
||||
|
||||
== Dropped Support
|
||||
|
||||
As a part of the changes to `HttpSessionStrategy` and its alignment to the counterpart from the reactive world, the support for managing multiple users' sessions in a single browser instance has been removed.
|
||||
The introduction of a new API to replace this functionality is under consideration for future releases.
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ apply plugin: 'io.spring.convention.spring-test'
|
||||
dependencies {
|
||||
testImplementation project(':spring-session-core')
|
||||
testImplementation project(':spring-session-data-redis')
|
||||
testImplementation project(':spring-session-hazelcast')
|
||||
// testImplementation project(':spring-session-hazelcast')
|
||||
testImplementation project(':spring-session-jdbc')
|
||||
testImplementation 'org.springframework:spring-jdbc'
|
||||
testImplementation 'org.springframework:spring-messaging'
|
||||
@@ -22,6 +22,7 @@ dependencies {
|
||||
testImplementation 'org.assertj:assertj-core'
|
||||
testImplementation 'com.hazelcast:hazelcast'
|
||||
testImplementation 'io.lettuce:lettuce-core'
|
||||
testImplementation 'jakarta.annotation:jakarta.annotation-api'
|
||||
testImplementation 'jakarta.servlet:jakarta.servlet-api'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
|
||||
|
||||
@@ -19,7 +19,7 @@ package docs.security;
|
||||
import java.time.Duration;
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -19,7 +19,7 @@ package docs.security;
|
||||
import java.time.Duration;
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'io.spring.convention.repository'
|
||||
id 'io.spring.convention.springdependencymangement'
|
||||
id 'io.spring.convention.checkstyle'
|
||||
id 'io.spring.convention.tests-configuration'
|
||||
id 'io.spring.convention.integration-test'
|
||||
id 'org.springframework.propdeps'
|
||||
}
|
||||
|
||||
configurations {
|
||||
classesOnlyElements {
|
||||
canBeConsumed = true
|
||||
canBeResolved = false
|
||||
}
|
||||
}
|
||||
|
||||
artifacts {
|
||||
classesOnlyElements(compileJava.destinationDir)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(':spring-session-core')
|
||||
optional "com.hazelcast:hazelcast:4.2.4"
|
||||
api "org.springframework:spring-context"
|
||||
api "jakarta.annotation:jakarta.annotation-api"
|
||||
|
||||
testImplementation "jakarta.servlet:jakarta.servlet-api"
|
||||
testImplementation "org.assertj:assertj-core"
|
||||
testImplementation "org.mockito:mockito-core"
|
||||
testImplementation "org.springframework:spring-test"
|
||||
testImplementation "org.springframework:spring-web"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api"
|
||||
testImplementation "org.springframework.security:spring-security-core"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
|
||||
|
||||
integrationTestCompile "org.testcontainers:testcontainers"
|
||||
integrationTestCompile "com.hazelcast:hazelcast:4.2.4"
|
||||
integrationTestCompile project(":spring-session-hazelcast")
|
||||
}
|
||||
|
||||
@@ -1,251 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.hazelcast;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import com.hazelcast.map.IMap;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.hazelcast.Hazelcast4IndexedSessionRepository.HazelcastSession;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Base class for {@link Hazelcast4IndexedSessionRepository} integration tests.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
abstract class AbstractHazelcast4IndexedSessionRepositoryITests {
|
||||
|
||||
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
@Autowired
|
||||
private HazelcastInstance hazelcastInstance;
|
||||
|
||||
@Autowired
|
||||
private Hazelcast4IndexedSessionRepository repository;
|
||||
|
||||
@Test
|
||||
void createAndDestroySession() {
|
||||
HazelcastSession sessionToSave = this.repository.createSession();
|
||||
String sessionId = sessionToSave.getId();
|
||||
|
||||
IMap<String, MapSession> hazelcastMap = this.hazelcastInstance
|
||||
.getMap(Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME);
|
||||
|
||||
this.repository.save(sessionToSave);
|
||||
|
||||
assertThat(hazelcastMap.get(sessionId)).isEqualTo(sessionToSave);
|
||||
|
||||
this.repository.deleteById(sessionId);
|
||||
|
||||
assertThat(hazelcastMap.get(sessionId)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void changeSessionIdWhenOnlyChangeId() {
|
||||
String attrName = "changeSessionId";
|
||||
String attrValue = "changeSessionId-value";
|
||||
HazelcastSession toSave = this.repository.createSession();
|
||||
toSave.setAttribute(attrName, attrValue);
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
HazelcastSession findById = this.repository.findById(toSave.getId());
|
||||
|
||||
assertThat(findById.<String>getAttribute(attrName)).isEqualTo(attrValue);
|
||||
|
||||
String originalFindById = findById.getId();
|
||||
String changeSessionId = findById.changeSessionId();
|
||||
|
||||
this.repository.save(findById);
|
||||
|
||||
assertThat(this.repository.findById(originalFindById)).isNull();
|
||||
|
||||
HazelcastSession findByChangeSessionId = this.repository.findById(changeSessionId);
|
||||
|
||||
assertThat(findByChangeSessionId.<String>getAttribute(attrName)).isEqualTo(attrValue);
|
||||
|
||||
this.repository.deleteById(changeSessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void changeSessionIdWhenChangeTwice() {
|
||||
HazelcastSession toSave = this.repository.createSession();
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
String originalId = toSave.getId();
|
||||
String changeId1 = toSave.changeSessionId();
|
||||
String changeId2 = toSave.changeSessionId();
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
assertThat(this.repository.findById(originalId)).isNull();
|
||||
assertThat(this.repository.findById(changeId1)).isNull();
|
||||
assertThat(this.repository.findById(changeId2)).isNotNull();
|
||||
|
||||
this.repository.deleteById(changeId2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void changeSessionIdWhenSetAttributeOnChangedSession() {
|
||||
String attrName = "changeSessionId";
|
||||
String attrValue = "changeSessionId-value";
|
||||
|
||||
HazelcastSession toSave = this.repository.createSession();
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
HazelcastSession findById = this.repository.findById(toSave.getId());
|
||||
|
||||
findById.setAttribute(attrName, attrValue);
|
||||
|
||||
String originalFindById = findById.getId();
|
||||
String changeSessionId = findById.changeSessionId();
|
||||
|
||||
this.repository.save(findById);
|
||||
|
||||
assertThat(this.repository.findById(originalFindById)).isNull();
|
||||
|
||||
HazelcastSession findByChangeSessionId = this.repository.findById(changeSessionId);
|
||||
|
||||
assertThat(findByChangeSessionId.<String>getAttribute(attrName)).isEqualTo(attrValue);
|
||||
|
||||
this.repository.deleteById(changeSessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void changeSessionIdWhenHasNotSaved() {
|
||||
HazelcastSession toSave = this.repository.createSession();
|
||||
String originalId = toSave.getId();
|
||||
toSave.changeSessionId();
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
assertThat(this.repository.findById(toSave.getId())).isNotNull();
|
||||
assertThat(this.repository.findById(originalId)).isNull();
|
||||
|
||||
this.repository.deleteById(toSave.getId());
|
||||
}
|
||||
|
||||
@Test // gh-1076
|
||||
void attemptToUpdateSessionAfterDelete() {
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
String sessionId = session.getId();
|
||||
this.repository.save(session);
|
||||
session = this.repository.findById(sessionId);
|
||||
session.setAttribute("attributeName", "attributeValue");
|
||||
this.repository.deleteById(sessionId);
|
||||
this.repository.save(session);
|
||||
|
||||
assertThat(this.repository.findById(sessionId)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createAndUpdateSession() {
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
String sessionId = session.getId();
|
||||
|
||||
this.repository.save(session);
|
||||
|
||||
session = this.repository.findById(sessionId);
|
||||
session.setAttribute("attributeName", "attributeValue");
|
||||
|
||||
this.repository.save(session);
|
||||
|
||||
assertThat(this.repository.findById(sessionId)).isNotNull();
|
||||
|
||||
this.repository.deleteById(sessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSessionWithSecurityContextAndFindById() {
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
String sessionId = session.getId();
|
||||
|
||||
Authentication authentication = new UsernamePasswordAuthenticationToken("saves-" + System.currentTimeMillis(),
|
||||
"password", AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
||||
securityContext.setAuthentication(authentication);
|
||||
session.setAttribute(SPRING_SECURITY_CONTEXT, securityContext);
|
||||
|
||||
this.repository.save(session);
|
||||
|
||||
assertThat(this.repository.findById(sessionId)).isNotNull();
|
||||
|
||||
this.repository.deleteById(sessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createAndUpdateSessionWhileKeepingOriginalTimeToLiveConfiguredOnRepository() {
|
||||
final Duration defaultSessionTimeout = Duration.ofSeconds(1800);
|
||||
|
||||
final IMap<String, MapSession> hazelcastMap = this.hazelcastInstance
|
||||
.getMap(Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME);
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
String sessionId = session.getId();
|
||||
this.repository.save(session);
|
||||
|
||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(defaultSessionTimeout);
|
||||
assertThat(hazelcastMap.getEntryView(sessionId).getTtl()).isEqualTo(defaultSessionTimeout.toMillis());
|
||||
|
||||
session = this.repository.findById(sessionId);
|
||||
session.setLastAccessedTime(Instant.now());
|
||||
this.repository.save(session);
|
||||
|
||||
session = this.repository.findById(sessionId);
|
||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(defaultSessionTimeout);
|
||||
assertThat(hazelcastMap.getEntryView(sessionId).getTtl()).isEqualTo(defaultSessionTimeout.toMillis());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createAndUpdateSessionWhileKeepingTimeToLiveSetOnSession() {
|
||||
final Duration individualSessionTimeout = Duration.ofSeconds(23);
|
||||
|
||||
final IMap<String, MapSession> hazelcastMap = this.hazelcastInstance
|
||||
.getMap(Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME);
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
session.setMaxInactiveInterval(individualSessionTimeout);
|
||||
String sessionId = session.getId();
|
||||
this.repository.save(session);
|
||||
|
||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(individualSessionTimeout);
|
||||
assertThat(hazelcastMap.getEntryView(sessionId).getTtl()).isEqualTo(individualSessionTimeout.toMillis());
|
||||
|
||||
session = this.repository.findById(sessionId);
|
||||
session.setAttribute("attribute", "value");
|
||||
this.repository.save(session);
|
||||
|
||||
session = this.repository.findById(sessionId);
|
||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(individualSessionTimeout);
|
||||
assertThat(hazelcastMap.getEntryView(sessionId).getTtl()).isEqualTo(individualSessionTimeout.toMillis());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 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.hazelcast;
|
||||
|
||||
import com.hazelcast.client.HazelcastClient;
|
||||
import com.hazelcast.client.config.ClientConfig;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.utility.MountableFile;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||
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 Hazelcast4IndexedSessionRepository} using client-server
|
||||
* topology.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
class ClientServerHazelcast4IndexedSessionRepositoryITests extends AbstractHazelcast4IndexedSessionRepositoryITests {
|
||||
|
||||
private static GenericContainer container = new GenericContainer<>("hazelcast/hazelcast:4.2.4")
|
||||
.withExposedPorts(5701).withCopyFileToContainer(MountableFile.forClasspathResource("/hazelcast-server.xml"),
|
||||
"/opt/hazelcast/hazelcast.xml");
|
||||
|
||||
@BeforeAll
|
||||
static void setUpClass() {
|
||||
container.start();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDownClass() {
|
||||
container.stop();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableHazelcastHttpSession
|
||||
static class HazelcastSessionConfig {
|
||||
|
||||
@Bean
|
||||
HazelcastInstance hazelcastInstance() {
|
||||
ClientConfig clientConfig = new ClientConfig();
|
||||
clientConfig.getNetworkConfig()
|
||||
.addAddress(container.getContainerIpAddress() + ":" + container.getFirstMappedPort());
|
||||
clientConfig.getUserCodeDeploymentConfig().setEnabled(true).addClass(Session.class)
|
||||
.addClass(MapSession.class).addClass(Hazelcast4SessionUpdateEntryProcessor.class);
|
||||
return HazelcastClient.newHazelcastClient(clientConfig);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 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.hazelcast;
|
||||
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||
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 Hazelcast4IndexedSessionRepository} using embedded
|
||||
* topology.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
class EmbeddedHazelcast4IndexedSessionRepositoryITests extends AbstractHazelcast4IndexedSessionRepositoryITests {
|
||||
|
||||
@EnableHazelcastHttpSession
|
||||
@Configuration
|
||||
static class HazelcastSessionConfig {
|
||||
|
||||
@Bean
|
||||
HazelcastInstance hazelcastInstance() {
|
||||
return Hazelcast4ITestUtils.embeddedHazelcastServer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.hazelcast;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link Hazelcast4IndexedSessionRepository} using embedded
|
||||
* topology, with flush mode set to immediate.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
class FlushImmediateHazelcast4IndexedSessionRepositoryITests {
|
||||
|
||||
@Autowired
|
||||
private Hazelcast4IndexedSessionRepository repository;
|
||||
|
||||
@Test
|
||||
void createSessionWithSecurityContextAndFindByPrincipalName() {
|
||||
String username = "saves-" + System.currentTimeMillis();
|
||||
|
||||
Hazelcast4IndexedSessionRepository.HazelcastSession session = this.repository.createSession();
|
||||
String sessionId = session.getId();
|
||||
|
||||
Authentication authentication = new UsernamePasswordAuthenticationToken(username, "password",
|
||||
AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
||||
securityContext.setAuthentication(authentication);
|
||||
session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
|
||||
|
||||
this.repository.save(session);
|
||||
|
||||
Map<String, Hazelcast4IndexedSessionRepository.HazelcastSession> findByPrincipalName = this.repository
|
||||
.findByPrincipalName(username);
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(1);
|
||||
assertThat(findByPrincipalName.keySet()).containsOnly(sessionId);
|
||||
|
||||
this.repository.deleteById(sessionId);
|
||||
}
|
||||
|
||||
@EnableHazelcastHttpSession(flushMode = FlushMode.IMMEDIATE)
|
||||
@Configuration
|
||||
static class HazelcastSessionConfig {
|
||||
|
||||
@Bean
|
||||
HazelcastInstance hazelcastInstance() {
|
||||
return Hazelcast4ITestUtils.embeddedHazelcastServer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* 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.hazelcast;
|
||||
|
||||
import com.hazelcast.config.AttributeConfig;
|
||||
import com.hazelcast.config.Config;
|
||||
import com.hazelcast.config.IndexConfig;
|
||||
import com.hazelcast.config.IndexType;
|
||||
import com.hazelcast.config.NetworkConfig;
|
||||
import com.hazelcast.config.SerializerConfig;
|
||||
import com.hazelcast.core.Hazelcast;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
|
||||
import org.springframework.session.MapSession;
|
||||
|
||||
/**
|
||||
* Utility class for Hazelcast integration tests.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
final class Hazelcast4ITestUtils {
|
||||
|
||||
private Hazelcast4ITestUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link HazelcastInstance} for use in integration tests.
|
||||
* @return the Hazelcast instance
|
||||
*/
|
||||
static HazelcastInstance embeddedHazelcastServer() {
|
||||
Config config = new Config();
|
||||
NetworkConfig networkConfig = config.getNetworkConfig();
|
||||
networkConfig.setPort(0);
|
||||
networkConfig.getJoin().getMulticastConfig().setEnabled(false);
|
||||
AttributeConfig attributeConfig = new AttributeConfig()
|
||||
.setName(Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
|
||||
.setExtractorClassName(Hazelcast4PrincipalNameExtractor.class.getName());
|
||||
config.getMapConfig(Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME)
|
||||
.addAttributeConfig(attributeConfig).addIndexConfig(
|
||||
new IndexConfig(IndexType.HASH, Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE));
|
||||
SerializerConfig serializerConfig = new SerializerConfig();
|
||||
serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class);
|
||||
config.getSerializationConfig().addSerializerConfig(serializerConfig);
|
||||
return Hazelcast.newHazelcastInstance(config);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,241 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.hazelcast;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.events.SessionCreatedEvent;
|
||||
import org.springframework.session.events.SessionDeletedEvent;
|
||||
import org.springframework.session.events.SessionExpiredEvent;
|
||||
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Ensure that the appropriate SessionEvents are fired at the expected times. Additionally
|
||||
* ensure that the interactions with the {@link SessionRepository} abstraction behave as
|
||||
* expected after each SessionEvent.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
class SessionEventHazelcast4IndexedSessionRepositoryTests<S extends Session> {
|
||||
|
||||
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 2;
|
||||
|
||||
@Autowired
|
||||
private SessionRepository<S> repository;
|
||||
|
||||
@Autowired
|
||||
private SessionEventRegistry registry;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.registry.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveSessionTest() throws InterruptedException {
|
||||
String username = "saves-" + System.currentTimeMillis();
|
||||
|
||||
S sessionToSave = this.repository.createSession();
|
||||
|
||||
String expectedAttributeName = "a";
|
||||
String expectedAttributeValue = "b";
|
||||
sessionToSave.setAttribute(expectedAttributeName, expectedAttributeValue);
|
||||
Authentication toSaveToken = new UsernamePasswordAuthenticationToken(username, "password",
|
||||
AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||
SecurityContext toSaveContext = SecurityContextHolder.createEmptyContext();
|
||||
toSaveContext.setAuthentication(toSaveToken);
|
||||
sessionToSave.setAttribute("SPRING_SECURITY_CONTEXT", toSaveContext);
|
||||
sessionToSave.setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username);
|
||||
|
||||
this.repository.save(sessionToSave);
|
||||
|
||||
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||
assertThat(this.registry.<SessionCreatedEvent>getEvent(sessionToSave.getId()))
|
||||
.isInstanceOf(SessionCreatedEvent.class);
|
||||
|
||||
Session session = this.repository.findById(sessionToSave.getId());
|
||||
|
||||
assertThat(session.getId()).isEqualTo(sessionToSave.getId());
|
||||
assertThat(session.getAttributeNames()).isEqualTo(sessionToSave.getAttributeNames());
|
||||
assertThat(session.<String>getAttribute(expectedAttributeName))
|
||||
.isEqualTo(sessionToSave.getAttribute(expectedAttributeName));
|
||||
}
|
||||
|
||||
@Test
|
||||
void expiredSessionTest() throws InterruptedException {
|
||||
S sessionToSave = this.repository.createSession();
|
||||
|
||||
this.repository.save(sessionToSave);
|
||||
|
||||
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||
assertThat(this.registry.<SessionCreatedEvent>getEvent(sessionToSave.getId()))
|
||||
.isInstanceOf(SessionCreatedEvent.class);
|
||||
this.registry.clear();
|
||||
|
||||
assertThat(sessionToSave.getMaxInactiveInterval())
|
||||
.isEqualTo(Duration.ofSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS));
|
||||
|
||||
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||
assertThat(this.registry.<SessionExpiredEvent>getEvent(sessionToSave.getId()))
|
||||
.isInstanceOf(SessionExpiredEvent.class);
|
||||
|
||||
assertThat(this.repository.findById(sessionToSave.getId())).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletedSessionTest() throws InterruptedException {
|
||||
S sessionToSave = this.repository.createSession();
|
||||
|
||||
this.repository.save(sessionToSave);
|
||||
|
||||
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||
assertThat(this.registry.<SessionCreatedEvent>getEvent(sessionToSave.getId()))
|
||||
.isInstanceOf(SessionCreatedEvent.class);
|
||||
this.registry.clear();
|
||||
|
||||
this.repository.deleteById(sessionToSave.getId());
|
||||
|
||||
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||
assertThat(this.registry.<SessionDeletedEvent>getEvent(sessionToSave.getId()))
|
||||
.isInstanceOf(SessionDeletedEvent.class);
|
||||
|
||||
assertThat(this.repository.findById(sessionToSave.getId())).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveUpdatesTimeToLiveTest() throws InterruptedException {
|
||||
S sessionToSave = this.repository.createSession();
|
||||
sessionToSave.setMaxInactiveInterval(Duration.ofSeconds(3));
|
||||
this.repository.save(sessionToSave);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
// Get and save the session like SessionRepositoryFilter would.
|
||||
S sessionToUpdate = this.repository.findById(sessionToSave.getId());
|
||||
sessionToUpdate.setLastAccessedTime(Instant.now());
|
||||
this.repository.save(sessionToUpdate);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
assertThat(this.repository.findById(sessionToUpdate.getId())).isNotNull();
|
||||
}
|
||||
|
||||
@Test // gh-1077
|
||||
void changeSessionIdNoEventTest() throws InterruptedException {
|
||||
S sessionToSave = this.repository.createSession();
|
||||
sessionToSave.setMaxInactiveInterval(Duration.ofMinutes(30));
|
||||
|
||||
this.repository.save(sessionToSave);
|
||||
|
||||
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||
assertThat(this.registry.<SessionCreatedEvent>getEvent(sessionToSave.getId()))
|
||||
.isInstanceOf(SessionCreatedEvent.class);
|
||||
this.registry.clear();
|
||||
|
||||
sessionToSave.changeSessionId();
|
||||
this.repository.save(sessionToSave);
|
||||
|
||||
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isFalse();
|
||||
}
|
||||
|
||||
@Test // gh-1300
|
||||
void updateMaxInactiveIntervalTest() throws InterruptedException {
|
||||
S sessionToSave = this.repository.createSession();
|
||||
sessionToSave.setMaxInactiveInterval(Duration.ofMinutes(30));
|
||||
this.repository.save(sessionToSave);
|
||||
|
||||
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||
assertThat(this.registry.<SessionCreatedEvent>getEvent(sessionToSave.getId()))
|
||||
.isInstanceOf(SessionCreatedEvent.class);
|
||||
this.registry.clear();
|
||||
|
||||
S sessionToUpdate = this.repository.findById(sessionToSave.getId());
|
||||
sessionToUpdate.setLastAccessedTime(Instant.now());
|
||||
sessionToUpdate.setMaxInactiveInterval(Duration.ofSeconds(1));
|
||||
this.repository.save(sessionToUpdate);
|
||||
|
||||
assertThat(this.registry.receivedEvent(sessionToUpdate.getId())).isTrue();
|
||||
assertThat(this.registry.<SessionExpiredEvent>getEvent(sessionToUpdate.getId()))
|
||||
.isInstanceOf(SessionExpiredEvent.class);
|
||||
assertThat(this.repository.findById(sessionToUpdate.getId())).isNull();
|
||||
}
|
||||
|
||||
@Test // gh-1899
|
||||
void updateSessionAndExpireAfterOriginalTimeToLiveTest() throws InterruptedException {
|
||||
S sessionToSave = this.repository.createSession();
|
||||
this.repository.save(sessionToSave);
|
||||
|
||||
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||
assertThat(this.registry.<SessionCreatedEvent>getEvent(sessionToSave.getId()))
|
||||
.isInstanceOf(SessionCreatedEvent.class);
|
||||
this.registry.clear();
|
||||
|
||||
S sessionToUpdate = this.repository.findById(sessionToSave.getId());
|
||||
sessionToUpdate.setLastAccessedTime(Instant.now());
|
||||
this.repository.save(sessionToUpdate);
|
||||
|
||||
assertThat(this.registry.receivedEvent(sessionToUpdate.getId())).isTrue();
|
||||
assertThat(this.registry.<SessionExpiredEvent>getEvent(sessionToUpdate.getId()))
|
||||
.isInstanceOf(SessionExpiredEvent.class);
|
||||
// Assert this after the expired event was received because it would otherwise do
|
||||
// its own expiration check and explicitly delete the session from Hazelcast
|
||||
// regardless of the TTL of the IMap entry.
|
||||
assertThat(this.repository.findById(sessionToUpdate.getId())).isNull();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableHazelcastHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
|
||||
static class HazelcastSessionConfig {
|
||||
|
||||
@Bean
|
||||
HazelcastInstance embeddedHazelcast() {
|
||||
return Hazelcast4ITestUtils.embeddedHazelcastServer();
|
||||
}
|
||||
|
||||
@Bean
|
||||
SessionEventRegistry sessionEventRegistry() {
|
||||
return new SessionEventRegistry();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* 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.hazelcast;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.session.events.AbstractSessionEvent;
|
||||
|
||||
class SessionEventRegistry implements ApplicationListener<AbstractSessionEvent> {
|
||||
|
||||
private Map<String, AbstractSessionEvent> events = new HashMap<>();
|
||||
|
||||
private ConcurrentMap<String, Object> locks = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(AbstractSessionEvent event) {
|
||||
String sessionId = event.getSessionId();
|
||||
this.events.put(sessionId, event);
|
||||
Object lock = getLock(sessionId);
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
this.events.clear();
|
||||
this.locks.clear();
|
||||
}
|
||||
|
||||
boolean receivedEvent(String sessionId) throws InterruptedException {
|
||||
return waitForEvent(sessionId) != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
<E extends AbstractSessionEvent> E getEvent(String sessionId) throws InterruptedException {
|
||||
return (E) waitForEvent(sessionId);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <E extends AbstractSessionEvent> E waitForEvent(String sessionId) throws InterruptedException {
|
||||
Object lock = getLock(sessionId);
|
||||
synchronized (lock) {
|
||||
if (!this.events.containsKey(sessionId)) {
|
||||
lock.wait(10000);
|
||||
}
|
||||
}
|
||||
return (E) this.events.get(sessionId);
|
||||
}
|
||||
|
||||
private Object getLock(String sessionId) {
|
||||
return this.locks.computeIfAbsent(sessionId, (k) -> new Object());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?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.2.xsd">
|
||||
|
||||
<network>
|
||||
<join>
|
||||
<multicast enabled="false"/>
|
||||
</join>
|
||||
</network>
|
||||
|
||||
<user-code-deployment enabled="true">
|
||||
<class-cache-mode>ETERNAL</class-cache-mode>
|
||||
<provider-mode>LOCAL_AND_CACHED_CLASSES</provider-mode>
|
||||
</user-code-deployment>
|
||||
|
||||
</hazelcast>
|
||||
@@ -1 +0,0 @@
|
||||
ryuk.container.timeout=120
|
||||
@@ -1,478 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.hazelcast;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import com.hazelcast.core.EntryEvent;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import com.hazelcast.map.IMap;
|
||||
import com.hazelcast.map.listener.EntryAddedListener;
|
||||
import com.hazelcast.map.listener.EntryEvictedListener;
|
||||
import com.hazelcast.map.listener.EntryExpiredListener;
|
||||
import com.hazelcast.map.listener.EntryRemovedListener;
|
||||
import com.hazelcast.query.Predicates;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.session.DelegatingIndexResolver;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.IndexResolver;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.PrincipalNameIndexResolver;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.events.AbstractSessionEvent;
|
||||
import org.springframework.session.events.SessionCreatedEvent;
|
||||
import org.springframework.session.events.SessionDeletedEvent;
|
||||
import org.springframework.session.events.SessionExpiredEvent;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.session.SessionRepository} implementation using Hazelcast
|
||||
* 4 that stores sessions in Hazelcast's distributed {@link IMap}.
|
||||
*
|
||||
* <p>
|
||||
* An example of how to create a new instance can be seen below:
|
||||
*
|
||||
* <pre class="code">
|
||||
* Config config = new Config();
|
||||
*
|
||||
* // ... configure Hazelcast ...
|
||||
*
|
||||
* HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config);
|
||||
*
|
||||
* Hazelcast4IndexedSessionRepository sessionRepository =
|
||||
* new Hazelcast4IndexedSessionRepository(hazelcastInstance);
|
||||
* </pre>
|
||||
*
|
||||
* In order to support finding sessions by principal name using
|
||||
* {@link #findByIndexNameAndIndexValue(String, String)} method, custom configuration of
|
||||
* {@code IMap} supplied to this implementation is required.
|
||||
*
|
||||
* The following snippet demonstrates how to define required configuration using
|
||||
* programmatic Hazelcast Configuration:
|
||||
*
|
||||
* <pre class="code">
|
||||
* AttributeConfig attributeConfig = new AttributeConfig()
|
||||
* .setName(Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
|
||||
* .setExtractorClassName(Hazelcast4PrincipalNameExtractor.class.getName());
|
||||
*
|
||||
* Config config = new Config();
|
||||
*
|
||||
* config.getMapConfig(Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME)
|
||||
* .addAttributeConfig(attributeConfig)
|
||||
* .addIndexConfig(new IndexConfig(
|
||||
* IndexType.HASH,
|
||||
* Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE));
|
||||
*
|
||||
* Hazelcast.newHazelcastInstance(config);
|
||||
* </pre>
|
||||
*
|
||||
* This implementation listens for events on the Hazelcast-backed SessionRepository and
|
||||
* translates those events into the corresponding Spring Session events. Publish the
|
||||
* Spring Session events with the given {@link ApplicationEventPublisher}.
|
||||
*
|
||||
* <ul>
|
||||
* <li>entryAdded - {@link SessionCreatedEvent}</li>
|
||||
* <li>entryEvicted - {@link SessionExpiredEvent}</li>
|
||||
* <li>entryExpired - {@link SessionExpiredEvent}</li>
|
||||
* <li>entryRemoved - {@link SessionDeletedEvent}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class Hazelcast4IndexedSessionRepository
|
||||
implements FindByIndexNameSessionRepository<Hazelcast4IndexedSessionRepository.HazelcastSession>,
|
||||
EntryAddedListener<String, MapSession>, EntryEvictedListener<String, MapSession>,
|
||||
EntryRemovedListener<String, MapSession>, EntryExpiredListener<String, MapSession> {
|
||||
|
||||
/**
|
||||
* The default name of map used by Spring Session to store sessions.
|
||||
*/
|
||||
public static final String DEFAULT_SESSION_MAP_NAME = "spring:session:sessions";
|
||||
|
||||
/**
|
||||
* The principal name custom attribute name.
|
||||
*/
|
||||
public static final String PRINCIPAL_NAME_ATTRIBUTE = "principalName";
|
||||
|
||||
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
private static final Log logger = LogFactory.getLog(Hazelcast4IndexedSessionRepository.class);
|
||||
|
||||
private final HazelcastInstance hazelcastInstance;
|
||||
|
||||
private ApplicationEventPublisher eventPublisher = (event) -> {
|
||||
};
|
||||
|
||||
/**
|
||||
* If non-null, this value is used to override
|
||||
* {@link MapSession#setMaxInactiveInterval(Duration)}.
|
||||
*/
|
||||
private Integer defaultMaxInactiveInterval;
|
||||
|
||||
private IndexResolver<Session> indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>());
|
||||
|
||||
private String sessionMapName = DEFAULT_SESSION_MAP_NAME;
|
||||
|
||||
private FlushMode flushMode = FlushMode.ON_SAVE;
|
||||
|
||||
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
private IMap<String, MapSession> sessions;
|
||||
|
||||
private UUID sessionListenerId;
|
||||
|
||||
/**
|
||||
* Create a new {@link Hazelcast4IndexedSessionRepository} instance.
|
||||
* @param hazelcastInstance the {@link HazelcastInstance} to use for managing sessions
|
||||
*/
|
||||
public Hazelcast4IndexedSessionRepository(HazelcastInstance hazelcastInstance) {
|
||||
Assert.notNull(hazelcastInstance, "HazelcastInstance must not be null");
|
||||
this.hazelcastInstance = hazelcastInstance;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
this.sessions = this.hazelcastInstance.getMap(this.sessionMapName);
|
||||
this.sessionListenerId = this.sessions.addEntryListener(this, true);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void close() {
|
||||
this.sessions.removeEntryListener(this.sessionListenerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link ApplicationEventPublisher} that is used to publish
|
||||
* {@link AbstractSessionEvent session events}. The default is to not publish session
|
||||
* events.
|
||||
* @param applicationEventPublisher the {@link ApplicationEventPublisher} that is used
|
||||
* to publish session events. Cannot be null.
|
||||
*/
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||
Assert.notNull(applicationEventPublisher, "ApplicationEventPublisher cannot be null");
|
||||
this.eventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum inactive interval in seconds between requests before newly created
|
||||
* sessions will be invalidated. A negative time indicates that the session will never
|
||||
* timeout. The default is 1800 (30 minutes).
|
||||
* @param defaultMaxInactiveInterval the maximum inactive interval in seconds
|
||||
*/
|
||||
public void setDefaultMaxInactiveInterval(Integer defaultMaxInactiveInterval) {
|
||||
this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link IndexResolver} to use.
|
||||
* @param indexResolver the index resolver
|
||||
*/
|
||||
public void setIndexResolver(IndexResolver<Session> indexResolver) {
|
||||
Assert.notNull(indexResolver, "indexResolver cannot be null");
|
||||
this.indexResolver = indexResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of map used to store sessions.
|
||||
* @param sessionMapName the session map name
|
||||
*/
|
||||
public void setSessionMapName(String sessionMapName) {
|
||||
Assert.hasText(sessionMapName, "Map name must not be empty");
|
||||
this.sessionMapName = sessionMapName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Hazelcast flush mode. Default flush mode is {@link FlushMode#ON_SAVE}.
|
||||
* @param flushMode the new Hazelcast flush mode
|
||||
*/
|
||||
public void setFlushMode(FlushMode flushMode) {
|
||||
Assert.notNull(flushMode, "flushMode cannot be null");
|
||||
this.flushMode = flushMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the save mode.
|
||||
* @param saveMode the save mode
|
||||
*/
|
||||
public void setSaveMode(SaveMode saveMode) {
|
||||
Assert.notNull(saveMode, "saveMode must not be null");
|
||||
this.saveMode = saveMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HazelcastSession createSession() {
|
||||
MapSession cached = new MapSession();
|
||||
if (this.defaultMaxInactiveInterval != null) {
|
||||
cached.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval));
|
||||
}
|
||||
HazelcastSession session = new HazelcastSession(cached, true);
|
||||
session.flushImmediateIfNecessary();
|
||||
return session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(HazelcastSession session) {
|
||||
if (session.isNew) {
|
||||
this.sessions.set(session.getId(), session.getDelegate(), session.getMaxInactiveInterval().getSeconds(),
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
else if (session.sessionIdChanged) {
|
||||
this.sessions.delete(session.originalId);
|
||||
session.originalId = session.getId();
|
||||
this.sessions.set(session.getId(), session.getDelegate(), session.getMaxInactiveInterval().getSeconds(),
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
else if (session.hasChanges()) {
|
||||
Hazelcast4SessionUpdateEntryProcessor entryProcessor = new Hazelcast4SessionUpdateEntryProcessor();
|
||||
if (session.lastAccessedTimeChanged) {
|
||||
entryProcessor.setLastAccessedTime(session.getLastAccessedTime());
|
||||
}
|
||||
if (session.maxInactiveIntervalChanged) {
|
||||
entryProcessor.setMaxInactiveInterval(session.getMaxInactiveInterval());
|
||||
}
|
||||
if (!session.delta.isEmpty()) {
|
||||
entryProcessor.setDelta(new HashMap<>(session.delta));
|
||||
}
|
||||
this.sessions.executeOnKey(session.getId(), entryProcessor);
|
||||
}
|
||||
session.clearChangeFlags();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HazelcastSession findById(String id) {
|
||||
MapSession saved = this.sessions.get(id);
|
||||
if (saved == null) {
|
||||
return null;
|
||||
}
|
||||
if (saved.isExpired()) {
|
||||
deleteById(saved.getId());
|
||||
return null;
|
||||
}
|
||||
return new HazelcastSession(saved, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(String id) {
|
||||
this.sessions.remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, HazelcastSession> findByIndexNameAndIndexValue(String indexName, String indexValue) {
|
||||
if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Collection<MapSession> sessions = this.sessions.values(Predicates.equal(PRINCIPAL_NAME_ATTRIBUTE, indexValue));
|
||||
Map<String, HazelcastSession> sessionMap = new HashMap<>(sessions.size());
|
||||
for (MapSession session : sessions) {
|
||||
sessionMap.put(session.getId(), new HazelcastSession(session, false));
|
||||
}
|
||||
return sessionMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entryAdded(EntryEvent<String, MapSession> event) {
|
||||
MapSession session = event.getValue();
|
||||
if (session.getId().equals(session.getOriginalId())) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Session created with id: " + session.getId());
|
||||
}
|
||||
this.eventPublisher.publishEvent(new SessionCreatedEvent(this, session));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entryEvicted(EntryEvent<String, MapSession> event) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Session expired with id: " + event.getOldValue().getId());
|
||||
}
|
||||
this.eventPublisher.publishEvent(new SessionExpiredEvent(this, event.getOldValue()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entryRemoved(EntryEvent<String, MapSession> event) {
|
||||
MapSession session = event.getOldValue();
|
||||
if (session != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Session deleted with id: " + session.getId());
|
||||
}
|
||||
this.eventPublisher.publishEvent(new SessionDeletedEvent(this, session));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entryExpired(EntryEvent<String, MapSession> event) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Session expired with id: " + event.getOldValue().getId());
|
||||
}
|
||||
this.eventPublisher.publishEvent(new SessionExpiredEvent(this, event.getOldValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom implementation of {@link Session} that uses a {@link MapSession} as the
|
||||
* basis for its mapping. It keeps track if changes have been made since last save.
|
||||
*
|
||||
* @author Aleksandar Stojsavljevic
|
||||
*/
|
||||
final class HazelcastSession implements Session {
|
||||
|
||||
private final MapSession delegate;
|
||||
|
||||
private boolean isNew;
|
||||
|
||||
private boolean sessionIdChanged;
|
||||
|
||||
private boolean lastAccessedTimeChanged;
|
||||
|
||||
private boolean maxInactiveIntervalChanged;
|
||||
|
||||
private String originalId;
|
||||
|
||||
private Map<String, Object> delta = new HashMap<>();
|
||||
|
||||
HazelcastSession(MapSession cached, boolean isNew) {
|
||||
this.delegate = cached;
|
||||
this.isNew = isNew;
|
||||
this.originalId = cached.getId();
|
||||
if (this.isNew || (Hazelcast4IndexedSessionRepository.this.saveMode == SaveMode.ALWAYS)) {
|
||||
getAttributeNames()
|
||||
.forEach((attributeName) -> this.delta.put(attributeName, cached.getAttribute(attributeName)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastAccessedTime(Instant lastAccessedTime) {
|
||||
this.delegate.setLastAccessedTime(lastAccessedTime);
|
||||
this.lastAccessedTimeChanged = true;
|
||||
flushImmediateIfNecessary();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpired() {
|
||||
return this.delegate.isExpired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getCreationTime() {
|
||||
return this.delegate.getCreationTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return this.delegate.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String changeSessionId() {
|
||||
String newSessionId = this.delegate.changeSessionId();
|
||||
this.sessionIdChanged = true;
|
||||
return newSessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getLastAccessedTime() {
|
||||
return this.delegate.getLastAccessedTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxInactiveInterval(Duration interval) {
|
||||
Assert.notNull(interval, "interval must not be null");
|
||||
this.delegate.setMaxInactiveInterval(interval);
|
||||
this.maxInactiveIntervalChanged = true;
|
||||
flushImmediateIfNecessary();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getMaxInactiveInterval() {
|
||||
return this.delegate.getMaxInactiveInterval();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getAttribute(String attributeName) {
|
||||
T attributeValue = this.delegate.getAttribute(attributeName);
|
||||
if (attributeValue != null
|
||||
&& Hazelcast4IndexedSessionRepository.this.saveMode.equals(SaveMode.ON_GET_ATTRIBUTE)) {
|
||||
this.delta.put(attributeName, attributeValue);
|
||||
}
|
||||
return attributeValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAttributeNames() {
|
||||
return this.delegate.getAttributeNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String attributeName, Object attributeValue) {
|
||||
this.delegate.setAttribute(attributeName, attributeValue);
|
||||
this.delta.put(attributeName, attributeValue);
|
||||
if (SPRING_SECURITY_CONTEXT.equals(attributeName)) {
|
||||
Map<String, String> indexes = Hazelcast4IndexedSessionRepository.this.indexResolver
|
||||
.resolveIndexesFor(this);
|
||||
String principal = (attributeValue != null) ? indexes.get(PRINCIPAL_NAME_INDEX_NAME) : null;
|
||||
this.delegate.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal);
|
||||
this.delta.put(PRINCIPAL_NAME_INDEX_NAME, principal);
|
||||
}
|
||||
flushImmediateIfNecessary();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String attributeName) {
|
||||
setAttribute(attributeName, null);
|
||||
}
|
||||
|
||||
MapSession getDelegate() {
|
||||
return this.delegate;
|
||||
}
|
||||
|
||||
boolean hasChanges() {
|
||||
return (this.lastAccessedTimeChanged || this.maxInactiveIntervalChanged || !this.delta.isEmpty());
|
||||
}
|
||||
|
||||
void clearChangeFlags() {
|
||||
this.isNew = false;
|
||||
this.lastAccessedTimeChanged = false;
|
||||
this.sessionIdChanged = false;
|
||||
this.maxInactiveIntervalChanged = false;
|
||||
this.delta.clear();
|
||||
}
|
||||
|
||||
private void flushImmediateIfNecessary() {
|
||||
if (Hazelcast4IndexedSessionRepository.this.flushMode == FlushMode.IMMEDIATE) {
|
||||
Hazelcast4IndexedSessionRepository.this.save(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* 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.hazelcast;
|
||||
|
||||
import com.hazelcast.query.extractor.ValueCollector;
|
||||
import com.hazelcast.query.extractor.ValueExtractor;
|
||||
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.MapSession;
|
||||
|
||||
/**
|
||||
* Hazelcast {@link ValueExtractor} responsible for extracting principal name from the
|
||||
* {@link MapSession} to be used with Hazelcast 4.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class Hazelcast4PrincipalNameExtractor implements ValueExtractor<MapSession, String> {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void extract(MapSession target, String argument, ValueCollector collector) {
|
||||
String principalName = target.getAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
|
||||
if (principalName != null) {
|
||||
collector.addObject(principalName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.hazelcast;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.hazelcast.map.EntryProcessor;
|
||||
import com.hazelcast.map.ExtendedMapEntry;
|
||||
|
||||
import org.springframework.session.MapSession;
|
||||
|
||||
/**
|
||||
* Hazelcast {@link EntryProcessor} responsible for handling updates to session when using
|
||||
* Hazelcast 4.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class Hazelcast4SessionUpdateEntryProcessor implements EntryProcessor<String, MapSession, Object> {
|
||||
|
||||
private Instant lastAccessedTime;
|
||||
|
||||
private Duration maxInactiveInterval;
|
||||
|
||||
private Map<String, Object> delta;
|
||||
|
||||
@Override
|
||||
public Object process(Map.Entry<String, MapSession> entry) {
|
||||
MapSession value = entry.getValue();
|
||||
if (value == null) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
if (this.lastAccessedTime != null) {
|
||||
value.setLastAccessedTime(this.lastAccessedTime);
|
||||
}
|
||||
if (this.maxInactiveInterval != null) {
|
||||
value.setMaxInactiveInterval(this.maxInactiveInterval);
|
||||
}
|
||||
if (this.delta != null) {
|
||||
for (final Map.Entry<String, Object> attribute : this.delta.entrySet()) {
|
||||
if (attribute.getValue() != null) {
|
||||
value.setAttribute(attribute.getKey(), attribute.getValue());
|
||||
}
|
||||
else {
|
||||
value.removeAttribute(attribute.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
((ExtendedMapEntry<String, MapSession>) entry).setValue(value, value.getMaxInactiveInterval().getSeconds(),
|
||||
TimeUnit.SECONDS);
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
void setLastAccessedTime(Instant lastAccessedTime) {
|
||||
this.lastAccessedTime = lastAccessedTime;
|
||||
}
|
||||
|
||||
void setMaxInactiveInterval(Duration maxInactiveInterval) {
|
||||
this.maxInactiveInterval = maxInactiveInterval;
|
||||
}
|
||||
|
||||
void setDelta(Map<String, Object> delta) {
|
||||
this.delta = delta;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,470 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.hazelcast;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import com.hazelcast.map.EntryProcessor;
|
||||
import com.hazelcast.map.IMap;
|
||||
import com.hazelcast.map.listener.MapListener;
|
||||
import com.hazelcast.query.impl.predicates.EqualPredicate;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.hazelcast.Hazelcast4IndexedSessionRepository.HazelcastSession;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
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.verifyNoMoreInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link Hazelcast4IndexedSessionRepository}.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class Hazelcast4IndexedSessionRepositoryTests {
|
||||
|
||||
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
private HazelcastInstance hazelcastInstance = mock(HazelcastInstance.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private IMap<String, MapSession> sessions = mock(IMap.class);
|
||||
|
||||
private Hazelcast4IndexedSessionRepository repository;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
given(this.hazelcastInstance.<String, MapSession>getMap(anyString())).willReturn(this.sessions);
|
||||
this.repository = new Hazelcast4IndexedSessionRepository(this.hazelcastInstance);
|
||||
this.repository.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorNullHazelcastInstance() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new Hazelcast4IndexedSessionRepository(null))
|
||||
.withMessage("HazelcastInstance must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setSaveModeNull() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setSaveMode(null))
|
||||
.withMessage("saveMode must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSessionDefaultMaxInactiveInterval() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
|
||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(new MapSession().getMaxInactiveInterval());
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSessionCustomMaxInactiveInterval() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
int interval = 1;
|
||||
this.repository.setDefaultMaxInactiveInterval(interval);
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
|
||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofSeconds(interval));
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveNewFlushModeOnSave() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
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));
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveNewFlushModeImmediate() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
this.repository.setFlushMode(FlushMode.IMMEDIATE);
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||
eq(TimeUnit.SECONDS));
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveUpdatedAttributeFlushModeOnSave() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
session.setAttribute("testName", "testValue");
|
||||
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));
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveUpdatedAttributeFlushModeImmediate() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
this.repository.setFlushMode(FlushMode.IMMEDIATE);
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
session.setAttribute("testName", "testValue");
|
||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||
eq(TimeUnit.SECONDS));
|
||||
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
|
||||
|
||||
this.repository.save(session);
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeAttributeFlushModeOnSave() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
session.removeAttribute("testName");
|
||||
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));
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeAttributeFlushModeImmediate() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
this.repository.setFlushMode(FlushMode.IMMEDIATE);
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
session.removeAttribute("testName");
|
||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||
eq(TimeUnit.SECONDS));
|
||||
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
|
||||
|
||||
this.repository.save(session);
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveUpdatedLastAccessedTimeFlushModeOnSave() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
session.setLastAccessedTime(Instant.now());
|
||||
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));
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveUpdatedLastAccessedTimeFlushModeImmediate() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
this.repository.setFlushMode(FlushMode.IMMEDIATE);
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
session.setLastAccessedTime(Instant.now());
|
||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||
eq(TimeUnit.SECONDS));
|
||||
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
|
||||
|
||||
this.repository.save(session);
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveUpdatedMaxInactiveIntervalInSecondsFlushModeOnSave() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
session.setMaxInactiveInterval(Duration.ofSeconds(1));
|
||||
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));
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveUpdatedMaxInactiveIntervalInSecondsFlushModeImmediate() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
this.repository.setFlushMode(FlushMode.IMMEDIATE);
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
String sessionId = session.getId();
|
||||
session.setMaxInactiveInterval(Duration.ofSeconds(1));
|
||||
verify(this.sessions, times(1)).set(eq(sessionId), eq(session.getDelegate()), isA(Long.class),
|
||||
eq(TimeUnit.SECONDS));
|
||||
verify(this.sessions, times(1)).executeOnKey(eq(sessionId), any(EntryProcessor.class));
|
||||
|
||||
this.repository.save(session);
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveUnchangedFlushModeOnSave() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
this.repository.save(session);
|
||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||
eq(TimeUnit.SECONDS));
|
||||
|
||||
this.repository.save(session);
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveUnchangedFlushModeImmediate() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
this.repository.setFlushMode(FlushMode.IMMEDIATE);
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||
eq(TimeUnit.SECONDS));
|
||||
|
||||
this.repository.save(session);
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSessionNotFound() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
String sessionId = "testSessionId";
|
||||
|
||||
HazelcastSession session = this.repository.findById(sessionId);
|
||||
|
||||
assertThat(session).isNull();
|
||||
verify(this.sessions, times(1)).get(eq(sessionId));
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSessionExpired() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
MapSession expired = new MapSession();
|
||||
expired.setLastAccessedTime(Instant.now().minusSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS + 1));
|
||||
given(this.sessions.get(eq(expired.getId()))).willReturn(expired);
|
||||
|
||||
HazelcastSession session = this.repository.findById(expired.getId());
|
||||
|
||||
assertThat(session).isNull();
|
||||
verify(this.sessions, times(1)).get(eq(expired.getId()));
|
||||
verify(this.sessions, times(1)).remove(eq(expired.getId()));
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSessionFound() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
MapSession saved = new MapSession();
|
||||
saved.setAttribute("savedName", "savedValue");
|
||||
given(this.sessions.get(eq(saved.getId()))).willReturn(saved);
|
||||
|
||||
HazelcastSession session = this.repository.findById(saved.getId());
|
||||
|
||||
assertThat(session.getId()).isEqualTo(saved.getId());
|
||||
assertThat(session.<String>getAttribute("savedName")).isEqualTo("savedValue");
|
||||
verify(this.sessions, times(1)).get(eq(saved.getId()));
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void delete() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
String sessionId = "testSessionId";
|
||||
|
||||
this.repository.deleteById(sessionId);
|
||||
|
||||
verify(this.sessions, times(1)).remove(eq(sessionId));
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findByIndexNameAndIndexValueUnknownIndexName() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
String indexValue = "testIndexValue";
|
||||
|
||||
Map<String, HazelcastSession> sessions = this.repository.findByIndexNameAndIndexValue("testIndexName",
|
||||
indexValue);
|
||||
|
||||
assertThat(sessions).isEmpty();
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findByIndexNameAndIndexValuePrincipalIndexNameNotFound() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
String principal = "username";
|
||||
|
||||
Map<String, HazelcastSession> sessions = this.repository
|
||||
.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal);
|
||||
|
||||
assertThat(sessions).isEmpty();
|
||||
verify(this.sessions, times(1)).values(isA(EqualPredicate.class));
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findByIndexNameAndIndexValuePrincipalIndexNameFound() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
String principal = "username";
|
||||
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, "notused",
|
||||
AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||
List<MapSession> saved = new ArrayList<>(2);
|
||||
MapSession saved1 = new MapSession();
|
||||
saved1.setAttribute(SPRING_SECURITY_CONTEXT, authentication);
|
||||
saved.add(saved1);
|
||||
MapSession saved2 = new MapSession();
|
||||
saved2.setAttribute(SPRING_SECURITY_CONTEXT, authentication);
|
||||
saved.add(saved2);
|
||||
given(this.sessions.values(isA(EqualPredicate.class))).willReturn(saved);
|
||||
|
||||
Map<String, HazelcastSession> sessions = this.repository
|
||||
.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal);
|
||||
|
||||
assertThat(sessions).hasSize(2);
|
||||
verify(this.sessions, times(1)).values(isA(EqualPredicate.class));
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test // gh-1120
|
||||
void getAttributeNamesAndRemove() {
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
session.setAttribute("attribute1", "value1");
|
||||
session.setAttribute("attribute2", "value2");
|
||||
|
||||
for (String attributeName : session.getAttributeNames()) {
|
||||
session.removeAttribute(attributeName);
|
||||
}
|
||||
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void saveWithSaveModeOnSetAttribute() {
|
||||
verify(this.sessions).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
this.repository.setSaveMode(SaveMode.ON_SET_ATTRIBUTE);
|
||||
MapSession delegate = new MapSession();
|
||||
delegate.setAttribute("attribute1", "value1");
|
||||
delegate.setAttribute("attribute2", "value2");
|
||||
delegate.setAttribute("attribute3", "value3");
|
||||
HazelcastSession session = this.repository.new HazelcastSession(delegate, false);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
this.repository.save(session);
|
||||
ArgumentCaptor<Hazelcast4SessionUpdateEntryProcessor> captor = ArgumentCaptor
|
||||
.forClass(Hazelcast4SessionUpdateEntryProcessor.class);
|
||||
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
|
||||
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(1);
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void saveWithSaveModeOnGetAttribute() {
|
||||
verify(this.sessions).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
this.repository.setSaveMode(SaveMode.ON_GET_ATTRIBUTE);
|
||||
MapSession delegate = new MapSession();
|
||||
delegate.setAttribute("attribute1", "value1");
|
||||
delegate.setAttribute("attribute2", "value2");
|
||||
delegate.setAttribute("attribute3", "value3");
|
||||
HazelcastSession session = this.repository.new HazelcastSession(delegate, false);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
this.repository.save(session);
|
||||
ArgumentCaptor<Hazelcast4SessionUpdateEntryProcessor> captor = ArgumentCaptor
|
||||
.forClass(Hazelcast4SessionUpdateEntryProcessor.class);
|
||||
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
|
||||
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(2);
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void saveWithSaveModeAlways() {
|
||||
verify(this.sessions).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
this.repository.setSaveMode(SaveMode.ALWAYS);
|
||||
MapSession delegate = new MapSession();
|
||||
delegate.setAttribute("attribute1", "value1");
|
||||
delegate.setAttribute("attribute2", "value2");
|
||||
delegate.setAttribute("attribute3", "value3");
|
||||
HazelcastSession session = this.repository.new HazelcastSession(delegate, false);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
this.repository.save(session);
|
||||
ArgumentCaptor<Hazelcast4SessionUpdateEntryProcessor> captor = ArgumentCaptor
|
||||
.forClass(Hazelcast4SessionUpdateEntryProcessor.class);
|
||||
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
|
||||
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(3);
|
||||
verifyNoMoreInteractions(this.sessions);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,18 +1,11 @@
|
||||
apply plugin: 'io.spring.convention.spring-module'
|
||||
|
||||
configurations {
|
||||
hazelcast4
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(':spring-session-core')
|
||||
api "com.hazelcast:hazelcast"
|
||||
api "jakarta.annotation:jakarta.annotation-api"
|
||||
api "org.springframework:spring-context"
|
||||
|
||||
hazelcast4(project(path: ":hazelcast4", configuration: 'classesOnlyElements'))
|
||||
compileOnly(project(":hazelcast4"))
|
||||
|
||||
testImplementation "jakarta.servlet:jakarta.servlet-api"
|
||||
testImplementation "org.assertj:assertj-core"
|
||||
testImplementation "org.mockito:mockito-core"
|
||||
@@ -20,13 +13,7 @@ dependencies {
|
||||
testImplementation "org.springframework:spring-web"
|
||||
testImplementation "org.springframework.security:spring-security-core"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api"
|
||||
testRuntimeOnly project(':hazelcast4')
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
|
||||
|
||||
integrationTestCompile "com.hazelcast:hazelcast-client"
|
||||
integrationTestCompile "org.testcontainers:testcontainers"
|
||||
}
|
||||
|
||||
jar {
|
||||
from configurations.hazelcast4
|
||||
}
|
||||
|
||||
@@ -16,9 +16,12 @@
|
||||
|
||||
package org.springframework.session.hazelcast;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import com.hazelcast.core.IMap;
|
||||
import com.hazelcast.instance.HazelcastInstanceProxy;
|
||||
import com.hazelcast.instance.impl.HazelcastInstanceProxy;
|
||||
import com.hazelcast.map.IMap;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -225,4 +228,51 @@ abstract class AbstractHazelcastIndexedSessionRepositoryITests {
|
||||
this.repository.deleteById(session.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createAndUpdateSessionWhileKeepingOriginalTimeToLiveConfiguredOnRepository() {
|
||||
final Duration defaultSessionTimeout = Duration.ofSeconds(1800);
|
||||
|
||||
final IMap<String, MapSession> hazelcastMap = this.hazelcastInstance
|
||||
.getMap(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME);
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
String sessionId = session.getId();
|
||||
this.repository.save(session);
|
||||
|
||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(defaultSessionTimeout);
|
||||
assertThat(hazelcastMap.getEntryView(sessionId).getTtl()).isEqualTo(defaultSessionTimeout.toMillis());
|
||||
|
||||
session = this.repository.findById(sessionId);
|
||||
session.setLastAccessedTime(Instant.now());
|
||||
this.repository.save(session);
|
||||
|
||||
session = this.repository.findById(sessionId);
|
||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(defaultSessionTimeout);
|
||||
assertThat(hazelcastMap.getEntryView(sessionId).getTtl()).isEqualTo(defaultSessionTimeout.toMillis());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createAndUpdateSessionWhileKeepingTimeToLiveSetOnSession() {
|
||||
final Duration individualSessionTimeout = Duration.ofSeconds(23);
|
||||
|
||||
final IMap<String, MapSession> hazelcastMap = this.hazelcastInstance
|
||||
.getMap(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME);
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
session.setMaxInactiveInterval(individualSessionTimeout);
|
||||
String sessionId = session.getId();
|
||||
this.repository.save(session);
|
||||
|
||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(individualSessionTimeout);
|
||||
assertThat(hazelcastMap.getEntryView(sessionId).getTtl()).isEqualTo(individualSessionTimeout.toMillis());
|
||||
|
||||
session = this.repository.findById(sessionId);
|
||||
session.setAttribute("attribute", "value");
|
||||
this.repository.save(session);
|
||||
|
||||
session = this.repository.findById(sessionId);
|
||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(individualSessionTimeout);
|
||||
assertThat(hazelcastMap.getEntryView(sessionId).getTtl()).isEqualTo(individualSessionTimeout.toMillis());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.hazelcast.client.config.ClientConfig;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.utility.MountableFile;
|
||||
@@ -44,11 +45,13 @@ import org.springframework.test.context.web.WebAppConfiguration;
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
@Disabled("Re-enable when Hazelcast image uses JDK 17")
|
||||
class ClientServerHazelcastIndexedSessionRepositoryITests extends AbstractHazelcastIndexedSessionRepositoryITests {
|
||||
|
||||
private static GenericContainer container = new GenericContainer<>("hazelcast/hazelcast:3.12.12")
|
||||
private static GenericContainer container = new GenericContainer<>("hazelcast/hazelcast:5.0.2")
|
||||
.withExposedPorts(5701).withCopyFileToContainer(MountableFile.forClasspathResource("/hazelcast-server.xml"),
|
||||
"/opt/hazelcast/hazelcast.xml");
|
||||
"/opt/hazelcast/hazelcast.xml")
|
||||
.withEnv("HAZELCAST_CONFIG", "hazelcast.xml");
|
||||
|
||||
@BeforeAll
|
||||
static void setUpClass() {
|
||||
|
||||
@@ -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.
|
||||
@@ -16,9 +16,10 @@
|
||||
|
||||
package org.springframework.session.hazelcast;
|
||||
|
||||
import com.hazelcast.config.AttributeConfig;
|
||||
import com.hazelcast.config.Config;
|
||||
import com.hazelcast.config.MapAttributeConfig;
|
||||
import com.hazelcast.config.MapIndexConfig;
|
||||
import com.hazelcast.config.IndexConfig;
|
||||
import com.hazelcast.config.IndexType;
|
||||
import com.hazelcast.config.NetworkConfig;
|
||||
import com.hazelcast.config.SerializerConfig;
|
||||
import com.hazelcast.core.Hazelcast;
|
||||
@@ -45,12 +46,12 @@ final class HazelcastITestUtils {
|
||||
NetworkConfig networkConfig = config.getNetworkConfig();
|
||||
networkConfig.setPort(0);
|
||||
networkConfig.getJoin().getMulticastConfig().setEnabled(false);
|
||||
MapAttributeConfig attributeConfig = new MapAttributeConfig()
|
||||
AttributeConfig attributeConfig = new AttributeConfig()
|
||||
.setName(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
|
||||
.setExtractor(PrincipalNameExtractor.class.getName());
|
||||
.setExtractorClassName(PrincipalNameExtractor.class.getName());
|
||||
config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME)
|
||||
.addMapAttributeConfig(attributeConfig).addMapIndexConfig(
|
||||
new MapIndexConfig(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));
|
||||
.addAttributeConfig(attributeConfig).addIndexConfig(
|
||||
new IndexConfig(IndexType.HASH, HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE));
|
||||
SerializerConfig serializerConfig = new SerializerConfig();
|
||||
serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class);
|
||||
config.getSerializationConfig().addSerializerConfig(serializerConfig);
|
||||
|
||||
@@ -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.13.xsd">
|
||||
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-5.0.xsd">
|
||||
|
||||
<network>
|
||||
<join>
|
||||
|
||||
@@ -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.
|
||||
@@ -23,16 +23,18 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
|
||||
import com.hazelcast.core.EntryEvent;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import com.hazelcast.core.IMap;
|
||||
import com.hazelcast.map.IMap;
|
||||
import com.hazelcast.map.listener.EntryAddedListener;
|
||||
import com.hazelcast.map.listener.EntryEvictedListener;
|
||||
import com.hazelcast.map.listener.EntryExpiredListener;
|
||||
import com.hazelcast.map.listener.EntryRemovedListener;
|
||||
import com.hazelcast.query.Predicates;
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -52,7 +54,6 @@ import org.springframework.session.events.SessionCreatedEvent;
|
||||
import org.springframework.session.events.SessionDeletedEvent;
|
||||
import org.springframework.session.events.SessionExpiredEvent;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.session.SessionRepository} implementation that stores
|
||||
@@ -80,16 +81,17 @@ import org.springframework.util.ClassUtils;
|
||||
* programmatic Hazelcast Configuration:
|
||||
*
|
||||
* <pre class="code">
|
||||
* MapAttributeConfig attributeConfig = new MapAttributeConfig()
|
||||
* AttributeConfig attributeConfig = new AttributeConfig()
|
||||
* .setName(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
|
||||
* .setExtractor(PrincipalNameExtractor.class.getName());
|
||||
* .setExtractorClassName(rincipalNameExtractor.class.getName());
|
||||
*
|
||||
* Config config = new Config();
|
||||
*
|
||||
* config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME)
|
||||
* .addMapAttributeConfig(attributeConfig)
|
||||
* .addMapIndexConfig(new MapIndexConfig(
|
||||
* HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));
|
||||
* .addAttributeConfig(attributeConfig)
|
||||
* .addIndexConfig(new IndexConfig(
|
||||
* IndexType.HASH,
|
||||
* HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE));
|
||||
*
|
||||
* Hazelcast.newHazelcastInstance(config);
|
||||
* </pre>
|
||||
@@ -108,12 +110,13 @@ import org.springframework.util.ClassUtils;
|
||||
* @author Tommy Ludwig
|
||||
* @author Mark Anderson
|
||||
* @author Aleksandar Stojsavljevic
|
||||
* @author Eleftheria Stein
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public class HazelcastIndexedSessionRepository
|
||||
implements FindByIndexNameSessionRepository<HazelcastIndexedSessionRepository.HazelcastSession>,
|
||||
EntryAddedListener<String, MapSession>, EntryEvictedListener<String, MapSession>,
|
||||
EntryRemovedListener<String, MapSession> {
|
||||
EntryRemovedListener<String, MapSession>, EntryExpiredListener<String, MapSession> {
|
||||
|
||||
/**
|
||||
* The default name of map used by Spring Session to store sessions.
|
||||
@@ -127,8 +130,6 @@ public class HazelcastIndexedSessionRepository
|
||||
|
||||
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
private static final boolean SUPPORTS_SET_TTL = ClassUtils.hasAtLeastOneMethodWithName(IMap.class, "setTtl");
|
||||
|
||||
private static final Log logger = LogFactory.getLog(HazelcastIndexedSessionRepository.class);
|
||||
|
||||
private final HazelcastInstance hazelcastInstance;
|
||||
@@ -152,7 +153,7 @@ public class HazelcastIndexedSessionRepository
|
||||
|
||||
private IMap<String, MapSession> sessions;
|
||||
|
||||
private String sessionListenerId;
|
||||
private UUID sessionListenerId;
|
||||
|
||||
/**
|
||||
* Create a new {@link HazelcastIndexedSessionRepository} instance.
|
||||
@@ -261,9 +262,6 @@ public class HazelcastIndexedSessionRepository
|
||||
entryProcessor.setLastAccessedTime(session.getLastAccessedTime());
|
||||
}
|
||||
if (session.maxInactiveIntervalChanged) {
|
||||
if (SUPPORTS_SET_TTL) {
|
||||
updateTtl(session);
|
||||
}
|
||||
entryProcessor.setMaxInactiveInterval(session.getMaxInactiveInterval());
|
||||
}
|
||||
if (!session.delta.isEmpty()) {
|
||||
@@ -274,10 +272,6 @@ public class HazelcastIndexedSessionRepository
|
||||
session.clearChangeFlags();
|
||||
}
|
||||
|
||||
private void updateTtl(HazelcastSession session) {
|
||||
this.sessions.setTtl(session.getId(), session.getMaxInactiveInterval().getSeconds(), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HazelcastSession findById(String id) {
|
||||
MapSession saved = this.sessions.get(id);
|
||||
@@ -339,6 +333,14 @@ public class HazelcastIndexedSessionRepository
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entryExpired(EntryEvent<String, MapSession> event) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Session expired with id: " + event.getOldValue().getId());
|
||||
}
|
||||
this.eventPublisher.publishEvent(new SessionExpiredEvent(this, event.getOldValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom implementation of {@link Session} that uses a {@link MapSession} as the
|
||||
* basis for its mapping. It keeps track if changes have been made since last save.
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.springframework.session.MapSession;
|
||||
|
||||
/**
|
||||
* A {@link com.hazelcast.nio.serialization.Serializer} implementation that handles the
|
||||
* (de)serialization of {@link MapSession} stored on {@link com.hazelcast.core.IMap}.
|
||||
* (de)serialization of {@link MapSession} stored on {@link com.hazelcast.map.IMap}.
|
||||
*
|
||||
* <p>
|
||||
* The use of this serializer is optional and provides faster serialization of sessions.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user