Compare commits

...

33 Commits

Author SHA1 Message Date
Vedran Pavic
0c2f756533 Release 2.1.0.M2 2018-08-21 06:33:12 +02:00
Vedran Pavic
de16c304ea Add support using JDBC repository without transactions
Closes gh-1046
2018-08-21 06:05:52 +02:00
Vedran Pavic
3ce3962ebd Upgrade Spring Security to 5.1.0.RC1
Closes gh-1144
2018-08-20 20:41:38 +02:00
Vedran Pavic
3c4a309a0f Upgrade Spring Data to Lovelace-RC2
Closes gh-1143
2018-08-20 11:51:28 +02:00
Vedran Pavic
38de434158 Add support for @SpringSessionRedisOperations in reactive Redis repository
Closes gh-1164
2018-08-20 07:23:23 +02:00
Vedran Pavic
7ef0faf259 Update integration tests 2018-08-20 06:31:10 +02:00
Vedran Pavic
f65cee0a7b Upgrade dependencies 2018-08-20 06:31:10 +02:00
Vedran Pavic
a2cd1e37fa Add support for configuring custom RedisSerializer in reactive config
Closes gh-1149
2018-08-20 06:31:09 +02:00
Vedran Pavic
b768042506 Upgrade Spring Framework to 5.1.0.RC2
Closes gh-1141
2018-08-17 12:21:48 +02:00
Vedran Pavic
3140bd06b2 Add FindByIndexNameSessionRepository#findByPrincipalName default method
Closes gh-1158
2018-08-17 08:04:09 +02:00
Vedran Pavic
172c18d666 Upgrade Reactor to Californium-M2
Closes gh-1142
2018-08-16 07:13:59 +02:00
Vedran Pavic
7fdf2876b2 Polish 2018-08-13 07:44:47 +02:00
Vedran Pavic
87c2e53b5a Insert new attributes conditionally in JDBC repo
At present, the insert of new attributes in JdbcOperationsSessionRepository is done unconditionally. This can cause data integrity violation errors with concurrent requests, where one request attempts to add new session attribute while the other, concurrent request, deletes the session.

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

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

99
Jenkinsfile vendored
View File

