Compare commits

...

28 Commits

Author SHA1 Message Date
Rob Winch
88071796a2 Release 2.1.7.RELEASE 2019-06-14 16:49:34 -05:00
Vedran Pavic
76bc9563bb Upgrade Spring Data to Lovelace-SR9
Resolves: #1443
2019-06-14 15:42:41 +02:00
Vedran Pavic
819b341a9f Update integration tests 2019-06-13 18:43:30 +02:00
Vedran Pavic
3b0be07c8e Upgrade test dependencies 2019-06-13 18:43:17 +02:00
Vedran Pavic
6761f2cc68 Upgrade Spring Framework to 5.1.8.RELEASE
Resolves: #1442
2019-06-13 18:42:59 +02:00
Vedran Pavic
c8f266cea2 Upgrade Reactor to Californium-SR9
Resolves: #1446
2019-06-11 11:26:40 +02:00
Vedran Pavic
7d71aa5d00 Separate "filtered" attribute for ERROR dispatch
Resolves: #1451
See: spring-projects/spring-framework#22989
2019-06-11 08:34:04 +02:00
Vedran Pavic
bee9c7c2e2 Polish build
See: spring-projects/spring-security#6200
2019-06-11 08:33:39 +02:00
Vedran Pavic
8788165966 Upgrade samples to Spring Boot 2.1.5.RELEASE
Resolves: #1444
2019-06-03 07:47:28 +02:00
Vedran Pavic
aadc806447 Update integration tests 2019-06-02 19:23:19 +02:00
Vedran Pavic
0fb942f795 Upgrade test dependencies 2019-06-02 19:23:07 +02:00
Vedran Pavic
49ebaa5cb7 Save reactive Redis session on subscribe
This commit ensures ReactiveRedisOperationsSessionRepository#save does work only after subscribe. Without this, multiple invocations of #save over the course of same request can lead to race condition situations.

Resolves: #1440
2019-06-02 19:21:49 +02:00
Vedran Pavic
a48edd5e71 Update Jenkinsfile to use explicit JAVA_HOME 2019-05-27 09:44:33 +02:00
Rob Winch
17ddfc0fa8 Redis save uses then
We need to ensure that the session id is changed before we save the
changes. Otherwise the rename of the session id will override the
changes we just made.

Fixes: gh-1428
2019-05-16 15:49:24 -05:00
Rob Winch
f2f9562a01 Next Development Version 2019-05-13 14:44:42 -05:00
Rob Winch
4c50da3982 Release 2.1.6.RELEASE 2019-05-13 12:23:21 -05:00
Vedran Pavic
b1d53874e4 Ensure Redis session with immediate flush respects defaultMaxInactiveInterval
Resolves: #1423
2019-05-13 19:09:45 +02:00
Vedran Pavic
cc2a75b668 Upgrade Spring Data to Lovelace-SR8
Resolves: #1418
2019-05-13 17:49:30 +02:00
Vedran Pavic
006acba02a Upgrade Reactor to Californium-SR8
Resolves: #1419
2019-05-13 17:49:10 +02:00
Vedran Pavic
168ad85cc5 Upgrade Spring Data to Lovelace-SR7
Resolves: #1418
2019-05-10 22:31:28 +02:00
Vedran Pavic
3f61a30568 Update integration tests 2019-05-10 08:45:42 +02:00
Vedran Pavic
318036828d Upgrade test dependencies 2019-05-10 08:41:45 +02:00
Vedran Pavic
326e7070b1 Upgrade Hazelcast to 3.11.4
Resolves: #1421
2019-05-10 08:36:50 +02:00
Vedran Pavic
81ca7d5f83 Upgrade samples to Spring Boot 2.1.4.RELEASE
Resolves: #1420
2019-05-10 08:18:43 +02:00
Vedran Pavic
de8e47e4c3 Upgrade Reactor to Californium-SR7
Resolves: #1419
2019-05-10 08:18:43 +02:00
Vedran Pavic
ef6b9f8812 Upgrade Spring Framework to 5.1.7.RELEASE
Resolves: #1417
2019-05-10 08:16:42 +02:00
Vedran Pavic
14be439da0 Fix JdbcOperationsSessionRepository lazy deserialization
Resolves: #1412
2019-05-06 23:38:47 +02:00
Joe Grandja
2631ddc446 Next Development Version 2019-04-02 16:06:36 -04:00
28 changed files with 234 additions and 184 deletions

18
Jenkinsfile vendored
View File

@@ -15,7 +15,9 @@ try {
node('ubuntu1804') {
checkout scm
try {
sh './gradlew clean check --no-daemon --refresh-dependencies'
withEnv(["JAVA_HOME=${tool 'jdk8'}"]) {
sh './gradlew clean check --no-daemon --refresh-dependencies --stacktrace'
}
}
catch (e) {
currentBuild.result = 'FAILED: check'
@@ -35,7 +37,7 @@ try {
checkout scm
try {
withEnv(["JAVA_HOME=${tool 'jdk9'}"]) {
sh './gradlew clean test --no-daemon --refresh-dependencies'
sh './gradlew clean test --no-daemon --refresh-dependencies --stacktrace'
}
}
catch (e) {
@@ -53,7 +55,7 @@ try {
checkout scm
try {
withEnv(["JAVA_HOME=${tool 'jdk10'}"]) {
sh './gradlew clean test --no-daemon --refresh-dependencies'
sh './gradlew clean test --no-daemon --refresh-dependencies --stacktrace'
}
}
catch (e) {
@@ -71,7 +73,7 @@ try {
checkout scm
try {
withEnv(["JAVA_HOME=${tool 'jdk11'}"]) {
sh './gradlew clean test integrationTest --no-daemon --refresh-dependencies'
sh './gradlew clean test integrationTest --no-daemon --refresh-dependencies --stacktrace'
}
}
catch (e) {
@@ -93,7 +95,9 @@ try {
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'
withEnv(["JAVA_HOME=${tool 'jdk8'}"]) {
sh './gradlew deployArtifacts finalizeDeployArtifacts --no-daemon --refresh-dependencies --stacktrace -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password=$SIGNING_PASSWORD -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD'
}
}
}
}
@@ -112,7 +116,9 @@ try {
checkout scm
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'
withEnv(["JAVA_HOME=${tool 'jdk8'}"]) {
sh './gradlew deployDocs --no-daemon --refresh-dependencies --stacktrace -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME'
}
}
}
catch (e) {

View File

@@ -4,7 +4,7 @@ buildscript {
snapshotBuild = version.endsWith('SNAPSHOT')
milestoneBuild = !(releaseBuild || snapshotBuild)
springBootVersion = '2.1.3.RELEASE'
springBootVersion = '2.1.5.RELEASE'
}
repositories {
@@ -23,16 +23,8 @@ apply plugin: 'io.spring.convention.root'
group = 'org.springframework.session'
description = 'Spring Session'
gradle.taskGraph.whenReady { graph ->
def jacocoEnabled = graph.allTasks.any { it instanceof JacocoReport }
subprojects {
plugins.withType(JavaPlugin) {
sourceCompatibility = 1.8
}
plugins.withType(JacocoPlugin) {
tasks.withType(Test) {
jacoco.enabled = jacocoEnabled
}
}
subprojects {
plugins.withType(JavaPlugin) {
sourceCompatibility = JavaVersion.VERSION_1_8
}
}

View File

@@ -1 +1 @@
version=2.1.5.RELEASE
version=2.1.7.RELEASE

View File

@@ -1,33 +1,33 @@
dependencyManagement {
imports {
mavenBom 'com.fasterxml.jackson:jackson-bom:2.9.6'
mavenBom 'io.projectreactor:reactor-bom:Californium-SR6'
mavenBom 'org.springframework:spring-framework-bom:5.1.6.RELEASE'
mavenBom 'org.springframework.data:spring-data-releasetrain:Lovelace-SR6'
mavenBom 'io.projectreactor:reactor-bom:Californium-SR9'
mavenBom 'org.springframework:spring-framework-bom:5.1.8.RELEASE'
mavenBom 'org.springframework.data:spring-data-releasetrain:Lovelace-SR9'
mavenBom 'org.springframework.security:spring-security-bom:5.1.5.RELEASE'
mavenBom 'org.testcontainers:testcontainers-bom:1.10.6'
mavenBom 'org.testcontainers:testcontainers-bom:1.11.3'
}
dependencies {
dependencySet(group: 'com.hazelcast', version: '3.11.2') {
dependencySet(group: 'com.hazelcast', version: '3.11.4') {
entry 'hazelcast'
entry 'hazelcast-client'
}
dependency 'com.h2database:h2:1.4.197'
dependency 'com.microsoft.sqlserver:mssql-jdbc:7.2.1.jre8'
dependency 'com.h2database:h2:1.4.199'
dependency 'com.microsoft.sqlserver:mssql-jdbc:7.2.2.jre8'
dependency 'com.zaxxer:HikariCP:3.3.1'
dependency 'edu.umd.cs.mtc:multithreadedtc:1.01'
dependency 'io.lettuce:lettuce-core:5.1.4.RELEASE'
dependency 'io.lettuce:lettuce-core:5.1.7.RELEASE'
dependency 'javax.annotation:javax.annotation-api:1.3.2'
dependency 'javax.servlet:javax.servlet-api:4.0.1'
dependency 'junit:junit:4.12'
dependency 'mysql:mysql-connector-java:8.0.15'
dependency 'mysql:mysql-connector-java:8.0.16'
dependency 'org.apache.derby:derby:10.14.2.0'
dependency 'org.assertj:assertj-core:3.11.1'
dependency 'org.hsqldb:hsqldb:2.4.1'
dependency 'org.mariadb.jdbc:mariadb-java-client:2.4.0'
dependency 'org.mockito:mockito-core:2.24.0'
dependency 'org.assertj:assertj-core:3.12.2'
dependency 'org.hsqldb:hsqldb:2.5.0'
dependency 'org.mariadb.jdbc:mariadb-java-client:2.4.1'
dependency 'org.mockito:mockito-core:2.28.2'
dependency 'org.postgresql:postgresql:42.2.5'
}
}

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:5.0.3";
private static final String DOCKER_IMAGE = "redis:5.0.5";
@Autowired
private MockMvc mockMvc;

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:5.0.3";
private static final String DOCKER_IMAGE = "redis:5.0.5";
@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:5.0.3";
private static final String DOCKER_IMAGE = "redis:5.0.5";
@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:5.0.3";
private static final String DOCKER_IMAGE = "redis:5.0.5";
@Autowired
private MockMvc mockMvc;

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:5.0.3";
private static final String DOCKER_IMAGE = "redis:5.0.5";
@LocalServerPort
private int port;

View File

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

View File

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

View File

@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
@Profile("embedded-redis")
public class EmbeddedRedisConfig {
private static final String DOCKER_IMAGE = "redis:5.0.3";
private static final String DOCKER_IMAGE = "redis:5.0.5";
@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:5.0.3";
private static final String DOCKER_IMAGE = "redis:5.0.5";
@Autowired
private SessionRepositoryFilter<? extends Session> sessionRepositoryFilter;

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ 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;
@@ -66,8 +67,11 @@ abstract class OncePerRequestFilter implements Filter {
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String alreadyFilteredAttributeName = this.alreadyFilteredAttributeName;
alreadyFilteredAttributeName = updateForErrorDispatch(
alreadyFilteredAttributeName, request);
boolean hasAlreadyFilteredAttribute = request
.getAttribute(this.alreadyFilteredAttributeName) != null;
.getAttribute(alreadyFilteredAttributeName) != null;
if (hasAlreadyFilteredAttribute) {
@@ -76,17 +80,28 @@ abstract class OncePerRequestFilter implements Filter {
}
else {
// Do invoke this filter...
request.setAttribute(this.alreadyFilteredAttributeName, Boolean.TRUE);
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
doFilterInternal(httpRequest, httpResponse, filterChain);
}
finally {
// Remove the "already filtered" request attribute for this request.
request.removeAttribute(this.alreadyFilteredAttributeName);
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}
private String updateForErrorDispatch(String alreadyFilteredAttributeName,
ServletRequest request) {
// Jetty does ERROR dispatch within sendError, so request attribute is still present
// Use a separate attribute for ERROR dispatches
if (DispatcherType.ERROR.equals(request.getDispatcherType())
&& request.getAttribute(alreadyFilteredAttributeName) != null) {
return alreadyFilteredAttributeName + ".ERROR";
}
return alreadyFilteredAttributeName;
}
/**
* Same contract as for {@code doFilter}, but guaranteed to be just invoked once per
* request within a single request thread.

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import java.time.Instant;
import org.junit.Test;
import org.junit.runner.RunWith;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
@@ -68,6 +69,17 @@ public class ReactiveRedisOperationsSessionRepositoryITests extends AbstractRedi
assertThat(this.repository.findById(toSave.getId()).block()).isNull();
}
@Test // gh-1399
public void saveMultipleTimes() {
ReactiveRedisOperationsSessionRepository.RedisSession session = this.repository
.createSession().block();
session.setAttribute("attribute1", "value1");
Mono<Void> save1 = this.repository.save(session);
session.setAttribute("attribute2", "value2");
Mono<Void> save2 = this.repository.save(session);
Mono.zip(save1, save2).block();
}
@Test
public void putAllOnSingleAttrDoesNotRemoveOld() {
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -143,23 +143,15 @@ public class ReactiveRedisOperationsSessionRepository implements
@Override
public Mono<Void> save(RedisSession session) {
Mono<Void> result = session.saveChangeSessionId().and(session.saveDelta())
.and((s) -> {
session.isNew = false;
s.onComplete();
});
if (session.isNew) {
return result;
}
else {
String sessionKey = getSessionKey(
session.hasChangedSessionId() ? session.originalSessionId
: session.getId());
return this.sessionRedisOperations.hasKey(sessionKey)
.flatMap((exists) -> exists ? result
: Mono.error(new IllegalStateException(
"Session was invalidated")));
return session.save();
}
String sessionKey = getSessionKey(
session.hasChangedSessionId() ? session.originalSessionId
: session.getId());
return this.sessionRedisOperations.hasKey(sessionKey).flatMap((exists) -> exists
? session.save()
: Mono.error(new IllegalStateException("Session was invalidated")));
}
@Override
@@ -305,7 +297,7 @@ public class ReactiveRedisOperationsSessionRepository implements
private void flushImmediateIfNecessary() {
if (ReactiveRedisOperationsSessionRepository.this.redisFlushMode == RedisFlushMode.IMMEDIATE) {
saveDelta();
save();
}
}
@@ -314,6 +306,11 @@ public class ReactiveRedisOperationsSessionRepository implements
flushImmediateIfNecessary();
}
private Mono<Void> save() {
return Mono.defer(() -> saveChangeSessionId().then(saveDelta())
.doOnSuccess((aVoid) -> this.isNew = false));
}
private Mono<Void> saveDelta() {
if (this.delta.isEmpty()) {
return Mono.empty();
@@ -321,7 +318,7 @@ public class ReactiveRedisOperationsSessionRepository implements
String sessionKey = getSessionKey(getId());
Mono<Boolean> update = ReactiveRedisOperationsSessionRepository.this.sessionRedisOperations
.opsForHash().putAll(sessionKey, this.delta);
.opsForHash().putAll(sessionKey, new HashMap<>(this.delta));
Mono<Boolean> setTtl = ReactiveRedisOperationsSessionRepository.this.sessionRedisOperations
.expire(sessionKey, getMaxInactiveInterval());

View File

@@ -418,7 +418,7 @@ public class RedisOperationsSessionRepository implements
@Override
public void save(RedisSession session) {
session.saveDelta();
session.save();
if (session.isNew()) {
String sessionCreatedKey = getSessionCreatedChannel(session.getId());
this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta);
@@ -516,12 +516,13 @@ public class RedisOperationsSessionRepository implements
@Override
public RedisSession createSession() {
RedisSession redisSession = new RedisSession();
if (this.defaultMaxInactiveInterval != null) {
redisSession.setMaxInactiveInterval(
Duration.ofSeconds(this.defaultMaxInactiveInterval));
}
return redisSession;
Duration maxInactiveInterval = Duration
.ofSeconds((this.defaultMaxInactiveInterval != null)
? this.defaultMaxInactiveInterval
: MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
RedisSession session = new RedisSession(maxInactiveInterval);
session.flushImmediateIfNecessary();
return session;
}
@Override
@@ -711,14 +712,17 @@ public class RedisOperationsSessionRepository implements
/**
* Creates a new instance ensuring to mark all of the new attributes to be
* persisted in the next save operation.
*
* @param maxInactiveInterval the amount of time that the {@link Session} should
* be kept alive between client requests.
*/
RedisSession() {
RedisSession(Duration maxInactiveInterval) {
this(new MapSession());
this.cached.setMaxInactiveInterval(maxInactiveInterval);
this.delta.put(CREATION_TIME_ATTR, getCreationTime().toEpochMilli());
this.delta.put(MAX_INACTIVE_ATTR, (int) getMaxInactiveInterval().getSeconds());
this.delta.put(LAST_ACCESSED_ATTR, getLastAccessedTime().toEpochMilli());
this.isNew = true;
this.flushImmediateIfNecessary();
}
/**
@@ -808,7 +812,7 @@ public class RedisOperationsSessionRepository implements
private void flushImmediateIfNecessary() {
if (RedisOperationsSessionRepository.this.redisFlushMode == RedisFlushMode.IMMEDIATE) {
saveDelta();
save();
}
}
@@ -817,16 +821,20 @@ public class RedisOperationsSessionRepository implements
this.flushImmediateIfNecessary();
}
private void save() {
saveChangeSessionId();
saveDelta();
}
/**
* Saves any attributes that have been changed and updates the expiration of this
* session.
*/
private void saveDelta() {
String sessionId = getId();
saveChangeSessionId(sessionId);
if (this.delta.isEmpty()) {
return;
}
String sessionId = getId();
getSessionBoundHashOperations(sessionId).putAll(this.delta);
String principalSessionKey = getSessionAttrNameKey(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
@@ -859,30 +867,32 @@ public class RedisOperationsSessionRepository implements
.onExpirationUpdated(originalExpiration, this);
}
private void saveChangeSessionId(String sessionId) {
if (!sessionId.equals(this.originalSessionId)) {
if (!isNew()) {
String originalSessionIdKey = getSessionKey(this.originalSessionId);
String sessionIdKey = getSessionKey(sessionId);
try {
RedisOperationsSessionRepository.this.sessionRedisOperations
.rename(originalSessionIdKey, sessionIdKey);
}
catch (NonTransientDataAccessException ex) {
handleErrNoSuchKeyError(ex);
}
String originalExpiredKey = getExpiredKey(this.originalSessionId);
String expiredKey = getExpiredKey(sessionId);
try {
RedisOperationsSessionRepository.this.sessionRedisOperations
.rename(originalExpiredKey, expiredKey);
}
catch (NonTransientDataAccessException ex) {
handleErrNoSuchKeyError(ex);
}
}
this.originalSessionId = sessionId;
private void saveChangeSessionId() {
String sessionId = getId();
if (sessionId.equals(this.originalSessionId)) {
return;
}
if (!isNew()) {
String originalSessionIdKey = getSessionKey(this.originalSessionId);
String sessionIdKey = getSessionKey(sessionId);
try {
RedisOperationsSessionRepository.this.sessionRedisOperations
.rename(originalSessionIdKey, sessionIdKey);
}
catch (NonTransientDataAccessException ex) {
handleErrNoSuchKeyError(ex);
}
String originalExpiredKey = getExpiredKey(this.originalSessionId);
String expiredKey = getExpiredKey(sessionId);
try {
RedisOperationsSessionRepository.this.sessionRedisOperations
.rename(originalExpiredKey, expiredKey);
}
catch (NonTransientDataAccessException ex) {
handleErrNoSuchKeyError(ex);
}
}
this.originalSessionId = sessionId;
}
private void handleErrNoSuchKeyError(NonTransientDataAccessException ex) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -132,9 +132,10 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
@Test
public void createSessionDefaultMaxInactiveInterval() {
StepVerifier.create(this.repository.createSession()).consumeNextWith(
(session) -> assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration
.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS)))
StepVerifier.create(this.repository.createSession())
.consumeNextWith((session) -> assertThat(session.getMaxInactiveInterval())
.isEqualTo(Duration.ofSeconds(
MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS)))
.verifyComplete();
}
@@ -155,30 +156,26 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
given(this.redisOperations.expire(anyString(), any()))
.willReturn(Mono.just(true));
StepVerifier
.create(this.repository.createSession().doOnNext(this.repository::save))
.consumeNextWith((session) -> {
verify(this.redisOperations).opsForHash();
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
verify(this.redisOperations).expire(anyString(), any());
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.hashOperations);
RedisSession newSession = this.repository.new RedisSession();
StepVerifier.create(this.repository.save(newSession)).verifyComplete();
Map<String, Object> delta = this.delta.getAllValues().get(0);
assertThat(delta.size()).isEqualTo(3);
assertThat(delta.get(
ReactiveRedisOperationsSessionRepository.CREATION_TIME_KEY))
.isEqualTo(session.getCreationTime().toEpochMilli());
assertThat(delta.get(
ReactiveRedisOperationsSessionRepository.MAX_INACTIVE_INTERVAL_KEY))
.isEqualTo((int) Duration.ofSeconds(
MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS)
.getSeconds());
assertThat(delta.get(
ReactiveRedisOperationsSessionRepository.LAST_ACCESSED_TIME_KEY))
.isEqualTo(
session.getLastAccessedTime().toEpochMilli());
}).verifyComplete();
verify(this.redisOperations).opsForHash();
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
verify(this.redisOperations).expire(anyString(), any());
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.hashOperations);
Map<String, Object> delta = this.delta.getAllValues().get(0);
assertThat(delta.size()).isEqualTo(3);
assertThat(delta.get(ReactiveRedisOperationsSessionRepository.CREATION_TIME_KEY))
.isEqualTo(newSession.getCreationTime().toEpochMilli());
assertThat(delta
.get(ReactiveRedisOperationsSessionRepository.MAX_INACTIVE_INTERVAL_KEY))
.isEqualTo(
(int) newSession.getMaxInactiveInterval().getSeconds());
assertThat(delta
.get(ReactiveRedisOperationsSessionRepository.LAST_ACCESSED_TIME_KEY))
.isEqualTo(newSession.getLastAccessedTime().toEpochMilli());
}
@Test
@@ -207,7 +204,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
RedisSession session = this.repository.new RedisSession(this.cached);
session.setLastAccessedTime(Instant.ofEpochMilli(12345678L));
Mono.just(session).subscribe(this.repository::save);
StepVerifier.create(this.repository.save(session)).verifyComplete();
verify(this.redisOperations).hasKey(anyString());
verify(this.redisOperations).opsForHash();
@@ -232,7 +229,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
String attrName = "attrName";
RedisSession session = this.repository.new RedisSession(this.cached);
session.setAttribute(attrName, "attrValue");
Mono.just(session).subscribe(this.repository::save);
StepVerifier.create(this.repository.save(session)).verifyComplete();
verify(this.redisOperations).hasKey(anyString());
verify(this.redisOperations).opsForHash();
@@ -257,7 +254,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
String attrName = "attrName";
RedisSession session = this.repository.new RedisSession(new MapSession());
session.removeAttribute(attrName);
Mono.just(session).subscribe(this.repository::save);
StepVerifier.create(this.repository.save(session)).verifyComplete();
verify(this.redisOperations).hasKey(anyString());
verify(this.redisOperations).opsForHash();
@@ -333,24 +330,25 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
given(this.hashOperations.entries(anyString()))
.willReturn(Flux.fromIterable(map.entrySet()));
StepVerifier.create(this.repository.findById("test")).consumeNextWith((session) -> {
verify(this.redisOperations).opsForHash();
verify(this.hashOperations).entries(anyString());
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.hashOperations);
StepVerifier.create(this.repository.findById("test"))
.consumeNextWith((session) -> {
verify(this.redisOperations).opsForHash();
verify(this.hashOperations).entries(anyString());
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.hashOperations);
assertThat(session.getId()).isEqualTo(expected.getId());
assertThat(session.getAttributeNames())
.isEqualTo(expected.getAttributeNames());
assertThat(session.<String>getAttribute(attribute1))
.isEqualTo(expected.getAttribute(attribute1));
assertThat(session.<String>getAttribute(attribute2))
.isEqualTo(expected.getAttribute(attribute2));
assertThat(session.getId()).isEqualTo(expected.getId());
assertThat(session.getAttributeNames())
.isEqualTo(expected.getAttributeNames());
assertThat(session.<String>getAttribute(attribute1))
.isEqualTo(expected.getAttribute(attribute1));
assertThat(session.<String>getAttribute(attribute2))
.isEqualTo(expected.getAttribute(attribute2));
assertThat(session.getCreationTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(expected.getCreationTime()
.truncatedTo(ChronoUnit.MILLIS));
assertThat(session.getMaxInactiveInterval())
.isEqualTo(expected.getMaxInactiveInterval());
.isEqualTo(expected.getMaxInactiveInterval());
assertThat(
session.getLastAccessedTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(expected.getLastAccessedTime()

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -343,7 +343,8 @@ public class RedisOperationsSessionRepositoryTests {
@Test
public void redisSessionGetAttributes() {
String attrName = "attrName";
RedisSession session = this.redisRepository.new RedisSession();
RedisSession session = this.redisRepository.new RedisSession(
Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS));
assertThat(session.getAttributeNames()).isEmpty();
session.setAttribute(attrName, "attrValue");
assertThat(session.getAttributeNames()).containsOnly(attrName);
@@ -757,6 +758,23 @@ public class RedisOperationsSessionRepositoryTests {
.isEqualTo(session.getCreationTime().toEpochMilli());
}
@Test // gh-1409
public void flushModeImmediateCreateWithCustomMaxInactiveInterval() {
given(this.redisOperations.boundHashOps(anyString()))
.willReturn(this.boundHashOperations);
given(this.redisOperations.boundSetOps(anyString()))
.willReturn(this.boundSetOperations);
given(this.redisOperations.boundValueOps(anyString()))
.willReturn(this.boundValueOperations);
this.redisRepository.setDefaultMaxInactiveInterval(60);
this.redisRepository.setRedisFlushMode(RedisFlushMode.IMMEDIATE);
this.redisRepository.createSession();
Map<String, Object> delta = getDelta();
assertThat(delta.size()).isEqualTo(3);
assertThat(delta.get(RedisOperationsSessionRepository.MAX_INACTIVE_ATTR))
.isEqualTo(60);
}
@Test
public void flushModeImmediateSetAttribute() {
given(this.redisOperations.boundHashOps(anyString()))

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.11.2")
"hazelcast/hazelcast:3.11.4")
.withExposedPorts(5701)
.withEnv("JAVA_OPTS",
"-Dhazelcast.config=/opt/hazelcast/config_ext/hazelcast.xml")

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -797,18 +797,14 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
MapSession delegate = (MapSession) ReflectionTestUtils.getField(session,
"delegate");
Supplier attribute1 = delegate.getAttribute("attribute1");
assertThat(ReflectionTestUtils.getField(attribute1, "value")).isNull();
assertThat((String) session.getAttribute("attribute1")).isEqualTo("value1");
assertThat(delegate).isNotNull();
assertThat(ReflectionTestUtils
.getField((Supplier) delegate.getAttribute("attribute1"), "value"))
.isEqualTo("value1");
assertThat(ReflectionTestUtils
.getField((Supplier) delegate.getAttribute("attribute2"), "value"))
.isNull();
assertThat(ReflectionTestUtils.getField(attribute1, "value")).isEqualTo("value1");
Supplier attribute2 = delegate.getAttribute("attribute2");
assertThat(ReflectionTestUtils.getField(attribute2, "value")).isNull();
assertThat((String) session.getAttribute("attribute2")).isEqualTo("value2");
assertThat(ReflectionTestUtils
.getField((Supplier) delegate.getAttribute("attribute2"), "value"))
.isEqualTo("value2");
assertThat(ReflectionTestUtils.getField(attribute2, "value")).isEqualTo("value2");
}
@Test // gh-1203

View File

@@ -72,7 +72,7 @@ final class DatabaseContainers {
private static class MariaDb5Container extends MariaDBContainer<MariaDb5Container> {
MariaDb5Container() {
super("mariadb:5.5.63");
super("mariadb:5.5.64");
}
@Override
@@ -88,7 +88,7 @@ final class DatabaseContainers {
private static class MariaDb10Container extends MariaDBContainer<MariaDb10Container> {
MariaDb10Container() {
super("mariadb:10.3.12");
super("mariadb:10.3.15");
}
@Override
@@ -103,7 +103,7 @@ final class DatabaseContainers {
private static class MySql5Container extends MySQLContainer<MySql5Container> {
MySql5Container() {
super("mysql:5.7.25");
super("mysql:5.7.26");
}
@Override
@@ -123,7 +123,7 @@ final class DatabaseContainers {
private static class MySql8Container extends MySQLContainer<MySql8Container> {
MySql8Container() {
super("mysql:8.0.15");
super("mysql:8.0.16");
}
@Override
@@ -143,7 +143,7 @@ final class DatabaseContainers {
extends PostgreSQLContainer<PostgreSql9Container> {
PostgreSql9Container() {
super("postgres:9.6.11");
super("postgres:9.6.13");
}
}
@@ -152,7 +152,7 @@ final class DatabaseContainers {
extends PostgreSQLContainer<PostgreSql10Container> {
PostgreSql10Container() {
super("postgres:10.6");
super("postgres:10.8");
}
}
@@ -161,7 +161,7 @@ final class DatabaseContainers {
extends PostgreSQLContainer<PostgreSql11Container> {
PostgreSql11Container() {
super("postgres:11.1");
super("postgres:11.3");
}
}
@@ -170,7 +170,7 @@ final class DatabaseContainers {
extends MSSQLServerContainer<SqlServer2017Container> {
SqlServer2017Container() {
super("mcr.microsoft.com/mssql/server:2017-CU13");
super("mcr.microsoft.com/mssql/server:2017-CU15");
}
}

View File

@@ -1 +1 @@
mcr.microsoft.com/mssql/server:2017-CU13
mcr.microsoft.com/mssql/server:2017-CU15

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -532,7 +532,8 @@ public class JdbcOperationsSessionRepository implements
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
ps.setString(1, attributeName);
setObjectAsBlob(ps, 2, session.getAttribute(attributeName));
getLobHandler().getLobCreator().setBlobAsBytes(ps, 2,
serialize(session.getAttribute(attributeName)));
ps.setString(3, session.getId());
}
@@ -547,7 +548,8 @@ public class JdbcOperationsSessionRepository implements
this.jdbcOperations.update(this.createSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
ps.setString(1, attributeName);
setObjectAsBlob(ps, 2, session.getAttribute(attributeName));
getLobHandler().getLobCreator().setBlobAsBytes(ps, 2,
serialize(session.getAttribute(attributeName)));
ps.setString(3, session.getId());
});
}
@@ -561,7 +563,8 @@ public class JdbcOperationsSessionRepository implements
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
setObjectAsBlob(ps, 1, session.getAttribute(attributeName));
getLobHandler().getLobCreator().setBlobAsBytes(ps, 1,
serialize(session.getAttribute(attributeName)));
ps.setString(2, session.primaryKey);
ps.setString(3, attributeName);
}
@@ -576,7 +579,8 @@ public class JdbcOperationsSessionRepository implements
else {
this.jdbcOperations.update(this.updateSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
setObjectAsBlob(ps, 1, session.getAttribute(attributeName));
getLobHandler().getLobCreator().setBlobAsBytes(ps, 1,
serialize(session.getAttribute(attributeName)));
ps.setString(2, session.primaryKey);
ps.setString(3, attributeName);
});
@@ -659,16 +663,17 @@ public class JdbcOperationsSessionRepository implements
getQuery(DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY);
}
private void setObjectAsBlob(PreparedStatement ps, int paramIndex, Object object)
throws SQLException {
byte[] bytes = (byte[]) this.conversionService.convert(object,
TypeDescriptor.valueOf(Object.class),
TypeDescriptor.valueOf(byte[].class));
this.lobHandler.getLobCreator().setBlobAsBytes(ps, paramIndex, bytes);
private LobHandler getLobHandler() {
return this.lobHandler;
}
private Object getBlobAsObject(ResultSet rs, String columnName) throws SQLException {
byte[] bytes = this.lobHandler.getBlobAsBytes(rs, columnName);
private byte[] serialize(Object object) {
return (byte[]) this.conversionService.convert(object,
TypeDescriptor.valueOf(Object.class),
TypeDescriptor.valueOf(byte[].class));
}
private Object deserialize(byte[] bytes) {
return this.conversionService.convert(bytes, TypeDescriptor.valueOf(byte[].class),
TypeDescriptor.valueOf(Object.class));
}
@@ -898,8 +903,9 @@ public class JdbcOperationsSessionRepository implements
}
String attributeName = rs.getString("ATTRIBUTE_NAME");
if (attributeName != null) {
Object attributeValue = getBlobAsObject(rs, "ATTRIBUTE_BYTES");
session.delegate.setAttribute(attributeName, lazily(() -> attributeValue));
byte[] bytes = getLobHandler().getBlobAsBytes(rs, "ATTRIBUTE_BYTES");
session.delegate.setAttribute(attributeName,
lazily(() -> deserialize(bytes)));
}
sessions.add(session);
}