@@ -1,9 +1,9 @@
def projectProperties = [
[$class: 'BuildDiscarderProperty',
strategy: [$class: 'LogRotator', numToKeepStr: '5']],
pipelineTriggers([cron('@daily')])
]
properties(projectProperties)
properties([
buildDiscarder(logRotator(numToKeepStr: '10')),
pipelineTriggers([
cron('@daily')
]),
])
def SUCCESS = hudson.model.Result.SUCCESS.toString()
currentBuild.result = SUCCESS
@@ -11,49 +11,44 @@ currentBuild.result = SUCCESS
try {
parallel check: {
stage('Check') {
node {
checkout scm
try {
sh "./gradlew clean check --refresh-dependencies --no-daemon"
} catch(Exception e) {
currentBuild.result = 'FAILED: check'
throw e
} finally {
junit '**/build/*-results/*.xml'
}
}
}
},
springio: {
stage('Spring IO') {
node {
checkout scm
try {
sh "./gradlew clean springIoCheck -PplatformVersion=Cairo-BUILD-SNAPSHOT -PexcludeProjects='**/samples/**' --refresh-dependencies --no-daemon --stacktrace"
} catch(Exception e) {
currentBuild.result = 'FAILED: springio'
throw e
} finally {
junit '**/build/spring-io*-results/*.xml'
timeout(time: 30, unit: 'MINUTES') {
node {
checkout scm
try {
sh './gradlew clean check --no-daemon --refresh-dependencies'
}
catch (e) {
currentBuild.result = 'FAILED: check'
throw e
}
finally {
junit '**/build/*-results/*.xml'
}
}
}
}
}
if(currentBuild.result == 'SUCCESS') {
if (currentBuild.result == 'SUCCESS') {
parallel artifacts: {
stage('Deploy Artifacts') {
node {
checkout scm
withCredentials([file(credentialsId: 'spring-signing-secring.gpg', variable: 'SIGNING_KEYRING_FILE')]) {
withCredentials([string(credentialsId: 'spring-gpg-passphrase', variable: 'SIGNING_PASSWORD')]) {
withCredentials([usernamePassword(credentialsId: 'oss-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) {
withCredentials([usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD')]) {
sh "./gradlew deployArtifacts finalizeDeployArtifacts -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password='$SIGNING_PASSWORD' -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
try {
withCredentials([file(credentialsId: 'spring-signing-secring.gpg', variable: 'SIGNING_KEYRING_FILE')]) {
withCredentials([string(credentialsId: 'spring-gpg-passphrase', variable: 'SIGNING_PASSWORD')]) {
withCredentials([usernamePassword(credentialsId: 'oss-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) {
withCredentials([usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD')]) {
sh './gradlew deployArtifacts finalizeDeployArtifacts --stacktrace --no-daemon --refresh-dependencies -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password=$SIGNING_PASSWORD -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD'
}
}
}
}
}
catch (e) {
currentBuild.result = 'FAILED: artifacts'
throw e
}
}
}
},
@@ -61,32 +56,38 @@ try {
stage('Deploy Docs') {
node {
checkout scm
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace"
try {
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
sh './gradlew deployDocs --stacktrace --no-daemon --refresh-dependencies -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME'
}
}
catch (e) {
currentBuild.result = 'FAILED: docs'
throw e
}
}
}
}
}
} finally {
}
finally {
def buildStatus = currentBuild.result
def buildNotSuccess = !SUCCESS.equals(buildStatus)
def buildNotSuccess = !SUCCESS.equals(buildStatus)
def lastBuildNotSuccess = !SUCCESS.equals(currentBuild.previousBuild?.result)
if(buildNotSuccess || lastBuildNotSuccess) {
stage('Notifiy') {
if (buildNotSuccess || lastBuildNotSuccess) {
stage('Notify') {
node {
final def RECIPIENTS = [[$class: 'DevelopersRecipientProvider'], [$class: 'RequesterRecipientProvider']]
def subject = "${buildStatus}: Build ${env.JOB_NAME} ${env.BUILD_NUMBER} status is now ${buildStatus}"
def details = """The build status changed to ${buildStatus}. For details see ${env.BUILD_URL}"""
def details = "The build status changed to ${buildStatus}. For details see ${env.BUILD_URL}"
emailext (
subject: subject,
body: details,
recipientProviders: RECIPIENTS,
to: "$SPRING_SESSION_TEAM_EMAILS"
emailext(
subject: subject,
body: details,
recipientProviders: RECIPIENTS,
to: "$SPRING_SESSION_TEAM_EMAILS"
)
}
}

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -52,9 +52,7 @@ public class FindByIndexNameSessionRepositoryTests {
// tag::findby-username[]
String username = "username";
Map<String, Session> sessionIdToSession = this.sessionRepository
.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
username);
.findByPrincipalName(username);
// end::findby-username[]
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,6 +43,7 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMv
/**
* @author rwinch
* @author Vedran Pavic
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = RememberMeSecurityConfiguration.class)
@@ -86,5 +87,6 @@ public class RememberMeSecurityConfigurationTests<T extends Session> {
.isEqualTo(Duration.ofDays(30));
}
}
// end::class[]

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,6 +43,7 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMv
/**
* @author rwinch
* @author Vedran Pavic
*/
@RunWith(SpringRunner.class)
@ContextConfiguration
@@ -86,5 +87,6 @@ public class RememberMeSecurityConfigurationXmlTests<T extends Session> {
.isEqualTo(Duration.ofDays(30));
}
}
// end::class[]

View File

@@ -1,2 +1,2 @@
springBootVersion=2.0.3.RELEASE
version=2.0.5.BUILD-SNAPSHOT
springBootVersion=2.1.0.M1
version=2.1.0.M2

View File

@@ -1,31 +1,32 @@
dependencyManagement {
imports {
mavenBom 'com.fasterxml.jackson:jackson-bom:2.9.6'
mavenBom 'io.projectreactor:reactor-bom:Bismuth-SR10'
mavenBom 'org.springframework:spring-framework-bom:5.0.7.RELEASE'
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-SR8'
mavenBom 'org.springframework.security:spring-security-bom:5.0.6.RELEASE'
mavenBom 'org.testcontainers:testcontainers-bom:1.8.1'
mavenBom 'io.projectreactor:reactor-bom:Californium-M2'
mavenBom 'org.springframework:spring-framework-bom:5.1.0.RC2'
mavenBom 'org.springframework.data:spring-data-releasetrain:Lovelace-RC2'
mavenBom 'org.springframework.security:spring-security-bom:5.1.0.RC1'
mavenBom 'org.testcontainers:testcontainers-bom:1.8.3'
}
dependencies {
dependencySet(group: 'com.hazelcast', version: '3.9.4') {
dependencySet(group: 'com.hazelcast', version: '3.10.4') {
entry 'hazelcast'
entry 'hazelcast-client'
}
dependency 'com.h2database:h2:1.4.197'
dependency 'com.microsoft.sqlserver:mssql-jdbc:6.4.0.jre8'
dependency 'com.microsoft.sqlserver:mssql-jdbc:7.0.0.jre8'
dependency 'edu.umd.cs.mtc:multithreadedtc:1.01'
dependency 'io.lettuce:lettuce-core:5.0.4.RELEASE'
dependency 'javax.servlet:javax.servlet-api:3.1.0'
dependency 'io.lettuce:lettuce-core:5.1.0.M1'
dependency 'javax.annotation:javax.annotation-api:1.3.2'
dependency 'javax.servlet:javax.servlet-api:4.0.1'
dependency 'junit:junit:4.12'
dependency 'mysql:mysql-connector-java:8.0.11'
dependency 'mysql:mysql-connector-java:8.0.12'
dependency 'org.apache.derby:derby:10.14.2.0'
dependency 'org.assertj:assertj-core:3.10.0'
dependency 'org.assertj:assertj-core:3.11.0'
dependency 'org.hsqldb:hsqldb:2.4.1'
dependency 'org.mariadb.jdbc:mariadb-java-client:2.2.5'
dependency 'org.mockito:mockito-core:2.18.3'
dependency 'org.postgresql:postgresql:42.2.2'
dependency 'org.mariadb.jdbc:mariadb-java-client:2.2.6'
dependency 'org.mockito:mockito-core:2.21.0'
dependency 'org.postgresql:postgresql:42.2.4'
}
}

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,10 +44,7 @@ public class IndexController {
@RequestMapping("/")
public String index(Principal principal, Model model) {
Collection<? extends Session> usersSessions = this.sessions
.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
principal.getName())
.values();
.findByPrincipalName(principal.getName()).values();
model.addAttribute("sessions", usersSessions);
return "index";
}
@@ -56,9 +53,8 @@ public class IndexController {
@RequestMapping(value = "/sessions/{sessionIdToDelete}", method = RequestMethod.DELETE)
public String removeSession(Principal principal,
@PathVariable String sessionIdToDelete) {
Set<String> usersSessionIds = this.sessions.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
principal.getName()).keySet();
Set<String> usersSessionIds = this.sessions
.findByPrincipalName(principal.getName()).keySet();
if (usersSessionIds.contains(sessionIdToDelete)) {
this.sessions.deleteById(sessionIdToDelete);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,6 +34,7 @@ import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDr
/**
* @author Eddú Meléndez
* @author Vedran Pavic
*/
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,7 +36,7 @@ public class LoginPage extends BasePage {
}
public void assertAt() {
assertThat(getDriver().getTitle()).isEqualTo("Login Page");
assertThat(getDriver().getTitle()).isEqualTo("Please sign in");
}
public Form form() {
@@ -51,7 +51,7 @@ public class LoginPage extends BasePage {
@FindBy(name = "password")
private WebElement password;
@FindBy(name = "submit")
@FindBy(tagName = "button")
private WebElement button;
public Form(SearchContext context) {

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,7 +36,7 @@ public class LoginPage extends BasePage {
}
public void assertAt() {
assertThat(getDriver().getTitle()).isEqualTo("Login Page");
assertThat(getDriver().getTitle()).isEqualTo("Please sign in");
}
public Form form() {
@@ -51,7 +51,7 @@ public class LoginPage extends BasePage {
@FindBy(name = "password")
private WebElement password;
@FindBy(name = "submit")
@FindBy(tagName = "button")
private WebElement button;
public Form(SearchContext context) {

View File

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

View File

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

View File

@@ -1,3 +1,5 @@
ext['spring.version'] = '5.1.0.RC2'
dependencyManagement {
dependencies {
dependency 'ch.qos.logback:logback-classic:1.2.3'
@@ -5,7 +7,7 @@ dependencyManagement {
dependency 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.1'
dependency 'javax.servlet.jsp:javax.servlet.jsp-api:2.3.2-b02'
dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5'
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.29.3'
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.32.0'
dependency 'org.slf4j:jcl-over-slf4j:1.7.25'
dependency 'org.slf4j:log4j-over-slf4j:1.7.25'
dependency 'org.webjars:bootstrap:2.3.2'

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,7 +36,7 @@ public class LoginPage extends BasePage {
}
public void assertAt() {
assertThat(getDriver().getTitle()).isEqualTo("Login Page");
assertThat(getDriver().getTitle()).isEqualTo("Please sign in");
}
public Form form() {
@@ -51,7 +51,7 @@ public class LoginPage extends BasePage {
@FindBy(name = "password")
private WebElement password;
@FindBy(name = "submit")
@FindBy(tagName = "button")
private WebElement button;
public Form(SearchContext context) {

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,7 +34,7 @@ public class LoginPage extends BasePage {
@FindBy(name = "password")
private WebElement password;
@FindBy(css = "input[type='submit']")
@FindBy(tagName = "button")
private WebElement button;
public LoginPage(WebDriver driver) {
@@ -47,7 +47,7 @@ public class LoginPage extends BasePage {
}
public void assertAt() {
assertThat(getDriver().getTitle()).isEqualTo("Login Page");
assertThat(getDriver().getTitle()).isEqualTo("Please sign in");
}
public HomePage login(String user, String password) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -73,7 +73,6 @@ public class MapSessionRepository implements SessionRepository<MapSession> {
public void save(MapSession session) {
if (!session.getId().equals(session.getOriginalId())) {
this.sessions.remove(session.getOriginalId());
session.setOriginalId(session.getId());
}
this.sessions.put(session.getId(), new MapSession(session));
}

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -65,9 +65,8 @@ public class SpringSessionBackedSessionRegistry<S extends Session>
@Override
public List<SessionInformation> getAllSessions(Object principal,
boolean includeExpiredSessions) {
Collection<S> sessions = this.sessionRepository.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
name(principal)).values();
Collection<S> sessions = this.sessionRepository
.findByPrincipalName(name(principal)).values();
List<SessionInformation> infos = new ArrayList<>();
for (S session : sessions) {
if (includeExpiredSessions || !Boolean.TRUE.equals(session

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -162,9 +162,8 @@ public class SpringSessionBackedSessionRegistryTest {
Map<String, Session> sessions = new LinkedHashMap<>();
sessions.put(session1.getId(), session1);
sessions.put(session2.getId(), session2);
when(this.sessionRepository.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, USER_NAME))
.thenReturn(sessions);
when(this.sessionRepository.findByPrincipalName(USER_NAME))
.thenReturn(sessions);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

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

View File

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

View File

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

View File

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

View File

@@ -118,6 +118,10 @@ public class ReactiveRedisOperationsSessionRepository implements
this.redisFlushMode = redisFlushMode;
}
public ReactiveRedisOperations<String, Object> getSessionRedisOperations() {
return this.sessionRedisOperations;
}
@Override
public Mono<RedisSession> createSession() {
return Mono.defer(() -> {

View File

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

View File

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

View File

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

View File

@@ -743,6 +743,31 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(session.<String>getAttribute("testName")).isEqualTo("testValue2");
}
@Test // gh-1031
public void saveDeleted() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
this.repository.save(session);
session = this.repository.findById(session.getId());
this.repository.deleteById(session.getId());
session.setLastAccessedTime(Instant.now());
this.repository.save(session);
assertThat(this.repository.findById(session.getId())).isNull();
}
@Test // gh-1031
public void saveDeletedAddAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
this.repository.save(session);
session = this.repository.findById(session.getId());
this.repository.deleteById(session.getId());
session.setLastAccessedTime(Instant.now());
session.setAttribute("testName", "testValue1");
this.repository.save(session);
assertThat(this.repository.findById(session.getId())).isNull();
}
private String getSecurityName() {
return this.context.getAuthentication().getName();
}

View File

@@ -86,7 +86,7 @@ public class MariaDb10JdbcOperationsSessionRepositoryITests
private static class MariaDb10Container extends MariaDBContainer<MariaDb10Container> {
MariaDb10Container() {
super("mariadb:10.3.8");
super("mariadb:10.3.9");
}
@Override

View File

@@ -86,7 +86,7 @@ public class MariaDb5JdbcOperationsSessionRepositoryITests
private static class MariaDb5Container extends MariaDBContainer<MariaDb5Container> {
MariaDb5Container() {
super("mariadb:5.5.60");
super("mariadb:5.5.61");
}
@Override

View File

@@ -85,7 +85,7 @@ public class MySql5JdbcOperationsSessionRepositoryITests
private static class MySql5Container extends MySQLContainer<MySql5Container> {
MySql5Container() {
super("mysql:5.7.22");
super("mysql:5.7.23");
}
@Override

View File

@@ -85,7 +85,7 @@ public class MySql8JdbcOperationsSessionRepositoryITests
private static class MySql8Container extends MySQLContainer<MySql8Container> {
MySql8Container() {
super("mysql:8.0.11");
super("mysql:8.0.12");
}
@Override

View File

@@ -86,7 +86,7 @@ public class PostgreSql10JdbcOperationsSessionRepositoryITests
extends PostgreSQLContainer<PostgreSql10Container> {
PostgreSql10Container() {
super("postgres:10.4");
super("postgres:10.5");
}
}

View File

@@ -86,7 +86,7 @@ public class PostgreSql9JdbcOperationsSessionRepositoryITests
extends PostgreSQLContainer<PostgreSql9Container> {
PostgreSql9Container() {
super("postgres:9.6.9");
super("postgres:9.6.10");
}
}

View File

@@ -86,7 +86,7 @@ public class SqlServerJdbcOperationsSessionRepositoryITests
extends MSSQLServerContainer<SqlServer2007Container> {
SqlServer2007Container() {
super("microsoft/mssql-server-linux:2017-CU8");
super("microsoft/mssql-server-linux:2017-CU9");
withStartupTimeoutSeconds(240);
withConnectTimeoutSeconds(240);
}

View File

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

View File

@@ -52,7 +52,9 @@ import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionOperations;
import org.springframework.transaction.support.TransactionTemplate;
@@ -144,7 +146,9 @@ public class JdbcOperationsSessionRepository implements
private static final String CREATE_SESSION_ATTRIBUTE_QUERY =
"INSERT INTO %TABLE_NAME%_ATTRIBUTES(SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) " +
"VALUES (?, ?, ?)";
"SELECT PRIMARY_ID, ?, ? " +
"FROM %TABLE_NAME% " +
"WHERE SESSION_ID = ?";
private static final String GET_SESSION_QUERY =
"SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES " +
@@ -187,10 +191,17 @@ public class JdbcOperationsSessionRepository implements
private final JdbcOperations jdbcOperations;
private final TransactionOperations transactionOperations;
private final ResultSetExtractor<List<JdbcSession>> extractor = new SessionResultSetExtractor();
private TransactionOperations transactionOperations = new TransactionOperations() {
@Override
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
return action.doInTransaction(null);
}
};
/**
* The name of database table used by Spring Session to store sessions.
*/
@@ -227,14 +238,29 @@ public class JdbcOperationsSessionRepository implements
/**
* Create a new {@link JdbcOperationsSessionRepository} instance which uses the
* provided {@link JdbcOperations} to manage sessions.
* <p>
* The created instance will execute all data access operations in a transaction with
* propagation level of {@link TransactionDefinition#PROPAGATION_REQUIRES_NEW}.
* @param jdbcOperations the {@link JdbcOperations} to use
* @param transactionManager the {@link PlatformTransactionManager} to use
*/
public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations,
PlatformTransactionManager transactionManager) {
this(jdbcOperations);
Assert.notNull(transactionManager, "TransactionManager must not be null");
this.transactionOperations = createTransactionTemplate(transactionManager);
}
/**
* Create a new {@link JdbcOperationsSessionRepository} instance which uses the
* provided {@link JdbcOperations} to manage sessions.
* <p>
* The created instance will not execute data access operations in a transaction.
* @param jdbcOperations the {@link JdbcOperations} to use
*/
public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations) {
Assert.notNull(jdbcOperations, "JdbcOperations must not be null");
this.jdbcOperations = jdbcOperations;
this.transactionOperations = createTransactionTemplate(transactionManager);
this.conversionService = createDefaultConversionService();
prepareQueries();
}
@@ -381,9 +407,9 @@ public class JdbcOperationsSessionRepository implements
ps.setLong(6, session.getExpiryTime().toEpochMilli());
ps.setString(7, session.getPrincipalName());
});
if (!session.getAttributeNames().isEmpty()) {
final List<String> attributeNames = new ArrayList<>(session.getAttributeNames());
insertSessionAttributes(session, attributeNames);
Set<String> attributeNames = session.getAttributeNames();
if (!attributeNames.isEmpty()) {
insertSessionAttributes(session, new ArrayList<>(attributeNames));
}
}
@@ -410,17 +436,23 @@ public class JdbcOperationsSessionRepository implements
.filter((entry) -> entry.getValue() == DeltaValue.ADDED)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
insertSessionAttributes(session, addedAttributeNames);
if (!addedAttributeNames.isEmpty()) {
insertSessionAttributes(session, addedAttributeNames);
}
List<String> updatedAttributeNames = session.delta.entrySet().stream()
.filter((entry) -> entry.getValue() == DeltaValue.UPDATED)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
updateSessionAttributes(session, updatedAttributeNames);
if (!updatedAttributeNames.isEmpty()) {
updateSessionAttributes(session, updatedAttributeNames);
}
List<String> removedAttributeNames = session.delta.entrySet().stream()
.filter((entry) -> entry.getValue() == DeltaValue.REMOVED)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
deleteSessionAttributes(session, removedAttributeNames);
if (!removedAttributeNames.isEmpty()) {
deleteSessionAttributes(session, removedAttributeNames);
}
}
});
@@ -490,18 +522,16 @@ public class JdbcOperationsSessionRepository implements
}
private void insertSessionAttributes(JdbcSession session, List<String> attributeNames) {
if (attributeNames == null || attributeNames.isEmpty()) {
return;
}
Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
if (attributeNames.size() > 1) {
this.jdbcOperations.batchUpdate(this.createSessionAttributeQuery, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
ps.setString(1, session.primaryKey);
ps.setString(2, attributeName);
serialize(ps, 3, session.getAttribute(attributeName));
ps.setString(1, attributeName);
serialize(ps, 2, session.getAttribute(attributeName));
ps.setString(3, session.getId());
}
@Override
@@ -514,17 +544,15 @@ public class JdbcOperationsSessionRepository implements
else {
this.jdbcOperations.update(this.createSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
ps.setString(1, session.primaryKey);
ps.setString(2, attributeName);
serialize(ps, 3, session.getAttribute(attributeName));
ps.setString(1, attributeName);
serialize(ps, 2, session.getAttribute(attributeName));
ps.setString(3, session.getId());
});
}
}
private void updateSessionAttributes(JdbcSession session, List<String> attributeNames) {
if (attributeNames == null || attributeNames.isEmpty()) {
return;
}
Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
if (attributeNames.size() > 1) {
this.jdbcOperations.batchUpdate(this.updateSessionAttributeQuery, new BatchPreparedStatementSetter() {
@@ -554,9 +582,7 @@ public class JdbcOperationsSessionRepository implements
}
private void deleteSessionAttributes(JdbcSession session, List<String> attributeNames) {
if (attributeNames == null || attributeNames.isEmpty()) {
return;
}
Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
if (attributeNames.size() > 1) {
this.jdbcOperations.batchUpdate(this.deleteSessionAttributeQuery, new BatchPreparedStatementSetter() {

View File

@@ -42,7 +42,10 @@ import org.springframework.transaction.TransactionDefinition;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.endsWith;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.startsWith;
@@ -88,7 +91,7 @@ public class JdbcOperationsSessionRepositoryTests {
assertThatThrownBy(
() -> new JdbcOperationsSessionRepository(this.jdbcOperations, null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Property 'transactionManager' is required");
.hasMessage("TransactionManager must not be null");
}
@Test
@@ -688,6 +691,89 @@ public class JdbcOperationsSessionRepositoryTests {
assertThat(session.getAttributeNames()).isEmpty();
}
@Test
public void saveNewWithoutTransaction() {
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.createSession();
this.repository.save(session);
verify(this.jdbcOperations, times(1)).update(
startsWith("INSERT INTO SPRING_SESSION"),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
public void saveUpdatedWithoutTransaction() {
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(
"primaryKey", new MapSession());
session.setLastAccessedTime(Instant.now());
this.repository.save(session);
verify(this.jdbcOperations, times(1)).update(startsWith("UPDATE SPRING_SESSION"),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
@SuppressWarnings("unchecked")
public void findByIdWithoutTransaction() {
given(this.jdbcOperations.query(anyString(), any(PreparedStatementSetter.class),
any(ResultSetExtractor.class))).willReturn(Collections.emptyList());
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
this.repository.findById("testSessionId");
verify(this.jdbcOperations, times(1)).query(endsWith("WHERE S.SESSION_ID = ?"),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class));
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
public void deleteByIdWithoutTransaction() {
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
this.repository.deleteById("testSessionId");
verify(this.jdbcOperations, times(1)).update(
eq("DELETE FROM SPRING_SESSION WHERE SESSION_ID = ?"), anyString());
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
@SuppressWarnings("unchecked")
public void findByIndexNameAndIndexValueWithoutTransaction() {
given(this.jdbcOperations.query(anyString(), any(PreparedStatementSetter.class),
any(ResultSetExtractor.class))).willReturn(Collections.emptyList());
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
this.repository.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
"testIndexValue");
verify(this.jdbcOperations, times(1)).query(
endsWith("WHERE S.PRINCIPAL_NAME = ?"),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class));
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
public void cleanUpExpiredSessionsWithoutTransaction() {
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
this.repository.cleanUpExpiredSessions();
verify(this.jdbcOperations, times(1)).update(
eq("DELETE FROM SPRING_SESSION WHERE EXPIRY_TIME < ?"), anyLong());
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
private void assertPropagationRequiresNew() {
ArgumentCaptor<TransactionDefinition> argument =
ArgumentCaptor.forClass(TransactionDefinition.class);