From 8cc8fbb7fd7d4012f5ef2dc59e1c608443b2d486 Mon Sep 17 00:00:00 2001 From: Vedran Pavic Date: Wed, 18 Sep 2019 20:50:38 +0200 Subject: [PATCH] Harmonize naming of session repositories Resolves: #1455 --- .../data/redis/AbstractRedisITests.java | 2 +- ...ReactiveRedisSessionRepositoryITests.java} | 39 +- ... RedisIndexedSessionRepositoryITests.java} | 20 +- ...java => RedisSessionRepositoryITests.java} | 12 +- ...sionRepositoryFlushImmediatelyITests.java} | 2 +- ...ctiveRedisOperationsSessionRepository.java | 286 +----- .../redis/ReactiveRedisSessionRepository.java | 312 +++++++ .../redis/RedisIndexedSessionRepository.java | 857 +++++++++++++++++ .../RedisOperationsSessionRepository.java | 832 +---------------- .../redis/RedisSessionExpirationPolicy.java | 2 +- ...itory.java => RedisSessionRepository.java} | 22 +- .../ConfigureNotifyKeyspaceEventsAction.java | 4 +- .../SpringSessionRedisConnectionFactory.java | 4 +- .../SpringSessionRedisOperations.java | 8 +- .../web/http/EnableRedisHttpSession.java | 4 +- .../http/RedisHttpSessionConfiguration.java | 16 +- .../web/server/EnableRedisWebSession.java | 4 +- .../server/RedisWebSessionConfiguration.java | 13 +- ... ReactiveRedisSessionRepositoryTests.java} | 16 +- ...> RedisIndexedSessionRepositoryTests.java} | 67 +- .../RedisSessionExpirationPolicyTests.java | 2 +- ....java => RedisSessionRepositoryTests.java} | 12 +- .../RedisHttpSessionConfigurationTests.java | 36 +- .../annotation/web/http/gh109/Gh109Tests.java | 8 +- .../RedisWebSessionConfigurationTests.java | 46 +- .../src/docs/asciidoc/index.adoc | 104 +-- .../src/test/java/docs/IndexDocTests.java | 41 +- .../docs/http/HazelcastHttpSessionConfig.java | 10 +- ...elcastIndexedSessionRepositoryITests.java} | 10 +- ...elcastIndexedSessionRepositoryITests.java} | 7 +- ...elcastIndexedSessionRepositoryITests.java} | 6 +- .../hazelcast/HazelcastITestUtils.java | 7 +- .../HazelcastIndexedSessionRepository.java | 469 ++++++++++ .../hazelcast/HazelcastSessionRepository.java | 435 +-------- .../SessionUpdateEntryProcessor.java | 3 +- .../SpringSessionHazelcastInstance.java | 4 +- .../web/http/EnableHazelcastHttpSession.java | 7 +- .../HazelcastHttpSessionConfiguration.java | 13 +- ...zelcastIndexedSessionRepositoryTests.java} | 12 +- ...azelcastHttpSessionConfigurationTests.java | 46 +- ...erJdbcIndexedSessionRepositoryITests.java} | 7 +- ...ctJdbcIndexedSessionRepositoryITests.java} | 165 ++-- ...11JdbcIndexedSessionRepositoryITests.java} | 5 +- ...byJdbcIndexedSessionRepositoryITests.java} | 4 +- ...H2JdbcIndexedSessionRepositoryITests.java} | 4 +- ...dbJdbcIndexedSessionRepositoryITests.java} | 4 +- ...10JdbcIndexedSessionRepositoryITests.java} | 5 +- ...b5JdbcIndexedSessionRepositoryITests.java} | 5 +- ...l5JdbcIndexedSessionRepositoryITests.java} | 4 +- ...l8JdbcIndexedSessionRepositoryITests.java} | 4 +- ...leJdbcIndexedSessionRepositoryITests.java} | 4 +- ...10JdbcIndexedSessionRepositoryITests.java} | 4 +- ...11JdbcIndexedSessionRepositoryITests.java} | 4 +- ...l9JdbcIndexedSessionRepositoryITests.java} | 4 +- ...erJdbcIndexedSessionRepositoryITests.java} | 6 +- .../jdbc/JdbcIndexedSessionRepository.java | 867 ++++++++++++++++++ .../jdbc/JdbcOperationsSessionRepository.java | 854 +---------------- .../annotation/SpringSessionDataSource.java | 4 +- .../web/http/EnableJdbcHttpSession.java | 4 +- .../http/JdbcHttpSessionConfiguration.java | 12 +- ...=> JdbcIndexedSessionRepositoryTests.java} | 98 +- .../JdbcHttpSessionConfigurationTests.java | 33 +- .../java/sample/config/SessionConfig.java | 7 +- .../src/main/java/sample/SessionConfig.java | 9 +- 64 files changed, 3029 insertions(+), 2888 deletions(-) rename spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/{ReactiveRedisOperationsSessionRepositoryITests.java => ReactiveRedisSessionRepositoryITests.java} (77%) rename spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/{RedisOperationsSessionRepositoryITests.java => RedisIndexedSessionRepositoryITests.java} (96%) rename spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/{SimpleRedisOperationsSessionRepositoryITests.java => RedisSessionRepositoryITests.java} (95%) rename spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/flushimmediately/{RedisOperationsSessionRepositoryFlushImmediatelyITests.java => RedisIndexedSessionRepositoryFlushImmediatelyITests.java} (94%) create mode 100644 spring-session-data-redis/src/main/java/org/springframework/session/data/redis/ReactiveRedisSessionRepository.java create mode 100644 spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisIndexedSessionRepository.java rename spring-session-data-redis/src/main/java/org/springframework/session/data/redis/{SimpleRedisOperationsSessionRepository.java => RedisSessionRepository.java} (89%) rename spring-session-data-redis/src/test/java/org/springframework/session/data/redis/{ReactiveRedisOperationsSessionRepositoryTests.java => ReactiveRedisSessionRepositoryTests.java} (96%) rename spring-session-data-redis/src/test/java/org/springframework/session/data/redis/{RedisOperationsSessionRepositoryTests.java => RedisIndexedSessionRepositoryTests.java} (94%) rename spring-session-data-redis/src/test/java/org/springframework/session/data/redis/{SimpleRedisOperationsSessionRepositoryTests.java => RedisSessionRepositoryTests.java} (97%) rename spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/{AbstractHazelcastRepositoryITests.java => AbstractHazelcastIndexedSessionRepositoryITests.java} (95%) rename spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/{HazelcastClientRepositoryITests.java => ClientServerHazelcastIndexedSessionRepositoryITests.java} (92%) rename spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/{HazelcastServerRepositoryITests.java => EmbeddedHazelcastIndexedSessionRepositoryITests.java} (88%) create mode 100644 spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/HazelcastIndexedSessionRepository.java rename spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/{HazelcastSessionRepositoryTests.java => HazelcastIndexedSessionRepositoryTests.java} (97%) rename spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/{AbstractContainerJdbcOperationsSessionRepositoryITests.java => AbstractContainerJdbcIndexedSessionRepositoryITests.java} (88%) rename spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/{AbstractJdbcOperationsSessionRepositoryITests.java => AbstractJdbcIndexedSessionRepositoryITests.java} (75%) rename spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/{Db211JdbcOperationsSessionRepositoryITests.java => Db211JdbcIndexedSessionRepositoryITests.java} (88%) rename spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/{DerbyJdbcOperationsSessionRepositoryITests.java => DerbyJdbcIndexedSessionRepositoryITests.java} (89%) rename spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/{H2JdbcOperationsSessionRepositoryITests.java => H2JdbcIndexedSessionRepositoryITests.java} (89%) rename spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/{HsqldbJdbcOperationsSessionRepositoryITests.java => HsqldbJdbcIndexedSessionRepositoryITests.java} (89%) rename spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/{MariaDb10JdbcOperationsSessionRepositoryITests.java => MariaDb10JdbcIndexedSessionRepositoryITests.java} (88%) rename spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/{MariaDb5JdbcOperationsSessionRepositoryITests.java => MariaDb5JdbcIndexedSessionRepositoryITests.java} (88%) rename spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/{MySql5JdbcOperationsSessionRepositoryITests.java => MySql5JdbcIndexedSessionRepositoryITests.java} (88%) rename spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/{MySql8JdbcOperationsSessionRepositoryITests.java => MySql8JdbcIndexedSessionRepositoryITests.java} (88%) rename spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/{OracleJdbcOperationsSessionRepositoryITests.java => OracleJdbcIndexedSessionRepositoryITests.java} (92%) rename spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/{PostgreSql10JdbcOperationsSessionRepositoryITests.java => PostgreSql10JdbcIndexedSessionRepositoryITests.java} (88%) rename spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/{PostgreSql11JdbcOperationsSessionRepositoryITests.java => PostgreSql11JdbcIndexedSessionRepositoryITests.java} (88%) rename spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/{PostgreSql9JdbcOperationsSessionRepositoryITests.java => PostgreSql9JdbcIndexedSessionRepositoryITests.java} (89%) rename spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/{SqlServerJdbcOperationsSessionRepositoryITests.java => SqlServerJdbcIndexedSessionRepositoryITests.java} (87%) create mode 100644 spring-session-jdbc/src/main/java/org/springframework/session/jdbc/JdbcIndexedSessionRepository.java rename spring-session-jdbc/src/test/java/org/springframework/session/jdbc/{JdbcOperationsSessionRepositoryTests.java => JdbcIndexedSessionRepositoryTests.java} (84%) diff --git a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/AbstractRedisITests.java b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/AbstractRedisITests.java index 83c29857..31611fbf 100644 --- a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/AbstractRedisITests.java +++ b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/AbstractRedisITests.java @@ -23,7 +23,7 @@ import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; /** - * Base class for {@link RedisOperationsSessionRepository} integration tests. + * Base class for Redis integration tests. * * @author Vedran Pavic */ diff --git a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/ReactiveRedisOperationsSessionRepositoryITests.java b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/ReactiveRedisSessionRepositoryITests.java similarity index 77% rename from spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/ReactiveRedisOperationsSessionRepositoryITests.java rename to spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/ReactiveRedisSessionRepositoryITests.java index 0bcb652d..c58a9670 100644 --- a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/ReactiveRedisOperationsSessionRepositoryITests.java +++ b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/ReactiveRedisSessionRepositoryITests.java @@ -25,6 +25,7 @@ import reactor.core.publisher.Mono; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.session.Session; +import org.springframework.session.data.redis.ReactiveRedisSessionRepository.RedisSession; import org.springframework.session.data.redis.config.annotation.web.server.EnableRedisWebSession; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -34,21 +35,21 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** - * Integration tests for {@link ReactiveRedisOperationsSessionRepository}. + * Integration tests for {@link ReactiveRedisSessionRepository}. * * @author Vedran Pavic */ @ExtendWith(SpringExtension.class) @ContextConfiguration @WebAppConfiguration -class ReactiveRedisOperationsSessionRepositoryITests extends AbstractRedisITests { +class ReactiveRedisSessionRepositoryITests extends AbstractRedisITests { @Autowired - private ReactiveRedisOperationsSessionRepository repository; + private ReactiveRedisSessionRepository repository; @Test void saves() { - ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository.createSession().block(); + RedisSession toSave = this.repository.createSession().block(); String expectedAttributeName = "a"; String expectedAttributeValue = "b"; @@ -70,7 +71,7 @@ class ReactiveRedisOperationsSessionRepositoryITests extends AbstractRedisITests @Test // gh-1399 void saveMultipleTimes() { - ReactiveRedisOperationsSessionRepository.RedisSession session = this.repository.createSession().block(); + RedisSession session = this.repository.createSession().block(); session.setAttribute("attribute1", "value1"); Mono save1 = this.repository.save(session); session.setAttribute("attribute2", "value2"); @@ -80,7 +81,7 @@ class ReactiveRedisOperationsSessionRepositoryITests extends AbstractRedisITests @Test void putAllOnSingleAttrDoesNotRemoveOld() { - ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository.createSession().block(); + RedisSession toSave = this.repository.createSession().block(); toSave.setAttribute("a", "b"); this.repository.save(toSave).block(); @@ -103,13 +104,12 @@ class ReactiveRedisOperationsSessionRepositoryITests extends AbstractRedisITests void changeSessionIdWhenOnlyChangeId() { String attrName = "changeSessionId"; String attrValue = "changeSessionId-value"; - ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository.createSession().block(); + RedisSession toSave = this.repository.createSession().block(); toSave.setAttribute(attrName, attrValue); this.repository.save(toSave).block(); - ReactiveRedisOperationsSessionRepository.RedisSession findById = this.repository.findById(toSave.getId()) - .block(); + RedisSession findById = this.repository.findById(toSave.getId()).block(); assertThat(findById.getAttribute(attrName)).isEqualTo(attrValue); @@ -120,15 +120,14 @@ class ReactiveRedisOperationsSessionRepositoryITests extends AbstractRedisITests assertThat(this.repository.findById(originalFindById).block()).isNull(); - ReactiveRedisOperationsSessionRepository.RedisSession findByChangeSessionId = this.repository - .findById(changeSessionId).block(); + RedisSession findByChangeSessionId = this.repository.findById(changeSessionId).block(); assertThat(findByChangeSessionId.getAttribute(attrName)).isEqualTo(attrValue); } @Test void changeSessionIdWhenChangeTwice() { - ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository.createSession().block(); + RedisSession toSave = this.repository.createSession().block(); this.repository.save(toSave).block(); @@ -148,12 +147,11 @@ class ReactiveRedisOperationsSessionRepositoryITests extends AbstractRedisITests String attrName = "changeSessionId"; String attrValue = "changeSessionId-value"; - ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository.createSession().block(); + RedisSession toSave = this.repository.createSession().block(); this.repository.save(toSave).block(); - ReactiveRedisOperationsSessionRepository.RedisSession findById = this.repository.findById(toSave.getId()) - .block(); + RedisSession findById = this.repository.findById(toSave.getId()).block(); findById.setAttribute(attrName, attrValue); @@ -164,15 +162,14 @@ class ReactiveRedisOperationsSessionRepositoryITests extends AbstractRedisITests assertThat(this.repository.findById(originalFindById).block()).isNull(); - ReactiveRedisOperationsSessionRepository.RedisSession findByChangeSessionId = this.repository - .findById(changeSessionId).block(); + RedisSession findByChangeSessionId = this.repository.findById(changeSessionId).block(); assertThat(findByChangeSessionId.getAttribute(attrName)).isEqualTo(attrValue); } @Test void changeSessionIdWhenHasNotSaved() { - ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository.createSession().block(); + RedisSession toSave = this.repository.createSession().block(); String originalId = toSave.getId(); toSave.changeSessionId(); @@ -185,7 +182,7 @@ class ReactiveRedisOperationsSessionRepositoryITests extends AbstractRedisITests // gh-954 @Test void changeSessionIdSaveTwice() { - ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository.createSession().block(); + RedisSession toSave = this.repository.createSession().block(); String originalId = toSave.getId(); toSave.changeSessionId(); @@ -199,12 +196,12 @@ class ReactiveRedisOperationsSessionRepositoryITests extends AbstractRedisITests // gh-1111 @Test void changeSessionSaveOldSessionInstance() { - ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository.createSession().block(); + RedisSession toSave = this.repository.createSession().block(); String sessionId = toSave.getId(); this.repository.save(toSave).block(); - ReactiveRedisOperationsSessionRepository.RedisSession session = this.repository.findById(sessionId).block(); + RedisSession session = this.repository.findById(sessionId).block(); session.changeSessionId(); session.setLastAccessedTime(Instant.now()); this.repository.save(session).block(); diff --git a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisOperationsSessionRepositoryITests.java b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryITests.java similarity index 96% rename from spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisOperationsSessionRepositoryITests.java rename to spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryITests.java index c971d4d8..dd96cee9 100644 --- a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisOperationsSessionRepositoryITests.java +++ b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryITests.java @@ -37,7 +37,7 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.Session; import org.springframework.session.data.SessionEventRegistry; -import org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession; +import org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession; import org.springframework.session.data.redis.config.annotation.SpringSessionRedisOperations; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; import org.springframework.session.events.SessionCreatedEvent; @@ -48,17 +48,23 @@ import org.springframework.test.context.web.WebAppConfiguration; import static org.assertj.core.api.Assertions.assertThat; +/** + * Integration tests for {@link RedisIndexedSessionRepository}. + * + * @author Rob Winch + * @author Vedran Pavic + */ @ExtendWith(SpringExtension.class) @ContextConfiguration @WebAppConfiguration -class RedisOperationsSessionRepositoryITests extends AbstractRedisITests { +class RedisIndexedSessionRepositoryITests extends AbstractRedisITests { private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; private static final String INDEX_NAME = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME; @Autowired - private RedisOperationsSessionRepository repository; + private RedisIndexedSessionRepository repository; @Autowired private SessionEventRegistry registry; @@ -88,7 +94,7 @@ class RedisOperationsSessionRepositoryITests extends AbstractRedisITests { void saves() throws InterruptedException { String username = "saves-" + System.currentTimeMillis(); - String usernameSessionKey = "RedisOperationsSessionRepositoryITests:index:" + INDEX_NAME + ":" + username; + String usernameSessionKey = "RedisIndexedSessionRepositoryITests:index:" + INDEX_NAME + ":" + username; RedisSession toSave = this.repository.createSession(); String expectedAttributeName = "a"; @@ -180,7 +186,7 @@ class RedisOperationsSessionRepositoryITests extends AbstractRedisITests { this.repository.save(toSave); - String body = "RedisOperationsSessionRepositoryITests:sessions:expires:" + toSave.getId(); + String body = "RedisIndexedSessionRepositoryITests:sessions:expires:" + toSave.getId(); String channel = "__keyevent@0__:expired"; DefaultMessage message = new DefaultMessage(channel.getBytes(StandardCharsets.UTF_8), body.getBytes(StandardCharsets.UTF_8)); @@ -342,7 +348,7 @@ class RedisOperationsSessionRepositoryITests extends AbstractRedisITests { this.repository.save(toSave); - String body = "RedisOperationsSessionRepositoryITests:sessions:expires:" + toSave.getId(); + String body = "RedisIndexedSessionRepositoryITests:sessions:expires:" + toSave.getId(); String channel = "__keyevent@0__:expired"; DefaultMessage message = new DefaultMessage(channel.getBytes(StandardCharsets.UTF_8), body.getBytes(StandardCharsets.UTF_8)); @@ -607,7 +613,7 @@ class RedisOperationsSessionRepositoryITests extends AbstractRedisITests { } @Configuration - @EnableRedisHttpSession(redisNamespace = "RedisOperationsSessionRepositoryITests") + @EnableRedisHttpSession(redisNamespace = "RedisIndexedSessionRepositoryITests") static class Config extends BaseConfig { @Bean diff --git a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/SimpleRedisOperationsSessionRepositoryITests.java b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisSessionRepositoryITests.java similarity index 95% rename from spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/SimpleRedisOperationsSessionRepositoryITests.java rename to spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisSessionRepositoryITests.java index 8bb63d2d..2db4f6c7 100644 --- a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/SimpleRedisOperationsSessionRepositoryITests.java +++ b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisSessionRepositoryITests.java @@ -32,7 +32,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.session.MapSession; import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; -import org.springframework.session.data.redis.SimpleRedisOperationsSessionRepository.RedisSession; +import org.springframework.session.data.redis.RedisSessionRepository.RedisSession; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; @@ -41,17 +41,17 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** - * Integration tests for {@link SimpleRedisOperationsSessionRepository}. + * Integration tests for {@link RedisSessionRepository}. * * @author Vedran Pavic */ @ExtendWith(SpringExtension.class) @ContextConfiguration @WebAppConfiguration -class SimpleRedisOperationsSessionRepositoryITests extends AbstractRedisITests { +class RedisSessionRepositoryITests extends AbstractRedisITests { @Autowired - private SimpleRedisOperationsSessionRepository sessionRepository; + private RedisSessionRepository sessionRepository; @Test void save_NewSession_ShouldSaveSession() { @@ -227,11 +227,11 @@ class SimpleRedisOperationsSessionRepositoryITests extends AbstractRedisITests { static class Config extends BaseConfig { @Bean - public SimpleRedisOperationsSessionRepository sessionRepository(RedisConnectionFactory redisConnectionFactory) { + public RedisSessionRepository sessionRepository(RedisConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.afterPropertiesSet(); - return new SimpleRedisOperationsSessionRepository(redisTemplate); + return new RedisSessionRepository(redisTemplate); } } diff --git a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/flushimmediately/RedisOperationsSessionRepositoryFlushImmediatelyITests.java b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/flushimmediately/RedisIndexedSessionRepositoryFlushImmediatelyITests.java similarity index 94% rename from spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/flushimmediately/RedisOperationsSessionRepositoryFlushImmediatelyITests.java rename to spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/flushimmediately/RedisIndexedSessionRepositoryFlushImmediatelyITests.java index c43484d8..87f53d06 100644 --- a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/flushimmediately/RedisOperationsSessionRepositoryFlushImmediatelyITests.java +++ b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/flushimmediately/RedisIndexedSessionRepositoryFlushImmediatelyITests.java @@ -35,7 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat; @ExtendWith(SpringExtension.class) @ContextConfiguration @WebAppConfiguration -class RedisOperationsSessionRepositoryFlushImmediatelyITests extends AbstractRedisITests { +class RedisIndexedSessionRepositoryFlushImmediatelyITests extends AbstractRedisITests { @Autowired private SessionRepository sessionRepository; diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/ReactiveRedisOperationsSessionRepository.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/ReactiveRedisOperationsSessionRepository.java index 03e78513..ee7e6446 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/ReactiveRedisOperationsSessionRepository.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/ReactiveRedisOperationsSessionRepository.java @@ -16,71 +16,29 @@ package org.springframework.session.data.redis; -import java.time.Duration; -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; - import org.springframework.data.redis.core.ReactiveRedisOperations; -import org.springframework.session.MapSession; import org.springframework.session.ReactiveSessionRepository; -import org.springframework.session.SaveMode; -import org.springframework.session.Session; import org.springframework.util.Assert; /** - * A {@link ReactiveSessionRepository} that is implemented using Spring Data's - * {@link ReactiveRedisOperations}. + * This {@link ReactiveSessionRepository} implementation is kept in order to support + * migration to {@link ReactiveRedisSessionRepository} in a backwards compatible manner. * * @author Vedran Pavic - * @since 2.0 + * @since 2.0.0 + * @deprecated since 2.2.0 in favor of {@link ReactiveRedisSessionRepository} */ -public class ReactiveRedisOperationsSessionRepository - implements ReactiveSessionRepository { +@Deprecated +public class ReactiveRedisOperationsSessionRepository extends ReactiveRedisSessionRepository { /** - * The default namespace for each key and channel in Redis used by Spring Session. + * Create a new {@link ReactiveRedisOperationsSessionRepository} instance. + * @param sessionRedisOperations the {@link ReactiveRedisOperations} to use for + * managing sessions + * @see ReactiveRedisSessionRepository#ReactiveRedisSessionRepository(ReactiveRedisOperations) */ - public static final String DEFAULT_NAMESPACE = "spring:session"; - - private final ReactiveRedisOperations sessionRedisOperations; - - /** - * The namespace for every key used by Spring Session in Redis. - */ - private String namespace = DEFAULT_NAMESPACE + ":"; - - /** - * If non-null, this value is used to override the default value for - * {@link RedisSession#setMaxInactiveInterval(Duration)}. - */ - private Integer defaultMaxInactiveInterval; - - private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE; - public ReactiveRedisOperationsSessionRepository(ReactiveRedisOperations sessionRedisOperations) { - Assert.notNull(sessionRedisOperations, "sessionRedisOperations cannot be null"); - this.sessionRedisOperations = sessionRedisOperations; - } - - public void setRedisKeyNamespace(String namespace) { - Assert.hasText(namespace, "namespace cannot be null or empty"); - this.namespace = namespace.trim() + ":"; - } - - /** - * Sets the maximum inactive interval in seconds between requests before newly created - * sessions will be invalidated. A negative time indicates that the session will never - * timeout. The default is 1800 (30 minutes). - * @param defaultMaxInactiveInterval the number of seconds that the {@link Session} - * should be kept alive between client requests. - */ - public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval) { - this.defaultMaxInactiveInterval = defaultMaxInactiveInterval; + super(sessionRedisOperations); } /** @@ -93,226 +51,4 @@ public class ReactiveRedisOperationsSessionRepository Assert.notNull(redisFlushMode, "redisFlushMode cannot be null"); } - /** - * Set the save mode. - * @param saveMode the save mode - */ - public void setSaveMode(SaveMode saveMode) { - Assert.notNull(saveMode, "saveMode must not be null"); - this.saveMode = saveMode; - } - - /** - * Returns the {@link ReactiveRedisOperations} used for sessions. - * @return the {@link ReactiveRedisOperations} used for sessions - * @since 2.1.0 - */ - public ReactiveRedisOperations getSessionRedisOperations() { - return this.sessionRedisOperations; - } - - @Override - public Mono createSession() { - return Mono.defer(() -> { - MapSession cached = new MapSession(); - if (this.defaultMaxInactiveInterval != null) { - cached.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval)); - } - RedisSession session = new RedisSession(cached, true); - return Mono.just(session); - }); - } - - @Override - public Mono save(RedisSession session) { - if (session.isNew) { - 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 - public Mono findById(String id) { - String sessionKey = getSessionKey(id); - - // @formatter:off - return this.sessionRedisOperations.opsForHash().entries(sessionKey) - .collectMap((e) -> e.getKey().toString(), Map.Entry::getValue) - .filter((map) -> !map.isEmpty()) - .map(new RedisSessionMapper(id)) - .filter((session) -> !session.isExpired()) - .map((session) -> new RedisSession(session, false)) - .switchIfEmpty(Mono.defer(() -> deleteById(id).then(Mono.empty()))); - // @formatter:on - } - - @Override - public Mono deleteById(String id) { - String sessionKey = getSessionKey(id); - - return this.sessionRedisOperations.delete(sessionKey).then(); - } - - private static String getAttributeKey(String attributeName) { - return RedisSessionMapper.ATTRIBUTE_PREFIX + attributeName; - } - - private String getSessionKey(String sessionId) { - return this.namespace + "sessions:" + sessionId; - } - - /** - * A custom implementation of {@link Session} that uses a {@link MapSession} as the - * basis for its mapping. It keeps track of any attributes that have changed. When - * {@link RedisSession#saveDelta()} is invoked all the attributes that have been - * changed will be persisted. - */ - final class RedisSession implements Session { - - private final MapSession cached; - - private final Map delta = new HashMap<>(); - - private boolean isNew; - - private String originalSessionId; - - RedisSession(MapSession cached, boolean isNew) { - this.cached = cached; - this.isNew = isNew; - this.originalSessionId = cached.getId(); - if (this.isNew) { - this.delta.put(RedisSessionMapper.CREATION_TIME_KEY, cached.getCreationTime().toEpochMilli()); - this.delta.put(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, - (int) cached.getMaxInactiveInterval().getSeconds()); - this.delta.put(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, cached.getLastAccessedTime().toEpochMilli()); - } - if (this.isNew || (ReactiveRedisOperationsSessionRepository.this.saveMode == SaveMode.ALWAYS)) { - getAttributeNames().forEach((attributeName) -> this.delta.put(getAttributeKey(attributeName), - cached.getAttribute(attributeName))); - } - } - - @Override - public String getId() { - return this.cached.getId(); - } - - @Override - public String changeSessionId() { - return this.cached.changeSessionId(); - } - - @Override - public T getAttribute(String attributeName) { - T attributeValue = this.cached.getAttribute(attributeName); - if (attributeValue != null - && ReactiveRedisOperationsSessionRepository.this.saveMode.equals(SaveMode.ON_GET_ATTRIBUTE)) { - this.delta.put(getAttributeKey(attributeName), attributeValue); - } - return attributeValue; - } - - @Override - public Set getAttributeNames() { - return this.cached.getAttributeNames(); - } - - @Override - public void setAttribute(String attributeName, Object attributeValue) { - this.cached.setAttribute(attributeName, attributeValue); - this.delta.put(getAttributeKey(attributeName), attributeValue); - } - - @Override - public void removeAttribute(String attributeName) { - this.cached.removeAttribute(attributeName); - this.delta.put(getAttributeKey(attributeName), null); - } - - @Override - public Instant getCreationTime() { - return this.cached.getCreationTime(); - } - - @Override - public void setLastAccessedTime(Instant lastAccessedTime) { - this.cached.setLastAccessedTime(lastAccessedTime); - this.delta.put(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, getLastAccessedTime().toEpochMilli()); - } - - @Override - public Instant getLastAccessedTime() { - return this.cached.getLastAccessedTime(); - } - - @Override - public void setMaxInactiveInterval(Duration interval) { - this.cached.setMaxInactiveInterval(interval); - this.delta.put(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, (int) getMaxInactiveInterval().getSeconds()); - } - - @Override - public Duration getMaxInactiveInterval() { - return this.cached.getMaxInactiveInterval(); - } - - @Override - public boolean isExpired() { - return this.cached.isExpired(); - } - - private boolean hasChangedSessionId() { - return !getId().equals(this.originalSessionId); - } - - private Mono save() { - return Mono.defer(() -> saveChangeSessionId().then(saveDelta()).doOnSuccess((aVoid) -> this.isNew = false)); - } - - private Mono saveDelta() { - if (this.delta.isEmpty()) { - return Mono.empty(); - } - - String sessionKey = getSessionKey(getId()); - Mono update = ReactiveRedisOperationsSessionRepository.this.sessionRedisOperations.opsForHash() - .putAll(sessionKey, new HashMap<>(this.delta)); - Mono setTtl = ReactiveRedisOperationsSessionRepository.this.sessionRedisOperations - .expire(sessionKey, getMaxInactiveInterval()); - - return update.and(setTtl).and((s) -> { - this.delta.clear(); - s.onComplete(); - }).then(); - } - - private Mono saveChangeSessionId() { - if (!hasChangedSessionId()) { - return Mono.empty(); - } - - String sessionId = getId(); - - Publisher replaceSessionId = (s) -> { - this.originalSessionId = sessionId; - s.onComplete(); - }; - - if (this.isNew) { - return Mono.from(replaceSessionId); - } - else { - String originalSessionKey = getSessionKey(this.originalSessionId); - String sessionKey = getSessionKey(sessionId); - - return ReactiveRedisOperationsSessionRepository.this.sessionRedisOperations - .rename(originalSessionKey, sessionKey).and(replaceSessionId); - } - } - - } - } diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/ReactiveRedisSessionRepository.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/ReactiveRedisSessionRepository.java new file mode 100644 index 00000000..81e80824 --- /dev/null +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/ReactiveRedisSessionRepository.java @@ -0,0 +1,312 @@ +/* + * Copyright 2014-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.session.data.redis; + +import java.time.Duration; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; + +import org.springframework.data.redis.core.ReactiveRedisOperations; +import org.springframework.session.MapSession; +import org.springframework.session.ReactiveSessionRepository; +import org.springframework.session.SaveMode; +import org.springframework.session.Session; +import org.springframework.util.Assert; + +/** + * A {@link ReactiveSessionRepository} that is implemented using Spring Data's + * {@link ReactiveRedisOperations}. + * + * @author Vedran Pavic + * @since 2.2.0 + */ +public class ReactiveRedisSessionRepository + implements ReactiveSessionRepository { + + /** + * The default namespace for each key and channel in Redis used by Spring Session. + */ + public static final String DEFAULT_NAMESPACE = "spring:session"; + + private final ReactiveRedisOperations sessionRedisOperations; + + /** + * The namespace for every key used by Spring Session in Redis. + */ + private String namespace = DEFAULT_NAMESPACE + ":"; + + /** + * If non-null, this value is used to override the default value for + * {@link RedisSession#setMaxInactiveInterval(Duration)}. + */ + private Integer defaultMaxInactiveInterval; + + private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE; + + /** + * Create a new {@link ReactiveRedisSessionRepository} instance. + * @param sessionRedisOperations the {@link ReactiveRedisOperations} to use for + * managing sessions + */ + public ReactiveRedisSessionRepository(ReactiveRedisOperations sessionRedisOperations) { + Assert.notNull(sessionRedisOperations, "sessionRedisOperations cannot be null"); + this.sessionRedisOperations = sessionRedisOperations; + } + + public void setRedisKeyNamespace(String namespace) { + Assert.hasText(namespace, "namespace cannot be null or empty"); + this.namespace = namespace.trim() + ":"; + } + + /** + * Sets the maximum inactive interval in seconds between requests before newly created + * sessions will be invalidated. A negative time indicates that the session will never + * timeout. The default is 1800 (30 minutes). + * @param defaultMaxInactiveInterval the number of seconds that the {@link Session} + * should be kept alive between client requests. + */ + public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval) { + this.defaultMaxInactiveInterval = defaultMaxInactiveInterval; + } + + /** + * Set the save mode. + * @param saveMode the save mode + */ + public void setSaveMode(SaveMode saveMode) { + Assert.notNull(saveMode, "saveMode must not be null"); + this.saveMode = saveMode; + } + + /** + * Returns the {@link ReactiveRedisOperations} used for sessions. + * @return the {@link ReactiveRedisOperations} used for sessions + */ + public ReactiveRedisOperations getSessionRedisOperations() { + return this.sessionRedisOperations; + } + + @Override + public Mono createSession() { + return Mono.defer(() -> { + MapSession cached = new MapSession(); + if (this.defaultMaxInactiveInterval != null) { + cached.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval)); + } + RedisSession session = new RedisSession(cached, true); + return Mono.just(session); + }); + } + + @Override + public Mono save(RedisSession session) { + if (session.isNew) { + 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 + public Mono findById(String id) { + String sessionKey = getSessionKey(id); + + // @formatter:off + return this.sessionRedisOperations.opsForHash().entries(sessionKey) + .collectMap((e) -> e.getKey().toString(), Map.Entry::getValue) + .filter((map) -> !map.isEmpty()) + .map(new RedisSessionMapper(id)) + .filter((session) -> !session.isExpired()) + .map((session) -> new RedisSession(session, false)) + .switchIfEmpty(Mono.defer(() -> deleteById(id).then(Mono.empty()))); + // @formatter:on + } + + @Override + public Mono deleteById(String id) { + String sessionKey = getSessionKey(id); + + return this.sessionRedisOperations.delete(sessionKey).then(); + } + + private static String getAttributeKey(String attributeName) { + return RedisSessionMapper.ATTRIBUTE_PREFIX + attributeName; + } + + private String getSessionKey(String sessionId) { + return this.namespace + "sessions:" + sessionId; + } + + /** + * A custom implementation of {@link Session} that uses a {@link MapSession} as the + * basis for its mapping. It keeps track of any attributes that have changed. When + * {@link RedisSession#saveDelta()} is invoked all the attributes that have been + * changed will be persisted. + */ + final class RedisSession implements Session { + + private final MapSession cached; + + private final Map delta = new HashMap<>(); + + private boolean isNew; + + private String originalSessionId; + + RedisSession(MapSession cached, boolean isNew) { + this.cached = cached; + this.isNew = isNew; + this.originalSessionId = cached.getId(); + if (this.isNew) { + this.delta.put(RedisSessionMapper.CREATION_TIME_KEY, cached.getCreationTime().toEpochMilli()); + this.delta.put(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, + (int) cached.getMaxInactiveInterval().getSeconds()); + this.delta.put(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, cached.getLastAccessedTime().toEpochMilli()); + } + if (this.isNew || (ReactiveRedisSessionRepository.this.saveMode == SaveMode.ALWAYS)) { + getAttributeNames().forEach((attributeName) -> this.delta.put(getAttributeKey(attributeName), + cached.getAttribute(attributeName))); + } + } + + @Override + public String getId() { + return this.cached.getId(); + } + + @Override + public String changeSessionId() { + return this.cached.changeSessionId(); + } + + @Override + public T getAttribute(String attributeName) { + T attributeValue = this.cached.getAttribute(attributeName); + if (attributeValue != null + && ReactiveRedisSessionRepository.this.saveMode.equals(SaveMode.ON_GET_ATTRIBUTE)) { + this.delta.put(getAttributeKey(attributeName), attributeValue); + } + return attributeValue; + } + + @Override + public Set getAttributeNames() { + return this.cached.getAttributeNames(); + } + + @Override + public void setAttribute(String attributeName, Object attributeValue) { + this.cached.setAttribute(attributeName, attributeValue); + this.delta.put(getAttributeKey(attributeName), attributeValue); + } + + @Override + public void removeAttribute(String attributeName) { + this.cached.removeAttribute(attributeName); + this.delta.put(getAttributeKey(attributeName), null); + } + + @Override + public Instant getCreationTime() { + return this.cached.getCreationTime(); + } + + @Override + public void setLastAccessedTime(Instant lastAccessedTime) { + this.cached.setLastAccessedTime(lastAccessedTime); + this.delta.put(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, getLastAccessedTime().toEpochMilli()); + } + + @Override + public Instant getLastAccessedTime() { + return this.cached.getLastAccessedTime(); + } + + @Override + public void setMaxInactiveInterval(Duration interval) { + this.cached.setMaxInactiveInterval(interval); + this.delta.put(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, (int) getMaxInactiveInterval().getSeconds()); + } + + @Override + public Duration getMaxInactiveInterval() { + return this.cached.getMaxInactiveInterval(); + } + + @Override + public boolean isExpired() { + return this.cached.isExpired(); + } + + private boolean hasChangedSessionId() { + return !getId().equals(this.originalSessionId); + } + + private Mono save() { + return Mono.defer(() -> saveChangeSessionId().then(saveDelta()).doOnSuccess((aVoid) -> this.isNew = false)); + } + + private Mono saveDelta() { + if (this.delta.isEmpty()) { + return Mono.empty(); + } + + String sessionKey = getSessionKey(getId()); + Mono update = ReactiveRedisSessionRepository.this.sessionRedisOperations.opsForHash() + .putAll(sessionKey, new HashMap<>(this.delta)); + Mono setTtl = ReactiveRedisSessionRepository.this.sessionRedisOperations.expire(sessionKey, + getMaxInactiveInterval()); + + return update.and(setTtl).and((s) -> { + this.delta.clear(); + s.onComplete(); + }).then(); + } + + private Mono saveChangeSessionId() { + if (!hasChangedSessionId()) { + return Mono.empty(); + } + + String sessionId = getId(); + + Publisher replaceSessionId = (s) -> { + this.originalSessionId = sessionId; + s.onComplete(); + }; + + if (this.isNew) { + return Mono.from(replaceSessionId); + } + else { + String originalSessionKey = getSessionKey(this.originalSessionId); + String sessionKey = getSessionKey(sessionId); + + return ReactiveRedisSessionRepository.this.sessionRedisOperations.rename(originalSessionKey, sessionKey) + .and(replaceSessionId); + } + } + + } + +} diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisIndexedSessionRepository.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisIndexedSessionRepository.java new file mode 100644 index 00000000..5e7e2eae --- /dev/null +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisIndexedSessionRepository.java @@ -0,0 +1,857 @@ +/* + * Copyright 2014-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.session.data.redis; + +import java.time.Duration; +import java.time.Instant; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.core.NestedExceptionUtils; +import org.springframework.dao.NonTransientDataAccessException; +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.MessageListener; +import org.springframework.data.redis.core.BoundHashOperations; +import org.springframework.data.redis.core.RedisOperations; +import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.session.DelegatingIndexResolver; +import org.springframework.session.FindByIndexNameSessionRepository; +import org.springframework.session.FlushMode; +import org.springframework.session.IndexResolver; +import org.springframework.session.MapSession; +import org.springframework.session.PrincipalNameIndexResolver; +import org.springframework.session.SaveMode; +import org.springframework.session.Session; +import org.springframework.session.events.SessionCreatedEvent; +import org.springframework.session.events.SessionDeletedEvent; +import org.springframework.session.events.SessionDestroyedEvent; +import org.springframework.session.events.SessionExpiredEvent; +import org.springframework.session.web.http.SessionRepositoryFilter; +import org.springframework.util.Assert; + +/** + *

+ * A {@link org.springframework.session.SessionRepository} that is implemented using + * Spring Data's {@link org.springframework.data.redis.core.RedisOperations}. In a web + * environment, this is typically used in combination with {@link SessionRepositoryFilter} + * . This implementation supports {@link SessionDeletedEvent} and + * {@link SessionExpiredEvent} by implementing {@link MessageListener}. + *

+ * + *

Creating a new instance

+ * + * A typical example of how to create a new instance can be seen below: + * + *
+ * RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
+ *
+ * // ... configure redisTemplate ...
+ *
+ * RedisIndexedSessionRepository redisSessionRepository =
+ *         new RedisIndexedSessionRepository(redisTemplate);
+ * 
+ * + *

+ * For additional information on how to create a RedisTemplate, refer to the + * Spring Data Redis Reference. + *

+ * + *

Storage Details

+ * + * The sections below outline how Redis is updated for each operation. An example of + * creating a new session can be found below. The subsequent sections describe the + * details. + * + *
+ * HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 maxInactiveInterval 1800 lastAccessedTime 1404360000000 sessionAttr:attrName someAttrValue sessionAttr2:attrName someAttrValue2
+ * EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
+ * APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
+ * EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
+ * SADD spring:session:expirations:1439245080000 expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe
+ * EXPIRE spring:session:expirations1439245080000 2100
+ * 
+ * + *

Saving a Session

+ * + *

+ * Each session is stored in Redis as a + * Hash. Each session is set and + * updated using the HMSET command. An + * example of how each session is stored can be seen below. + *

+ * + *
+ * HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 maxInactiveInterval 1800 lastAccessedTime 1404360000000 sessionAttr:attrName someAttrValue sessionAttr:attrName2 someAttrValue2
+ * 
+ * + *

+ * In this example, the session following statements are true about the session: + *

+ *
    + *
  • The session id is 33fdd1b6-b496-4b33-9f7d-df96679d32fe
  • + *
  • The session was created at 1404360000000 in milliseconds since midnight of 1/1/1970 + * GMT.
  • + *
  • The session expires in 1800 seconds (30 minutes).
  • + *
  • The session was last accessed at 1404360000000 in milliseconds since midnight of + * 1/1/1970 GMT.
  • + *
  • The session has two attributes. The first is "attrName" with the value of + * "someAttrValue". The second session attribute is named "attrName2" with the value of + * "someAttrValue2".
  • + *
+ * + * + *

Optimized Writes

+ * + *

+ * The {@link RedisIndexedSessionRepository.RedisSession} keeps track of the properties + * that have changed and only updates those. This means if an attribute is written once + * and read many times we only need to write that attribute once. For example, assume the + * session attribute "sessionAttr2" from earlier was updated. The following would be + * executed upon saving: + *

+ * + *
+ * HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe sessionAttr:attrName2 newValue
+ * 
+ * + *

SessionCreatedEvent

+ * + *

+ * When a session is created an event is sent to Redis with the channel of + * "spring:session:channel:created:33fdd1b6-b496-4b33-9f7d-df96679d32fe" such that + * "33fdd1b6-b496-4b33-9f7d-df96679d32fe" is the sesion id. The body of the event will be + * the session that was created. + *

+ * + *

+ * If registered as a {@link MessageListener}, then {@link RedisIndexedSessionRepository} + * will then translate the Redis message into a {@link SessionCreatedEvent}. + *

+ * + *

Expiration

+ * + *

+ * An expiration is associated to each session using the + * EXPIRE command based upon the + * {@link RedisIndexedSessionRepository.RedisSession#getMaxInactiveInterval()} . For + * example: + *

+ * + *
+ * EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
+ * 
+ * + *

+ * You will note that the expiration that is set is 5 minutes after the session actually + * expires. This is necessary so that the value of the session can be accessed when the + * session expires. An expiration is set on the session itself five minutes after it + * actually expires to ensure it is cleaned up, but only after we perform any necessary + * processing. + *

+ * + *

+ * NOTE: The {@link #findById(String)} method ensures that no expired sessions will + * be returned. This means there is no need to check the expiration before using a session + *

+ * + *

+ * Spring Session relies on the expired and delete + * keyspace notifications from Redis + * to fire a SessionDestroyedEvent. It is the SessionDestroyedEvent that ensures resources + * associated with the Session are cleaned up. For example, when using Spring Session's + * WebSocket support the Redis expired or delete event is what triggers any WebSocket + * connections associated with the session to be closed. + *

+ * + *

+ * Expiration is not tracked directly on the session key itself since this would mean the + * session data would no longer be available. Instead a special session expires key is + * used. In our example the expires key is: + *

+ * + *
+ * APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
+ * EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
+ * 
+ * + *

+ * When a session expires key is deleted or expires, the keyspace notification triggers a + * lookup of the actual session and a {@link SessionDestroyedEvent} is fired. + *

+ * + *

+ * One problem with relying on Redis expiration exclusively is that Redis makes no + * guarantee of when the expired event will be fired if the key has not been accessed. + * Specifically the background task that Redis uses to clean up expired keys is a low + * priority task and may not trigger the key expiration. For additional details see + * Timing of expired events section in + * the Redis documentation. + *

+ * + *

+ * To circumvent the fact that expired events are not guaranteed to happen we can ensure + * that each key is accessed when it is expected to expire. This means that if the TTL is + * expired on the key, Redis will remove the key and fire the expired event when we try to + * access the key. + *

+ * + *

+ * For this reason, each session expiration is also tracked to the nearest minute. This + * allows a background task to access the potentially expired sessions to ensure that + * Redis expired events are fired in a more deterministic fashion. For example: + *

+ * + *
+ * SADD spring:session:expirations:1439245080000 expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe
+ * EXPIRE spring:session:expirations1439245080000 2100
+ * 
+ * + *

+ * The background task will then use these mappings to explicitly request each session + * expires key. By accessing the key, rather than deleting it, we ensure that Redis + * deletes the key for us only if the TTL is expired. + *

+ *

+ * NOTE: We do not explicitly delete the keys since in some instances there may be + * a race condition that incorrectly identifies a key as expired when it is not. Short of + * using distributed locks (which would kill our performance) there is no way to ensure + * the consistency of the expiration mapping. By simply accessing the key, we ensure that + * the key is only removed if the TTL on that key is expired. + *

+ * + * @author Rob Winch + * @author Vedran Pavic + * @since 2.2.0 + */ +public class RedisIndexedSessionRepository + implements FindByIndexNameSessionRepository, MessageListener { + + private static final Log logger = LogFactory.getLog(RedisIndexedSessionRepository.class); + + private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; + + /** + * The default Redis database used by Spring Session. + */ + public static final int DEFAULT_DATABASE = 0; + + /** + * The default namespace for each key and channel in Redis used by Spring Session. + */ + public static final String DEFAULT_NAMESPACE = "spring:session"; + + private int database = DEFAULT_DATABASE; + + /** + * The namespace for every key used by Spring Session in Redis. + */ + private String namespace = DEFAULT_NAMESPACE + ":"; + + private String sessionCreatedChannelPrefix; + + private String sessionDeletedChannel; + + private String sessionExpiredChannel; + + private final RedisOperations sessionRedisOperations; + + private final RedisSessionExpirationPolicy expirationPolicy; + + private final IndexResolver indexResolver; + + private ApplicationEventPublisher eventPublisher = (event) -> { + }; + + /** + * If non-null, this value is used to override the default value for + * {@link RedisSession#setMaxInactiveInterval(Duration)}. + */ + private Integer defaultMaxInactiveInterval; + + private RedisSerializer defaultSerializer = new JdkSerializationRedisSerializer(); + + private FlushMode flushMode = FlushMode.ON_SAVE; + + private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE; + + /** + * Creates a new instance. For an example, refer to the class level javadoc. + * @param sessionRedisOperations the {@link RedisOperations} to use for managing the + * sessions. Cannot be null. + */ + public RedisIndexedSessionRepository(RedisOperations sessionRedisOperations) { + Assert.notNull(sessionRedisOperations, "sessionRedisOperations cannot be null"); + this.sessionRedisOperations = sessionRedisOperations; + this.expirationPolicy = new RedisSessionExpirationPolicy(sessionRedisOperations, this::getExpirationsKey, + this::getSessionKey); + this.indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>()); + configureSessionChannels(); + } + + /** + * Sets the {@link ApplicationEventPublisher} that is used to publish + * {@link SessionDestroyedEvent}. The default is to not publish a + * {@link SessionDestroyedEvent}. + * @param applicationEventPublisher the {@link ApplicationEventPublisher} that is used + * to publish {@link SessionDestroyedEvent}. Cannot be null. + */ + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + Assert.notNull(applicationEventPublisher, "applicationEventPublisher cannot be null"); + this.eventPublisher = applicationEventPublisher; + } + + /** + * Sets the maximum inactive interval in seconds between requests before newly created + * sessions will be invalidated. A negative time indicates that the session will never + * timeout. The default is 1800 (30 minutes). + * @param defaultMaxInactiveInterval the number of seconds that the {@link Session} + * should be kept alive between client requests. + */ + public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval) { + this.defaultMaxInactiveInterval = defaultMaxInactiveInterval; + } + + /** + * Sets the default redis serializer. Replaces default serializer which is based on + * {@link JdkSerializationRedisSerializer}. + * @param defaultSerializer the new default redis serializer + */ + public void setDefaultSerializer(RedisSerializer defaultSerializer) { + Assert.notNull(defaultSerializer, "defaultSerializer cannot be null"); + this.defaultSerializer = defaultSerializer; + } + + /** + * Sets the redis flush mode. Default flush mode is {@link FlushMode#ON_SAVE}. + * @param flushMode the flush mode + */ + public void setFlushMode(FlushMode flushMode) { + Assert.notNull(flushMode, "flushMode cannot be null"); + this.flushMode = flushMode; + } + + /** + * Set the save mode. + * @param saveMode the save mode + */ + public void setSaveMode(SaveMode saveMode) { + Assert.notNull(saveMode, "saveMode must not be null"); + this.saveMode = saveMode; + } + + /** + * Sets the database index to use. Defaults to {@link #DEFAULT_DATABASE}. + * @param database the database index to use + */ + public void setDatabase(int database) { + this.database = database; + configureSessionChannels(); + } + + private void configureSessionChannels() { + this.sessionCreatedChannelPrefix = this.namespace + "event:" + this.database + ":created:"; + this.sessionDeletedChannel = "__keyevent@" + this.database + "__:del"; + this.sessionExpiredChannel = "__keyevent@" + this.database + "__:expired"; + } + + /** + * Returns the {@link RedisOperations} used for sessions. + * @return the {@link RedisOperations} used for sessions + */ + public RedisOperations getSessionRedisOperations() { + return this.sessionRedisOperations; + } + + @Override + public void save(RedisSession session) { + session.save(); + if (session.isNew()) { + String sessionCreatedKey = getSessionCreatedChannel(session.getId()); + this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta); + session.setNew(false); + } + } + + public void cleanupExpiredSessions() { + this.expirationPolicy.cleanExpiredSessions(); + } + + @Override + public RedisSession findById(String id) { + return getSession(id, false); + } + + @Override + public Map findByIndexNameAndIndexValue(String indexName, String indexValue) { + if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) { + return Collections.emptyMap(); + } + String principalKey = getPrincipalKey(indexValue); + Set sessionIds = this.sessionRedisOperations.boundSetOps(principalKey).members(); + Map sessions = new HashMap<>(sessionIds.size()); + for (Object id : sessionIds) { + RedisSession session = findById((String) id); + if (session != null) { + sessions.put(session.getId(), session); + } + } + return sessions; + } + + /** + * Gets the session. + * @param id the session id + * @param allowExpired if true, will also include expired sessions that have not been + * deleted. If false, will ensure expired sessions are not returned. + * @return the Redis session + */ + private RedisSession getSession(String id, boolean allowExpired) { + Map entries = getSessionBoundHashOperations(id).entries(); + if (entries.isEmpty()) { + return null; + } + MapSession loaded = loadSession(id, entries); + if (!allowExpired && loaded.isExpired()) { + return null; + } + RedisSession result = new RedisSession(loaded, false); + result.originalLastAccessTime = loaded.getLastAccessedTime(); + return result; + } + + private MapSession loadSession(String id, Map entries) { + MapSession loaded = new MapSession(id); + for (Map.Entry entry : entries.entrySet()) { + String key = (String) entry.getKey(); + if (RedisSessionMapper.CREATION_TIME_KEY.equals(key)) { + loaded.setCreationTime(Instant.ofEpochMilli((long) entry.getValue())); + } + else if (RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY.equals(key)) { + loaded.setMaxInactiveInterval(Duration.ofSeconds((int) entry.getValue())); + } + else if (RedisSessionMapper.LAST_ACCESSED_TIME_KEY.equals(key)) { + loaded.setLastAccessedTime(Instant.ofEpochMilli((long) entry.getValue())); + } + else if (key.startsWith(RedisSessionMapper.ATTRIBUTE_PREFIX)) { + loaded.setAttribute(key.substring(RedisSessionMapper.ATTRIBUTE_PREFIX.length()), entry.getValue()); + } + } + return loaded; + } + + @Override + public void deleteById(String sessionId) { + RedisSession session = getSession(sessionId, true); + if (session == null) { + return; + } + + cleanupPrincipalIndex(session); + this.expirationPolicy.onDelete(session); + + String expireKey = getExpiredKey(session.getId()); + this.sessionRedisOperations.delete(expireKey); + + session.setMaxInactiveInterval(Duration.ZERO); + save(session); + } + + @Override + public RedisSession createSession() { + MapSession cached = new MapSession(); + if (this.defaultMaxInactiveInterval != null) { + cached.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval)); + } + RedisSession session = new RedisSession(cached, true); + session.flushImmediateIfNecessary(); + return session; + } + + @Override + public void onMessage(Message message, byte[] pattern) { + byte[] messageChannel = message.getChannel(); + byte[] messageBody = message.getBody(); + + String channel = new String(messageChannel); + + if (channel.startsWith(this.sessionCreatedChannelPrefix)) { + // TODO: is this thread safe? + @SuppressWarnings("unchecked") + Map loaded = (Map) this.defaultSerializer.deserialize(message.getBody()); + handleCreated(loaded, channel); + return; + } + + String body = new String(messageBody); + if (!body.startsWith(getExpiredKeyPrefix())) { + return; + } + + boolean isDeleted = channel.equals(this.sessionDeletedChannel); + if (isDeleted || channel.equals(this.sessionExpiredChannel)) { + int beginIndex = body.lastIndexOf(":") + 1; + int endIndex = body.length(); + String sessionId = body.substring(beginIndex, endIndex); + + RedisSession session = getSession(sessionId, true); + + if (session == null) { + logger.warn("Unable to publish SessionDestroyedEvent for session " + sessionId); + return; + } + + if (logger.isDebugEnabled()) { + logger.debug("Publishing SessionDestroyedEvent for session " + sessionId); + } + + cleanupPrincipalIndex(session); + + if (isDeleted) { + handleDeleted(session); + } + else { + handleExpired(session); + } + } + } + + private void cleanupPrincipalIndex(RedisSession session) { + String sessionId = session.getId(); + Map indexes = RedisIndexedSessionRepository.this.indexResolver.resolveIndexesFor(session); + String principal = indexes.get(PRINCIPAL_NAME_INDEX_NAME); + if (principal != null) { + this.sessionRedisOperations.boundSetOps(getPrincipalKey(principal)).remove(sessionId); + } + } + + private void handleCreated(Map loaded, String channel) { + String id = channel.substring(channel.lastIndexOf(":") + 1); + Session session = loadSession(id, loaded); + publishEvent(new SessionCreatedEvent(this, session)); + } + + private void handleDeleted(RedisSession session) { + publishEvent(new SessionDeletedEvent(this, session)); + } + + private void handleExpired(RedisSession session) { + publishEvent(new SessionExpiredEvent(this, session)); + } + + private void publishEvent(ApplicationEvent event) { + try { + this.eventPublisher.publishEvent(event); + } + catch (Throwable ex) { + logger.error("Error publishing " + event + ".", ex); + } + } + + public void setRedisKeyNamespace(String namespace) { + Assert.hasText(namespace, "namespace cannot be null or empty"); + this.namespace = namespace.trim() + ":"; + configureSessionChannels(); + } + + /** + * Gets the Hash key for this session by prefixing it appropriately. + * @param sessionId the session id + * @return the Hash key for this session by prefixing it appropriately. + */ + String getSessionKey(String sessionId) { + return this.namespace + "sessions:" + sessionId; + } + + String getPrincipalKey(String principalName) { + return this.namespace + "index:" + FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME + ":" + + principalName; + } + + String getExpirationsKey(long expiration) { + return this.namespace + "expirations:" + expiration; + } + + private String getExpiredKey(String sessionId) { + return getExpiredKeyPrefix() + sessionId; + } + + private String getSessionCreatedChannel(String sessionId) { + return getSessionCreatedChannelPrefix() + sessionId; + } + + private String getExpiredKeyPrefix() { + return this.namespace + "sessions:expires:"; + } + + /** + * Gets the prefix for the channel that {@link SessionCreatedEvent}s are published to. + * The suffix is the session id of the session that was created. + * @return the prefix for the channel that {@link SessionCreatedEvent}s are published + * to + */ + public String getSessionCreatedChannelPrefix() { + return this.sessionCreatedChannelPrefix; + } + + /** + * Gets the name of the channel that {@link SessionDeletedEvent}s are published to. + * @return the name for the channel that {@link SessionDeletedEvent}s are published to + */ + public String getSessionDeletedChannel() { + return this.sessionDeletedChannel; + } + + /** + * Gets the name of the channel that {@link SessionExpiredEvent}s are published to. + * @return the name for the channel that {@link SessionExpiredEvent}s are published to + */ + public String getSessionExpiredChannel() { + return this.sessionExpiredChannel; + } + + /** + * Gets the {@link BoundHashOperations} to operate on a {@link Session}. + * @param sessionId the id of the {@link Session} to work with + * @return the {@link BoundHashOperations} to operate on a {@link Session} + */ + private BoundHashOperations getSessionBoundHashOperations(String sessionId) { + String key = getSessionKey(sessionId); + return this.sessionRedisOperations.boundHashOps(key); + } + + /** + * Gets the key for the specified session attribute. + * @param attributeName the attribute name + * @return the attribute key name + */ + static String getSessionAttrNameKey(String attributeName) { + return RedisSessionMapper.ATTRIBUTE_PREFIX + attributeName; + } + + /** + * A custom implementation of {@link Session} that uses a {@link MapSession} as the + * basis for its mapping. It keeps track of any attributes that have changed. When + * {@link RedisIndexedSessionRepository.RedisSession#saveDelta()} is invoked all the + * attributes that have been changed will be persisted. + * + * @author Rob Winch + */ + final class RedisSession implements Session { + + private final MapSession cached; + + private Instant originalLastAccessTime; + + private Map delta = new HashMap<>(); + + private boolean isNew; + + private String originalPrincipalName; + + private String originalSessionId; + + RedisSession(MapSession cached, boolean isNew) { + this.cached = cached; + this.isNew = isNew; + this.originalSessionId = cached.getId(); + Map indexes = RedisIndexedSessionRepository.this.indexResolver.resolveIndexesFor(this); + this.originalPrincipalName = indexes.get(PRINCIPAL_NAME_INDEX_NAME); + if (this.isNew) { + this.delta.put(RedisSessionMapper.CREATION_TIME_KEY, cached.getCreationTime().toEpochMilli()); + this.delta.put(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, + (int) cached.getMaxInactiveInterval().getSeconds()); + this.delta.put(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, cached.getLastAccessedTime().toEpochMilli()); + } + if (this.isNew || (RedisIndexedSessionRepository.this.saveMode == SaveMode.ALWAYS)) { + getAttributeNames().forEach((attributeName) -> this.delta.put(getSessionAttrNameKey(attributeName), + cached.getAttribute(attributeName))); + } + } + + public void setNew(boolean isNew) { + this.isNew = isNew; + } + + @Override + public void setLastAccessedTime(Instant lastAccessedTime) { + this.cached.setLastAccessedTime(lastAccessedTime); + this.delta.put(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, getLastAccessedTime().toEpochMilli()); + flushImmediateIfNecessary(); + } + + @Override + public boolean isExpired() { + return this.cached.isExpired(); + } + + public boolean isNew() { + return this.isNew; + } + + @Override + public Instant getCreationTime() { + return this.cached.getCreationTime(); + } + + @Override + public String getId() { + return this.cached.getId(); + } + + @Override + public String changeSessionId() { + return this.cached.changeSessionId(); + } + + @Override + public Instant getLastAccessedTime() { + return this.cached.getLastAccessedTime(); + } + + @Override + public void setMaxInactiveInterval(Duration interval) { + this.cached.setMaxInactiveInterval(interval); + this.delta.put(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, (int) getMaxInactiveInterval().getSeconds()); + flushImmediateIfNecessary(); + } + + @Override + public Duration getMaxInactiveInterval() { + return this.cached.getMaxInactiveInterval(); + } + + @Override + public T getAttribute(String attributeName) { + T attributeValue = this.cached.getAttribute(attributeName); + if (attributeValue != null + && RedisIndexedSessionRepository.this.saveMode.equals(SaveMode.ON_GET_ATTRIBUTE)) { + this.delta.put(getSessionAttrNameKey(attributeName), attributeValue); + } + return attributeValue; + } + + @Override + public Set getAttributeNames() { + return this.cached.getAttributeNames(); + } + + @Override + public void setAttribute(String attributeName, Object attributeValue) { + this.cached.setAttribute(attributeName, attributeValue); + this.delta.put(getSessionAttrNameKey(attributeName), attributeValue); + flushImmediateIfNecessary(); + } + + @Override + public void removeAttribute(String attributeName) { + this.cached.removeAttribute(attributeName); + this.delta.put(getSessionAttrNameKey(attributeName), null); + flushImmediateIfNecessary(); + } + + private void flushImmediateIfNecessary() { + if (RedisIndexedSessionRepository.this.flushMode == FlushMode.IMMEDIATE) { + save(); + } + } + + private void save() { + saveChangeSessionId(); + saveDelta(); + } + + /** + * Saves any attributes that have been changed and updates the expiration of this + * session. + */ + private void saveDelta() { + if (this.delta.isEmpty()) { + return; + } + String sessionId = getId(); + getSessionBoundHashOperations(sessionId).putAll(this.delta); + String principalSessionKey = getSessionAttrNameKey( + FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME); + String securityPrincipalSessionKey = getSessionAttrNameKey(SPRING_SECURITY_CONTEXT); + if (this.delta.containsKey(principalSessionKey) || this.delta.containsKey(securityPrincipalSessionKey)) { + if (this.originalPrincipalName != null) { + String originalPrincipalRedisKey = getPrincipalKey(this.originalPrincipalName); + RedisIndexedSessionRepository.this.sessionRedisOperations.boundSetOps(originalPrincipalRedisKey) + .remove(sessionId); + } + Map indexes = RedisIndexedSessionRepository.this.indexResolver.resolveIndexesFor(this); + String principal = indexes.get(PRINCIPAL_NAME_INDEX_NAME); + this.originalPrincipalName = principal; + if (principal != null) { + String principalRedisKey = getPrincipalKey(principal); + RedisIndexedSessionRepository.this.sessionRedisOperations.boundSetOps(principalRedisKey) + .add(sessionId); + } + } + + this.delta = new HashMap<>(this.delta.size()); + + Long originalExpiration = (this.originalLastAccessTime != null) + ? this.originalLastAccessTime.plus(getMaxInactiveInterval()).toEpochMilli() : null; + RedisIndexedSessionRepository.this.expirationPolicy.onExpirationUpdated(originalExpiration, this); + } + + private void saveChangeSessionId() { + String sessionId = getId(); + if (sessionId.equals(this.originalSessionId)) { + return; + } + if (!isNew()) { + String originalSessionIdKey = getSessionKey(this.originalSessionId); + String sessionIdKey = getSessionKey(sessionId); + try { + RedisIndexedSessionRepository.this.sessionRedisOperations.rename(originalSessionIdKey, + sessionIdKey); + } + catch (NonTransientDataAccessException ex) { + handleErrNoSuchKeyError(ex); + } + String originalExpiredKey = getExpiredKey(this.originalSessionId); + String expiredKey = getExpiredKey(sessionId); + try { + RedisIndexedSessionRepository.this.sessionRedisOperations.rename(originalExpiredKey, expiredKey); + } + catch (NonTransientDataAccessException ex) { + handleErrNoSuchKeyError(ex); + } + } + this.originalSessionId = sessionId; + } + + private void handleErrNoSuchKeyError(NonTransientDataAccessException ex) { + if (!"ERR no such key".equals(NestedExceptionUtils.getMostSpecificCause(ex).getMessage())) { + throw ex; + } + } + + } + +} diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisOperationsSessionRepository.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisOperationsSessionRepository.java index b45dba0e..554a01c3 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisOperationsSessionRepository.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisOperationsSessionRepository.java @@ -16,334 +16,31 @@ package org.springframework.session.data.redis; -import java.time.Duration; -import java.time.Instant; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.core.NestedExceptionUtils; -import org.springframework.dao.NonTransientDataAccessException; -import org.springframework.data.redis.connection.Message; -import org.springframework.data.redis.connection.MessageListener; -import org.springframework.data.redis.core.BoundHashOperations; import org.springframework.data.redis.core.RedisOperations; -import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.session.DelegatingIndexResolver; -import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.FlushMode; -import org.springframework.session.IndexResolver; -import org.springframework.session.MapSession; -import org.springframework.session.PrincipalNameIndexResolver; -import org.springframework.session.SaveMode; -import org.springframework.session.Session; -import org.springframework.session.events.SessionCreatedEvent; -import org.springframework.session.events.SessionDeletedEvent; -import org.springframework.session.events.SessionDestroyedEvent; -import org.springframework.session.events.SessionExpiredEvent; -import org.springframework.session.web.http.SessionRepositoryFilter; +import org.springframework.session.SessionRepository; import org.springframework.util.Assert; /** - *

- * A {@link org.springframework.session.SessionRepository} that is implemented using - * Spring Data's {@link org.springframework.data.redis.core.RedisOperations}. In a web - * environment, this is typically used in combination with {@link SessionRepositoryFilter} - * . This implementation supports {@link SessionDeletedEvent} and - * {@link SessionExpiredEvent} by implementing {@link MessageListener}. - *

- * - *

Creating a new instance

- * - * A typical example of how to create a new instance can be seen below: - * - *
- * RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
- *
- * // ... configure redisTemplate ...
- *
- * RedisOperationsSessionRepository redisSessionRepository =
- *         new RedisOperationsSessionRepository(redisTemplate);
- * 
- * - *

- * For additional information on how to create a RedisTemplate, refer to the - * Spring Data Redis Reference. - *

- * - *

Storage Details

- * - * The sections below outline how Redis is updated for each operation. An example of - * creating a new session can be found below. The subsequent sections describe the - * details. - * - *
- * HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 maxInactiveInterval 1800 lastAccessedTime 1404360000000 sessionAttr:attrName someAttrValue sessionAttr2:attrName someAttrValue2
- * EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
- * APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
- * EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
- * SADD spring:session:expirations:1439245080000 expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe
- * EXPIRE spring:session:expirations1439245080000 2100
- * 
- * - *

Saving a Session

- * - *

- * Each session is stored in Redis as a - * Hash. Each session is set and - * updated using the HMSET command. An - * example of how each session is stored can be seen below. - *

- * - *
- * HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 maxInactiveInterval 1800 lastAccessedTime 1404360000000 sessionAttr:attrName someAttrValue sessionAttr:attrName2 someAttrValue2
- * 
- * - *

- * In this example, the session following statements are true about the session: - *

- *
    - *
  • The session id is 33fdd1b6-b496-4b33-9f7d-df96679d32fe
  • - *
  • The session was created at 1404360000000 in milliseconds since midnight of 1/1/1970 - * GMT.
  • - *
  • The session expires in 1800 seconds (30 minutes).
  • - *
  • The session was last accessed at 1404360000000 in milliseconds since midnight of - * 1/1/1970 GMT.
  • - *
  • The session has two attributes. The first is "attrName" with the value of - * "someAttrValue". The second session attribute is named "attrName2" with the value of - * "someAttrValue2".
  • - *
- * - * - *

Optimized Writes

- * - *

- * The - * {@link org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession} - * keeps track of the properties that have changed and only updates those. This means if - * an attribute is written once and read many times we only need to write that attribute - * once. For example, assume the session attribute "sessionAttr2" from earlier was - * updated. The following would be executed upon saving: - *

- * - *
- * HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe sessionAttr:attrName2 newValue
- * 
- * - *

SessionCreatedEvent

- * - *

- * When a session is created an event is sent to Redis with the channel of - * "spring:session:channel:created:33fdd1b6-b496-4b33-9f7d-df96679d32fe" such that - * "33fdd1b6-b496-4b33-9f7d-df96679d32fe" is the sesion id. The body of the event will be - * the session that was created. - *

- * - *

- * If registered as a {@link MessageListener}, then - * {@link RedisOperationsSessionRepository} will then translate the Redis message into a - * {@link SessionCreatedEvent}. - *

- * - *

Expiration

- * - *

- * An expiration is associated to each session using the - * EXPIRE command based upon the - * {@link org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession#getMaxInactiveInterval()} - * . For example: - *

- * - *
- * EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
- * 
- * - *

- * You will note that the expiration that is set is 5 minutes after the session actually - * expires. This is necessary so that the value of the session can be accessed when the - * session expires. An expiration is set on the session itself five minutes after it - * actually expires to ensure it is cleaned up, but only after we perform any necessary - * processing. - *

- * - *

- * NOTE: The {@link #findById(String)} method ensures that no expired sessions will - * be returned. This means there is no need to check the expiration before using a session - *

- * - *

- * Spring Session relies on the expired and delete - * keyspace notifications from Redis - * to fire a SessionDestroyedEvent. It is the SessionDestroyedEvent that ensures resources - * associated with the Session are cleaned up. For example, when using Spring Session's - * WebSocket support the Redis expired or delete event is what triggers any WebSocket - * connections associated with the session to be closed. - *

- * - *

- * Expiration is not tracked directly on the session key itself since this would mean the - * session data would no longer be available. Instead a special session expires key is - * used. In our example the expires key is: - *

- * - *
- * APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
- * EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
- * 
- * - *

- * When a session expires key is deleted or expires, the keyspace notification triggers a - * lookup of the actual session and a {@link SessionDestroyedEvent} is fired. - *

- * - *

- * One problem with relying on Redis expiration exclusively is that Redis makes no - * guarantee of when the expired event will be fired if the key has not been accessed. - * Specifically the background task that Redis uses to clean up expired keys is a low - * priority task and may not trigger the key expiration. For additional details see - * Timing of expired events section in - * the Redis documentation. - *

- * - *

- * To circumvent the fact that expired events are not guaranteed to happen we can ensure - * that each key is accessed when it is expected to expire. This means that if the TTL is - * expired on the key, Redis will remove the key and fire the expired event when we try to - * access the key. - *

- * - *

- * For this reason, each session expiration is also tracked to the nearest minute. This - * allows a background task to access the potentially expired sessions to ensure that - * Redis expired events are fired in a more deterministic fashion. For example: - *

- * - *
- * SADD spring:session:expirations:1439245080000 expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe
- * EXPIRE spring:session:expirations1439245080000 2100
- * 
- * - *

- * The background task will then use these mappings to explicitly request each session - * expires key. By accessing the key, rather than deleting it, we ensure that Redis - * deletes the key for us only if the TTL is expired. - *

- *

- * NOTE: We do not explicitly delete the keys since in some instances there may be - * a race condition that incorrectly identifies a key as expired when it is not. Short of - * using distributed locks (which would kill our performance) there is no way to ensure - * the consistency of the expiration mapping. By simply accessing the key, we ensure that - * the key is only removed if the TTL on that key is expired. - *

+ * This {@link SessionRepository} implementation is kept in order to support migration to + * {@link RedisIndexedSessionRepository} in a backwards compatible manner. * * @author Rob Winch * @author Vedran Pavic * @since 1.0 + * @deprecated since 2.2.0 in favor of {@link RedisIndexedSessionRepository} */ -public class RedisOperationsSessionRepository - implements FindByIndexNameSessionRepository, MessageListener { - - private static final Log logger = LogFactory.getLog(RedisOperationsSessionRepository.class); - - private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; - - /** - * The default Redis database used by Spring Session. - */ - public static final int DEFAULT_DATABASE = 0; - - /** - * The default namespace for each key and channel in Redis used by Spring Session. - */ - public static final String DEFAULT_NAMESPACE = "spring:session"; - - private int database = RedisOperationsSessionRepository.DEFAULT_DATABASE; - - /** - * The namespace for every key used by Spring Session in Redis. - */ - private String namespace = DEFAULT_NAMESPACE + ":"; - - private String sessionCreatedChannelPrefix; - - private String sessionDeletedChannel; - - private String sessionExpiredChannel; - - private final RedisOperations sessionRedisOperations; - - private final RedisSessionExpirationPolicy expirationPolicy; - - private final IndexResolver indexResolver; - - private ApplicationEventPublisher eventPublisher = (event) -> { - }; - - /** - * If non-null, this value is used to override the default value for - * {@link RedisSession#setMaxInactiveInterval(Duration)}. - */ - private Integer defaultMaxInactiveInterval; - - private RedisSerializer defaultSerializer = new JdkSerializationRedisSerializer(); - - private FlushMode flushMode = FlushMode.ON_SAVE; - - private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE; +@Deprecated +public class RedisOperationsSessionRepository extends RedisIndexedSessionRepository { /** * Creates a new instance. For an example, refer to the class level javadoc. * @param sessionRedisOperations the {@link RedisOperations} to use for managing the * sessions. Cannot be null. + * @see RedisIndexedSessionRepository#RedisIndexedSessionRepository(RedisOperations) */ public RedisOperationsSessionRepository(RedisOperations sessionRedisOperations) { - Assert.notNull(sessionRedisOperations, "sessionRedisOperations cannot be null"); - this.sessionRedisOperations = sessionRedisOperations; - this.expirationPolicy = new RedisSessionExpirationPolicy(sessionRedisOperations, this::getExpirationsKey, - this::getSessionKey); - this.indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>()); - configureSessionChannels(); - } - - /** - * Sets the {@link ApplicationEventPublisher} that is used to publish - * {@link SessionDestroyedEvent}. The default is to not publish a - * {@link SessionDestroyedEvent}. - * @param applicationEventPublisher the {@link ApplicationEventPublisher} that is used - * to publish {@link SessionDestroyedEvent}. Cannot be null. - */ - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - Assert.notNull(applicationEventPublisher, "applicationEventPublisher cannot be null"); - this.eventPublisher = applicationEventPublisher; - } - - /** - * Sets the maximum inactive interval in seconds between requests before newly created - * sessions will be invalidated. A negative time indicates that the session will never - * timeout. The default is 1800 (30 minutes). - * @param defaultMaxInactiveInterval the number of seconds that the {@link Session} - * should be kept alive between client requests. - */ - public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval) { - this.defaultMaxInactiveInterval = defaultMaxInactiveInterval; - } - - /** - * Sets the default redis serializer. Replaces default serializer which is based on - * {@link JdkSerializationRedisSerializer}. - * @param defaultSerializer the new default redis serializer - */ - public void setDefaultSerializer(RedisSerializer defaultSerializer) { - Assert.notNull(defaultSerializer, "defaultSerializer cannot be null"); - this.defaultSerializer = defaultSerializer; + super(sessionRedisOperations); } /** @@ -357,517 +54,4 @@ public class RedisOperationsSessionRepository setFlushMode(redisFlushMode.getFlushMode()); } - /** - * Sets the redis flush mode. Default flush mode is {@link FlushMode#ON_SAVE}. - * @param flushMode the flush mode - */ - public void setFlushMode(FlushMode flushMode) { - Assert.notNull(flushMode, "flushMode cannot be null"); - this.flushMode = flushMode; - } - - /** - * Set the save mode. - * @param saveMode the save mode - */ - public void setSaveMode(SaveMode saveMode) { - Assert.notNull(saveMode, "saveMode must not be null"); - this.saveMode = saveMode; - } - - /** - * Sets the database index to use. Defaults to {@link #DEFAULT_DATABASE}. - * @param database the database index to use - */ - public void setDatabase(int database) { - this.database = database; - configureSessionChannels(); - } - - private void configureSessionChannels() { - this.sessionCreatedChannelPrefix = this.namespace + "event:" + this.database + ":created:"; - this.sessionDeletedChannel = "__keyevent@" + this.database + "__:del"; - this.sessionExpiredChannel = "__keyevent@" + this.database + "__:expired"; - } - - /** - * Returns the {@link RedisOperations} used for sessions. - * @return the {@link RedisOperations} used for sessions - * @since 2.0.0 - */ - public RedisOperations getSessionRedisOperations() { - return this.sessionRedisOperations; - } - - @Override - public void save(RedisSession session) { - session.save(); - if (session.isNew()) { - String sessionCreatedKey = getSessionCreatedChannel(session.getId()); - this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta); - session.setNew(false); - } - } - - public void cleanupExpiredSessions() { - this.expirationPolicy.cleanExpiredSessions(); - } - - @Override - public RedisSession findById(String id) { - return getSession(id, false); - } - - @Override - public Map findByIndexNameAndIndexValue(String indexName, String indexValue) { - if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) { - return Collections.emptyMap(); - } - String principalKey = getPrincipalKey(indexValue); - Set sessionIds = this.sessionRedisOperations.boundSetOps(principalKey).members(); - Map sessions = new HashMap<>(sessionIds.size()); - for (Object id : sessionIds) { - RedisSession session = findById((String) id); - if (session != null) { - sessions.put(session.getId(), session); - } - } - return sessions; - } - - /** - * Gets the session. - * @param id the session id - * @param allowExpired if true, will also include expired sessions that have not been - * deleted. If false, will ensure expired sessions are not returned. - * @return the Redis session - */ - private RedisSession getSession(String id, boolean allowExpired) { - Map entries = getSessionBoundHashOperations(id).entries(); - if (entries.isEmpty()) { - return null; - } - MapSession loaded = loadSession(id, entries); - if (!allowExpired && loaded.isExpired()) { - return null; - } - RedisSession result = new RedisSession(loaded, false); - result.originalLastAccessTime = loaded.getLastAccessedTime(); - return result; - } - - private MapSession loadSession(String id, Map entries) { - MapSession loaded = new MapSession(id); - for (Map.Entry entry : entries.entrySet()) { - String key = (String) entry.getKey(); - if (RedisSessionMapper.CREATION_TIME_KEY.equals(key)) { - loaded.setCreationTime(Instant.ofEpochMilli((long) entry.getValue())); - } - else if (RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY.equals(key)) { - loaded.setMaxInactiveInterval(Duration.ofSeconds((int) entry.getValue())); - } - else if (RedisSessionMapper.LAST_ACCESSED_TIME_KEY.equals(key)) { - loaded.setLastAccessedTime(Instant.ofEpochMilli((long) entry.getValue())); - } - else if (key.startsWith(RedisSessionMapper.ATTRIBUTE_PREFIX)) { - loaded.setAttribute(key.substring(RedisSessionMapper.ATTRIBUTE_PREFIX.length()), entry.getValue()); - } - } - return loaded; - } - - @Override - public void deleteById(String sessionId) { - RedisSession session = getSession(sessionId, true); - if (session == null) { - return; - } - - cleanupPrincipalIndex(session); - this.expirationPolicy.onDelete(session); - - String expireKey = getExpiredKey(session.getId()); - this.sessionRedisOperations.delete(expireKey); - - session.setMaxInactiveInterval(Duration.ZERO); - save(session); - } - - @Override - public RedisSession createSession() { - MapSession cached = new MapSession(); - if (this.defaultMaxInactiveInterval != null) { - cached.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval)); - } - RedisSession session = new RedisSession(cached, true); - session.flushImmediateIfNecessary(); - return session; - } - - @Override - @SuppressWarnings("unchecked") - public void onMessage(Message message, byte[] pattern) { - byte[] messageChannel = message.getChannel(); - byte[] messageBody = message.getBody(); - - String channel = new String(messageChannel); - - if (channel.startsWith(this.sessionCreatedChannelPrefix)) { - // TODO: is this thread safe? - Map loaded = (Map) this.defaultSerializer.deserialize(message.getBody()); - handleCreated(loaded, channel); - return; - } - - String body = new String(messageBody); - if (!body.startsWith(getExpiredKeyPrefix())) { - return; - } - - boolean isDeleted = channel.equals(this.sessionDeletedChannel); - if (isDeleted || channel.equals(this.sessionExpiredChannel)) { - int beginIndex = body.lastIndexOf(":") + 1; - int endIndex = body.length(); - String sessionId = body.substring(beginIndex, endIndex); - - RedisSession session = getSession(sessionId, true); - - if (session == null) { - logger.warn("Unable to publish SessionDestroyedEvent for session " + sessionId); - return; - } - - if (logger.isDebugEnabled()) { - logger.debug("Publishing SessionDestroyedEvent for session " + sessionId); - } - - cleanupPrincipalIndex(session); - - if (isDeleted) { - handleDeleted(session); - } - else { - handleExpired(session); - } - } - } - - private void cleanupPrincipalIndex(RedisSession session) { - String sessionId = session.getId(); - Map indexes = RedisOperationsSessionRepository.this.indexResolver.resolveIndexesFor(session); - String principal = indexes.get(PRINCIPAL_NAME_INDEX_NAME); - if (principal != null) { - this.sessionRedisOperations.boundSetOps(getPrincipalKey(principal)).remove(sessionId); - } - } - - private void handleCreated(Map loaded, String channel) { - String id = channel.substring(channel.lastIndexOf(":") + 1); - Session session = loadSession(id, loaded); - publishEvent(new SessionCreatedEvent(this, session)); - } - - private void handleDeleted(RedisSession session) { - publishEvent(new SessionDeletedEvent(this, session)); - } - - private void handleExpired(RedisSession session) { - publishEvent(new SessionExpiredEvent(this, session)); - } - - private void publishEvent(ApplicationEvent event) { - try { - this.eventPublisher.publishEvent(event); - } - catch (Throwable ex) { - logger.error("Error publishing " + event + ".", ex); - } - } - - public void setRedisKeyNamespace(String namespace) { - Assert.hasText(namespace, "namespace cannot be null or empty"); - this.namespace = namespace.trim() + ":"; - configureSessionChannels(); - } - - /** - * Gets the Hash key for this session by prefixing it appropriately. - * @param sessionId the session id - * @return the Hash key for this session by prefixing it appropriately. - */ - String getSessionKey(String sessionId) { - return this.namespace + "sessions:" + sessionId; - } - - String getPrincipalKey(String principalName) { - return this.namespace + "index:" + FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME + ":" - + principalName; - } - - String getExpirationsKey(long expiration) { - return this.namespace + "expirations:" + expiration; - } - - private String getExpiredKey(String sessionId) { - return getExpiredKeyPrefix() + sessionId; - } - - private String getSessionCreatedChannel(String sessionId) { - return getSessionCreatedChannelPrefix() + sessionId; - } - - private String getExpiredKeyPrefix() { - return this.namespace + "sessions:expires:"; - } - - /** - * Gets the prefix for the channel that {@link SessionCreatedEvent}s are published to. - * The suffix is the session id of the session that was created. - * @return the prefix for the channel that {@link SessionCreatedEvent}s are published - * to - */ - public String getSessionCreatedChannelPrefix() { - return this.sessionCreatedChannelPrefix; - } - - /** - * Gets the name of the channel that {@link SessionDeletedEvent}s are published to. - * @return the name for the channel that {@link SessionDeletedEvent}s are published to - */ - public String getSessionDeletedChannel() { - return this.sessionDeletedChannel; - } - - /** - * Gets the name of the channel that {@link SessionExpiredEvent}s are published to. - * @return the name for the channel that {@link SessionExpiredEvent}s are published to - */ - public String getSessionExpiredChannel() { - return this.sessionExpiredChannel; - } - - /** - * Gets the {@link BoundHashOperations} to operate on a {@link Session}. - * @param sessionId the id of the {@link Session} to work with - * @return the {@link BoundHashOperations} to operate on a {@link Session} - */ - private BoundHashOperations getSessionBoundHashOperations(String sessionId) { - String key = getSessionKey(sessionId); - return this.sessionRedisOperations.boundHashOps(key); - } - - /** - * Gets the key for the specified session attribute. - * @param attributeName the attribute name - * @return the attribute key name - */ - static String getSessionAttrNameKey(String attributeName) { - return RedisSessionMapper.ATTRIBUTE_PREFIX + attributeName; - } - - /** - * A custom implementation of {@link Session} that uses a {@link MapSession} as the - * basis for its mapping. It keeps track of any attributes that have changed. When - * {@link org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession#saveDelta()} - * is invoked all the attributes that have been changed will be persisted. - * - * @author Rob Winch - * @since 1.0 - */ - final class RedisSession implements Session { - - private final MapSession cached; - - private Instant originalLastAccessTime; - - private Map delta = new HashMap<>(); - - private boolean isNew; - - private String originalPrincipalName; - - private String originalSessionId; - - RedisSession(MapSession cached, boolean isNew) { - this.cached = cached; - this.isNew = isNew; - this.originalSessionId = cached.getId(); - Map indexes = RedisOperationsSessionRepository.this.indexResolver.resolveIndexesFor(this); - this.originalPrincipalName = indexes.get(PRINCIPAL_NAME_INDEX_NAME); - if (this.isNew) { - this.delta.put(RedisSessionMapper.CREATION_TIME_KEY, cached.getCreationTime().toEpochMilli()); - this.delta.put(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, - (int) cached.getMaxInactiveInterval().getSeconds()); - this.delta.put(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, cached.getLastAccessedTime().toEpochMilli()); - } - if (this.isNew || (RedisOperationsSessionRepository.this.saveMode == SaveMode.ALWAYS)) { - getAttributeNames().forEach((attributeName) -> this.delta.put(getSessionAttrNameKey(attributeName), - cached.getAttribute(attributeName))); - } - } - - public void setNew(boolean isNew) { - this.isNew = isNew; - } - - @Override - public void setLastAccessedTime(Instant lastAccessedTime) { - this.cached.setLastAccessedTime(lastAccessedTime); - this.delta.put(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, getLastAccessedTime().toEpochMilli()); - flushImmediateIfNecessary(); - } - - @Override - public boolean isExpired() { - return this.cached.isExpired(); - } - - public boolean isNew() { - return this.isNew; - } - - @Override - public Instant getCreationTime() { - return this.cached.getCreationTime(); - } - - @Override - public String getId() { - return this.cached.getId(); - } - - @Override - public String changeSessionId() { - return this.cached.changeSessionId(); - } - - @Override - public Instant getLastAccessedTime() { - return this.cached.getLastAccessedTime(); - } - - @Override - public void setMaxInactiveInterval(Duration interval) { - this.cached.setMaxInactiveInterval(interval); - this.delta.put(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, (int) getMaxInactiveInterval().getSeconds()); - flushImmediateIfNecessary(); - } - - @Override - public Duration getMaxInactiveInterval() { - return this.cached.getMaxInactiveInterval(); - } - - @Override - public T getAttribute(String attributeName) { - T attributeValue = this.cached.getAttribute(attributeName); - if (attributeValue != null - && RedisOperationsSessionRepository.this.saveMode.equals(SaveMode.ON_GET_ATTRIBUTE)) { - this.delta.put(getSessionAttrNameKey(attributeName), attributeValue); - } - return attributeValue; - } - - @Override - public Set getAttributeNames() { - return this.cached.getAttributeNames(); - } - - @Override - public void setAttribute(String attributeName, Object attributeValue) { - this.cached.setAttribute(attributeName, attributeValue); - this.delta.put(getSessionAttrNameKey(attributeName), attributeValue); - flushImmediateIfNecessary(); - } - - @Override - public void removeAttribute(String attributeName) { - this.cached.removeAttribute(attributeName); - this.delta.put(getSessionAttrNameKey(attributeName), null); - flushImmediateIfNecessary(); - } - - private void flushImmediateIfNecessary() { - if (RedisOperationsSessionRepository.this.flushMode == FlushMode.IMMEDIATE) { - save(); - } - } - - private void save() { - saveChangeSessionId(); - saveDelta(); - } - - /** - * Saves any attributes that have been changed and updates the expiration of this - * session. - */ - private void saveDelta() { - if (this.delta.isEmpty()) { - return; - } - String sessionId = getId(); - getSessionBoundHashOperations(sessionId).putAll(this.delta); - String principalSessionKey = getSessionAttrNameKey( - FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME); - String securityPrincipalSessionKey = getSessionAttrNameKey(SPRING_SECURITY_CONTEXT); - if (this.delta.containsKey(principalSessionKey) || this.delta.containsKey(securityPrincipalSessionKey)) { - if (this.originalPrincipalName != null) { - String originalPrincipalRedisKey = getPrincipalKey(this.originalPrincipalName); - RedisOperationsSessionRepository.this.sessionRedisOperations.boundSetOps(originalPrincipalRedisKey) - .remove(sessionId); - } - Map indexes = RedisOperationsSessionRepository.this.indexResolver - .resolveIndexesFor(this); - String principal = indexes.get(PRINCIPAL_NAME_INDEX_NAME); - this.originalPrincipalName = principal; - if (principal != null) { - String principalRedisKey = getPrincipalKey(principal); - RedisOperationsSessionRepository.this.sessionRedisOperations.boundSetOps(principalRedisKey) - .add(sessionId); - } - } - - this.delta = new HashMap<>(this.delta.size()); - - Long originalExpiration = (this.originalLastAccessTime != null) - ? this.originalLastAccessTime.plus(getMaxInactiveInterval()).toEpochMilli() : null; - RedisOperationsSessionRepository.this.expirationPolicy.onExpirationUpdated(originalExpiration, this); - } - - 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) { - if (!"ERR no such key".equals(NestedExceptionUtils.getMostSpecificCause(ex).getMessage())) { - throw ex; - } - } - - } - } diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisSessionExpirationPolicy.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisSessionExpirationPolicy.java index 4465fcf7..cde14a81 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisSessionExpirationPolicy.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisSessionExpirationPolicy.java @@ -28,7 +28,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.RedisOperations; import org.springframework.session.Session; -import org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession; +import org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession; /** * A strategy for expiring {@link RedisSession} instances. This performs two operations: diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/SimpleRedisOperationsSessionRepository.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisSessionRepository.java similarity index 89% rename from spring-session-data-redis/src/main/java/org/springframework/session/data/redis/SimpleRedisOperationsSessionRepository.java rename to spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisSessionRepository.java index 6a4d17b7..cdbb52c9 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/SimpleRedisOperationsSessionRepository.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisSessionRepository.java @@ -40,8 +40,7 @@ import org.springframework.util.Assert; * @author Vedran Pavic * @since 2.2.0 */ -public class SimpleRedisOperationsSessionRepository - implements SessionRepository { +public class RedisSessionRepository implements SessionRepository { private static final String DEFAULT_KEY_NAMESPACE = "spring:session:"; @@ -56,11 +55,11 @@ public class SimpleRedisOperationsSessionRepository private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE; /** - * Create a new {@link SimpleRedisOperationsSessionRepository} instance. + * Create a new {@link RedisSessionRepository} instance. * @param sessionRedisOperations the {@link RedisOperations} to use for managing * sessions */ - public SimpleRedisOperationsSessionRepository(RedisOperations sessionRedisOperations) { + public RedisSessionRepository(RedisOperations sessionRedisOperations) { Assert.notNull(sessionRedisOperations, "sessionRedisOperations mut not be null"); this.sessionRedisOperations = sessionRedisOperations; } @@ -182,7 +181,7 @@ public class SimpleRedisOperationsSessionRepository (int) cached.getMaxInactiveInterval().getSeconds()); this.delta.put(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, cached.getLastAccessedTime().toEpochMilli()); } - if (this.isNew || (SimpleRedisOperationsSessionRepository.this.saveMode == SaveMode.ALWAYS)) { + if (this.isNew || (RedisSessionRepository.this.saveMode == SaveMode.ALWAYS)) { getAttributeNames().forEach((attributeName) -> this.delta.put(getAttributeKey(attributeName), cached.getAttribute(attributeName))); } @@ -201,8 +200,7 @@ public class SimpleRedisOperationsSessionRepository @Override public T getAttribute(String attributeName) { T attributeValue = this.cached.getAttribute(attributeName); - if (attributeValue != null - && SimpleRedisOperationsSessionRepository.this.saveMode.equals(SaveMode.ON_GET_ATTRIBUTE)) { + if (attributeValue != null && RedisSessionRepository.this.saveMode.equals(SaveMode.ON_GET_ATTRIBUTE)) { this.delta.put(getAttributeKey(attributeName), attributeValue); } return attributeValue; @@ -260,7 +258,7 @@ public class SimpleRedisOperationsSessionRepository } private void flushIfRequired() { - if (SimpleRedisOperationsSessionRepository.this.flushMode == FlushMode.IMMEDIATE) { + if (RedisSessionRepository.this.flushMode == FlushMode.IMMEDIATE) { save(); } } @@ -282,8 +280,7 @@ public class SimpleRedisOperationsSessionRepository if (!this.isNew) { String originalSessionIdKey = getSessionKey(this.originalSessionId); String sessionIdKey = getSessionKey(getId()); - SimpleRedisOperationsSessionRepository.this.sessionRedisOperations.rename(originalSessionIdKey, - sessionIdKey); + RedisSessionRepository.this.sessionRedisOperations.rename(originalSessionIdKey, sessionIdKey); } this.originalSessionId = getId(); } @@ -294,9 +291,8 @@ public class SimpleRedisOperationsSessionRepository return; } String key = getSessionKey(getId()); - SimpleRedisOperationsSessionRepository.this.sessionRedisOperations.opsForHash().putAll(key, - new HashMap<>(this.delta)); - SimpleRedisOperationsSessionRepository.this.sessionRedisOperations.expireAt(key, + RedisSessionRepository.this.sessionRedisOperations.opsForHash().putAll(key, new HashMap<>(this.delta)); + RedisSessionRepository.this.sessionRedisOperations.expireAt(key, Date.from(Instant.ofEpochMilli(getLastAccessedTime().toEpochMilli()) .plusSeconds(getMaxInactiveInterval().getSeconds()))); this.delta.clear(); diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/ConfigureNotifyKeyspaceEventsAction.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/ConfigureNotifyKeyspaceEventsAction.java index 300b6c4f..2d3fc275 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/ConfigureNotifyKeyspaceEventsAction.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/ConfigureNotifyKeyspaceEventsAction.java @@ -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. @@ -81,7 +81,7 @@ public class ConfigureNotifyKeyspaceEventsAction implements ConfigureRedisAction } catch (InvalidDataAccessApiUsageException ex) { throw new IllegalStateException( - "Unable to configure Redis to keyspace notifications. See https://docs.spring.io/spring-session/docs/current/reference/html5/#api-redisoperationssessionrepository-sessiondestroyedevent", + "Unable to configure Redis to keyspace notifications. See https://docs.spring.io/spring-session/docs/current/reference/html5/#api-redisindexedsessionrepository-sessiondestroyedevent", ex); } } diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/SpringSessionRedisConnectionFactory.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/SpringSessionRedisConnectionFactory.java index 6ec11fa6..3060c8db 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/SpringSessionRedisConnectionFactory.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/SpringSessionRedisConnectionFactory.java @@ -24,11 +24,11 @@ import java.lang.annotation.Target; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.session.data.redis.RedisOperationsSessionRepository; +import org.springframework.session.data.redis.RedisIndexedSessionRepository; /** * Qualifier annotation for a {@link RedisConnectionFactory} to be injected in - * {@link RedisOperationsSessionRepository}. + * {@link RedisIndexedSessionRepository}. * * @author Vedran Pavic * @since 2.0.0 diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/SpringSessionRedisOperations.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/SpringSessionRedisOperations.java index 69f7fdcb..58410510 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/SpringSessionRedisOperations.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/SpringSessionRedisOperations.java @@ -23,14 +23,18 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.beans.factory.annotation.Value; +import org.springframework.session.data.redis.ReactiveRedisSessionRepository; +import org.springframework.session.data.redis.RedisIndexedSessionRepository; +import org.springframework.session.data.redis.RedisSessionRepository; /** * Annotation used to inject the Redis accessor used by Spring Session's Redis session * repository. * * @author Vedran Pavic - * @see org.springframework.session.data.redis.RedisOperationsSessionRepository#getSessionRedisOperations() - * @see org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository#getSessionRedisOperations() + * @see RedisIndexedSessionRepository#getSessionRedisOperations() + * @see RedisSessionRepository#getSessionRedisOperations() + * @see ReactiveRedisSessionRepository#getSessionRedisOperations() * @since 2.0.0 */ @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE }) diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisHttpSession.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisHttpSession.java index 1b22458e..76b7a283 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisHttpSession.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisHttpSession.java @@ -32,7 +32,7 @@ import org.springframework.session.Session; import org.springframework.session.SessionRepository; import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; import org.springframework.session.data.redis.RedisFlushMode; -import org.springframework.session.data.redis.RedisOperationsSessionRepository; +import org.springframework.session.data.redis.RedisIndexedSessionRepository; import org.springframework.session.web.http.SessionRepositoryFilter; /** @@ -85,7 +85,7 @@ public @interface EnableRedisHttpSession { * the applications and they could function within the same Redis instance. * @return the unique namespace for keys */ - String redisNamespace() default RedisOperationsSessionRepository.DEFAULT_NAMESPACE; + String redisNamespace() default RedisIndexedSessionRepository.DEFAULT_NAMESPACE; /** * Flush mode for the Redis sessions. The default is {@code ON_SAVE} which only diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.java index 4b69204d..a352797e 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.java @@ -56,7 +56,7 @@ import org.springframework.session.SaveMode; import org.springframework.session.config.SessionRepositoryCustomizer; import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration; import org.springframework.session.data.redis.RedisFlushMode; -import org.springframework.session.data.redis.RedisOperationsSessionRepository; +import org.springframework.session.data.redis.RedisIndexedSessionRepository; import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction; import org.springframework.session.data.redis.config.ConfigureRedisAction; import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory; @@ -86,7 +86,7 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio private Integer maxInactiveIntervalInSeconds = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS; - private String redisNamespace = RedisOperationsSessionRepository.DEFAULT_NAMESPACE; + private String redisNamespace = RedisIndexedSessionRepository.DEFAULT_NAMESPACE; private FlushMode flushMode = FlushMode.ON_SAVE; @@ -106,16 +106,16 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio private Executor redisSubscriptionExecutor; - private List> sessionRepositoryCustomizers; + private List> sessionRepositoryCustomizers; private ClassLoader classLoader; private StringValueResolver embeddedValueResolver; @Bean - public RedisOperationsSessionRepository sessionRepository() { + public RedisIndexedSessionRepository sessionRepository() { RedisTemplate redisTemplate = createRedisTemplate(); - RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(redisTemplate); + RedisIndexedSessionRepository sessionRepository = new RedisIndexedSessionRepository(redisTemplate); sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher); if (this.defaultRedisSerializer != null) { sessionRepository.setDefaultSerializer(this.defaultRedisSerializer); @@ -135,7 +135,7 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio @Bean public RedisMessageListenerContainer springSessionRedisMessageListenerContainer( - RedisOperationsSessionRepository sessionRepository) { + RedisIndexedSessionRepository sessionRepository) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(this.redisConnectionFactory); if (this.redisTaskExecutor != null) { @@ -230,7 +230,7 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio @Autowired(required = false) public void setSessionRepositoryCustomizer( - ObjectProvider> sessionRepositoryCustomizers) { + ObjectProvider> sessionRepositoryCustomizers) { this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList()); } @@ -295,7 +295,7 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio && this.redisConnectionFactory instanceof JedisConnectionFactory) { return ((JedisConnectionFactory) this.redisConnectionFactory).getDatabase(); } - return RedisOperationsSessionRepository.DEFAULT_DATABASE; + return RedisIndexedSessionRepository.DEFAULT_DATABASE; } /** diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/server/EnableRedisWebSession.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/server/EnableRedisWebSession.java index 39766e47..ffe432e8 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/server/EnableRedisWebSession.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/server/EnableRedisWebSession.java @@ -30,7 +30,7 @@ import org.springframework.session.ReactiveSessionRepository; import org.springframework.session.SaveMode; import org.springframework.session.Session; import org.springframework.session.config.annotation.web.server.EnableSpringWebSession; -import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository; +import org.springframework.session.data.redis.ReactiveRedisSessionRepository; import org.springframework.session.data.redis.RedisFlushMode; import org.springframework.web.server.session.WebSessionManager; @@ -83,7 +83,7 @@ public @interface EnableRedisWebSession { * the applications and they could function within the same Redis instance. * @return the unique namespace for keys */ - String redisNamespace() default ReactiveRedisOperationsSessionRepository.DEFAULT_NAMESPACE; + String redisNamespace() default ReactiveRedisSessionRepository.DEFAULT_NAMESPACE; /** * Flush mode for the Redis sessions. The default is {@code ON_SAVE} which only diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/server/RedisWebSessionConfiguration.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/server/RedisWebSessionConfiguration.java index 31bfa991..648a2114 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/server/RedisWebSessionConfiguration.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/server/RedisWebSessionConfiguration.java @@ -40,7 +40,7 @@ import org.springframework.session.MapSession; import org.springframework.session.SaveMode; import org.springframework.session.config.ReactiveSessionRepositoryCustomizer; import org.springframework.session.config.annotation.web.server.SpringWebSessionConfiguration; -import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository; +import org.springframework.session.data.redis.ReactiveRedisSessionRepository; import org.springframework.session.data.redis.RedisFlushMode; import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory; import org.springframework.util.Assert; @@ -63,7 +63,7 @@ public class RedisWebSessionConfiguration extends SpringWebSessionConfiguration private Integer maxInactiveIntervalInSeconds = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS; - private String redisNamespace = ReactiveRedisOperationsSessionRepository.DEFAULT_NAMESPACE; + private String redisNamespace = ReactiveRedisSessionRepository.DEFAULT_NAMESPACE; private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE; @@ -71,17 +71,16 @@ public class RedisWebSessionConfiguration extends SpringWebSessionConfiguration private RedisSerializer defaultRedisSerializer; - private List> sessionRepositoryCustomizers; + private List> sessionRepositoryCustomizers; private ClassLoader classLoader; private StringValueResolver embeddedValueResolver; @Bean - public ReactiveRedisOperationsSessionRepository sessionRepository() { + public ReactiveRedisSessionRepository sessionRepository() { ReactiveRedisTemplate reactiveRedisTemplate = createReactiveRedisTemplate(); - ReactiveRedisOperationsSessionRepository sessionRepository = new ReactiveRedisOperationsSessionRepository( - reactiveRedisTemplate); + ReactiveRedisSessionRepository sessionRepository = new ReactiveRedisSessionRepository(reactiveRedisTemplate); sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds); if (StringUtils.hasText(this.redisNamespace)) { sessionRepository.setRedisKeyNamespace(this.redisNamespace); @@ -129,7 +128,7 @@ public class RedisWebSessionConfiguration extends SpringWebSessionConfiguration @Autowired(required = false) public void setSessionRepositoryCustomizer( - ObjectProvider> sessionRepositoryCustomizers) { + ObjectProvider> sessionRepositoryCustomizers) { this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList()); } diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/ReactiveRedisOperationsSessionRepositoryTests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/ReactiveRedisSessionRepositoryTests.java similarity index 96% rename from spring-session-data-redis/src/test/java/org/springframework/session/data/redis/ReactiveRedisOperationsSessionRepositoryTests.java rename to spring-session-data-redis/src/test/java/org/springframework/session/data/redis/ReactiveRedisSessionRepositoryTests.java index d8d45b0d..da58aadf 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/ReactiveRedisOperationsSessionRepositoryTests.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/ReactiveRedisSessionRepositoryTests.java @@ -33,7 +33,7 @@ import org.springframework.data.redis.core.ReactiveHashOperations; import org.springframework.data.redis.core.ReactiveRedisOperations; import org.springframework.session.MapSession; import org.springframework.session.SaveMode; -import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository.RedisSession; +import org.springframework.session.data.redis.ReactiveRedisSessionRepository.RedisSession; import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -46,11 +46,11 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; /** - * Tests for {@link ReactiveRedisOperationsSessionRepository}. + * Tests for {@link ReactiveRedisSessionRepository}. * * @author Vedran Pavic */ -class ReactiveRedisOperationsSessionRepositoryTests { +class ReactiveRedisSessionRepositoryTests { @SuppressWarnings("unchecked") private ReactiveRedisOperations redisOperations = mock(ReactiveRedisOperations.class); @@ -61,13 +61,13 @@ class ReactiveRedisOperationsSessionRepositoryTests { @SuppressWarnings("unchecked") private ArgumentCaptor> delta = ArgumentCaptor.forClass(Map.class); - private ReactiveRedisOperationsSessionRepository repository; + private ReactiveRedisSessionRepository repository; private MapSession cached; @BeforeEach void setUp() { - this.repository = new ReactiveRedisOperationsSessionRepository(this.redisOperations); + this.repository = new ReactiveRedisSessionRepository(this.redisOperations); this.cached = new MapSession(); this.cached.setId("session-id"); @@ -77,7 +77,7 @@ class ReactiveRedisOperationsSessionRepositoryTests { @Test void constructorWithNullReactiveRedisOperations() { - assertThatIllegalArgumentException().isThrownBy(() -> new ReactiveRedisOperationsSessionRepository(null)) + assertThatIllegalArgumentException().isThrownBy(() -> new ReactiveRedisSessionRepository(null)) .withMessageContaining("sessionRedisOperations cannot be null"); } @@ -206,7 +206,7 @@ class ReactiveRedisOperationsSessionRepositoryTests { verifyZeroInteractions(this.hashOperations); assertThat(this.delta.getAllValues().get(0)).isEqualTo( - map(RedisOperationsSessionRepository.getSessionAttrNameKey(attrName), session.getAttribute(attrName))); + map(RedisIndexedSessionRepository.getSessionAttrNameKey(attrName), session.getAttribute(attrName))); } @Test @@ -229,7 +229,7 @@ class ReactiveRedisOperationsSessionRepositoryTests { verifyZeroInteractions(this.hashOperations); assertThat(this.delta.getAllValues().get(0)) - .isEqualTo(map(RedisOperationsSessionRepository.getSessionAttrNameKey(attrName), null)); + .isEqualTo(map(RedisIndexedSessionRepository.getSessionAttrNameKey(attrName), null)); } @Test diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisOperationsSessionRepositoryTests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryTests.java similarity index 94% rename from spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisOperationsSessionRepositoryTests.java rename to spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryTests.java index 3c87b77b..658c8a45 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisOperationsSessionRepositoryTests.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryTests.java @@ -37,8 +37,6 @@ import org.mockito.MockitoAnnotations; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.redis.connection.DefaultMessage; -import org.springframework.data.redis.connection.RedisConnection; -import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.BoundHashOperations; import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.BoundValueOperations; @@ -50,7 +48,7 @@ import org.springframework.session.FlushMode; import org.springframework.session.MapSession; import org.springframework.session.SaveMode; import org.springframework.session.Session; -import org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession; +import org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession; import org.springframework.session.events.AbstractSessionEvent; import static org.assertj.core.api.Assertions.assertThat; @@ -67,49 +65,40 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; -@SuppressWarnings({ "unchecked", "rawtypes", "deprecation" }) -class RedisOperationsSessionRepositoryTests { - - private static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; +class RedisIndexedSessionRepositoryTests { @Mock - RedisConnectionFactory factory; + private RedisOperations redisOperations; @Mock - RedisConnection connection; + private BoundValueOperations boundValueOperations; @Mock - RedisOperations redisOperations; + private BoundHashOperations boundHashOperations; @Mock - BoundValueOperations boundValueOperations; + private BoundSetOperations boundSetOperations; @Mock - BoundHashOperations boundHashOperations; + private ApplicationEventPublisher publisher; @Mock - BoundSetOperations boundSetOperations; - - @Mock - ApplicationEventPublisher publisher; - - @Mock - RedisSerializer defaultSerializer; + private RedisSerializer defaultSerializer; @Captor - ArgumentCaptor event; + private ArgumentCaptor event; @Captor - ArgumentCaptor> delta; + private ArgumentCaptor> delta; private MapSession cached; - private RedisOperationsSessionRepository redisRepository; + private RedisIndexedSessionRepository redisRepository; @BeforeEach void setup() { MockitoAnnotations.initMocks(this); - this.redisRepository = new RedisOperationsSessionRepository(this.redisOperations); + this.redisRepository = new RedisIndexedSessionRepository(this.redisOperations); this.redisRepository.setDefaultSerializer(this.defaultSerializer); this.cached = new MapSession(); @@ -277,7 +266,7 @@ class RedisOperationsSessionRepositoryTests { this.redisRepository.save(session); assertThat(getDelta()).isEqualTo( - map(RedisOperationsSessionRepository.getSessionAttrNameKey(attrName), session.getAttribute(attrName))); + map(RedisIndexedSessionRepository.getSessionAttrNameKey(attrName), session.getAttribute(attrName))); } @Test @@ -291,7 +280,7 @@ class RedisOperationsSessionRepositoryTests { this.redisRepository.save(session); - assertThat(getDelta()).isEqualTo(map(RedisOperationsSessionRepository.getSessionAttrNameKey(attrName), null)); + assertThat(getDelta()).isEqualTo(map(RedisIndexedSessionRepository.getSessionAttrNameKey(attrName), null)); } @Test @@ -320,6 +309,7 @@ class RedisOperationsSessionRepositoryTests { } @Test + @SuppressWarnings("unchecked") void delete() { String attrName = "attrName"; MapSession expected = new MapSession(); @@ -327,7 +317,7 @@ class RedisOperationsSessionRepositoryTests { expected.setAttribute(attrName, "attrValue"); given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); - Map map = map(RedisOperationsSessionRepository.getSessionAttrNameKey(attrName), expected.getAttribute(attrName), + Map map = map(RedisIndexedSessionRepository.getSessionAttrNameKey(attrName), expected.getAttribute(attrName), RedisSessionMapper.CREATION_TIME_KEY, expected.getCreationTime().toEpochMilli(), RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, (int) expected.getMaxInactiveInterval().getSeconds(), RedisSessionMapper.LAST_ACCESSED_TIME_KEY, expected.getLastAccessedTime().toEpochMilli()); @@ -353,6 +343,7 @@ class RedisOperationsSessionRepositoryTests { } @Test + @SuppressWarnings("unchecked") void getSessionNotFound() { String id = "abc"; given(this.redisOperations.boundHashOps(getKey(id))).willReturn(this.boundHashOperations); @@ -362,6 +353,7 @@ class RedisOperationsSessionRepositoryTests { } @Test + @SuppressWarnings("unchecked") void getSessionFound() { String attribute1 = "attribute1"; String attribute2 = "attribute2"; @@ -370,8 +362,8 @@ class RedisOperationsSessionRepositoryTests { expected.setAttribute(attribute1, "test"); expected.setAttribute(attribute2, null); given(this.redisOperations.boundHashOps(getKey(expected.getId()))).willReturn(this.boundHashOperations); - Map map = map(RedisOperationsSessionRepository.getSessionAttrNameKey(attribute1), - expected.getAttribute(attribute1), RedisOperationsSessionRepository.getSessionAttrNameKey(attribute2), + Map map = map(RedisIndexedSessionRepository.getSessionAttrNameKey(attribute1), + expected.getAttribute(attribute1), RedisIndexedSessionRepository.getSessionAttrNameKey(attribute2), expected.getAttribute(attribute2), RedisSessionMapper.CREATION_TIME_KEY, expected.getCreationTime().toEpochMilli(), RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, (int) expected.getMaxInactiveInterval().getSeconds(), RedisSessionMapper.LAST_ACCESSED_TIME_KEY, @@ -391,6 +383,7 @@ class RedisOperationsSessionRepositoryTests { } @Test + @SuppressWarnings("unchecked") void getSessionExpired() { String expiredId = "expired-id"; given(this.redisOperations.boundHashOps(getKey(expiredId))).willReturn(this.boundHashOperations); @@ -402,6 +395,7 @@ class RedisOperationsSessionRepositoryTests { } @Test + @SuppressWarnings("unchecked") void findByPrincipalNameExpired() { String expiredId = "expired-id"; given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); @@ -417,6 +411,7 @@ class RedisOperationsSessionRepositoryTests { } @Test + @SuppressWarnings("unchecked") void findByPrincipalName() { Instant lastAccessed = Instant.now().minusMillis(10); Instant createdTime = lastAccessed.minusMillis(10); @@ -446,7 +441,6 @@ class RedisOperationsSessionRepositoryTests { @Test void cleanupExpiredSessions() { - String expiredId = "expired-id"; given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); Set expiredIds = new HashSet<>(Arrays.asList("expired-key1", "expired-key2")); @@ -497,6 +491,7 @@ class RedisOperationsSessionRepositoryTests { } @Test + @SuppressWarnings("unchecked") void onMessageDeletedSessionFound() { String deletedId = "deleted-id"; given(this.redisOperations.boundHashOps(getKey(deletedId))).willReturn(this.boundHashOperations); @@ -523,6 +518,7 @@ class RedisOperationsSessionRepositoryTests { } @Test + @SuppressWarnings("unchecked") void onMessageDeletedSessionNotFound() { String deletedId = "deleted-id"; given(this.redisOperations.boundHashOps(getKey(deletedId))).willReturn(this.boundHashOperations); @@ -545,6 +541,7 @@ class RedisOperationsSessionRepositoryTests { } @Test + @SuppressWarnings("unchecked") void onMessageExpiredSessionFound() { String expiredId = "expired-id"; given(this.redisOperations.boundHashOps(getKey(expiredId))).willReturn(this.boundHashOperations); @@ -571,6 +568,7 @@ class RedisOperationsSessionRepositoryTests { } @Test + @SuppressWarnings("unchecked") void onMessageExpiredSessionNotFound() { String expiredId = "expired-id"; given(this.redisOperations.boundHashOps(getKey(expiredId))).willReturn(this.boundHashOperations); @@ -677,7 +675,7 @@ class RedisOperationsSessionRepositoryTests { Map delta = getDelta(2); assertThat(delta.size()).isEqualTo(1); assertThat(delta).isEqualTo( - map(RedisOperationsSessionRepository.getSessionAttrNameKey(attrName), session.getAttribute(attrName))); + map(RedisIndexedSessionRepository.getSessionAttrNameKey(attrName), session.getAttribute(attrName))); } @Test @@ -694,10 +692,11 @@ class RedisOperationsSessionRepositoryTests { Map delta = getDelta(2); assertThat(delta.size()).isEqualTo(1); assertThat(delta).isEqualTo( - map(RedisOperationsSessionRepository.getSessionAttrNameKey(attrName), session.getAttribute(attrName))); + map(RedisIndexedSessionRepository.getSessionAttrNameKey(attrName), session.getAttribute(attrName))); } @Test + @SuppressWarnings("unchecked") void flushModeSetMaxInactiveIntervalInSeconds() { given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); @@ -736,12 +735,6 @@ class RedisOperationsSessionRepositoryTests { .withMessage("flushMode cannot be null"); } - @Test - void setRedisFlushModeNull() { - assertThatIllegalArgumentException().isThrownBy(() -> this.redisRepository.setRedisFlushMode(null)) - .withMessage("redisFlushMode cannot be null"); - } - @Test void changeRedisNamespace() { String namespace = "foo:bar"; diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisSessionExpirationPolicyTests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisSessionExpirationPolicyTests.java index 1f4f24e0..af62e4b0 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisSessionExpirationPolicyTests.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisSessionExpirationPolicyTests.java @@ -66,7 +66,7 @@ class RedisSessionExpirationPolicyTests { @BeforeEach void setup() { MockitoAnnotations.initMocks(this); - RedisOperationsSessionRepository repository = new RedisOperationsSessionRepository(this.sessionRedisOperations); + RedisIndexedSessionRepository repository = new RedisIndexedSessionRepository(this.sessionRedisOperations); this.policy = new RedisSessionExpirationPolicy(this.sessionRedisOperations, repository::getExpirationsKey, repository::getSessionKey); this.session = new MapSession(); diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/SimpleRedisOperationsSessionRepositoryTests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisSessionRepositoryTests.java similarity index 97% rename from spring-session-data-redis/src/test/java/org/springframework/session/data/redis/SimpleRedisOperationsSessionRepositoryTests.java rename to spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisSessionRepositoryTests.java index 012eeef1..9ffda6d2 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/SimpleRedisOperationsSessionRepositoryTests.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisSessionRepositoryTests.java @@ -36,7 +36,7 @@ import org.springframework.data.redis.core.RedisOperations; import org.springframework.session.FlushMode; import org.springframework.session.MapSession; import org.springframework.session.SaveMode; -import org.springframework.session.data.redis.SimpleRedisOperationsSessionRepository.RedisSession; +import org.springframework.session.data.redis.RedisSessionRepository.RedisSession; import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -48,11 +48,11 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; /** - * Tests for {@link SimpleRedisOperationsSessionRepository}. + * Tests for {@link RedisSessionRepository}. * * @author Vedran Pavic */ -class SimpleRedisOperationsSessionRepositoryTests { +class RedisSessionRepositoryTests { private static final String TEST_SESSION_ID = "session-id"; @@ -67,18 +67,18 @@ class SimpleRedisOperationsSessionRepositoryTests { @Captor private ArgumentCaptor> delta; - private SimpleRedisOperationsSessionRepository sessionRepository; + private RedisSessionRepository sessionRepository; @BeforeEach void setUp() { MockitoAnnotations.initMocks(this); given(this.sessionRedisOperations.opsForHash()).willReturn(this.sessionHashOperations); - this.sessionRepository = new SimpleRedisOperationsSessionRepository(this.sessionRedisOperations); + this.sessionRepository = new RedisSessionRepository(this.sessionRedisOperations); } @Test void constructor_NullRedisOperations_ShouldThrowException() { - assertThatIllegalArgumentException().isThrownBy(() -> new ReactiveRedisOperationsSessionRepository(null)) + assertThatIllegalArgumentException().isThrownBy(() -> new ReactiveRedisSessionRepository(null)) .withMessageContaining("sessionRedisOperations cannot be null"); } diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationTests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationTests.java index 23a3f1e3..bee60f6a 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationTests.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationTests.java @@ -39,7 +39,7 @@ import org.springframework.session.FlushMode; import org.springframework.session.SaveMode; import org.springframework.session.config.SessionRepositoryCustomizer; import org.springframework.session.data.redis.RedisFlushMode; -import org.springframework.session.data.redis.RedisOperationsSessionRepository; +import org.springframework.session.data.redis.RedisIndexedSessionRepository; import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory; import org.springframework.test.util.ReflectionTestUtils; @@ -56,7 +56,6 @@ import static org.mockito.Mockito.mock; * @author Mark Paluch * @author Vedran Pavic */ -@SuppressWarnings("deprecation") class RedisHttpSessionConfigurationTests { private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600; @@ -97,8 +96,7 @@ class RedisHttpSessionConfigurationTests { @Test void customFlushImmediately() { registerAndRefresh(RedisConfig.class, CustomFlushImmediatelyConfiguration.class); - RedisOperationsSessionRepository sessionRepository = this.context - .getBean(RedisOperationsSessionRepository.class); + RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class); assertThat(sessionRepository).isNotNull(); assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); } @@ -106,8 +104,7 @@ class RedisHttpSessionConfigurationTests { @Test void customFlushImmediatelyLegacy() { registerAndRefresh(RedisConfig.class, CustomFlushImmediatelyLegacyConfiguration.class); - RedisOperationsSessionRepository sessionRepository = this.context - .getBean(RedisOperationsSessionRepository.class); + RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class); assertThat(sessionRepository).isNotNull(); assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); } @@ -115,8 +112,7 @@ class RedisHttpSessionConfigurationTests { @Test void setCustomFlushImmediately() { registerAndRefresh(RedisConfig.class, CustomFlushImmediatelySetConfiguration.class); - RedisOperationsSessionRepository sessionRepository = this.context - .getBean(RedisOperationsSessionRepository.class); + RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class); assertThat(sessionRepository).isNotNull(); assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); } @@ -124,8 +120,7 @@ class RedisHttpSessionConfigurationTests { @Test void setCustomFlushImmediatelyLegacy() { registerAndRefresh(RedisConfig.class, CustomFlushImmediatelySetLegacyConfiguration.class); - RedisOperationsSessionRepository sessionRepository = this.context - .getBean(RedisOperationsSessionRepository.class); + RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class); assertThat(sessionRepository).isNotNull(); assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); } @@ -151,14 +146,14 @@ class RedisHttpSessionConfigurationTests { @Test void customSaveModeAnnotation() { registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionAnnotationConfiguration.class); - assertThat(this.context.getBean(RedisOperationsSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode", + assertThat(this.context.getBean(RedisIndexedSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode", SaveMode.ALWAYS); } @Test void customSaveModeSetter() { registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionSetterConfiguration.class); - assertThat(this.context.getBean(RedisOperationsSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode", + assertThat(this.context.getBean(RedisIndexedSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode", SaveMode.ALWAYS); } @@ -166,7 +161,7 @@ class RedisHttpSessionConfigurationTests { void qualifiedConnectionFactoryRedisConfig() { registerAndRefresh(RedisConfig.class, QualifiedConnectionFactoryRedisConfig.class); - RedisOperationsSessionRepository repository = this.context.getBean(RedisOperationsSessionRepository.class); + RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class); RedisConnectionFactory redisConnectionFactory = this.context.getBean("qualifiedRedisConnectionFactory", RedisConnectionFactory.class); assertThat(repository).isNotNull(); @@ -182,7 +177,7 @@ class RedisHttpSessionConfigurationTests { void primaryConnectionFactoryRedisConfig() { registerAndRefresh(RedisConfig.class, PrimaryConnectionFactoryRedisConfig.class); - RedisOperationsSessionRepository repository = this.context.getBean(RedisOperationsSessionRepository.class); + RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class); RedisConnectionFactory redisConnectionFactory = this.context.getBean("primaryRedisConnectionFactory", RedisConnectionFactory.class); assertThat(repository).isNotNull(); @@ -198,7 +193,7 @@ class RedisHttpSessionConfigurationTests { void qualifiedAndPrimaryConnectionFactoryRedisConfig() { registerAndRefresh(RedisConfig.class, QualifiedAndPrimaryConnectionFactoryRedisConfig.class); - RedisOperationsSessionRepository repository = this.context.getBean(RedisOperationsSessionRepository.class); + RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class); RedisConnectionFactory redisConnectionFactory = this.context.getBean("qualifiedRedisConnectionFactory", RedisConnectionFactory.class); assertThat(repository).isNotNull(); @@ -214,7 +209,7 @@ class RedisHttpSessionConfigurationTests { void namedConnectionFactoryRedisConfig() { registerAndRefresh(RedisConfig.class, NamedConnectionFactoryRedisConfig.class); - RedisOperationsSessionRepository repository = this.context.getBean(RedisOperationsSessionRepository.class); + RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class); RedisConnectionFactory redisConnectionFactory = this.context.getBean("redisConnectionFactory", RedisConnectionFactory.class); assertThat(repository).isNotNull(); @@ -245,8 +240,7 @@ class RedisHttpSessionConfigurationTests { @Test void sessionRepositoryCustomizer() { registerAndRefresh(RedisConfig.class, SessionRepositoryCustomizerConfiguration.class); - RedisOperationsSessionRepository sessionRepository = this.context - .getBean(RedisOperationsSessionRepository.class); + RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class); assertThat(sessionRepository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", MAX_INACTIVE_INTERVAL_IN_SECONDS); } @@ -294,6 +288,7 @@ class RedisHttpSessionConfigurationTests { } @Configuration + @SuppressWarnings("deprecation") static class CustomFlushImmediatelySetLegacyConfiguration extends RedisHttpSessionConfiguration { CustomFlushImmediatelySetLegacyConfiguration() { @@ -310,6 +305,7 @@ class RedisHttpSessionConfigurationTests { @Configuration @EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE) + @SuppressWarnings("deprecation") static class CustomFlushImmediatelyLegacyConfiguration { } @@ -434,13 +430,13 @@ class RedisHttpSessionConfigurationTests { @Bean @Order(0) - public SessionRepositoryCustomizer sessionRepositoryCustomizerOne() { + public SessionRepositoryCustomizer sessionRepositoryCustomizerOne() { return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(0); } @Bean @Order(1) - public SessionRepositoryCustomizer sessionRepositoryCustomizerTwo() { + public SessionRepositoryCustomizer sessionRepositoryCustomizerTwo() { return (sessionRepository) -> sessionRepository .setDefaultMaxInactiveInterval(MAX_INACTIVE_INTERVAL_IN_SECONDS); } diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/gh109/Gh109Tests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/gh109/Gh109Tests.java index 12e007db..94ee0d5e 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/gh109/Gh109Tests.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/gh109/Gh109Tests.java @@ -27,7 +27,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisOperations; -import org.springframework.session.data.redis.RedisOperationsSessionRepository; +import org.springframework.session.data.redis.RedisIndexedSessionRepository; import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -42,7 +42,6 @@ import static org.mockito.Mockito.mock; * * @author Rob Winch * @author Mark Paluch - * @since 1.0.2 */ @ExtendWith(SpringExtension.class) @ContextConfiguration @@ -63,10 +62,9 @@ class Gh109Tests { * override sessionRepository construction to set the custom session-timeout */ @Bean - public RedisOperationsSessionRepository sessionRepository(RedisOperations sessionRedisTemplate, + public RedisIndexedSessionRepository sessionRepository(RedisOperations sessionRedisTemplate, ApplicationEventPublisher applicationEventPublisher) { - RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository( - sessionRedisTemplate); + RedisIndexedSessionRepository sessionRepository = new RedisIndexedSessionRepository(sessionRedisTemplate); sessionRepository.setDefaultMaxInactiveInterval(this.sessionTimeout); return sessionRepository; } diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/server/RedisWebSessionConfigurationTests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/server/RedisWebSessionConfigurationTests.java index 94ea22e2..281adadc 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/server/RedisWebSessionConfigurationTests.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/server/RedisWebSessionConfigurationTests.java @@ -32,7 +32,7 @@ import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.session.SaveMode; import org.springframework.session.config.ReactiveSessionRepositoryCustomizer; -import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository; +import org.springframework.session.data.redis.ReactiveRedisSessionRepository; import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory; import org.springframework.session.data.redis.config.annotation.SpringSessionRedisOperations; import org.springframework.test.util.ReflectionTestUtils; @@ -70,8 +70,7 @@ class RedisWebSessionConfigurationTests { void defaultConfiguration() { registerAndRefresh(RedisConfig.class, DefaultConfig.class); - ReactiveRedisOperationsSessionRepository repository = this.context - .getBean(ReactiveRedisOperationsSessionRepository.class); + ReactiveRedisSessionRepository repository = this.context.getBean(ReactiveRedisSessionRepository.class); assertThat(repository).isNotNull(); } @@ -79,8 +78,7 @@ class RedisWebSessionConfigurationTests { void springSessionRedisOperationsResolvingConfiguration() { registerAndRefresh(RedisConfig.class, SpringSessionRedisOperationsResolvingConfig.class); - ReactiveRedisOperationsSessionRepository repository = this.context - .getBean(ReactiveRedisOperationsSessionRepository.class); + ReactiveRedisSessionRepository repository = this.context.getBean(ReactiveRedisSessionRepository.class); assertThat(repository).isNotNull(); ReactiveRedisOperations springSessionRedisOperations = this.context .getBean(SpringSessionRedisOperationsResolvingConfig.class).getSpringSessionRedisOperations(); @@ -93,8 +91,7 @@ class RedisWebSessionConfigurationTests { void customNamespace() { registerAndRefresh(RedisConfig.class, CustomNamespaceConfig.class); - ReactiveRedisOperationsSessionRepository repository = this.context - .getBean(ReactiveRedisOperationsSessionRepository.class); + ReactiveRedisSessionRepository repository = this.context.getBean(ReactiveRedisSessionRepository.class); assertThat(repository).isNotNull(); assertThat(ReflectionTestUtils.getField(repository, "namespace")).isEqualTo(REDIS_NAMESPACE + ":"); } @@ -103,8 +100,7 @@ class RedisWebSessionConfigurationTests { void customMaxInactiveInterval() { registerAndRefresh(RedisConfig.class, CustomMaxInactiveIntervalConfig.class); - ReactiveRedisOperationsSessionRepository repository = this.context - .getBean(ReactiveRedisOperationsSessionRepository.class); + ReactiveRedisSessionRepository repository = this.context.getBean(ReactiveRedisSessionRepository.class); assertThat(repository).isNotNull(); assertThat(ReflectionTestUtils.getField(repository, "defaultMaxInactiveInterval")) .isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS); @@ -113,23 +109,22 @@ class RedisWebSessionConfigurationTests { @Test void customSaveModeAnnotation() { registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionAnnotationConfiguration.class); - assertThat(this.context.getBean(ReactiveRedisOperationsSessionRepository.class)) - .hasFieldOrPropertyWithValue("saveMode", SaveMode.ALWAYS); + assertThat(this.context.getBean(ReactiveRedisSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode", + SaveMode.ALWAYS); } @Test void customSaveModeSetter() { registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionSetterConfiguration.class); - assertThat(this.context.getBean(ReactiveRedisOperationsSessionRepository.class)) - .hasFieldOrPropertyWithValue("saveMode", SaveMode.ALWAYS); + assertThat(this.context.getBean(ReactiveRedisSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode", + SaveMode.ALWAYS); } @Test void qualifiedConnectionFactoryRedisConfig() { registerAndRefresh(RedisConfig.class, QualifiedConnectionFactoryRedisConfig.class); - ReactiveRedisOperationsSessionRepository repository = this.context - .getBean(ReactiveRedisOperationsSessionRepository.class); + ReactiveRedisSessionRepository repository = this.context.getBean(ReactiveRedisSessionRepository.class); ReactiveRedisConnectionFactory redisConnectionFactory = this.context.getBean("qualifiedRedisConnectionFactory", ReactiveRedisConnectionFactory.class); assertThat(repository).isNotNull(); @@ -145,8 +140,7 @@ class RedisWebSessionConfigurationTests { void primaryConnectionFactoryRedisConfig() { registerAndRefresh(RedisConfig.class, PrimaryConnectionFactoryRedisConfig.class); - ReactiveRedisOperationsSessionRepository repository = this.context - .getBean(ReactiveRedisOperationsSessionRepository.class); + ReactiveRedisSessionRepository repository = this.context.getBean(ReactiveRedisSessionRepository.class); ReactiveRedisConnectionFactory redisConnectionFactory = this.context.getBean("primaryRedisConnectionFactory", ReactiveRedisConnectionFactory.class); assertThat(repository).isNotNull(); @@ -162,8 +156,7 @@ class RedisWebSessionConfigurationTests { void qualifiedAndPrimaryConnectionFactoryRedisConfig() { registerAndRefresh(RedisConfig.class, QualifiedAndPrimaryConnectionFactoryRedisConfig.class); - ReactiveRedisOperationsSessionRepository repository = this.context - .getBean(ReactiveRedisOperationsSessionRepository.class); + ReactiveRedisSessionRepository repository = this.context.getBean(ReactiveRedisSessionRepository.class); ReactiveRedisConnectionFactory redisConnectionFactory = this.context.getBean("qualifiedRedisConnectionFactory", ReactiveRedisConnectionFactory.class); assertThat(repository).isNotNull(); @@ -179,8 +172,7 @@ class RedisWebSessionConfigurationTests { void namedConnectionFactoryRedisConfig() { registerAndRefresh(RedisConfig.class, NamedConnectionFactoryRedisConfig.class); - ReactiveRedisOperationsSessionRepository repository = this.context - .getBean(ReactiveRedisOperationsSessionRepository.class); + ReactiveRedisSessionRepository repository = this.context.getBean(ReactiveRedisSessionRepository.class); ReactiveRedisConnectionFactory redisConnectionFactory = this.context.getBean("redisConnectionFactory", ReactiveRedisConnectionFactory.class); assertThat(repository).isNotNull(); @@ -200,12 +192,11 @@ class RedisWebSessionConfigurationTests { } @Test - @SuppressWarnings("unchecked") void customRedisSerializerConfig() { registerAndRefresh(RedisConfig.class, CustomRedisSerializerConfig.class); - ReactiveRedisOperationsSessionRepository repository = this.context - .getBean(ReactiveRedisOperationsSessionRepository.class); + ReactiveRedisSessionRepository repository = this.context.getBean(ReactiveRedisSessionRepository.class); + @SuppressWarnings("unchecked") RedisSerializer redisSerializer = this.context.getBean("springSessionDefaultRedisSerializer", RedisSerializer.class); assertThat(repository).isNotNull(); @@ -227,8 +218,7 @@ class RedisWebSessionConfigurationTests { @Test void sessionRepositoryCustomizer() { registerAndRefresh(RedisConfig.class, SessionRepositoryCustomizerConfiguration.class); - ReactiveRedisOperationsSessionRepository sessionRepository = this.context - .getBean(ReactiveRedisOperationsSessionRepository.class); + ReactiveRedisSessionRepository sessionRepository = this.context.getBean(ReactiveRedisSessionRepository.class); assertThat(sessionRepository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", MAX_INACTIVE_INTERVAL_IN_SECONDS); } @@ -364,13 +354,13 @@ class RedisWebSessionConfigurationTests { @Bean @Order(0) - public ReactiveSessionRepositoryCustomizer sessionRepositoryCustomizerOne() { + public ReactiveSessionRepositoryCustomizer sessionRepositoryCustomizerOne() { return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(0); } @Bean @Order(1) - public ReactiveSessionRepositoryCustomizer sessionRepositoryCustomizerTwo() { + public ReactiveSessionRepositoryCustomizer sessionRepositoryCustomizerTwo() { return (sessionRepository) -> sessionRepository .setDefaultMaxInactiveInterval(MAX_INACTIVE_INTERVAL_IN_SECONDS); } diff --git a/spring-session-docs/src/docs/asciidoc/index.adoc b/spring-session-docs/src/docs/asciidoc/index.adoc index d43abc7a..e064ce08 100644 --- a/spring-session-docs/src/docs/asciidoc/index.adoc +++ b/spring-session-docs/src/docs/asciidoc/index.adoc @@ -75,7 +75,7 @@ To get started with Spring Session, the best place to start is our Sample Applic | | {gh-samples-url}spring-session-sample-boot-redis-simple[HttpSession with simple Redis `SessionRepository`] -| Demonstrates how to use Spring Session to replace the `HttpSession` with Redis using `SimpleRedisOperationsSessionRepository`. +| Demonstrates how to use Spring Session to replace the `HttpSession` with Redis using `RedisSessionRepository`. | |=== @@ -590,12 +590,12 @@ You can browse the complete link:../../api/[Javadoc] online. The key APIs are de * <> * <> * <> -* <> -* <> +* <> +* <> * <> * <> -* <> -* <> +* <> +* <> [[api-session]] === Using `Session` @@ -727,31 +727,31 @@ Note that no infrastructure for session expirations is configured for you. This is because things such as session expiration are highly implementation-dependent. This means that, if you require cleaning up expired sessions, you are responsible for cleaning up the expired sessions. -[[api-redisoperationssessionrepository]] -=== Using `RedisOperationsSessionRepository` +[[api-redisindexedsessionrepository]] +=== Using `RedisIndexedSessionRepository` -`RedisOperationsSessionRepository` is a `SessionRepository` that is implemented by using Spring Data's `RedisOperations`. +`RedisIndexedSessionRepository` is a `SessionRepository` that is implemented by using Spring Data's `RedisOperations`. In a web environment, this is typically used in combination with `SessionRepositoryFilter`. The implementation supports `SessionDestroyedEvent` and `SessionCreatedEvent` through `SessionMessageListener`. -[[api-redisoperationssessionrepository-new]] -==== Instantiating a `RedisOperationsSessionRepository` +[[api-redisindexedsessionrepository-new]] +==== Instantiating a `RedisIndexedSessionRepository` You can see a typical example of how to create a new instance in the following listing: ==== [source,java,indent=0] ---- -include::{indexdoc-tests}[tags=new-redisoperationssessionrepository] +include::{indexdoc-tests}[tags=new-redisindexedsessionrepository] ---- ==== For additional information on how to create a `RedisConnectionFactory`, see the Spring Data Redis Reference. -[[api-redisoperationssessionrepository-config]] +[[api-redisindexedsessionrepository-config]] ==== Using `@EnableRedisHttpSession` -In a web environment, the simplest way to create a new `RedisOperationsSessionRepository` is to use `@EnableRedisHttpSession`. +In a web environment, the simplest way to create a new `RedisIndexedSessionRepository` is to use `@EnableRedisHttpSession`. You can find complete example usage in the <>. You can use the following attributes to customize the configuration: @@ -766,11 +766,11 @@ You can customize the serialization by creating a bean named `springSessionDefau ==== Redis `TaskExecutor` -`RedisOperationsSessionRepository` is subscribed to receive events from Redis by using a `RedisMessageListenerContainer`. +`RedisIndexedSessionRepository` is subscribed to receive events from Redis by using a `RedisMessageListenerContainer`. You can customize the way those events are dispatched by creating a bean named `springSessionRedisTaskExecutor`, a bean `springSessionRedisSubscriptionExecutor`, or both. You can find more details on configuring Redis task executors https://docs.spring.io/spring-data-redis/docs/{spring-data-redis-version}/reference/html/#redis:pubsub:subscribe:containers[here]. -[[api-redisoperationssessionrepository-storage]] +[[api-redisindexedsessionrepository-storage]] ==== Storage Details The following sections outline how Redis is updated for each operation. @@ -819,10 +819,10 @@ In the preceding example, the following statements are true about the session: The first is `attrName`, with a value of `someAttrValue`. The second session attribute is named `attrName2`, with a value of `someAttrValue2`. -[[api-redisoperationssessionrepository-writes]] +[[api-redisindexedsessionrepository-writes]] ===== Optimized Writes -The `Session` instances managed by `RedisOperationsSessionRepository` keeps track of the properties that have changed and updates only those. +The `Session` instances managed by `RedisIndexedSessionRepository` keeps track of the properties that have changed and updates only those. This means that, if an attribute is written once and read many times, we need to write that attribute only once. For example, assume the `sessionAttr2` session attribute from the lsiting in the preceding section was updated. The following command would be run upon saving: @@ -833,7 +833,7 @@ HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe sessionAttr:a ---- ==== -[[api-redisoperationssessionrepository-expiration]] +[[api-redisindexedsessionrepository-expiration]] ===== Session Expiration An expiration is associated with each session by using the `EXPIRE` command, based upon the `Session.getMaxInactiveInterval()`. @@ -852,7 +852,7 @@ An expiration is set on the session itself five minutes after it actually expire NOTE: The `SessionRepository.findById(String)` method ensures that no expired sessions are returned. This means that you need not check the expiration before using a session. -Spring Session relies on the delete and expired https://redis.io/topics/notifications[keyspace notifications] from Redis to fire a <> and a <>, respectively. +Spring Session relies on the delete and expired https://redis.io/topics/notifications[keyspace notifications] from Redis to fire a <> and a <>, respectively. `SessionDeletedEvent` or `SessionExpiredEvent` ensure that resources associated with the `Session` are cleaned up. For example, when you use Spring Session's WebSocket support, the Redis expired or delete event triggers any WebSocket connections associated with the session to be closed. @@ -893,12 +893,12 @@ Short of using distributed locks (which would kill our performance), there is no By simply accessing the key, we ensure that the key is only removed if the TTL on that key is expired. -[[api-redisoperationssessionrepository-sessiondestroyedevent]] +[[api-redisindexedsessionrepository-sessiondestroyedevent]] ==== `SessionDeletedEvent` and `SessionExpiredEvent` `SessionDeletedEvent` and `SessionExpiredEvent` are both types of `SessionDestroyedEvent`. -`RedisOperationsSessionRepository` supports firing a `SessionDeletedEvent` when a `Session` is deleted or a `SessionExpiredEvent` when a `Session` expires. +`RedisIndexedSessionRepository` supports firing a `SessionDeletedEvent` when a `Session` is deleted or a `SessionExpiredEvent` when a `Session` expires. This is necessary to ensure resources associated with the `Session` are properly cleaned up. For example, when integrating with WebSockets, the `SessionDestroyedEvent` is in charge of closing any active WebSocket connections. @@ -937,15 +937,15 @@ include::{docs-test-resources-dir}docs/HttpSessionConfigurationNoOpConfigureRedi ---- ==== -[[api-redisoperationssessionrepository-sessioncreatedevent]] +[[api-redisindexedsessionrepository-sessioncreatedevent]] ==== Using `SessionCreatedEvent` When a session is created, an event is sent to Redis with a channel ID of `spring:session:channel:created:33fdd1b6-b496-4b33-9f7d-df96679d32fe`, where `33fdd1b6-b496-4b33-9f7d-df96679d32fe` is the session ID. The body of the event is the session that was created. -If registered as a `MessageListener` (the default), `RedisOperationsSessionRepository` then translates the Redis message into a `SessionCreatedEvent`. +If registered as a `MessageListener` (the default), `RedisIndexedSessionRepository` then translates the Redis message into a `SessionCreatedEvent`. -[[api-redisoperationssessionrepository-cli]] +[[api-redisindexedsessionrepository-cli]] ==== Viewing the Session in Redis After https://redis.io/topics/quickstart[installing redis-cli], you can inspect the values in Redis https://redis.io/commands#hash[using the redis-cli]. @@ -980,30 +980,30 @@ redis 127.0.0.1:6379> hget spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed ---- ==== -[[api-reactiveredisoperationssessionrepository]] -=== Using `ReactiveRedisOperationsSessionRepository` +[[api-reactiveredissessionrepository]] +=== Using `ReactiveRedisSessionRepository` -`ReactiveRedisOperationsSessionRepository` is a `ReactiveSessionRepository` that is implemented by using Spring Data's `ReactiveRedisOperations`. +`ReactiveRedisSessionRepository` is a `ReactiveSessionRepository` that is implemented by using Spring Data's `ReactiveRedisOperations`. In a web environment, this is typically used in combination with `WebSessionStore`. -[[api-reactiveredisoperationssessionrepository-new]] -==== Instantiating a `ReactiveRedisOperationsSessionRepository` +[[api-reactiveredissessionrepository-new]] +==== Instantiating a `ReactiveRedisSessionRepository` The following example shows how to create a new instance: ==== [source,java,indent=0] ---- -include::{indexdoc-tests}[tags=new-reactiveredisoperationssessionrepository] +include::{indexdoc-tests}[tags=new-reactiveredissessionrepository] ---- ==== For additional information on how to create a `ReactiveRedisConnectionFactory`, see the Spring Data Redis Reference. -[[api-reactiveredisoperationssessionrepository-config]] +[[api-reactiveredissessionrepository-config]] ==== Using `@EnableRedisWebSession` -In a web environment, the simplest way to create a new `ReactiveRedisOperationsSessionRepository` is to use `@EnableRedisWebSession`. +In a web environment, the simplest way to create a new `ReactiveRedisSessionRepository` is to use `@EnableRedisWebSession`. You can use the following attributes to customize the configuration: * *maxInactiveIntervalInSeconds*: The amount of time before the session expires, in seconds @@ -1011,13 +1011,13 @@ You can use the following attributes to customize the configuration: * *flushMode*: Allows specifying when data is written to Redis. The default is only when `save` is invoked on `ReactiveSessionRepository`. A value of `FlushMode.IMMEDIATE` writes to Redis as soon as possible. -[[api-reactiveredisoperationssessionrepository-writes]] +[[api-reactiveredissessionrepository-writes]] ===== Optimized Writes -The `Session` instances managed by `ReactiveRedisOperationsSessionRepository` keep track of the properties that have changed and updates only those. +The `Session` instances managed by `ReactiveRedisSessionRepository` keep track of the properties that have changed and updates only those. This means that, if an attribute is written once and read many times, we need to write that attribute only once. -[[api-reactiveredisoperationssessionrepository-cli]] +[[api-reactiveredissessionrepository-cli]] ==== Viewing the Session in Redis After https://redis.io/topics/quickstart[installing redis-cli], you can inspect the values in Redis https://redis.io/commands#hash[using the redis-cli]. @@ -1101,31 +1101,31 @@ The `ReactiveMapSessionRepository` allows for persisting `Session` in a `Map`, w You can use the implementation with a `ConcurrentHashMap` as a testing or convenience mechanism. Alternatively, you can use it with distributed `Map` implementations, with the requirement that the supplied `Map` must be non-blocking. -[[api-jdbcoperationssessionrepository]] -=== Using `JdbcOperationsSessionRepository` +[[api-jdbcindexedsessionrepository]] +=== Using `JdbcIndexedSessionRepository` -`JdbcOperationsSessionRepository` is a `SessionRepository` implementation that uses Spring's `JdbcOperations` to store sessions in a relational database. +`JdbcIndexedSessionRepository` is a `SessionRepository` implementation that uses Spring's `JdbcOperations` to store sessions in a relational database. In a web environment, this is typically used in combination with `SessionRepositoryFilter`. Note that this implementation does not support publishing of session events. -[[api-jdbcoperationssessionrepository-new]] -==== Instantiating a `JdbcOperationsSessionRepository` +[[api-jdbcindexedsessionrepository-new]] +==== Instantiating a `JdbcIndexedSessionRepository` The following example shows how to create a new instance: ==== [source,java,indent=0] ---- -include::{indexdoc-tests}[tags=new-jdbcoperationssessionrepository] +include::{indexdoc-tests}[tags=new-jdbcindexedsessionrepository] ---- ==== For additional information on how to create and configure `JdbcTemplate` and `PlatformTransactionManager`, see the https://docs.spring.io/spring/docs/{spring-framework-version}/spring-framework-reference/data-access.html[Spring Framework Reference Documentation]. -[[api-jdbcoperationssessionrepository-config]] +[[api-jdbcindexedsessionrepository-config]] ==== Using `@EnableJdbcHttpSession` -In a web environment, the simplest way to create a new `JdbcOperationsSessionRepository` is to use `@EnableJdbcHttpSession`. +In a web environment, the simplest way to create a new `JdbcIndexedSessionRepository` is to use `@EnableJdbcHttpSession`. You can find complete example usage in the <> You can use the following attributes to customize the configuration: @@ -1142,7 +1142,7 @@ You can customize the default serialization and deserialization of the session b When working in a typical Spring environment, the default `ConversionService` bean (named `conversionService`) is automatically picked up and used for serialization and deserialization. However, you can override the default `ConversionService` by providing a bean named `springSessionConversionService`. -[[api-jdbcoperationssessionrepository-storage]] +[[api-jdbcindexedsessionrepository-storage]] ==== Storage Details By default, this implementation uses `SPRING_SESSION` and `SPRING_SESSION_ATTRIBUTES` tables to store sessions. @@ -1172,24 +1172,24 @@ include::{session-jdbc-main-resources-dir}org/springframework/session/jdbc/schem ==== Transaction Management -All JDBC operations in `JdbcOperationsSessionRepository` are executed in a transactional manner. +All JDBC operations in `JdbcIndexedSessionRepository` are executed in a transactional manner. Transactions are executed with propagation set to `REQUIRES_NEW` in order to avoid unexpected behavior due to interference with existing transactions (for example, running a `save` operation in a thread that already participates in a read-only transaction). -[[api-hazelcastsessionrepository]] -=== Using `HazelcastSessionRepository` +[[api-hazelcastindexedsessionrepository]] +=== Using `HazelcastIndexedSessionRepository` -`HazelcastSessionRepository` is a `SessionRepository` implementation that stores sessions in Hazelcast's distributed `IMap`. +`HazelcastIndexedSessionRepository` is a `SessionRepository` implementation that stores sessions in Hazelcast's distributed `IMap`. In a web environment, this is typically used in combination with `SessionRepositoryFilter`. -[[api-hazelcastsessionrepository-new]] -==== Instantiating a `HazelcastSessionRepository` +[[api-hazelcastindexedsessionrepository-new]] +==== Instantiating a `HazelcastIndexedSessionRepository` The following example shows how to create a new instance: ==== [source,java,indent=0] ---- -include::{indexdoc-tests}[tags=new-hazelcastsessionrepository] +include::{indexdoc-tests}[tags=new-hazelcastindexedsessionrepository] ---- ==== @@ -1351,7 +1351,7 @@ The minimum requirements for Spring Session are: * Java 8+. * If you run in a Servlet Container (not required), Servlet 3.1+. * If you use other Spring libraries (not required), the minimum required version is Spring 5.0.x. -* `@EnableRedisHttpSession` requires Redis 2.8+. This is necessary to support <> +* `@EnableRedisHttpSession` requires Redis 2.8+. This is necessary to support <> * `@EnableHazelcastHttpSession` requires Hazelcast 3.6+. This is necessary to support <> NOTE: At its core, Spring Session has a required dependency only on `spring-jcl`. diff --git a/spring-session-docs/src/test/java/docs/IndexDocTests.java b/spring-session-docs/src/test/java/docs/IndexDocTests.java index 3e61f605..a9410e66 100644 --- a/spring-session-docs/src/test/java/docs/IndexDocTests.java +++ b/spring-session-docs/src/test/java/docs/IndexDocTests.java @@ -36,10 +36,10 @@ import org.springframework.session.MapSessionRepository; import org.springframework.session.ReactiveSessionRepository; import org.springframework.session.Session; import org.springframework.session.SessionRepository; -import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository; -import org.springframework.session.data.redis.RedisOperationsSessionRepository; -import org.springframework.session.hazelcast.HazelcastSessionRepository; -import org.springframework.session.jdbc.JdbcOperationsSessionRepository; +import org.springframework.session.data.redis.ReactiveRedisSessionRepository; +import org.springframework.session.data.redis.RedisIndexedSessionRepository; +import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository; +import org.springframework.session.jdbc.JdbcIndexedSessionRepository; import org.springframework.session.web.http.SessionRepositoryFilter; import org.springframework.transaction.support.TransactionTemplate; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; @@ -119,32 +119,31 @@ class IndexDocTests { @Test @SuppressWarnings("unused") - void newRedisOperationsSessionRepository() { - // tag::new-redisoperationssessionrepository[] + void newRedisIndexedSessionRepository() { + // tag::new-redisindexedsessionrepository[] RedisTemplate redisTemplate = new RedisTemplate<>(); // ... configure redisTemplate ... - SessionRepository repository = new RedisOperationsSessionRepository(redisTemplate); - // end::new-redisoperationssessionrepository[] + SessionRepository repository = new RedisIndexedSessionRepository(redisTemplate); + // end::new-redisindexedsessionrepository[] } @Test @SuppressWarnings("unused") - void newReactiveRedisOperationsSessionRepository() { + void newReactiveRedisSessionRepository() { LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(); RedisSerializationContext serializationContext = RedisSerializationContext .newSerializationContext(new JdkSerializationRedisSerializer()).build(); - // tag::new-reactiveredisoperationssessionrepository[] + // tag::new-reactiveredissessionrepository[] // ... create and configure connectionFactory and serializationContext ... ReactiveRedisTemplate redisTemplate = new ReactiveRedisTemplate<>(connectionFactory, serializationContext); - ReactiveSessionRepository repository = new ReactiveRedisOperationsSessionRepository( - redisTemplate); - // end::new-reactiveredisoperationssessionrepository[] + ReactiveSessionRepository repository = new ReactiveRedisSessionRepository(redisTemplate); + // end::new-reactiveredissessionrepository[] } @Test @@ -157,8 +156,8 @@ class IndexDocTests { @Test @SuppressWarnings("unused") - void newJdbcOperationsSessionRepository() { - // tag::new-jdbcoperationssessionrepository[] + void newJdbcIndexedSessionRepository() { + // tag::new-jdbcindexedsessionrepository[] JdbcTemplate jdbcTemplate = new JdbcTemplate(); // ... configure jdbcTemplate ... @@ -167,15 +166,15 @@ class IndexDocTests { // ... configure transactionTemplate ... - SessionRepository repository = new JdbcOperationsSessionRepository(jdbcTemplate, + SessionRepository repository = new JdbcIndexedSessionRepository(jdbcTemplate, transactionTemplate); - // end::new-jdbcoperationssessionrepository[] + // end::new-jdbcindexedsessionrepository[] } @Test @SuppressWarnings("unused") - void newHazelcastSessionRepository() { - // tag::new-hazelcastsessionrepository[] + void newHazelcastIndexedSessionRepository() { + // tag::new-hazelcastindexedsessionrepository[] Config config = new Config(); @@ -183,8 +182,8 @@ class IndexDocTests { HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config); - HazelcastSessionRepository repository = new HazelcastSessionRepository(hazelcastInstance); - // end::new-hazelcastsessionrepository[] + HazelcastIndexedSessionRepository repository = new HazelcastIndexedSessionRepository(hazelcastInstance); + // end::new-hazelcastindexedsessionrepository[] } @Test diff --git a/spring-session-docs/src/test/java/docs/http/HazelcastHttpSessionConfig.java b/spring-session-docs/src/test/java/docs/http/HazelcastHttpSessionConfig.java index 249d7071..c3c7fd75 100644 --- a/spring-session-docs/src/test/java/docs/http/HazelcastHttpSessionConfig.java +++ b/spring-session-docs/src/test/java/docs/http/HazelcastHttpSessionConfig.java @@ -24,7 +24,7 @@ import com.hazelcast.core.HazelcastInstance; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.session.hazelcast.HazelcastSessionRepository; +import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository; import org.springframework.session.hazelcast.PrincipalNameExtractor; import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession; @@ -36,14 +36,14 @@ public class HazelcastHttpSessionConfig { @Bean public HazelcastInstance hazelcastInstance() { MapAttributeConfig attributeConfig = new MapAttributeConfig() - .setName(HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE) + .setName(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE) .setExtractor(PrincipalNameExtractor.class.getName()); Config config = new Config(); - config.getMapConfig(HazelcastSessionRepository.DEFAULT_SESSION_MAP_NAME) // <2> - .addMapAttributeConfig(attributeConfig) - .addMapIndexConfig(new MapIndexConfig(HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false)); + config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME) // <2> + .addMapAttributeConfig(attributeConfig).addMapIndexConfig( + new MapIndexConfig(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false)); return Hazelcast.newHazelcastInstance(config); // <3> } diff --git a/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/AbstractHazelcastRepositoryITests.java b/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/AbstractHazelcastIndexedSessionRepositoryITests.java similarity index 95% rename from spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/AbstractHazelcastRepositoryITests.java rename to spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/AbstractHazelcastIndexedSessionRepositoryITests.java index e95c77e2..fa258446 100644 --- a/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/AbstractHazelcastRepositoryITests.java +++ b/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/AbstractHazelcastIndexedSessionRepositoryITests.java @@ -30,17 +30,17 @@ import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.MapSession; -import org.springframework.session.hazelcast.HazelcastSessionRepository.HazelcastSession; +import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository.HazelcastSession; import static org.assertj.core.api.Assertions.assertThat; /** - * Abstract base class for Hazelcast integration tests. + * Base class for {@link HazelcastIndexedSessionRepository} integration tests. * * @author Tommy Ludwig * @author Vedran Pavic */ -abstract class AbstractHazelcastRepositoryITests { +abstract class AbstractHazelcastIndexedSessionRepositoryITests { private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; @@ -48,7 +48,7 @@ abstract class AbstractHazelcastRepositoryITests { private HazelcastInstance hazelcastInstance; @Autowired - private HazelcastSessionRepository repository; + private HazelcastIndexedSessionRepository repository; @Test void createAndDestroySession() { @@ -56,7 +56,7 @@ abstract class AbstractHazelcastRepositoryITests { String sessionId = sessionToSave.getId(); IMap hazelcastMap = this.hazelcastInstance - .getMap(HazelcastSessionRepository.DEFAULT_SESSION_MAP_NAME); + .getMap(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME); assertThat(hazelcastMap.size()).isEqualTo(0); diff --git a/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/HazelcastClientRepositoryITests.java b/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/ClientServerHazelcastIndexedSessionRepositoryITests.java similarity index 92% rename from spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/HazelcastClientRepositoryITests.java rename to spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/ClientServerHazelcastIndexedSessionRepositoryITests.java index 4ee8b535..79f95d7c 100644 --- a/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/HazelcastClientRepositoryITests.java +++ b/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/ClientServerHazelcastIndexedSessionRepositoryITests.java @@ -35,17 +35,16 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; /** - * Integration tests that check the underlying data source - in this case Hazelcast - * Client. + * Integration tests for {@link HazelcastIndexedSessionRepository} using client-server + * topology. * * @author Vedran Pavic * @author Artem Bilan - * @since 1.1 */ @ExtendWith(SpringExtension.class) @ContextConfiguration @WebAppConfiguration -class HazelcastClientRepositoryITests extends AbstractHazelcastRepositoryITests { +class ClientServerHazelcastIndexedSessionRepositoryITests extends AbstractHazelcastIndexedSessionRepositoryITests { private static GenericContainer container = new GenericContainer<>("hazelcast/hazelcast:3.12.2") .withExposedPorts(5701).withEnv("JAVA_OPTS", "-Dhazelcast.config=/opt/hazelcast/config_ext/hazelcast.xml") diff --git a/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/HazelcastServerRepositoryITests.java b/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/EmbeddedHazelcastIndexedSessionRepositoryITests.java similarity index 88% rename from spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/HazelcastServerRepositoryITests.java rename to spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/EmbeddedHazelcastIndexedSessionRepositoryITests.java index 542becaf..db584776 100644 --- a/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/HazelcastServerRepositoryITests.java +++ b/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/EmbeddedHazelcastIndexedSessionRepositoryITests.java @@ -27,8 +27,8 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; /** - * Integration tests that check the underlying data source - in this case Hazelcast - * Server. + * Integration tests for {@link HazelcastIndexedSessionRepository} using embedded + * topology. * * @author Tommy Ludwig * @author Vedran Pavic @@ -36,7 +36,7 @@ import org.springframework.test.context.web.WebAppConfiguration; @ExtendWith(SpringExtension.class) @ContextConfiguration @WebAppConfiguration -class HazelcastServerRepositoryITests extends AbstractHazelcastRepositoryITests { +class EmbeddedHazelcastIndexedSessionRepositoryITests extends AbstractHazelcastIndexedSessionRepositoryITests { @EnableHazelcastHttpSession @Configuration diff --git a/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/HazelcastITestUtils.java b/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/HazelcastITestUtils.java index 0f51a556..8a86ac67 100644 --- a/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/HazelcastITestUtils.java +++ b/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/HazelcastITestUtils.java @@ -42,7 +42,7 @@ public final class HazelcastITestUtils { */ public static HazelcastInstance embeddedHazelcastServer(int port) { MapAttributeConfig attributeConfig = new MapAttributeConfig() - .setName(HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE) + .setName(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE) .setExtractor(PrincipalNameExtractor.class.getName()); Config config = new Config(); @@ -53,8 +53,9 @@ public final class HazelcastITestUtils { networkConfig.getJoin().getMulticastConfig().setEnabled(false); - config.getMapConfig(HazelcastSessionRepository.DEFAULT_SESSION_MAP_NAME).addMapAttributeConfig(attributeConfig) - .addMapIndexConfig(new MapIndexConfig(HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false)); + config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME) + .addMapAttributeConfig(attributeConfig).addMapIndexConfig( + new MapIndexConfig(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false)); return Hazelcast.newHazelcastInstance(config); } diff --git a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/HazelcastIndexedSessionRepository.java b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/HazelcastIndexedSessionRepository.java new file mode 100644 index 00000000..027f7528 --- /dev/null +++ b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/HazelcastIndexedSessionRepository.java @@ -0,0 +1,469 @@ +/* + * Copyright 2014-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.session.hazelcast; + +import java.time.Duration; +import java.time.Instant; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +import com.hazelcast.core.EntryEvent; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.core.IMap; +import com.hazelcast.map.listener.EntryAddedListener; +import com.hazelcast.map.listener.EntryEvictedListener; +import com.hazelcast.map.listener.EntryRemovedListener; +import com.hazelcast.query.Predicates; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.session.DelegatingIndexResolver; +import org.springframework.session.FindByIndexNameSessionRepository; +import org.springframework.session.FlushMode; +import org.springframework.session.IndexResolver; +import org.springframework.session.MapSession; +import org.springframework.session.PrincipalNameIndexResolver; +import org.springframework.session.SaveMode; +import org.springframework.session.Session; +import org.springframework.session.events.AbstractSessionEvent; +import org.springframework.session.events.SessionCreatedEvent; +import org.springframework.session.events.SessionDeletedEvent; +import org.springframework.session.events.SessionExpiredEvent; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * A {@link org.springframework.session.SessionRepository} implementation that stores + * sessions in Hazelcast's distributed {@link IMap}. + * + *

+ * An example of how to create a new instance can be seen below: + * + *

+ * Config config = new Config();
+ *
+ * // ... configure Hazelcast ...
+ *
+ * HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config);
+ *
+ * HazelcastIndexedSessionRepository sessionRepository =
+ *         new HazelcastIndexedSessionRepository(hazelcastInstance);
+ * 
+ * + * In order to support finding sessions by principal name using + * {@link #findByIndexNameAndIndexValue(String, String)} method, custom configuration of + * {@code IMap} supplied to this implementation is required. + * + * The following snippet demonstrates how to define required configuration using + * programmatic Hazelcast Configuration: + * + *
+ * MapAttributeConfig attributeConfig = new MapAttributeConfig()
+ *         .setName(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
+ *         .setExtractor(PrincipalNameExtractor.class.getName());
+ *
+ * Config config = new Config();
+ *
+ * config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME)
+ *         .addMapAttributeConfig(attributeConfig)
+ *         .addMapIndexConfig(new MapIndexConfig(
+ *                 HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));
+ *
+ * Hazelcast.newHazelcastInstance(config);
+ * 
+ * + * This implementation listens for events on the Hazelcast-backed SessionRepository and + * translates those events into the corresponding Spring Session events. Publish the + * Spring Session events with the given {@link ApplicationEventPublisher}. + * + *
    + *
  • entryAdded - {@link SessionCreatedEvent}
  • + *
  • entryEvicted - {@link SessionExpiredEvent}
  • + *
  • entryRemoved - {@link SessionDeletedEvent}
  • + *
+ * + * @author Vedran Pavic + * @author Tommy Ludwig + * @author Mark Anderson + * @author Aleksandar Stojsavljevic + * @since 2.2.0 + */ +public class HazelcastIndexedSessionRepository + implements FindByIndexNameSessionRepository, + EntryAddedListener, EntryEvictedListener, + EntryRemovedListener { + + /** + * The default name of map used by Spring Session to store sessions. + */ + public static final String DEFAULT_SESSION_MAP_NAME = "spring:session:sessions"; + + /** + * The principal name custom attribute name. + */ + public static final String PRINCIPAL_NAME_ATTRIBUTE = "principalName"; + + private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; + + private static final boolean SUPPORTS_SET_TTL = ClassUtils.hasAtLeastOneMethodWithName(IMap.class, "setTtl"); + + private static final Log logger = LogFactory.getLog(HazelcastIndexedSessionRepository.class); + + private final HazelcastInstance hazelcastInstance; + + private final IndexResolver indexResolver; + + private ApplicationEventPublisher eventPublisher = (event) -> { + }; + + /** + * If non-null, this value is used to override + * {@link MapSession#setMaxInactiveInterval(Duration)}. + */ + private Integer defaultMaxInactiveInterval; + + private String sessionMapName = DEFAULT_SESSION_MAP_NAME; + + private FlushMode flushMode = FlushMode.ON_SAVE; + + private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE; + + private IMap sessions; + + private String sessionListenerId; + + /** + * Create a new {@link HazelcastIndexedSessionRepository} instance. + * @param hazelcastInstance the {@link HazelcastInstance} to use for managing sessions + */ + public HazelcastIndexedSessionRepository(HazelcastInstance hazelcastInstance) { + Assert.notNull(hazelcastInstance, "HazelcastInstance must not be null"); + this.hazelcastInstance = hazelcastInstance; + this.indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>()); + } + + @PostConstruct + public void init() { + this.sessions = this.hazelcastInstance.getMap(this.sessionMapName); + this.sessionListenerId = this.sessions.addEntryListener(this, true); + } + + @PreDestroy + public void close() { + this.sessions.removeEntryListener(this.sessionListenerId); + } + + /** + * Sets the {@link ApplicationEventPublisher} that is used to publish + * {@link AbstractSessionEvent session events}. The default is to not publish session + * events. + * @param applicationEventPublisher the {@link ApplicationEventPublisher} that is used + * to publish session events. Cannot be null. + */ + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + Assert.notNull(applicationEventPublisher, "ApplicationEventPublisher cannot be null"); + this.eventPublisher = applicationEventPublisher; + } + + /** + * Set the maximum inactive interval in seconds between requests before newly created + * sessions will be invalidated. A negative time indicates that the session will never + * timeout. The default is 1800 (30 minutes). + * @param defaultMaxInactiveInterval the maximum inactive interval in seconds + */ + public void setDefaultMaxInactiveInterval(Integer defaultMaxInactiveInterval) { + this.defaultMaxInactiveInterval = defaultMaxInactiveInterval; + } + + /** + * Set the name of map used to store sessions. + * @param sessionMapName the session map name + */ + public void setSessionMapName(String sessionMapName) { + Assert.hasText(sessionMapName, "Map name must not be empty"); + this.sessionMapName = sessionMapName; + } + + /** + * Sets the Hazelcast flush mode. Default flush mode is {@link FlushMode#ON_SAVE}. + * @param flushMode the new Hazelcast flush mode + */ + public void setFlushMode(FlushMode flushMode) { + Assert.notNull(flushMode, "flushMode cannot be null"); + this.flushMode = flushMode; + } + + /** + * Set the save mode. + * @param saveMode the save mode + */ + public void setSaveMode(SaveMode saveMode) { + Assert.notNull(saveMode, "saveMode must not be null"); + this.saveMode = saveMode; + } + + @Override + public HazelcastSession createSession() { + MapSession cached = new MapSession(); + if (this.defaultMaxInactiveInterval != null) { + cached.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval)); + } + HazelcastSession session = new HazelcastSession(cached, true); + session.flushImmediateIfNecessary(); + return session; + } + + @Override + public void save(HazelcastSession session) { + if (session.isNew) { + this.sessions.set(session.getId(), session.getDelegate(), session.getMaxInactiveInterval().getSeconds(), + TimeUnit.SECONDS); + } + else if (session.sessionIdChanged) { + this.sessions.delete(session.originalId); + session.originalId = session.getId(); + this.sessions.set(session.getId(), session.getDelegate(), session.getMaxInactiveInterval().getSeconds(), + TimeUnit.SECONDS); + } + else if (session.hasChanges()) { + SessionUpdateEntryProcessor entryProcessor = new SessionUpdateEntryProcessor(); + if (session.lastAccessedTimeChanged) { + entryProcessor.setLastAccessedTime(session.getLastAccessedTime()); + } + if (session.maxInactiveIntervalChanged) { + if (SUPPORTS_SET_TTL) { + updateTtl(session); + } + entryProcessor.setMaxInactiveInterval(session.getMaxInactiveInterval()); + } + if (!session.delta.isEmpty()) { + entryProcessor.setDelta(new HashMap<>(session.delta)); + } + this.sessions.executeOnKey(session.getId(), entryProcessor); + } + session.clearChangeFlags(); + } + + private void updateTtl(HazelcastSession session) { + this.sessions.setTtl(session.getId(), session.getMaxInactiveInterval().getSeconds(), TimeUnit.SECONDS); + } + + @Override + public HazelcastSession findById(String id) { + MapSession saved = this.sessions.get(id); + if (saved == null) { + return null; + } + if (saved.isExpired()) { + deleteById(saved.getId()); + return null; + } + return new HazelcastSession(saved, false); + } + + @Override + public void deleteById(String id) { + this.sessions.remove(id); + } + + @Override + public Map findByIndexNameAndIndexValue(String indexName, String indexValue) { + if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) { + return Collections.emptyMap(); + } + Collection sessions = this.sessions.values(Predicates.equal(PRINCIPAL_NAME_ATTRIBUTE, indexValue)); + Map sessionMap = new HashMap<>(sessions.size()); + for (MapSession session : sessions) { + sessionMap.put(session.getId(), new HazelcastSession(session, false)); + } + return sessionMap; + } + + @Override + public void entryAdded(EntryEvent event) { + MapSession session = event.getValue(); + if (session.getId().equals(session.getOriginalId())) { + if (logger.isDebugEnabled()) { + logger.debug("Session created with id: " + session.getId()); + } + this.eventPublisher.publishEvent(new SessionCreatedEvent(this, session)); + } + } + + @Override + public void entryEvicted(EntryEvent event) { + if (logger.isDebugEnabled()) { + logger.debug("Session expired with id: " + event.getOldValue().getId()); + } + this.eventPublisher.publishEvent(new SessionExpiredEvent(this, event.getOldValue())); + } + + @Override + public void entryRemoved(EntryEvent event) { + MapSession session = event.getOldValue(); + if (session != null) { + if (logger.isDebugEnabled()) { + logger.debug("Session deleted with id: " + session.getId()); + } + this.eventPublisher.publishEvent(new SessionDeletedEvent(this, session)); + } + } + + /** + * A custom implementation of {@link Session} that uses a {@link MapSession} as the + * basis for its mapping. It keeps track if changes have been made since last save. + * + * @author Aleksandar Stojsavljevic + */ + final class HazelcastSession implements Session { + + private final MapSession delegate; + + private boolean isNew; + + private boolean sessionIdChanged; + + private boolean lastAccessedTimeChanged; + + private boolean maxInactiveIntervalChanged; + + private String originalId; + + private Map delta = new HashMap<>(); + + HazelcastSession(MapSession cached, boolean isNew) { + this.delegate = cached; + this.isNew = isNew; + this.originalId = cached.getId(); + if (this.isNew || (HazelcastIndexedSessionRepository.this.saveMode == SaveMode.ALWAYS)) { + getAttributeNames() + .forEach((attributeName) -> this.delta.put(attributeName, cached.getAttribute(attributeName))); + } + } + + @Override + public void setLastAccessedTime(Instant lastAccessedTime) { + this.delegate.setLastAccessedTime(lastAccessedTime); + this.lastAccessedTimeChanged = true; + flushImmediateIfNecessary(); + } + + @Override + public boolean isExpired() { + return this.delegate.isExpired(); + } + + @Override + public Instant getCreationTime() { + return this.delegate.getCreationTime(); + } + + @Override + public String getId() { + return this.delegate.getId(); + } + + @Override + public String changeSessionId() { + String newSessionId = this.delegate.changeSessionId(); + this.sessionIdChanged = true; + return newSessionId; + } + + @Override + public Instant getLastAccessedTime() { + return this.delegate.getLastAccessedTime(); + } + + @Override + public void setMaxInactiveInterval(Duration interval) { + this.delegate.setMaxInactiveInterval(interval); + this.maxInactiveIntervalChanged = true; + flushImmediateIfNecessary(); + } + + @Override + public Duration getMaxInactiveInterval() { + return this.delegate.getMaxInactiveInterval(); + } + + @Override + public T getAttribute(String attributeName) { + T attributeValue = this.delegate.getAttribute(attributeName); + if (attributeValue != null + && HazelcastIndexedSessionRepository.this.saveMode.equals(SaveMode.ON_GET_ATTRIBUTE)) { + this.delta.put(attributeName, attributeValue); + } + return attributeValue; + } + + @Override + public Set getAttributeNames() { + return this.delegate.getAttributeNames(); + } + + @Override + public void setAttribute(String attributeName, Object attributeValue) { + this.delegate.setAttribute(attributeName, attributeValue); + this.delta.put(attributeName, attributeValue); + if (SPRING_SECURITY_CONTEXT.equals(attributeName)) { + Map indexes = HazelcastIndexedSessionRepository.this.indexResolver + .resolveIndexesFor(this); + String principal = (attributeValue != null) ? indexes.get(PRINCIPAL_NAME_INDEX_NAME) : null; + this.delegate.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal); + } + flushImmediateIfNecessary(); + } + + @Override + public void removeAttribute(String attributeName) { + setAttribute(attributeName, null); + } + + MapSession getDelegate() { + return this.delegate; + } + + boolean hasChanges() { + return (this.lastAccessedTimeChanged || this.maxInactiveIntervalChanged || !this.delta.isEmpty()); + } + + void clearChangeFlags() { + this.isNew = false; + this.lastAccessedTimeChanged = false; + this.sessionIdChanged = false; + this.maxInactiveIntervalChanged = false; + this.delta.clear(); + } + + private void flushImmediateIfNecessary() { + if (HazelcastIndexedSessionRepository.this.flushMode == FlushMode.IMMEDIATE) { + HazelcastIndexedSessionRepository.this.save(this); + } + } + + } + +} diff --git a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/HazelcastSessionRepository.java b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/HazelcastSessionRepository.java index 181ebfde..25809b7a 100644 --- a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/HazelcastSessionRepository.java +++ b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/HazelcastSessionRepository.java @@ -16,190 +16,33 @@ package org.springframework.session.hazelcast; -import java.time.Duration; -import java.time.Instant; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; - -import com.hazelcast.core.EntryEvent; import com.hazelcast.core.HazelcastInstance; -import com.hazelcast.core.IMap; -import com.hazelcast.map.listener.EntryAddedListener; -import com.hazelcast.map.listener.EntryEvictedListener; -import com.hazelcast.map.listener.EntryRemovedListener; -import com.hazelcast.query.Predicates; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.session.DelegatingIndexResolver; -import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.FlushMode; -import org.springframework.session.IndexResolver; -import org.springframework.session.MapSession; -import org.springframework.session.PrincipalNameIndexResolver; -import org.springframework.session.SaveMode; -import org.springframework.session.Session; -import org.springframework.session.events.AbstractSessionEvent; -import org.springframework.session.events.SessionCreatedEvent; -import org.springframework.session.events.SessionDeletedEvent; -import org.springframework.session.events.SessionExpiredEvent; +import org.springframework.session.SessionRepository; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; /** - * A {@link org.springframework.session.SessionRepository} implementation that stores - * sessions in Hazelcast's distributed {@link IMap}. - * - *

- * An example of how to create a new instance can be seen below: - * - *

- * Config config = new Config();
- *
- * // ... configure Hazelcast ...
- *
- * HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config);
- *
- * HazelcastSessionRepository sessionRepository =
- *         new HazelcastSessionRepository(hazelcastInstance);
- * 
- * - * In order to support finding sessions by principal name using - * {@link #findByIndexNameAndIndexValue(String, String)} method, custom configuration of - * {@code IMap} supplied to this implementation is required. - * - * The following snippet demonstrates how to define required configuration using - * programmatic Hazelcast Configuration: - * - *
- * MapAttributeConfig attributeConfig = new MapAttributeConfig()
- *         .setName(HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
- *         .setExtractor(PrincipalNameExtractor.class.getName());
- *
- * Config config = new Config();
- *
- * config.getMapConfig(HazelcastSessionRepository.DEFAULT_SESSION_MAP_NAME)
- *         .addMapAttributeConfig(attributeConfig)
- *         .addMapIndexConfig(new MapIndexConfig(
- *                 HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));
- *
- * Hazelcast.newHazelcastInstance(config);
- * 
- * - * This implementation listens for events on the Hazelcast-backed SessionRepository and - * translates those events into the corresponding Spring Session events. Publish the - * Spring Session events with the given {@link ApplicationEventPublisher}. - * - *
    - *
  • entryAdded - {@link SessionCreatedEvent}
  • - *
  • entryEvicted - {@link SessionExpiredEvent}
  • - *
  • entryRemoved - {@link SessionDeletedEvent}
  • - *
+ * This {@link SessionRepository} implementation is kept in order to support migration to + * {@link HazelcastIndexedSessionRepository} in a backwards compatible manner. * * @author Vedran Pavic * @author Tommy Ludwig * @author Mark Anderson * @author Aleksandar Stojsavljevic * @since 1.3.0 + * @deprecated since 2.2.0 in favor of {@link HazelcastIndexedSessionRepository} */ -public class HazelcastSessionRepository - implements FindByIndexNameSessionRepository, - EntryAddedListener, EntryEvictedListener, - EntryRemovedListener { +@Deprecated +public class HazelcastSessionRepository extends HazelcastIndexedSessionRepository { /** - * The default name of map used by Spring Session to store sessions. + * Create a new {@link HazelcastSessionRepository} instance. + * @param hazelcastInstance the {@link HazelcastInstance} to use for managing sessions + * @see HazelcastIndexedSessionRepository#HazelcastIndexedSessionRepository(HazelcastInstance) */ - public static final String DEFAULT_SESSION_MAP_NAME = "spring:session:sessions"; - - /** - * The principal name custom attribute name. - */ - public static final String PRINCIPAL_NAME_ATTRIBUTE = "principalName"; - - private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; - - private static final boolean SUPPORTS_SET_TTL = ClassUtils.hasAtLeastOneMethodWithName(IMap.class, "setTtl"); - - private static final Log logger = LogFactory.getLog(HazelcastSessionRepository.class); - - private final HazelcastInstance hazelcastInstance; - - private final IndexResolver indexResolver; - - private ApplicationEventPublisher eventPublisher = (event) -> { - }; - - /** - * If non-null, this value is used to override - * {@link MapSession#setMaxInactiveInterval(Duration)}. - */ - private Integer defaultMaxInactiveInterval; - - private String sessionMapName = DEFAULT_SESSION_MAP_NAME; - - private FlushMode flushMode = FlushMode.ON_SAVE; - - private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE; - - private IMap sessions; - - private String sessionListenerId; - public HazelcastSessionRepository(HazelcastInstance hazelcastInstance) { - Assert.notNull(hazelcastInstance, "HazelcastInstance must not be null"); - this.hazelcastInstance = hazelcastInstance; - this.indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>()); - } - - @PostConstruct - public void init() { - this.sessions = this.hazelcastInstance.getMap(this.sessionMapName); - this.sessionListenerId = this.sessions.addEntryListener(this, true); - } - - @PreDestroy - public void close() { - this.sessions.removeEntryListener(this.sessionListenerId); - } - - /** - * Sets the {@link ApplicationEventPublisher} that is used to publish - * {@link AbstractSessionEvent session events}. The default is to not publish session - * events. - * @param applicationEventPublisher the {@link ApplicationEventPublisher} that is used - * to publish session events. Cannot be null. - */ - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - Assert.notNull(applicationEventPublisher, "ApplicationEventPublisher cannot be null"); - this.eventPublisher = applicationEventPublisher; - } - - /** - * Set the maximum inactive interval in seconds between requests before newly created - * sessions will be invalidated. A negative time indicates that the session will never - * timeout. The default is 1800 (30 minutes). - * @param defaultMaxInactiveInterval the maximum inactive interval in seconds - */ - public void setDefaultMaxInactiveInterval(Integer defaultMaxInactiveInterval) { - this.defaultMaxInactiveInterval = defaultMaxInactiveInterval; - } - - /** - * Set the name of map used to store sessions. - * @param sessionMapName the session map name - */ - public void setSessionMapName(String sessionMapName) { - Assert.hasText(sessionMapName, "Map name must not be empty"); - this.sessionMapName = sessionMapName; + super(hazelcastInstance); } /** @@ -214,262 +57,4 @@ public class HazelcastSessionRepository setFlushMode(hazelcastFlushMode.getFlushMode()); } - /** - * Sets the Hazelcast flush mode. Default flush mode is {@link FlushMode#ON_SAVE}. - * @param flushMode the new Hazelcast flush mode - */ - public void setFlushMode(FlushMode flushMode) { - Assert.notNull(flushMode, "flushMode cannot be null"); - this.flushMode = flushMode; - } - - /** - * Set the save mode. - * @param saveMode the save mode - */ - public void setSaveMode(SaveMode saveMode) { - Assert.notNull(saveMode, "saveMode must not be null"); - this.saveMode = saveMode; - } - - @Override - public HazelcastSession createSession() { - MapSession cached = new MapSession(); - if (this.defaultMaxInactiveInterval != null) { - cached.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval)); - } - HazelcastSession session = new HazelcastSession(cached, true); - session.flushImmediateIfNecessary(); - return session; - } - - @Override - public void save(HazelcastSession session) { - if (session.isNew) { - this.sessions.set(session.getId(), session.getDelegate(), session.getMaxInactiveInterval().getSeconds(), - TimeUnit.SECONDS); - } - else if (session.sessionIdChanged) { - this.sessions.delete(session.originalId); - session.originalId = session.getId(); - this.sessions.set(session.getId(), session.getDelegate(), session.getMaxInactiveInterval().getSeconds(), - TimeUnit.SECONDS); - } - else if (session.hasChanges()) { - SessionUpdateEntryProcessor entryProcessor = new SessionUpdateEntryProcessor(); - if (session.lastAccessedTimeChanged) { - entryProcessor.setLastAccessedTime(session.getLastAccessedTime()); - } - if (session.maxInactiveIntervalChanged) { - if (SUPPORTS_SET_TTL) { - updateTtl(session); - } - entryProcessor.setMaxInactiveInterval(session.getMaxInactiveInterval()); - } - if (!session.delta.isEmpty()) { - entryProcessor.setDelta(new HashMap<>(session.delta)); - } - this.sessions.executeOnKey(session.getId(), entryProcessor); - } - session.clearChangeFlags(); - } - - private void updateTtl(HazelcastSession session) { - this.sessions.setTtl(session.getId(), session.getMaxInactiveInterval().getSeconds(), TimeUnit.SECONDS); - } - - @Override - public HazelcastSession findById(String id) { - MapSession saved = this.sessions.get(id); - if (saved == null) { - return null; - } - if (saved.isExpired()) { - deleteById(saved.getId()); - return null; - } - return new HazelcastSession(saved, false); - } - - @Override - public void deleteById(String id) { - this.sessions.remove(id); - } - - @Override - public Map findByIndexNameAndIndexValue(String indexName, String indexValue) { - if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) { - return Collections.emptyMap(); - } - Collection sessions = this.sessions.values(Predicates.equal(PRINCIPAL_NAME_ATTRIBUTE, indexValue)); - Map sessionMap = new HashMap<>(sessions.size()); - for (MapSession session : sessions) { - sessionMap.put(session.getId(), new HazelcastSession(session, false)); - } - return sessionMap; - } - - @Override - public void entryAdded(EntryEvent event) { - MapSession session = event.getValue(); - if (session.getId().equals(session.getOriginalId())) { - if (logger.isDebugEnabled()) { - logger.debug("Session created with id: " + session.getId()); - } - this.eventPublisher.publishEvent(new SessionCreatedEvent(this, session)); - } - } - - @Override - public void entryEvicted(EntryEvent event) { - if (logger.isDebugEnabled()) { - logger.debug("Session expired with id: " + event.getOldValue().getId()); - } - this.eventPublisher.publishEvent(new SessionExpiredEvent(this, event.getOldValue())); - } - - @Override - public void entryRemoved(EntryEvent event) { - MapSession session = event.getOldValue(); - if (session != null) { - if (logger.isDebugEnabled()) { - logger.debug("Session deleted with id: " + session.getId()); - } - this.eventPublisher.publishEvent(new SessionDeletedEvent(this, session)); - } - } - - /** - * A custom implementation of {@link Session} that uses a {@link MapSession} as the - * basis for its mapping. It keeps track if changes have been made since last save. - * - * @author Aleksandar Stojsavljevic - */ - final class HazelcastSession implements Session { - - private final MapSession delegate; - - private boolean isNew; - - private boolean sessionIdChanged; - - private boolean lastAccessedTimeChanged; - - private boolean maxInactiveIntervalChanged; - - private String originalId; - - private Map delta = new HashMap<>(); - - HazelcastSession(MapSession cached, boolean isNew) { - this.delegate = cached; - this.isNew = isNew; - this.originalId = cached.getId(); - if (this.isNew || (HazelcastSessionRepository.this.saveMode == SaveMode.ALWAYS)) { - getAttributeNames() - .forEach((attributeName) -> this.delta.put(attributeName, cached.getAttribute(attributeName))); - } - } - - @Override - public void setLastAccessedTime(Instant lastAccessedTime) { - this.delegate.setLastAccessedTime(lastAccessedTime); - this.lastAccessedTimeChanged = true; - flushImmediateIfNecessary(); - } - - @Override - public boolean isExpired() { - return this.delegate.isExpired(); - } - - @Override - public Instant getCreationTime() { - return this.delegate.getCreationTime(); - } - - @Override - public String getId() { - return this.delegate.getId(); - } - - @Override - public String changeSessionId() { - String newSessionId = this.delegate.changeSessionId(); - this.sessionIdChanged = true; - return newSessionId; - } - - @Override - public Instant getLastAccessedTime() { - return this.delegate.getLastAccessedTime(); - } - - @Override - public void setMaxInactiveInterval(Duration interval) { - this.delegate.setMaxInactiveInterval(interval); - this.maxInactiveIntervalChanged = true; - flushImmediateIfNecessary(); - } - - @Override - public Duration getMaxInactiveInterval() { - return this.delegate.getMaxInactiveInterval(); - } - - @Override - public T getAttribute(String attributeName) { - T attributeValue = this.delegate.getAttribute(attributeName); - if (attributeValue != null && HazelcastSessionRepository.this.saveMode.equals(SaveMode.ON_GET_ATTRIBUTE)) { - this.delta.put(attributeName, attributeValue); - } - return attributeValue; - } - - @Override - public Set getAttributeNames() { - return this.delegate.getAttributeNames(); - } - - @Override - public void setAttribute(String attributeName, Object attributeValue) { - this.delegate.setAttribute(attributeName, attributeValue); - this.delta.put(attributeName, attributeValue); - if (SPRING_SECURITY_CONTEXT.equals(attributeName)) { - Map indexes = HazelcastSessionRepository.this.indexResolver.resolveIndexesFor(this); - String principal = (attributeValue != null) ? indexes.get(PRINCIPAL_NAME_INDEX_NAME) : null; - this.delegate.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal); - } - flushImmediateIfNecessary(); - } - - @Override - public void removeAttribute(String attributeName) { - setAttribute(attributeName, null); - } - - MapSession getDelegate() { - return this.delegate; - } - - boolean hasChanges() { - return (this.lastAccessedTimeChanged || this.maxInactiveIntervalChanged || !this.delta.isEmpty()); - } - - void clearChangeFlags() { - this.isNew = false; - this.lastAccessedTimeChanged = false; - this.sessionIdChanged = false; - this.maxInactiveIntervalChanged = false; - this.delta.clear(); - } - - private void flushImmediateIfNecessary() { - if (HazelcastSessionRepository.this.flushMode == FlushMode.IMMEDIATE) { - HazelcastSessionRepository.this.save(this); - } - } - - } - } diff --git a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/SessionUpdateEntryProcessor.java b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/SessionUpdateEntryProcessor.java index 48edaeca..9d0f0e3e 100644 --- a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/SessionUpdateEntryProcessor.java +++ b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/SessionUpdateEntryProcessor.java @@ -25,13 +25,14 @@ import com.hazelcast.map.AbstractEntryProcessor; import com.hazelcast.map.EntryProcessor; import org.springframework.session.MapSession; +import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository.HazelcastSession; /** * Hazelcast {@link EntryProcessor} responsible for handling updates to session. * * @author Vedran Pavic * @since 1.3.4 - * @see HazelcastSessionRepository#save(HazelcastSessionRepository.HazelcastSession) + * @see HazelcastIndexedSessionRepository#save(HazelcastSession) */ public class SessionUpdateEntryProcessor extends AbstractEntryProcessor implements Offloadable { diff --git a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/SpringSessionHazelcastInstance.java b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/SpringSessionHazelcastInstance.java index e5e70ff4..63db00bf 100644 --- a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/SpringSessionHazelcastInstance.java +++ b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/SpringSessionHazelcastInstance.java @@ -25,11 +25,11 @@ import java.lang.annotation.Target; import com.hazelcast.core.HazelcastInstance; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.session.hazelcast.HazelcastSessionRepository; +import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository; /** * Qualifier annotation for a {@link HazelcastInstance} to be injected in - * {@link HazelcastSessionRepository}. + * {@link HazelcastIndexedSessionRepository}. * * @author Vedran Pavic * @since 2.0.0 diff --git a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/EnableHazelcastHttpSession.java b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/EnableHazelcastHttpSession.java index 8339e1d4..43a57e7a 100644 --- a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/EnableHazelcastHttpSession.java +++ b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/EnableHazelcastHttpSession.java @@ -33,7 +33,7 @@ import org.springframework.session.Session; import org.springframework.session.SessionRepository; import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; import org.springframework.session.hazelcast.HazelcastFlushMode; -import org.springframework.session.hazelcast.HazelcastSessionRepository; +import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository; import org.springframework.session.web.http.SessionRepositoryFilter; /** @@ -81,10 +81,11 @@ public @interface EnableHazelcastHttpSession { /** * This is the name of the Map that will be used in Hazelcast to store the session - * data. Default is {@link HazelcastSessionRepository#DEFAULT_SESSION_MAP_NAME}. + * data. Default is + * {@link HazelcastIndexedSessionRepository#DEFAULT_SESSION_MAP_NAME}. * @return the name of the Map to store the sessions in Hazelcast */ - String sessionMapName() default HazelcastSessionRepository.DEFAULT_SESSION_MAP_NAME; + String sessionMapName() default HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME; /** * Flush mode for the Hazelcast sessions. The default is {@code ON_SAVE} which only diff --git a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfiguration.java b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfiguration.java index a13ae963..f3ae339c 100644 --- a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfiguration.java +++ b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfiguration.java @@ -36,7 +36,7 @@ import org.springframework.session.SaveMode; import org.springframework.session.config.SessionRepositoryCustomizer; import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration; import org.springframework.session.hazelcast.HazelcastFlushMode; -import org.springframework.session.hazelcast.HazelcastSessionRepository; +import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository; import org.springframework.session.hazelcast.config.annotation.SpringSessionHazelcastInstance; import org.springframework.session.web.http.SessionRepositoryFilter; import org.springframework.util.StringUtils; @@ -56,7 +56,7 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur private Integer maxInactiveIntervalInSeconds = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS; - private String sessionMapName = HazelcastSessionRepository.DEFAULT_SESSION_MAP_NAME; + private String sessionMapName = HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME; private FlushMode flushMode = FlushMode.ON_SAVE; @@ -66,11 +66,12 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur private ApplicationEventPublisher applicationEventPublisher; - private List> sessionRepositoryCustomizers; + private List> sessionRepositoryCustomizers; @Bean - public HazelcastSessionRepository sessionRepository() { - HazelcastSessionRepository sessionRepository = new HazelcastSessionRepository(this.hazelcastInstance); + public HazelcastIndexedSessionRepository sessionRepository() { + HazelcastIndexedSessionRepository sessionRepository = new HazelcastIndexedSessionRepository( + this.hazelcastInstance); sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher); if (StringUtils.hasText(this.sessionMapName)) { sessionRepository.setSessionMapName(this.sessionMapName); @@ -122,7 +123,7 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur @Autowired(required = false) public void setSessionRepositoryCustomizer( - ObjectProvider> sessionRepositoryCustomizers) { + ObjectProvider> sessionRepositoryCustomizers) { this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList()); } diff --git a/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/HazelcastSessionRepositoryTests.java b/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/HazelcastIndexedSessionRepositoryTests.java similarity index 97% rename from spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/HazelcastSessionRepositoryTests.java rename to spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/HazelcastIndexedSessionRepositoryTests.java index 947cd8d7..f648bfe5 100644 --- a/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/HazelcastSessionRepositoryTests.java +++ b/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/HazelcastIndexedSessionRepositoryTests.java @@ -39,7 +39,7 @@ import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.FlushMode; import org.springframework.session.MapSession; import org.springframework.session.SaveMode; -import org.springframework.session.hazelcast.HazelcastSessionRepository.HazelcastSession; +import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository.HazelcastSession; import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -57,12 +57,12 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; /** - * Tests for {@link HazelcastSessionRepository}. + * Tests for {@link HazelcastIndexedSessionRepository}. * * @author Vedran Pavic * @author Aleksandar Stojsavljevic */ -class HazelcastSessionRepositoryTests { +class HazelcastIndexedSessionRepositoryTests { private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; @@ -71,18 +71,18 @@ class HazelcastSessionRepositoryTests { @SuppressWarnings("unchecked") private IMap sessions = mock(IMap.class); - private HazelcastSessionRepository repository; + private HazelcastIndexedSessionRepository repository; @BeforeEach void setUp() { given(this.hazelcastInstance.getMap(anyString())).willReturn(this.sessions); - this.repository = new HazelcastSessionRepository(this.hazelcastInstance); + this.repository = new HazelcastIndexedSessionRepository(this.hazelcastInstance); this.repository.init(); } @Test void constructorNullHazelcastInstance() { - assertThatIllegalArgumentException().isThrownBy(() -> new HazelcastSessionRepository(null)) + assertThatIllegalArgumentException().isThrownBy(() -> new HazelcastIndexedSessionRepository(null)) .withMessage("HazelcastInstance must not be null"); } diff --git a/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfigurationTests.java b/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfigurationTests.java index 8353563d..41b2e483 100644 --- a/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfigurationTests.java +++ b/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfigurationTests.java @@ -31,7 +31,7 @@ import org.springframework.session.FlushMode; import org.springframework.session.SaveMode; import org.springframework.session.config.SessionRepositoryCustomizer; import org.springframework.session.hazelcast.HazelcastFlushMode; -import org.springframework.session.hazelcast.HazelcastSessionRepository; +import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository; import org.springframework.session.hazelcast.config.annotation.SpringSessionHazelcastInstance; import org.springframework.test.util.ReflectionTestUtils; @@ -47,7 +47,6 @@ import static org.mockito.Mockito.mock; * @author Vedran Pavic * @author Aleksandar Stojsavljevic */ -@SuppressWarnings("deprecation") class HazelcastHttpSessionConfigurationTests { private static final String MAP_NAME = "spring:test:sessions"; @@ -74,14 +73,14 @@ class HazelcastHttpSessionConfigurationTests { void defaultConfiguration() { registerAndRefresh(DefaultConfiguration.class); - assertThat(this.context.getBean(HazelcastSessionRepository.class)).isNotNull(); + assertThat(this.context.getBean(HazelcastIndexedSessionRepository.class)).isNotNull(); } @Test void customTableName() { registerAndRefresh(CustomSessionMapNameConfiguration.class); - HazelcastSessionRepository repository = this.context.getBean(HazelcastSessionRepository.class); + HazelcastIndexedSessionRepository repository = this.context.getBean(HazelcastIndexedSessionRepository.class); HazelcastHttpSessionConfiguration configuration = this.context.getBean(HazelcastHttpSessionConfiguration.class); assertThat(repository).isNotNull(); assertThat(ReflectionTestUtils.getField(configuration, "sessionMapName")).isEqualTo(MAP_NAME); @@ -91,7 +90,7 @@ class HazelcastHttpSessionConfigurationTests { void setCustomSessionMapName() { registerAndRefresh(BaseConfiguration.class, CustomSessionMapNameSetConfiguration.class); - HazelcastSessionRepository repository = this.context.getBean(HazelcastSessionRepository.class); + HazelcastIndexedSessionRepository repository = this.context.getBean(HazelcastIndexedSessionRepository.class); HazelcastHttpSessionConfiguration configuration = this.context.getBean(HazelcastHttpSessionConfiguration.class); assertThat(repository).isNotNull(); assertThat(ReflectionTestUtils.getField(configuration, "sessionMapName")).isEqualTo(MAP_NAME); @@ -101,7 +100,7 @@ class HazelcastHttpSessionConfigurationTests { void setCustomMaxInactiveIntervalInSeconds() { registerAndRefresh(BaseConfiguration.class, CustomMaxInactiveIntervalInSecondsSetConfiguration.class); - HazelcastSessionRepository repository = this.context.getBean(HazelcastSessionRepository.class); + HazelcastIndexedSessionRepository repository = this.context.getBean(HazelcastIndexedSessionRepository.class); assertThat(repository).isNotNull(); assertThat(ReflectionTestUtils.getField(repository, "defaultMaxInactiveInterval")) .isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS); @@ -111,7 +110,7 @@ class HazelcastHttpSessionConfigurationTests { void customMaxInactiveIntervalInSeconds() { registerAndRefresh(CustomMaxInactiveIntervalInSecondsConfiguration.class); - HazelcastSessionRepository repository = this.context.getBean(HazelcastSessionRepository.class); + HazelcastIndexedSessionRepository repository = this.context.getBean(HazelcastIndexedSessionRepository.class); assertThat(repository).isNotNull(); assertThat(ReflectionTestUtils.getField(repository, "defaultMaxInactiveInterval")) .isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS); @@ -121,7 +120,7 @@ class HazelcastHttpSessionConfigurationTests { void customFlushImmediately() { registerAndRefresh(CustomFlushImmediatelyConfiguration.class); - HazelcastSessionRepository repository = this.context.getBean(HazelcastSessionRepository.class); + HazelcastIndexedSessionRepository repository = this.context.getBean(HazelcastIndexedSessionRepository.class); assertThat(repository).isNotNull(); assertThat(ReflectionTestUtils.getField(repository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); } @@ -130,7 +129,7 @@ class HazelcastHttpSessionConfigurationTests { void customFlushImmediatelyLegacy() { registerAndRefresh(CustomFlushImmediatelyLegacyConfiguration.class); - HazelcastSessionRepository repository = this.context.getBean(HazelcastSessionRepository.class); + HazelcastIndexedSessionRepository repository = this.context.getBean(HazelcastIndexedSessionRepository.class); assertThat(repository).isNotNull(); assertThat(ReflectionTestUtils.getField(repository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); } @@ -139,7 +138,7 @@ class HazelcastHttpSessionConfigurationTests { void setCustomFlushImmediately() { registerAndRefresh(BaseConfiguration.class, CustomFlushImmediatelySetConfiguration.class); - HazelcastSessionRepository repository = this.context.getBean(HazelcastSessionRepository.class); + HazelcastIndexedSessionRepository repository = this.context.getBean(HazelcastIndexedSessionRepository.class); assertThat(repository).isNotNull(); assertThat(ReflectionTestUtils.getField(repository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); } @@ -148,7 +147,7 @@ class HazelcastHttpSessionConfigurationTests { void setCustomFlushImmediatelyLegacy() { registerAndRefresh(BaseConfiguration.class, CustomFlushImmediatelySetLegacyConfiguration.class); - HazelcastSessionRepository repository = this.context.getBean(HazelcastSessionRepository.class); + HazelcastIndexedSessionRepository repository = this.context.getBean(HazelcastIndexedSessionRepository.class); assertThat(repository).isNotNull(); assertThat(ReflectionTestUtils.getField(repository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); } @@ -156,22 +155,22 @@ class HazelcastHttpSessionConfigurationTests { @Test void customSaveModeAnnotation() { registerAndRefresh(BaseConfiguration.class, CustomSaveModeExpressionAnnotationConfiguration.class); - assertThat(this.context.getBean(HazelcastSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode", - SaveMode.ALWAYS); + assertThat(this.context.getBean(HazelcastIndexedSessionRepository.class)) + .hasFieldOrPropertyWithValue("saveMode", SaveMode.ALWAYS); } @Test void customSaveModeSetter() { registerAndRefresh(BaseConfiguration.class, CustomSaveModeExpressionSetterConfiguration.class); - assertThat(this.context.getBean(HazelcastSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode", - SaveMode.ALWAYS); + assertThat(this.context.getBean(HazelcastIndexedSessionRepository.class)) + .hasFieldOrPropertyWithValue("saveMode", SaveMode.ALWAYS); } @Test void qualifiedHazelcastInstanceConfiguration() { registerAndRefresh(QualifiedHazelcastInstanceConfiguration.class); - HazelcastSessionRepository repository = this.context.getBean(HazelcastSessionRepository.class); + HazelcastIndexedSessionRepository repository = this.context.getBean(HazelcastIndexedSessionRepository.class); HazelcastInstance hazelcastInstance = this.context.getBean("qualifiedHazelcastInstance", HazelcastInstance.class); assertThat(repository).isNotNull(); @@ -184,7 +183,7 @@ class HazelcastHttpSessionConfigurationTests { void primaryHazelcastInstanceConfiguration() { registerAndRefresh(PrimaryHazelcastInstanceConfiguration.class); - HazelcastSessionRepository repository = this.context.getBean(HazelcastSessionRepository.class); + HazelcastIndexedSessionRepository repository = this.context.getBean(HazelcastIndexedSessionRepository.class); HazelcastInstance hazelcastInstance = this.context.getBean("primaryHazelcastInstance", HazelcastInstance.class); assertThat(repository).isNotNull(); assertThat(hazelcastInstance).isNotNull(); @@ -196,7 +195,7 @@ class HazelcastHttpSessionConfigurationTests { void qualifiedAndPrimaryHazelcastInstanceConfiguration() { registerAndRefresh(QualifiedAndPrimaryHazelcastInstanceConfiguration.class); - HazelcastSessionRepository repository = this.context.getBean(HazelcastSessionRepository.class); + HazelcastIndexedSessionRepository repository = this.context.getBean(HazelcastIndexedSessionRepository.class); HazelcastInstance hazelcastInstance = this.context.getBean("qualifiedHazelcastInstance", HazelcastInstance.class); assertThat(repository).isNotNull(); @@ -209,7 +208,7 @@ class HazelcastHttpSessionConfigurationTests { void namedHazelcastInstanceConfiguration() { registerAndRefresh(NamedHazelcastInstanceConfiguration.class); - HazelcastSessionRepository repository = this.context.getBean(HazelcastSessionRepository.class); + HazelcastIndexedSessionRepository repository = this.context.getBean(HazelcastIndexedSessionRepository.class); HazelcastInstance hazelcastInstance = this.context.getBean("hazelcastInstance", HazelcastInstance.class); assertThat(repository).isNotNull(); assertThat(hazelcastInstance).isNotNull(); @@ -227,7 +226,8 @@ class HazelcastHttpSessionConfigurationTests { @Test void sessionRepositoryCustomizer() { registerAndRefresh(SessionRepositoryCustomizerConfiguration.class); - HazelcastSessionRepository sessionRepository = this.context.getBean(HazelcastSessionRepository.class); + HazelcastIndexedSessionRepository sessionRepository = this.context + .getBean(HazelcastIndexedSessionRepository.class); assertThat(sessionRepository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", MAX_INACTIVE_INTERVAL_IN_SECONDS); } @@ -303,6 +303,7 @@ class HazelcastHttpSessionConfigurationTests { } @Configuration + @SuppressWarnings("deprecation") static class CustomFlushImmediatelySetLegacyConfiguration extends HazelcastHttpSessionConfiguration { CustomFlushImmediatelySetLegacyConfiguration() { @@ -333,6 +334,7 @@ class HazelcastHttpSessionConfigurationTests { @Configuration @EnableHazelcastHttpSession(hazelcastFlushMode = HazelcastFlushMode.IMMEDIATE) + @SuppressWarnings("deprecation") static class CustomFlushImmediatelyLegacyConfiguration extends BaseConfiguration { } @@ -436,13 +438,13 @@ class HazelcastHttpSessionConfigurationTests { @Bean @Order(0) - public SessionRepositoryCustomizer sessionRepositoryCustomizerOne() { + public SessionRepositoryCustomizer sessionRepositoryCustomizerOne() { return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(0); } @Bean @Order(1) - public SessionRepositoryCustomizer sessionRepositoryCustomizerTwo() { + public SessionRepositoryCustomizer sessionRepositoryCustomizerTwo() { return (sessionRepository) -> sessionRepository .setDefaultMaxInactiveInterval(MAX_INACTIVE_INTERVAL_IN_SECONDS); } diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/AbstractContainerJdbcOperationsSessionRepositoryITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/AbstractContainerJdbcIndexedSessionRepositoryITests.java similarity index 88% rename from spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/AbstractContainerJdbcOperationsSessionRepositoryITests.java rename to spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/AbstractContainerJdbcIndexedSessionRepositoryITests.java index dd534b02..760ee8c7 100644 --- a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/AbstractContainerJdbcOperationsSessionRepositoryITests.java +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/AbstractContainerJdbcIndexedSessionRepositoryITests.java @@ -26,13 +26,12 @@ import org.springframework.jdbc.datasource.init.DataSourceInitializer; import org.springframework.jdbc.datasource.init.DatabasePopulator; /** - * Abstract base class for Testcontainers based {@link JdbcOperationsSessionRepository} - * integration tests. + * Base class for Testcontainers based {@link JdbcIndexedSessionRepository} integration + * tests. * * @author Vedran Pavic */ -abstract class AbstractContainerJdbcOperationsSessionRepositoryITests - extends AbstractJdbcOperationsSessionRepositoryITests { +abstract class AbstractContainerJdbcIndexedSessionRepositoryITests extends AbstractJdbcIndexedSessionRepositoryITests { static class BaseContainerConfig extends BaseConfig { diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/AbstractJdbcOperationsSessionRepositoryITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/AbstractJdbcIndexedSessionRepositoryITests.java similarity index 75% rename from spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/AbstractJdbcOperationsSessionRepositoryITests.java rename to spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/AbstractJdbcIndexedSessionRepositoryITests.java index 793fb34b..1df3c57c 100644 --- a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/AbstractJdbcOperationsSessionRepositoryITests.java +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/AbstractJdbcIndexedSessionRepositoryITests.java @@ -38,6 +38,7 @@ import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.MapSession; +import org.springframework.session.jdbc.JdbcIndexedSessionRepository.JdbcSession; import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.transaction.PlatformTransactionManager; @@ -46,18 +47,18 @@ import org.springframework.transaction.annotation.Transactional; import static org.assertj.core.api.Assertions.assertThat; /** - * Abstract base class for {@link JdbcOperationsSessionRepository} integration tests. + * Base class for {@link JdbcIndexedSessionRepository} integration tests. * * @author Vedran Pavic */ -abstract class AbstractJdbcOperationsSessionRepositoryITests { +abstract class AbstractJdbcIndexedSessionRepositoryITests { private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; private static final String INDEX_NAME = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME; @Autowired - private JdbcOperationsSessionRepository repository; + private JdbcIndexedSessionRepository repository; private SecurityContext context; @@ -76,10 +77,10 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test void saveWhenNoAttributesThenCanBeFound() { - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); this.repository.save(toSave); - JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(toSave.getId()); + JdbcSession session = this.repository.findById(toSave.getId()); assertThat(session).isNotNull(); assertThat(session.isChanged()).isFalse(); @@ -90,7 +91,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { void saves() { String username = "saves-" + System.currentTimeMillis(); - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); String expectedAttributeName = "a"; String expectedAttributeValue = "b"; toSave.setAttribute(expectedAttributeName, expectedAttributeValue); @@ -103,7 +104,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { this.repository.save(toSave); - JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(toSave.getId()); + JdbcSession session = this.repository.findById(toSave.getId()); assertThat(session.getId()).isEqualTo(toSave.getId()); assertThat(session.isChanged()).isFalse(); @@ -120,14 +121,14 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test @Transactional(readOnly = true) void savesInReadOnlyTransaction() { - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); this.repository.save(toSave); } @Test void putAllOnSingleAttrDoesNotRemoveOld() { - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); toSave.setAttribute("a", "b"); this.repository.save(toSave); @@ -138,7 +139,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { this.repository.save(toSave); toSave = this.repository.findById(toSave.getId()); - JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(toSave.getId()); + JdbcSession session = this.repository.findById(toSave.getId()); assertThat(session.isChanged()).isFalse(); assertThat(session.getDelta()).isEmpty(); assertThat(session.getAttributeNames().size()).isEqualTo(2); @@ -150,7 +151,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test void updateLastAccessedTime() { - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); toSave.setLastAccessedTime(Instant.now().minusSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS + 1)); this.repository.save(toSave); @@ -159,7 +160,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { toSave.setLastAccessedTime(lastAccessedTime); this.repository.save(toSave); - JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(toSave.getId()); + JdbcSession session = this.repository.findById(toSave.getId()); assertThat(session).isNotNull(); assertThat(session.isChanged()).isFalse(); @@ -172,13 +173,13 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test void findByPrincipalName() { String principalName = "findByPrincipalName" + UUID.randomUUID(); - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); toSave.setAttribute(INDEX_NAME, principalName); this.repository.save(toSave); - Map findByPrincipalName = this.repository - .findByIndexNameAndIndexValue(INDEX_NAME, principalName); + Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, + principalName); assertThat(findByPrincipalName).hasSize(1); assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); @@ -194,15 +195,15 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test void findByPrincipalNameExpireRemovesIndex() { String principalName = "findByPrincipalNameExpireRemovesIndex" + UUID.randomUUID(); - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); toSave.setAttribute(INDEX_NAME, principalName); toSave.setLastAccessedTime(Instant.now().minusSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS + 1)); this.repository.save(toSave); this.repository.cleanUpExpiredSessions(); - Map findByPrincipalName = this.repository - .findByIndexNameAndIndexValue(INDEX_NAME, principalName); + Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, + principalName); assertThat(findByPrincipalName).hasSize(0); assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId()); @@ -211,7 +212,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test void findByPrincipalNameNoPrincipalNameChange() { String principalName = "findByPrincipalNameNoPrincipalNameChange" + UUID.randomUUID(); - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); toSave.setAttribute(INDEX_NAME, principalName); this.repository.save(toSave); @@ -219,8 +220,8 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { toSave.setAttribute("other", "value"); this.repository.save(toSave); - Map findByPrincipalName = this.repository - .findByIndexNameAndIndexValue(INDEX_NAME, principalName); + Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, + principalName); assertThat(findByPrincipalName).hasSize(1); assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); @@ -233,7 +234,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test void findByPrincipalNameNoPrincipalNameChangeReload() { String principalName = "findByPrincipalNameNoPrincipalNameChangeReload" + UUID.randomUUID(); - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); toSave.setAttribute(INDEX_NAME, principalName); this.repository.save(toSave); @@ -243,8 +244,8 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { toSave.setAttribute("other", "value"); this.repository.save(toSave); - Map findByPrincipalName = this.repository - .findByIndexNameAndIndexValue(INDEX_NAME, principalName); + Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, + principalName); assertThat(findByPrincipalName).hasSize(1); assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); @@ -257,7 +258,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test void findByDeletedPrincipalName() { String principalName = "findByDeletedPrincipalName" + UUID.randomUUID(); - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); toSave.setAttribute(INDEX_NAME, principalName); this.repository.save(toSave); @@ -265,8 +266,8 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { toSave.setAttribute(INDEX_NAME, null); this.repository.save(toSave); - Map findByPrincipalName = this.repository - .findByIndexNameAndIndexValue(INDEX_NAME, principalName); + Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, + principalName); assertThat(findByPrincipalName).isEmpty(); } @@ -275,7 +276,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { void findByChangedPrincipalName() { String principalName = "findByChangedPrincipalName" + UUID.randomUUID(); String principalNameChanged = "findByChangedPrincipalName" + UUID.randomUUID(); - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); toSave.setAttribute(INDEX_NAME, principalName); this.repository.save(toSave); @@ -283,8 +284,8 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { toSave.setAttribute(INDEX_NAME, principalNameChanged); this.repository.save(toSave); - Map findByPrincipalName = this.repository - .findByIndexNameAndIndexValue(INDEX_NAME, principalName); + Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, + principalName); assertThat(findByPrincipalName).isEmpty(); findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, principalNameChanged); @@ -300,17 +301,17 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test void findByDeletedPrincipalNameReload() { String principalName = "findByDeletedPrincipalName" + UUID.randomUUID(); - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); toSave.setAttribute(INDEX_NAME, principalName); this.repository.save(toSave); - JdbcOperationsSessionRepository.JdbcSession getSession = this.repository.findById(toSave.getId()); + JdbcSession getSession = this.repository.findById(toSave.getId()); getSession.setAttribute(INDEX_NAME, null); this.repository.save(getSession); - Map findByPrincipalName = this.repository - .findByIndexNameAndIndexValue(INDEX_NAME, principalName); + Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, + principalName); assertThat(findByPrincipalName).isEmpty(); } @@ -319,18 +320,18 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { void findByChangedPrincipalNameReload() { String principalName = "findByChangedPrincipalName" + UUID.randomUUID(); String principalNameChanged = "findByChangedPrincipalName" + UUID.randomUUID(); - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); toSave.setAttribute(INDEX_NAME, principalName); this.repository.save(toSave); - JdbcOperationsSessionRepository.JdbcSession getSession = this.repository.findById(toSave.getId()); + JdbcSession getSession = this.repository.findById(toSave.getId()); getSession.setAttribute(INDEX_NAME, principalNameChanged); this.repository.save(getSession); - Map findByPrincipalName = this.repository - .findByIndexNameAndIndexValue(INDEX_NAME, principalName); + Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, + principalName); assertThat(findByPrincipalName).isEmpty(); findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, principalNameChanged); @@ -345,13 +346,13 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test void findBySecurityPrincipalName() { - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); this.repository.save(toSave); - Map findByPrincipalName = this.repository - .findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName()); + Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, + getSecurityName()); assertThat(findByPrincipalName).hasSize(1); assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); @@ -366,15 +367,15 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test void findBySecurityPrincipalNameExpireRemovesIndex() { - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); toSave.setLastAccessedTime(Instant.now().minusSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS + 1)); this.repository.save(toSave); this.repository.cleanUpExpiredSessions(); - Map findByPrincipalName = this.repository - .findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName()); + Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, + getSecurityName()); assertThat(findByPrincipalName).hasSize(0); assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId()); @@ -382,7 +383,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test void findByPrincipalNameNoSecurityPrincipalNameChange() { - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); this.repository.save(toSave); @@ -390,8 +391,8 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { toSave.setAttribute("other", "value"); this.repository.save(toSave); - Map findByPrincipalName = this.repository - .findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName()); + Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, + getSecurityName()); assertThat(findByPrincipalName).hasSize(1); assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); @@ -403,7 +404,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test void findByPrincipalNameNoSecurityPrincipalNameChangeReload() { - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); this.repository.save(toSave); @@ -413,8 +414,8 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { toSave.setAttribute("other", "value"); this.repository.save(toSave); - Map findByPrincipalName = this.repository - .findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName()); + Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, + getSecurityName()); assertThat(findByPrincipalName).hasSize(1); assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); @@ -426,7 +427,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test void findByDeletedSecurityPrincipalName() { - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); this.repository.save(toSave); @@ -434,15 +435,15 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { toSave.setAttribute(SPRING_SECURITY_CONTEXT, null); this.repository.save(toSave); - Map findByPrincipalName = this.repository - .findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName()); + Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, + getSecurityName()); assertThat(findByPrincipalName).isEmpty(); } @Test void findByChangedSecurityPrincipalName() { - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); this.repository.save(toSave); @@ -450,8 +451,8 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.changedContext); this.repository.save(toSave); - Map findByPrincipalName = this.repository - .findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName()); + Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, + getSecurityName()); assertThat(findByPrincipalName).isEmpty(); findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, getChangedSecurityName()); @@ -466,35 +467,35 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test void findByDeletedSecurityPrincipalNameReload() { - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); this.repository.save(toSave); - JdbcOperationsSessionRepository.JdbcSession getSession = this.repository.findById(toSave.getId()); + JdbcSession getSession = this.repository.findById(toSave.getId()); getSession.setAttribute(INDEX_NAME, null); this.repository.save(getSession); - Map findByPrincipalName = this.repository - .findByIndexNameAndIndexValue(INDEX_NAME, getChangedSecurityName()); + Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, + getChangedSecurityName()); assertThat(findByPrincipalName).isEmpty(); } @Test void findByChangedSecurityPrincipalNameReload() { - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); this.repository.save(toSave); - JdbcOperationsSessionRepository.JdbcSession getSession = this.repository.findById(toSave.getId()); + JdbcSession getSession = this.repository.findById(toSave.getId()); getSession.setAttribute(SPRING_SECURITY_CONTEXT, this.changedContext); this.repository.save(getSession); - Map findByPrincipalName = this.repository - .findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName()); + Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, + getSecurityName()); assertThat(findByPrincipalName).isEmpty(); findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, getChangedSecurityName()); @@ -509,7 +510,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test void cleanupInactiveSessionsUsingRepositoryDefinedInterval() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession(); + JdbcSession session = this.repository.createSession(); this.repository.save(session); @@ -537,7 +538,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { // gh-580 @Test void cleanupInactiveSessionsUsingSessionDefinedInterval() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession(); + JdbcSession session = this.repository.createSession(); session.setMaxInactiveInterval(Duration.ofMinutes(45)); this.repository.save(session); @@ -567,12 +568,12 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { void changeSessionIdWhenOnlyChangeId() { String attrName = "changeSessionId"; String attrValue = "changeSessionId-value"; - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); toSave.setAttribute(attrName, attrValue); this.repository.save(toSave); - JdbcOperationsSessionRepository.JdbcSession findById = this.repository.findById(toSave.getId()); + JdbcSession findById = this.repository.findById(toSave.getId()); assertThat(findById.getAttribute(attrName)).isEqualTo(attrValue); @@ -583,7 +584,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { assertThat(this.repository.findById(originalFindById)).isNull(); - JdbcOperationsSessionRepository.JdbcSession findByChangeSessionId = this.repository.findById(changeSessionId); + JdbcSession findByChangeSessionId = this.repository.findById(changeSessionId); assertThat(findByChangeSessionId.isChanged()).isFalse(); assertThat(findByChangeSessionId.getDelta()).isEmpty(); @@ -592,7 +593,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test void changeSessionIdWhenChangeTwice() { - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); this.repository.save(toSave); @@ -612,11 +613,11 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { String attrName = "changeSessionId"; String attrValue = "changeSessionId-value"; - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); this.repository.save(toSave); - JdbcOperationsSessionRepository.JdbcSession findById = this.repository.findById(toSave.getId()); + JdbcSession findById = this.repository.findById(toSave.getId()); findById.setAttribute(attrName, attrValue); @@ -627,7 +628,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { assertThat(this.repository.findById(originalFindById)).isNull(); - JdbcOperationsSessionRepository.JdbcSession findByChangeSessionId = this.repository.findById(changeSessionId); + JdbcSession findByChangeSessionId = this.repository.findById(changeSessionId); assertThat(findByChangeSessionId.isChanged()).isFalse(); assertThat(findByChangeSessionId.getDelta()).isEmpty(); @@ -636,7 +637,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test void changeSessionIdWhenHasNotSaved() { - JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession(); + JdbcSession toSave = this.repository.createSession(); String originalId = toSave.getId(); toSave.changeSessionId(); @@ -648,7 +649,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test // gh-1070 void saveUpdatedAddAndModifyAttribute() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession(); + JdbcSession session = this.repository.createSession(); this.repository.save(session); session = this.repository.findById(session.getId()); session.setAttribute("testName", "testValue1"); @@ -661,7 +662,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test // gh-1070 void saveUpdatedAddAndRemoveAttribute() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession(); + JdbcSession session = this.repository.createSession(); this.repository.save(session); session = this.repository.findById(session.getId()); session.setAttribute("testName", "testValue"); @@ -674,7 +675,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test // gh-1070 void saveUpdatedModifyAndRemoveAttribute() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession(); + JdbcSession session = this.repository.createSession(); session.setAttribute("testName", "testValue1"); this.repository.save(session); session = this.repository.findById(session.getId()); @@ -688,7 +689,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test // gh-1070 void saveUpdatedRemoveAndAddAttribute() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession(); + JdbcSession session = this.repository.createSession(); session.setAttribute("testName", "testValue1"); this.repository.save(session); session = this.repository.findById(session.getId()); @@ -702,7 +703,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test // gh-1031 void saveDeleted() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession(); + JdbcSession session = this.repository.createSession(); this.repository.save(session); session = this.repository.findById(session.getId()); this.repository.deleteById(session.getId()); @@ -714,7 +715,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test // gh-1031 void saveDeletedAddAttribute() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession(); + JdbcSession session = this.repository.createSession(); this.repository.save(session); session = this.repository.findById(session.getId()); this.repository.deleteById(session.getId()); @@ -727,7 +728,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { @Test // gh-1133 void sessionFromStoreResolvesAttributesLazily() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession(); + JdbcSession session = this.repository.createSession(); session.setAttribute("attribute1", "value1"); session.setAttribute("attribute2", "value2"); this.repository.save(session); @@ -749,7 +750,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests { String attributeName = "largeAttribute"; int arraySize = 4000; - JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession(); + JdbcSession session = this.repository.createSession(); session.setAttribute(attributeName, new byte[arraySize]); this.repository.save(session); session = this.repository.findById(session.getId()); diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/Db211JdbcOperationsSessionRepositoryITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/Db211JdbcIndexedSessionRepositoryITests.java similarity index 88% rename from spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/Db211JdbcOperationsSessionRepositoryITests.java rename to spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/Db211JdbcIndexedSessionRepositoryITests.java index 3dec0bbe..e049037c 100644 --- a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/Db211JdbcOperationsSessionRepositoryITests.java +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/Db211JdbcIndexedSessionRepositoryITests.java @@ -27,15 +27,14 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; /** - * Integration tests for {@link JdbcOperationsSessionRepository} using IBM DB2 11.x - * database. + * Integration tests for {@link JdbcIndexedSessionRepository} using IBM DB2 11.x database. * * @author Vedran Pavic */ @ExtendWith(SpringExtension.class) @WebAppConfiguration @ContextConfiguration -class Db211JdbcOperationsSessionRepositoryITests extends AbstractContainerJdbcOperationsSessionRepositoryITests { +class Db211JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests { @Configuration static class Config extends BaseContainerConfig { diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/DerbyJdbcOperationsSessionRepositoryITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/DerbyJdbcIndexedSessionRepositoryITests.java similarity index 89% rename from spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/DerbyJdbcOperationsSessionRepositoryITests.java rename to spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/DerbyJdbcIndexedSessionRepositoryITests.java index 32cde2a7..409ca9c4 100644 --- a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/DerbyJdbcOperationsSessionRepositoryITests.java +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/DerbyJdbcIndexedSessionRepositoryITests.java @@ -28,14 +28,14 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; /** - * Integration tests for {@link JdbcOperationsSessionRepository} using Derby database. + * Integration tests for {@link JdbcIndexedSessionRepository} using Derby database. * * @author Vedran Pavic */ @ExtendWith(SpringExtension.class) @WebAppConfiguration @ContextConfiguration -class DerbyJdbcOperationsSessionRepositoryITests extends AbstractJdbcOperationsSessionRepositoryITests { +class DerbyJdbcIndexedSessionRepositoryITests extends AbstractJdbcIndexedSessionRepositoryITests { @Configuration static class Config extends BaseConfig { diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/H2JdbcOperationsSessionRepositoryITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/H2JdbcIndexedSessionRepositoryITests.java similarity index 89% rename from spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/H2JdbcOperationsSessionRepositoryITests.java rename to spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/H2JdbcIndexedSessionRepositoryITests.java index 9c1e9a4a..e8d23748 100644 --- a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/H2JdbcOperationsSessionRepositoryITests.java +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/H2JdbcIndexedSessionRepositoryITests.java @@ -28,14 +28,14 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; /** - * Integration tests for {@link JdbcOperationsSessionRepository} using H2 database. + * Integration tests for {@link JdbcIndexedSessionRepository} using H2 database. * * @author Vedran Pavic */ @ExtendWith(SpringExtension.class) @WebAppConfiguration @ContextConfiguration -class H2JdbcOperationsSessionRepositoryITests extends AbstractJdbcOperationsSessionRepositoryITests { +class H2JdbcIndexedSessionRepositoryITests extends AbstractJdbcIndexedSessionRepositoryITests { @Configuration static class Config extends BaseConfig { diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/HsqldbJdbcOperationsSessionRepositoryITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/HsqldbJdbcIndexedSessionRepositoryITests.java similarity index 89% rename from spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/HsqldbJdbcOperationsSessionRepositoryITests.java rename to spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/HsqldbJdbcIndexedSessionRepositoryITests.java index 1462d997..2b1e9eff 100644 --- a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/HsqldbJdbcOperationsSessionRepositoryITests.java +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/HsqldbJdbcIndexedSessionRepositoryITests.java @@ -28,14 +28,14 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; /** - * Integration tests for {@link JdbcOperationsSessionRepository} using HSQLDB database. + * Integration tests for {@link JdbcIndexedSessionRepository} using HSQLDB database. * * @author Vedran Pavic */ @ExtendWith(SpringExtension.class) @WebAppConfiguration @ContextConfiguration -class HsqldbJdbcOperationsSessionRepositoryITests extends AbstractJdbcOperationsSessionRepositoryITests { +class HsqldbJdbcIndexedSessionRepositoryITests extends AbstractJdbcIndexedSessionRepositoryITests { @Configuration static class Config extends BaseConfig { diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MariaDb10JdbcOperationsSessionRepositoryITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MariaDb10JdbcIndexedSessionRepositoryITests.java similarity index 88% rename from spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MariaDb10JdbcOperationsSessionRepositoryITests.java rename to spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MariaDb10JdbcIndexedSessionRepositoryITests.java index 9ae752b6..d51b0c8e 100644 --- a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MariaDb10JdbcOperationsSessionRepositoryITests.java +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MariaDb10JdbcIndexedSessionRepositoryITests.java @@ -27,15 +27,14 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; /** - * Integration tests for {@link JdbcOperationsSessionRepository} using MariaDB 10.x - * database. + * Integration tests for {@link JdbcIndexedSessionRepository} using MariaDB 10.x database. * * @author Vedran Pavic */ @ExtendWith(SpringExtension.class) @WebAppConfiguration @ContextConfiguration -class MariaDb10JdbcOperationsSessionRepositoryITests extends AbstractContainerJdbcOperationsSessionRepositoryITests { +class MariaDb10JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests { @Configuration static class Config extends BaseContainerConfig { diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MariaDb5JdbcOperationsSessionRepositoryITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MariaDb5JdbcIndexedSessionRepositoryITests.java similarity index 88% rename from spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MariaDb5JdbcOperationsSessionRepositoryITests.java rename to spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MariaDb5JdbcIndexedSessionRepositoryITests.java index e1dbe2a8..9094c76e 100644 --- a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MariaDb5JdbcOperationsSessionRepositoryITests.java +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MariaDb5JdbcIndexedSessionRepositoryITests.java @@ -27,15 +27,14 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; /** - * Integration tests for {@link JdbcOperationsSessionRepository} using MariaDB 5.x - * database. + * Integration tests for {@link JdbcIndexedSessionRepository} using MariaDB 5.x database. * * @author Vedran Pavic */ @ExtendWith(SpringExtension.class) @WebAppConfiguration @ContextConfiguration -class MariaDb5JdbcOperationsSessionRepositoryITests extends AbstractContainerJdbcOperationsSessionRepositoryITests { +class MariaDb5JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests { @Configuration static class Config extends BaseContainerConfig { diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MySql5JdbcOperationsSessionRepositoryITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MySql5JdbcIndexedSessionRepositoryITests.java similarity index 88% rename from spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MySql5JdbcOperationsSessionRepositoryITests.java rename to spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MySql5JdbcIndexedSessionRepositoryITests.java index 439cbe6f..d8b8154d 100644 --- a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MySql5JdbcOperationsSessionRepositoryITests.java +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MySql5JdbcIndexedSessionRepositoryITests.java @@ -27,14 +27,14 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; /** - * Integration tests for {@link JdbcOperationsSessionRepository} using MySQL 5.x database. + * Integration tests for {@link JdbcIndexedSessionRepository} using MySQL 5.x database. * * @author Vedran Pavic */ @ExtendWith(SpringExtension.class) @WebAppConfiguration @ContextConfiguration -class MySql5JdbcOperationsSessionRepositoryITests extends AbstractContainerJdbcOperationsSessionRepositoryITests { +class MySql5JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests { @Configuration static class Config extends BaseContainerConfig { diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MySql8JdbcOperationsSessionRepositoryITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MySql8JdbcIndexedSessionRepositoryITests.java similarity index 88% rename from spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MySql8JdbcOperationsSessionRepositoryITests.java rename to spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MySql8JdbcIndexedSessionRepositoryITests.java index fe14e690..cc9a9865 100644 --- a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MySql8JdbcOperationsSessionRepositoryITests.java +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MySql8JdbcIndexedSessionRepositoryITests.java @@ -27,14 +27,14 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; /** - * Integration tests for {@link JdbcOperationsSessionRepository} using MySQL 8.x database. + * Integration tests for {@link JdbcIndexedSessionRepository} using MySQL 8.x database. * * @author Vedran Pavic */ @ExtendWith(SpringExtension.class) @WebAppConfiguration @ContextConfiguration -class MySql8JdbcOperationsSessionRepositoryITests extends AbstractContainerJdbcOperationsSessionRepositoryITests { +class MySql8JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests { @Configuration static class Config extends BaseContainerConfig { diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/OracleJdbcOperationsSessionRepositoryITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/OracleJdbcIndexedSessionRepositoryITests.java similarity index 92% rename from spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/OracleJdbcOperationsSessionRepositoryITests.java rename to spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/OracleJdbcIndexedSessionRepositoryITests.java index 6db0119f..186ca71e 100644 --- a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/OracleJdbcOperationsSessionRepositoryITests.java +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/OracleJdbcIndexedSessionRepositoryITests.java @@ -31,7 +31,7 @@ import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.util.ClassUtils; /** - * Integration tests for {@link JdbcOperationsSessionRepository} using Oracle database. + * Integration tests for {@link JdbcIndexedSessionRepository} using Oracle database. *

* This test is conditional on presence of Oracle JDBC driver on the classpath and * Testcontainers property {@code oracle.container.image} being set. @@ -41,7 +41,7 @@ import org.springframework.util.ClassUtils; @ExtendWith(SpringExtension.class) @WebAppConfiguration @ContextConfiguration -class OracleJdbcOperationsSessionRepositoryITests extends AbstractContainerJdbcOperationsSessionRepositoryITests { +class OracleJdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests { @BeforeAll static void setUpClass() { diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql10JdbcOperationsSessionRepositoryITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql10JdbcIndexedSessionRepositoryITests.java similarity index 88% rename from spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql10JdbcOperationsSessionRepositoryITests.java rename to spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql10JdbcIndexedSessionRepositoryITests.java index c57e6489..8117cbb3 100644 --- a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql10JdbcOperationsSessionRepositoryITests.java +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql10JdbcIndexedSessionRepositoryITests.java @@ -27,7 +27,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; /** - * Integration tests for {@link JdbcOperationsSessionRepository} using PostgreSQL 10.x + * Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL 10.x * database. * * @author Vedran Pavic @@ -35,7 +35,7 @@ import org.springframework.test.context.web.WebAppConfiguration; @ExtendWith(SpringExtension.class) @WebAppConfiguration @ContextConfiguration -class PostgreSql10JdbcOperationsSessionRepositoryITests extends AbstractContainerJdbcOperationsSessionRepositoryITests { +class PostgreSql10JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests { @Configuration static class Config extends BaseContainerConfig { diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql11JdbcOperationsSessionRepositoryITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql11JdbcIndexedSessionRepositoryITests.java similarity index 88% rename from spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql11JdbcOperationsSessionRepositoryITests.java rename to spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql11JdbcIndexedSessionRepositoryITests.java index f109cce4..b3f49515 100644 --- a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql11JdbcOperationsSessionRepositoryITests.java +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql11JdbcIndexedSessionRepositoryITests.java @@ -27,7 +27,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; /** - * Integration tests for {@link JdbcOperationsSessionRepository} using PostgreSQL 11.x + * Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL 11.x * database. * * @author Vedran Pavic @@ -35,7 +35,7 @@ import org.springframework.test.context.web.WebAppConfiguration; @ExtendWith(SpringExtension.class) @WebAppConfiguration @ContextConfiguration -class PostgreSql11JdbcOperationsSessionRepositoryITests extends AbstractContainerJdbcOperationsSessionRepositoryITests { +class PostgreSql11JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests { @Configuration static class Config extends BaseContainerConfig { diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql9JdbcOperationsSessionRepositoryITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql9JdbcIndexedSessionRepositoryITests.java similarity index 89% rename from spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql9JdbcOperationsSessionRepositoryITests.java rename to spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql9JdbcIndexedSessionRepositoryITests.java index 015d6573..81b8afa4 100644 --- a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql9JdbcOperationsSessionRepositoryITests.java +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql9JdbcIndexedSessionRepositoryITests.java @@ -27,7 +27,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; /** - * Integration tests for {@link JdbcOperationsSessionRepository} using PostgreSQL 9.x + * Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL 9.x * database. * * @author Vedran Pavic @@ -35,7 +35,7 @@ import org.springframework.test.context.web.WebAppConfiguration; @ExtendWith(SpringExtension.class) @WebAppConfiguration @ContextConfiguration -class PostgreSql9JdbcOperationsSessionRepositoryITests extends AbstractContainerJdbcOperationsSessionRepositoryITests { +class PostgreSql9JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests { @Configuration static class Config extends BaseContainerConfig { diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/SqlServerJdbcOperationsSessionRepositoryITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/SqlServerJdbcIndexedSessionRepositoryITests.java similarity index 87% rename from spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/SqlServerJdbcOperationsSessionRepositoryITests.java rename to spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/SqlServerJdbcIndexedSessionRepositoryITests.java index fde69a3b..4f283d36 100644 --- a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/SqlServerJdbcOperationsSessionRepositoryITests.java +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/SqlServerJdbcIndexedSessionRepositoryITests.java @@ -27,15 +27,15 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; /** - * Integration tests for {@link JdbcOperationsSessionRepository} using Microsoft SQL - * Server 2017 database. + * Integration tests for {@link JdbcIndexedSessionRepository} using Microsoft SQL Server + * 2017 database. * * @author Vedran Pavic */ @ExtendWith(SpringExtension.class) @WebAppConfiguration @ContextConfiguration -class SqlServerJdbcOperationsSessionRepositoryITests extends AbstractContainerJdbcOperationsSessionRepositoryITests { +class SqlServerJdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests { @Configuration static class Config extends BaseContainerConfig { diff --git a/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/JdbcIndexedSessionRepository.java b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/JdbcIndexedSessionRepository.java new file mode 100644 index 00000000..0e959c53 --- /dev/null +++ b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/JdbcIndexedSessionRepository.java @@ -0,0 +1,867 @@ +/* + * Copyright 2014-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.session.jdbc; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.core.serializer.support.DeserializingConverter; +import org.springframework.core.serializer.support.SerializingConverter; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.jdbc.support.lob.DefaultLobHandler; +import org.springframework.jdbc.support.lob.LobHandler; +import org.springframework.session.DelegatingIndexResolver; +import org.springframework.session.FindByIndexNameSessionRepository; +import org.springframework.session.FlushMode; +import org.springframework.session.IndexResolver; +import org.springframework.session.MapSession; +import org.springframework.session.PrincipalNameIndexResolver; +import org.springframework.session.SaveMode; +import org.springframework.session.Session; +import org.springframework.transaction.support.TransactionOperations; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * A {@link org.springframework.session.SessionRepository} implementation that uses + * Spring's {@link JdbcOperations} to store sessions in a relational database. This + * implementation does not support publishing of session events. + *

+ * An example of how to create a new instance can be seen below: + * + *

+ * JdbcTemplate jdbcTemplate = new JdbcTemplate();
+ *
+ * // ... configure jdbcTemplate ...
+ *
+ * TransactionTemplate transactionTemplate = new TransactionTemplate();
+ *
+ * // ... configure transactionTemplate ...
+ *
+ * JdbcIndexedSessionRepository sessionRepository =
+ *         new JdbcIndexedSessionRepository(jdbcTemplate, transactionTemplate);
+ * 
+ * + * For additional information on how to create and configure {@code JdbcTemplate} and + * {@code TransactionTemplate}, refer to the + * Spring Framework Reference Documentation. + *

+ * By default, this implementation uses SPRING_SESSION and + * SPRING_SESSION_ATTRIBUTES tables to store sessions. Note that the table + * name can be customized using the {@link #setTableName(String)} method. In that case the + * table used to store attributes will be named using the provided table name, suffixed + * with _ATTRIBUTES. + * + * Depending on your database, the table definition can be described as below: + * + *

+ * CREATE TABLE SPRING_SESSION (
+ *   PRIMARY_ID CHAR(36) NOT NULL,
+ *   SESSION_ID CHAR(36) NOT NULL,
+ *   CREATION_TIME BIGINT NOT NULL,
+ *   LAST_ACCESS_TIME BIGINT NOT NULL,
+ *   MAX_INACTIVE_INTERVAL INT NOT NULL,
+ *   EXPIRY_TIME BIGINT NOT NULL,
+ *   PRINCIPAL_NAME VARCHAR(100),
+ *   CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
+ * );
+ *
+ * CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
+ * CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (EXPIRY_TIME);
+ * CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
+ *
+ * CREATE TABLE SPRING_SESSION_ATTRIBUTES (
+ *  SESSION_PRIMARY_ID CHAR(36) NOT NULL,
+ *  ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
+ *  ATTRIBUTE_BYTES BYTEA NOT NULL,
+ *  CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
+ *  CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
+ * );
+ *
+ * CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_PRIMARY_ID);
+ * 
+ * + * Due to the differences between the various database vendors, especially when it comes + * to storing binary data, make sure to use SQL script specific to your database. Scripts + * for most major database vendors are packaged as + * org/springframework/session/jdbc/schema-*.sql, where * is the + * target database type. + * + * @author Vedran Pavic + * @author Craig Andrews + * @since 2.2.0 + */ +public class JdbcIndexedSessionRepository + implements FindByIndexNameSessionRepository { + + /** + * The default name of database table used by Spring Session to store sessions. + */ + public static final String DEFAULT_TABLE_NAME = "SPRING_SESSION"; + + private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; + + // @formatter:off + private static final String CREATE_SESSION_QUERY = "INSERT INTO %TABLE_NAME%(PRIMARY_ID, SESSION_ID, CREATION_TIME, LAST_ACCESS_TIME, MAX_INACTIVE_INTERVAL, EXPIRY_TIME, PRINCIPAL_NAME) " + + "VALUES (?, ?, ?, ?, ?, ?, ?)"; + // @formatter:on + + // @formatter:off + private static final String CREATE_SESSION_ATTRIBUTE_QUERY = "INSERT INTO %TABLE_NAME%_ATTRIBUTES(SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) " + + "SELECT PRIMARY_ID, ?, ? " + + "FROM %TABLE_NAME% " + + "WHERE SESSION_ID = ?"; + // @formatter:on + + // @formatter:off + private static final String GET_SESSION_QUERY = "SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES " + + "FROM %TABLE_NAME% S " + + "LEFT OUTER JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID " + + "WHERE S.SESSION_ID = ?"; + // @formatter:on + + // @formatter:off + private static final String UPDATE_SESSION_QUERY = "UPDATE %TABLE_NAME% SET SESSION_ID = ?, LAST_ACCESS_TIME = ?, MAX_INACTIVE_INTERVAL = ?, EXPIRY_TIME = ?, PRINCIPAL_NAME = ? " + + "WHERE PRIMARY_ID = ?"; + // @formatter:on + + // @formatter:off + private static final String UPDATE_SESSION_ATTRIBUTE_QUERY = "UPDATE %TABLE_NAME%_ATTRIBUTES SET ATTRIBUTE_BYTES = ? " + + "WHERE SESSION_PRIMARY_ID = ? " + + "AND ATTRIBUTE_NAME = ?"; + // @formatter:on + + // @formatter:off + private static final String DELETE_SESSION_ATTRIBUTE_QUERY = "DELETE FROM %TABLE_NAME%_ATTRIBUTES " + + "WHERE SESSION_PRIMARY_ID = ? " + + "AND ATTRIBUTE_NAME = ?"; + // @formatter:on + + // @formatter:off + private static final String DELETE_SESSION_QUERY = "DELETE FROM %TABLE_NAME% " + + "WHERE SESSION_ID = ?"; + // @formatter:on + + // @formatter:off + private static final String LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY = "SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES " + + "FROM %TABLE_NAME% S " + + "LEFT OUTER JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID " + + "WHERE S.PRINCIPAL_NAME = ?"; + // @formatter:on + + // @formatter:off + private static final String DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY = "DELETE FROM %TABLE_NAME% " + + "WHERE EXPIRY_TIME < ?"; + // @formatter:on + + private static final Log logger = LogFactory.getLog(JdbcIndexedSessionRepository.class); + + private final JdbcOperations jdbcOperations; + + private final TransactionOperations transactionOperations; + + private final ResultSetExtractor> extractor = new SessionResultSetExtractor(); + + private final IndexResolver indexResolver; + + /** + * The name of database table used by Spring Session to store sessions. + */ + private String tableName = DEFAULT_TABLE_NAME; + + private String createSessionQuery; + + private String createSessionAttributeQuery; + + private String getSessionQuery; + + private String updateSessionQuery; + + private String updateSessionAttributeQuery; + + private String deleteSessionAttributeQuery; + + private String deleteSessionQuery; + + private String listSessionsByPrincipalNameQuery; + + private String deleteSessionsByExpiryTimeQuery; + + /** + * If non-null, this value is used to override the default value for + * {@link JdbcSession#setMaxInactiveInterval(Duration)}. + */ + private Integer defaultMaxInactiveInterval; + + private ConversionService conversionService; + + private LobHandler lobHandler; + + private FlushMode flushMode = FlushMode.ON_SAVE; + + private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE; + + /** + * Create a new {@link JdbcIndexedSessionRepository} instance which uses the provided + * {@link JdbcOperations} and {@link TransactionOperations} to manage sessions. + * @param jdbcOperations the {@link JdbcOperations} to use + * @param transactionOperations the {@link TransactionOperations} to use + */ + public JdbcIndexedSessionRepository(JdbcOperations jdbcOperations, TransactionOperations transactionOperations) { + Assert.notNull(jdbcOperations, "jdbcOperations must not be null"); + Assert.notNull(transactionOperations, "transactionOperations must not be null"); + this.jdbcOperations = jdbcOperations; + this.transactionOperations = transactionOperations; + this.indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>()); + this.conversionService = createDefaultConversionService(); + this.lobHandler = new DefaultLobHandler(); + prepareQueries(); + } + + /** + * Set the name of database table used to store sessions. + * @param tableName the database table name + */ + public void setTableName(String tableName) { + Assert.hasText(tableName, "Table name must not be empty"); + this.tableName = tableName.trim(); + prepareQueries(); + } + + /** + * Set the custom SQL query used to create the session. + * @param createSessionQuery the SQL query string + */ + public void setCreateSessionQuery(String createSessionQuery) { + Assert.hasText(createSessionQuery, "Query must not be empty"); + this.createSessionQuery = createSessionQuery; + } + + /** + * Set the custom SQL query used to create the session attribute. + * @param createSessionAttributeQuery the SQL query string + */ + public void setCreateSessionAttributeQuery(String createSessionAttributeQuery) { + Assert.hasText(createSessionAttributeQuery, "Query must not be empty"); + this.createSessionAttributeQuery = createSessionAttributeQuery; + } + + /** + * Set the custom SQL query used to retrieve the session. + * @param getSessionQuery the SQL query string + */ + public void setGetSessionQuery(String getSessionQuery) { + Assert.hasText(getSessionQuery, "Query must not be empty"); + this.getSessionQuery = getSessionQuery; + } + + /** + * Set the custom SQL query used to update the session. + * @param updateSessionQuery the SQL query string + */ + public void setUpdateSessionQuery(String updateSessionQuery) { + Assert.hasText(updateSessionQuery, "Query must not be empty"); + this.updateSessionQuery = updateSessionQuery; + } + + /** + * Set the custom SQL query used to update the session attribute. + * @param updateSessionAttributeQuery the SQL query string + */ + public void setUpdateSessionAttributeQuery(String updateSessionAttributeQuery) { + Assert.hasText(updateSessionAttributeQuery, "Query must not be empty"); + this.updateSessionAttributeQuery = updateSessionAttributeQuery; + } + + /** + * Set the custom SQL query used to delete the session attribute. + * @param deleteSessionAttributeQuery the SQL query string + */ + public void setDeleteSessionAttributeQuery(String deleteSessionAttributeQuery) { + Assert.hasText(deleteSessionAttributeQuery, "Query must not be empty"); + this.deleteSessionAttributeQuery = deleteSessionAttributeQuery; + } + + /** + * Set the custom SQL query used to delete the session. + * @param deleteSessionQuery the SQL query string + */ + public void setDeleteSessionQuery(String deleteSessionQuery) { + Assert.hasText(deleteSessionQuery, "Query must not be empty"); + this.deleteSessionQuery = deleteSessionQuery; + } + + /** + * Set the custom SQL query used to retrieve the sessions by principal name. + * @param listSessionsByPrincipalNameQuery the SQL query string + */ + public void setListSessionsByPrincipalNameQuery(String listSessionsByPrincipalNameQuery) { + Assert.hasText(listSessionsByPrincipalNameQuery, "Query must not be empty"); + this.listSessionsByPrincipalNameQuery = listSessionsByPrincipalNameQuery; + } + + /** + * Set the custom SQL query used to delete the sessions by last access time. + * @param deleteSessionsByExpiryTimeQuery the SQL query string + */ + public void setDeleteSessionsByExpiryTimeQuery(String deleteSessionsByExpiryTimeQuery) { + Assert.hasText(deleteSessionsByExpiryTimeQuery, "Query must not be empty"); + this.deleteSessionsByExpiryTimeQuery = deleteSessionsByExpiryTimeQuery; + } + + /** + * Set the maximum inactive interval in seconds between requests before newly created + * sessions will be invalidated. A negative time indicates that the session will never + * timeout. The default is 1800 (30 minutes). + * @param defaultMaxInactiveInterval the maximum inactive interval in seconds + */ + public void setDefaultMaxInactiveInterval(Integer defaultMaxInactiveInterval) { + this.defaultMaxInactiveInterval = defaultMaxInactiveInterval; + } + + public void setLobHandler(LobHandler lobHandler) { + Assert.notNull(lobHandler, "LobHandler must not be null"); + this.lobHandler = lobHandler; + } + + /** + * Sets the {@link ConversionService} to use. + * @param conversionService the converter to set + */ + public void setConversionService(ConversionService conversionService) { + Assert.notNull(conversionService, "conversionService must not be null"); + this.conversionService = conversionService; + } + + /** + * Set the flush mode. Default is {@link FlushMode#ON_SAVE}. + * @param flushMode the flush mode + */ + public void setFlushMode(FlushMode flushMode) { + Assert.notNull(flushMode, "flushMode must not be null"); + this.flushMode = flushMode; + } + + /** + * Set the save mode. + * @param saveMode the save mode + */ + public void setSaveMode(SaveMode saveMode) { + Assert.notNull(saveMode, "saveMode must not be null"); + this.saveMode = saveMode; + } + + @Override + public JdbcSession createSession() { + MapSession delegate = new MapSession(); + if (this.defaultMaxInactiveInterval != null) { + delegate.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval)); + } + JdbcSession session = new JdbcSession(delegate, UUID.randomUUID().toString(), true); + session.flushIfRequired(); + return session; + } + + @Override + public void save(final JdbcSession session) { + session.save(); + } + + @Override + public JdbcSession findById(final String id) { + final JdbcSession session = this.transactionOperations.execute((status) -> { + List sessions = JdbcIndexedSessionRepository.this.jdbcOperations.query( + JdbcIndexedSessionRepository.this.getSessionQuery, (ps) -> ps.setString(1, id), + JdbcIndexedSessionRepository.this.extractor); + if (sessions.isEmpty()) { + return null; + } + return sessions.get(0); + }); + + if (session != null) { + if (session.isExpired()) { + deleteById(id); + } + else { + return session; + } + } + return null; + } + + @Override + public void deleteById(final String id) { + this.transactionOperations.execute(() -> JdbcIndexedSessionRepository.this.jdbcOperations + .update(JdbcIndexedSessionRepository.this.deleteSessionQuery, id)); + } + + @Override + public Map findByIndexNameAndIndexValue(String indexName, final String indexValue) { + if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) { + return Collections.emptyMap(); + } + + List sessions = this.transactionOperations + .execute((status) -> JdbcIndexedSessionRepository.this.jdbcOperations.query( + JdbcIndexedSessionRepository.this.listSessionsByPrincipalNameQuery, + (ps) -> ps.setString(1, indexValue), JdbcIndexedSessionRepository.this.extractor)); + + Map sessionMap = new HashMap<>(sessions.size()); + + for (JdbcSession session : sessions) { + sessionMap.put(session.getId(), session); + } + + return sessionMap; + } + + private void insertSessionAttributes(JdbcSession session, List attributeNames) { + 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, attributeName); + getLobHandler().getLobCreator().setBlobAsBytes(ps, 2, + serialize(session.getAttribute(attributeName))); + ps.setString(3, session.getId()); + } + + @Override + public int getBatchSize() { + return attributeNames.size(); + } + + }); + } + else { + this.jdbcOperations.update(this.createSessionAttributeQuery, (ps) -> { + String attributeName = attributeNames.get(0); + ps.setString(1, attributeName); + getLobHandler().getLobCreator().setBlobAsBytes(ps, 2, serialize(session.getAttribute(attributeName))); + ps.setString(3, session.getId()); + }); + } + } + + private void updateSessionAttributes(JdbcSession session, List attributeNames) { + Assert.notEmpty(attributeNames, "attributeNames must not be null or empty"); + if (attributeNames.size() > 1) { + this.jdbcOperations.batchUpdate(this.updateSessionAttributeQuery, new BatchPreparedStatementSetter() { + + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + String attributeName = attributeNames.get(i); + getLobHandler().getLobCreator().setBlobAsBytes(ps, 1, + serialize(session.getAttribute(attributeName))); + ps.setString(2, session.primaryKey); + ps.setString(3, attributeName); + } + + @Override + public int getBatchSize() { + return attributeNames.size(); + } + + }); + } + else { + this.jdbcOperations.update(this.updateSessionAttributeQuery, (ps) -> { + String attributeName = attributeNames.get(0); + getLobHandler().getLobCreator().setBlobAsBytes(ps, 1, serialize(session.getAttribute(attributeName))); + ps.setString(2, session.primaryKey); + ps.setString(3, attributeName); + }); + } + } + + private void deleteSessionAttributes(JdbcSession session, List attributeNames) { + Assert.notEmpty(attributeNames, "attributeNames must not be null or empty"); + if (attributeNames.size() > 1) { + this.jdbcOperations.batchUpdate(this.deleteSessionAttributeQuery, 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); + } + + @Override + public int getBatchSize() { + return attributeNames.size(); + } + + }); + } + else { + this.jdbcOperations.update(this.deleteSessionAttributeQuery, (ps) -> { + String attributeName = attributeNames.get(0); + ps.setString(1, session.primaryKey); + ps.setString(2, attributeName); + }); + } + } + + public void cleanUpExpiredSessions() { + Integer deletedCount = this.transactionOperations + .execute((status) -> JdbcIndexedSessionRepository.this.jdbcOperations.update( + JdbcIndexedSessionRepository.this.deleteSessionsByExpiryTimeQuery, System.currentTimeMillis())); + + if (logger.isDebugEnabled()) { + logger.debug("Cleaned up " + deletedCount + " expired sessions"); + } + } + + private static GenericConversionService createDefaultConversionService() { + GenericConversionService converter = new GenericConversionService(); + converter.addConverter(Object.class, byte[].class, new SerializingConverter()); + converter.addConverter(byte[].class, Object.class, new DeserializingConverter()); + return converter; + } + + private String getQuery(String base) { + return StringUtils.replace(base, "%TABLE_NAME%", this.tableName); + } + + private void prepareQueries() { + this.createSessionQuery = getQuery(CREATE_SESSION_QUERY); + this.createSessionAttributeQuery = getQuery(CREATE_SESSION_ATTRIBUTE_QUERY); + this.getSessionQuery = getQuery(GET_SESSION_QUERY); + this.updateSessionQuery = getQuery(UPDATE_SESSION_QUERY); + this.updateSessionAttributeQuery = getQuery(UPDATE_SESSION_ATTRIBUTE_QUERY); + this.deleteSessionAttributeQuery = getQuery(DELETE_SESSION_ATTRIBUTE_QUERY); + this.deleteSessionQuery = getQuery(DELETE_SESSION_QUERY); + this.listSessionsByPrincipalNameQuery = getQuery(LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY); + this.deleteSessionsByExpiryTimeQuery = getQuery(DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY); + } + + private LobHandler getLobHandler() { + return this.lobHandler; + } + + 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)); + } + + private enum DeltaValue { + + ADDED, UPDATED, REMOVED + + } + + private static Supplier value(T value) { + return (value != null) ? () -> value : null; + } + + private static Supplier lazily(Supplier supplier) { + Supplier lazySupplier = new Supplier() { + + private T value; + + @Override + public T get() { + if (this.value == null) { + this.value = supplier.get(); + } + return this.value; + } + + }; + + return (supplier != null) ? lazySupplier : null; + } + + /** + * The {@link Session} to use for {@link JdbcIndexedSessionRepository}. + * + * @author Vedran Pavic + */ + final class JdbcSession implements Session { + + private final Session delegate; + + private final String primaryKey; + + private boolean isNew; + + private boolean changed; + + private Map delta = new HashMap<>(); + + JdbcSession(MapSession delegate, String primaryKey, boolean isNew) { + this.delegate = delegate; + this.primaryKey = primaryKey; + this.isNew = isNew; + if (this.isNew || (JdbcIndexedSessionRepository.this.saveMode == SaveMode.ALWAYS)) { + getAttributeNames().forEach((attributeName) -> this.delta.put(attributeName, DeltaValue.UPDATED)); + } + } + + boolean isNew() { + return this.isNew; + } + + boolean isChanged() { + return this.changed; + } + + Map getDelta() { + return this.delta; + } + + void clearChangeFlags() { + this.isNew = false; + this.changed = false; + this.delta.clear(); + } + + Instant getExpiryTime() { + return getLastAccessedTime().plus(getMaxInactiveInterval()); + } + + @Override + public String getId() { + return this.delegate.getId(); + } + + @Override + public String changeSessionId() { + this.changed = true; + return this.delegate.changeSessionId(); + } + + @Override + public T getAttribute(String attributeName) { + Supplier supplier = this.delegate.getAttribute(attributeName); + if (supplier == null) { + return null; + } + T attributeValue = supplier.get(); + if (attributeValue != null + && JdbcIndexedSessionRepository.this.saveMode.equals(SaveMode.ON_GET_ATTRIBUTE)) { + this.delta.put(attributeName, DeltaValue.UPDATED); + } + return attributeValue; + } + + @Override + public Set getAttributeNames() { + return this.delegate.getAttributeNames(); + } + + @Override + public void setAttribute(String attributeName, Object attributeValue) { + boolean attributeExists = (this.delegate.getAttribute(attributeName) != null); + boolean attributeRemoved = (attributeValue == null); + if (!attributeExists && attributeRemoved) { + return; + } + if (attributeExists) { + if (attributeRemoved) { + this.delta.merge(attributeName, DeltaValue.REMOVED, + (oldDeltaValue, deltaValue) -> (oldDeltaValue == DeltaValue.ADDED) ? null : deltaValue); + } + else { + this.delta.merge(attributeName, DeltaValue.UPDATED, (oldDeltaValue, + deltaValue) -> (oldDeltaValue == DeltaValue.ADDED) ? oldDeltaValue : deltaValue); + } + } + else { + this.delta.merge(attributeName, DeltaValue.ADDED, (oldDeltaValue, + deltaValue) -> (oldDeltaValue == DeltaValue.ADDED) ? oldDeltaValue : DeltaValue.UPDATED); + } + this.delegate.setAttribute(attributeName, value(attributeValue)); + if (PRINCIPAL_NAME_INDEX_NAME.equals(attributeName) || SPRING_SECURITY_CONTEXT.equals(attributeName)) { + this.changed = true; + } + flushIfRequired(); + } + + @Override + public void removeAttribute(String attributeName) { + setAttribute(attributeName, null); + } + + @Override + public Instant getCreationTime() { + return this.delegate.getCreationTime(); + } + + @Override + public void setLastAccessedTime(Instant lastAccessedTime) { + this.delegate.setLastAccessedTime(lastAccessedTime); + this.changed = true; + flushIfRequired(); + } + + @Override + public Instant getLastAccessedTime() { + return this.delegate.getLastAccessedTime(); + } + + @Override + public void setMaxInactiveInterval(Duration interval) { + this.delegate.setMaxInactiveInterval(interval); + this.changed = true; + flushIfRequired(); + } + + @Override + public Duration getMaxInactiveInterval() { + return this.delegate.getMaxInactiveInterval(); + } + + @Override + public boolean isExpired() { + return this.delegate.isExpired(); + } + + private void flushIfRequired() { + if (JdbcIndexedSessionRepository.this.flushMode == FlushMode.IMMEDIATE) { + save(); + } + } + + private void save() { + if (this.isNew) { + JdbcIndexedSessionRepository.this.transactionOperations.execute(() -> { + Map indexes = JdbcIndexedSessionRepository.this.indexResolver + .resolveIndexesFor(JdbcSession.this); + JdbcIndexedSessionRepository.this.jdbcOperations + .update(JdbcIndexedSessionRepository.this.createSessionQuery, (ps) -> { + ps.setString(1, JdbcSession.this.primaryKey); + ps.setString(2, getId()); + ps.setLong(3, getCreationTime().toEpochMilli()); + ps.setLong(4, getLastAccessedTime().toEpochMilli()); + ps.setInt(5, (int) getMaxInactiveInterval().getSeconds()); + ps.setLong(6, getExpiryTime().toEpochMilli()); + ps.setString(7, indexes.get(PRINCIPAL_NAME_INDEX_NAME)); + }); + Set attributeNames = getAttributeNames(); + if (!attributeNames.isEmpty()) { + insertSessionAttributes(JdbcSession.this, new ArrayList<>(attributeNames)); + } + }); + } + else { + JdbcIndexedSessionRepository.this.transactionOperations.execute(() -> { + if (JdbcSession.this.changed) { + Map indexes = JdbcIndexedSessionRepository.this.indexResolver + .resolveIndexesFor(JdbcSession.this); + JdbcIndexedSessionRepository.this.jdbcOperations + .update(JdbcIndexedSessionRepository.this.updateSessionQuery, (ps) -> { + ps.setString(1, getId()); + ps.setLong(2, getLastAccessedTime().toEpochMilli()); + ps.setInt(3, (int) getMaxInactiveInterval().getSeconds()); + ps.setLong(4, getExpiryTime().toEpochMilli()); + ps.setString(5, indexes.get(PRINCIPAL_NAME_INDEX_NAME)); + ps.setString(6, JdbcSession.this.primaryKey); + }); + } + List addedAttributeNames = JdbcSession.this.delta.entrySet().stream() + .filter((entry) -> entry.getValue() == DeltaValue.ADDED).map(Map.Entry::getKey) + .collect(Collectors.toList()); + if (!addedAttributeNames.isEmpty()) { + insertSessionAttributes(JdbcSession.this, addedAttributeNames); + } + List updatedAttributeNames = JdbcSession.this.delta.entrySet().stream() + .filter((entry) -> entry.getValue() == DeltaValue.UPDATED).map(Map.Entry::getKey) + .collect(Collectors.toList()); + if (!updatedAttributeNames.isEmpty()) { + updateSessionAttributes(JdbcSession.this, updatedAttributeNames); + } + List removedAttributeNames = JdbcSession.this.delta.entrySet().stream() + .filter((entry) -> entry.getValue() == DeltaValue.REMOVED).map(Map.Entry::getKey) + .collect(Collectors.toList()); + if (!removedAttributeNames.isEmpty()) { + deleteSessionAttributes(JdbcSession.this, removedAttributeNames); + } + }); + } + clearChangeFlags(); + } + + } + + private class SessionResultSetExtractor implements ResultSetExtractor> { + + @Override + public List extractData(ResultSet rs) throws SQLException, DataAccessException { + List sessions = new ArrayList<>(); + while (rs.next()) { + String id = rs.getString("SESSION_ID"); + JdbcSession session; + if (sessions.size() > 0 && getLast(sessions).getId().equals(id)) { + session = getLast(sessions); + } + else { + MapSession delegate = new MapSession(id); + String primaryKey = rs.getString("PRIMARY_ID"); + delegate.setCreationTime(Instant.ofEpochMilli(rs.getLong("CREATION_TIME"))); + delegate.setLastAccessedTime(Instant.ofEpochMilli(rs.getLong("LAST_ACCESS_TIME"))); + delegate.setMaxInactiveInterval(Duration.ofSeconds(rs.getInt("MAX_INACTIVE_INTERVAL"))); + session = new JdbcSession(delegate, primaryKey, false); + } + String attributeName = rs.getString("ATTRIBUTE_NAME"); + if (attributeName != null) { + byte[] bytes = getLobHandler().getBlobAsBytes(rs, "ATTRIBUTE_BYTES"); + session.delegate.setAttribute(attributeName, lazily(() -> deserialize(bytes))); + } + sessions.add(session); + } + return sessions; + } + + private JdbcSession getLast(List sessions) { + return sessions.get(sessions.size() - 1); + } + + } + +} diff --git a/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/JdbcOperationsSessionRepository.java b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/JdbcOperationsSessionRepository.java index 11a4bbe4..a41989c6 100644 --- a/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/JdbcOperationsSessionRepository.java +++ b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/JdbcOperationsSessionRepository.java @@ -16,229 +16,25 @@ package org.springframework.session.jdbc; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.TypeDescriptor; -import org.springframework.core.convert.support.GenericConversionService; -import org.springframework.core.serializer.support.DeserializingConverter; -import org.springframework.core.serializer.support.SerializingConverter; -import org.springframework.dao.DataAccessException; -import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.ResultSetExtractor; -import org.springframework.jdbc.support.lob.DefaultLobHandler; -import org.springframework.jdbc.support.lob.LobHandler; -import org.springframework.session.DelegatingIndexResolver; -import org.springframework.session.FindByIndexNameSessionRepository; -import org.springframework.session.FlushMode; -import org.springframework.session.IndexResolver; -import org.springframework.session.MapSession; -import org.springframework.session.PrincipalNameIndexResolver; -import org.springframework.session.SaveMode; -import org.springframework.session.Session; +import org.springframework.session.SessionRepository; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.support.TransactionOperations; import org.springframework.transaction.support.TransactionTemplate; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; /** - * A {@link org.springframework.session.SessionRepository} implementation that uses - * Spring's {@link JdbcOperations} to store sessions in a relational database. This - * implementation does not support publishing of session events. - *

- * An example of how to create a new instance can be seen below: - * - *

- * JdbcTemplate jdbcTemplate = new JdbcTemplate();
- *
- * // ... configure jdbcTemplate ...
- *
- * TransactionTemplate transactionTemplate = new TransactionTemplate();
- *
- * // ... configure transactionTemplate ...
- *
- * JdbcOperationsSessionRepository sessionRepository =
- *         new JdbcOperationsSessionRepository(jdbcTemplate, transactionTemplate);
- * 
- * - * For additional information on how to create and configure {@code JdbcTemplate} and - * {@code TransactionTemplate}, refer to the - * Spring Framework Reference Documentation. - *

- * By default, this implementation uses SPRING_SESSION and - * SPRING_SESSION_ATTRIBUTES tables to store sessions. Note that the table - * name can be customized using the {@link #setTableName(String)} method. In that case the - * table used to store attributes will be named using the provided table name, suffixed - * with _ATTRIBUTES. - * - * Depending on your database, the table definition can be described as below: - * - *

- * CREATE TABLE SPRING_SESSION (
- *   PRIMARY_ID CHAR(36) NOT NULL,
- *   SESSION_ID CHAR(36) NOT NULL,
- *   CREATION_TIME BIGINT NOT NULL,
- *   LAST_ACCESS_TIME BIGINT NOT NULL,
- *   MAX_INACTIVE_INTERVAL INT NOT NULL,
- *   EXPIRY_TIME BIGINT NOT NULL,
- *   PRINCIPAL_NAME VARCHAR(100),
- *   CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
- * );
- *
- * CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
- * CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (EXPIRY_TIME);
- * CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
- *
- * CREATE TABLE SPRING_SESSION_ATTRIBUTES (
- *  SESSION_PRIMARY_ID CHAR(36) NOT NULL,
- *  ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
- *  ATTRIBUTE_BYTES BYTEA NOT NULL,
- *  CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
- *  CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
- * );
- *
- * CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_PRIMARY_ID);
- * 
- * - * Due to the differences between the various database vendors, especially when it comes - * to storing binary data, make sure to use SQL script specific to your database. Scripts - * for most major database vendors are packaged as - * org/springframework/session/jdbc/schema-*.sql, where * is the - * target database type. + * This {@link SessionRepository} implementation is kept in order to support migration to + * {@link JdbcIndexedSessionRepository} in a backwards compatible manner. * * @author Vedran Pavic * @author Craig Andrews * @since 1.2.0 + * @deprecated since 2.2.0 in favor of {@link JdbcIndexedSessionRepository} */ -public class JdbcOperationsSessionRepository - implements FindByIndexNameSessionRepository { - - /** - * The default name of database table used by Spring Session to store sessions. - */ - public static final String DEFAULT_TABLE_NAME = "SPRING_SESSION"; - - private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; - - // @formatter:off - private static final String CREATE_SESSION_QUERY = "INSERT INTO %TABLE_NAME%(PRIMARY_ID, SESSION_ID, CREATION_TIME, LAST_ACCESS_TIME, MAX_INACTIVE_INTERVAL, EXPIRY_TIME, PRINCIPAL_NAME) " - + "VALUES (?, ?, ?, ?, ?, ?, ?)"; - // @formatter:on - - // @formatter:off - private static final String CREATE_SESSION_ATTRIBUTE_QUERY = "INSERT INTO %TABLE_NAME%_ATTRIBUTES(SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) " - + "SELECT PRIMARY_ID, ?, ? " - + "FROM %TABLE_NAME% " - + "WHERE SESSION_ID = ?"; - // @formatter:on - - // @formatter:off - private static final String GET_SESSION_QUERY = "SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES " - + "FROM %TABLE_NAME% S " - + "LEFT OUTER JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID " - + "WHERE S.SESSION_ID = ?"; - // @formatter:on - - // @formatter:off - private static final String UPDATE_SESSION_QUERY = "UPDATE %TABLE_NAME% SET SESSION_ID = ?, LAST_ACCESS_TIME = ?, MAX_INACTIVE_INTERVAL = ?, EXPIRY_TIME = ?, PRINCIPAL_NAME = ? " - + "WHERE PRIMARY_ID = ?"; - // @formatter:on - - // @formatter:off - private static final String UPDATE_SESSION_ATTRIBUTE_QUERY = "UPDATE %TABLE_NAME%_ATTRIBUTES SET ATTRIBUTE_BYTES = ? " - + "WHERE SESSION_PRIMARY_ID = ? " - + "AND ATTRIBUTE_NAME = ?"; - // @formatter:on - - // @formatter:off - private static final String DELETE_SESSION_ATTRIBUTE_QUERY = "DELETE FROM %TABLE_NAME%_ATTRIBUTES " - + "WHERE SESSION_PRIMARY_ID = ? " - + "AND ATTRIBUTE_NAME = ?"; - // @formatter:on - - // @formatter:off - private static final String DELETE_SESSION_QUERY = "DELETE FROM %TABLE_NAME% " - + "WHERE SESSION_ID = ?"; - // @formatter:on - - // @formatter:off - private static final String LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY = "SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES " - + "FROM %TABLE_NAME% S " - + "LEFT OUTER JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID " - + "WHERE S.PRINCIPAL_NAME = ?"; - // @formatter:on - - // @formatter:off - private static final String DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY = "DELETE FROM %TABLE_NAME% " - + "WHERE EXPIRY_TIME < ?"; - // @formatter:on - - private static final Log logger = LogFactory.getLog(JdbcOperationsSessionRepository.class); - - private final JdbcOperations jdbcOperations; - - private final TransactionOperations transactionOperations; - - private final ResultSetExtractor> extractor = new SessionResultSetExtractor(); - - private final IndexResolver indexResolver; - - /** - * The name of database table used by Spring Session to store sessions. - */ - private String tableName = DEFAULT_TABLE_NAME; - - private String createSessionQuery; - - private String createSessionAttributeQuery; - - private String getSessionQuery; - - private String updateSessionQuery; - - private String updateSessionAttributeQuery; - - private String deleteSessionAttributeQuery; - - private String deleteSessionQuery; - - private String listSessionsByPrincipalNameQuery; - - private String deleteSessionsByExpiryTimeQuery; - - /** - * If non-null, this value is used to override the default value for - * {@link JdbcSession#setMaxInactiveInterval(Duration)}. - */ - private Integer defaultMaxInactiveInterval; - - private ConversionService conversionService; - - private LobHandler lobHandler; - - private FlushMode flushMode = FlushMode.ON_SAVE; - - private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE; +@Deprecated +public class JdbcOperationsSessionRepository extends JdbcIndexedSessionRepository { /** * Create a new {@link JdbcOperationsSessionRepository} instance which uses the @@ -246,346 +42,42 @@ public class JdbcOperationsSessionRepository * sessions. * @param jdbcOperations the {@link JdbcOperations} to use * @param transactionOperations the {@link TransactionOperations} to use + * @see JdbcIndexedSessionRepository#JdbcIndexedSessionRepository(JdbcOperations, + * TransactionOperations) */ public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations, TransactionOperations transactionOperations) { - Assert.notNull(jdbcOperations, "jdbcOperations must not be null"); - Assert.notNull(transactionOperations, "transactionOperations must not be null"); - this.jdbcOperations = jdbcOperations; - this.transactionOperations = transactionOperations; - this.indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>()); - this.conversionService = createDefaultConversionService(); - this.lobHandler = new DefaultLobHandler(); - prepareQueries(); + super(jdbcOperations, transactionOperations); } /** - * Create a new {@link JdbcOperationsSessionRepository} instance which uses the - * provided {@link JdbcOperations} to manage sessions. + * Create a new {@link JdbcIndexedSessionRepository} instance which uses the provided + * {@link JdbcOperations} to manage sessions. *

* 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 * @deprecated since 2.2.0 in favor of - * {@link #JdbcOperationsSessionRepository(JdbcOperations, TransactionOperations)} + * {@link JdbcIndexedSessionRepository#JdbcIndexedSessionRepository(JdbcOperations, TransactionOperations)} */ @Deprecated public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations, PlatformTransactionManager transactionManager) { - this(jdbcOperations, createTransactionTemplate(transactionManager)); + super(jdbcOperations, createTransactionTemplate(transactionManager)); } /** - * Create a new {@link JdbcOperationsSessionRepository} instance which uses the - * provided {@link JdbcOperations} to manage sessions. + * Create a new {@link JdbcIndexedSessionRepository} instance which uses the provided + * {@link JdbcOperations} to manage sessions. *

* The created instance will not execute data access operations in a transaction. * @param jdbcOperations the {@link JdbcOperations} to use * @deprecated since 2.2.0 in favor of - * {@link #JdbcOperationsSessionRepository(JdbcOperations, TransactionOperations)} + * {@link JdbcIndexedSessionRepository#JdbcIndexedSessionRepository(JdbcOperations, TransactionOperations)} */ @Deprecated public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations) { - this(jdbcOperations, TransactionOperations.withoutTransaction()); - } - - /** - * Set the name of database table used to store sessions. - * @param tableName the database table name - */ - public void setTableName(String tableName) { - Assert.hasText(tableName, "Table name must not be empty"); - this.tableName = tableName.trim(); - prepareQueries(); - } - - /** - * Set the custom SQL query used to create the session. - * @param createSessionQuery the SQL query string - */ - public void setCreateSessionQuery(String createSessionQuery) { - Assert.hasText(createSessionQuery, "Query must not be empty"); - this.createSessionQuery = createSessionQuery; - } - - /** - * Set the custom SQL query used to create the session attribute. - * @param createSessionAttributeQuery the SQL query string - */ - public void setCreateSessionAttributeQuery(String createSessionAttributeQuery) { - Assert.hasText(createSessionAttributeQuery, "Query must not be empty"); - this.createSessionAttributeQuery = createSessionAttributeQuery; - } - - /** - * Set the custom SQL query used to retrieve the session. - * @param getSessionQuery the SQL query string - */ - public void setGetSessionQuery(String getSessionQuery) { - Assert.hasText(getSessionQuery, "Query must not be empty"); - this.getSessionQuery = getSessionQuery; - } - - /** - * Set the custom SQL query used to update the session. - * @param updateSessionQuery the SQL query string - */ - public void setUpdateSessionQuery(String updateSessionQuery) { - Assert.hasText(updateSessionQuery, "Query must not be empty"); - this.updateSessionQuery = updateSessionQuery; - } - - /** - * Set the custom SQL query used to update the session attribute. - * @param updateSessionAttributeQuery the SQL query string - */ - public void setUpdateSessionAttributeQuery(String updateSessionAttributeQuery) { - Assert.hasText(updateSessionAttributeQuery, "Query must not be empty"); - this.updateSessionAttributeQuery = updateSessionAttributeQuery; - } - - /** - * Set the custom SQL query used to delete the session attribute. - * @param deleteSessionAttributeQuery the SQL query string - */ - public void setDeleteSessionAttributeQuery(String deleteSessionAttributeQuery) { - Assert.hasText(deleteSessionAttributeQuery, "Query must not be empty"); - this.deleteSessionAttributeQuery = deleteSessionAttributeQuery; - } - - /** - * Set the custom SQL query used to delete the session. - * @param deleteSessionQuery the SQL query string - */ - public void setDeleteSessionQuery(String deleteSessionQuery) { - Assert.hasText(deleteSessionQuery, "Query must not be empty"); - this.deleteSessionQuery = deleteSessionQuery; - } - - /** - * Set the custom SQL query used to retrieve the sessions by principal name. - * @param listSessionsByPrincipalNameQuery the SQL query string - */ - public void setListSessionsByPrincipalNameQuery(String listSessionsByPrincipalNameQuery) { - Assert.hasText(listSessionsByPrincipalNameQuery, "Query must not be empty"); - this.listSessionsByPrincipalNameQuery = listSessionsByPrincipalNameQuery; - } - - /** - * Set the custom SQL query used to delete the sessions by last access time. - * @param deleteSessionsByExpiryTimeQuery the SQL query string - */ - public void setDeleteSessionsByExpiryTimeQuery(String deleteSessionsByExpiryTimeQuery) { - Assert.hasText(deleteSessionsByExpiryTimeQuery, "Query must not be empty"); - this.deleteSessionsByExpiryTimeQuery = deleteSessionsByExpiryTimeQuery; - } - - /** - * Set the maximum inactive interval in seconds between requests before newly created - * sessions will be invalidated. A negative time indicates that the session will never - * timeout. The default is 1800 (30 minutes). - * @param defaultMaxInactiveInterval the maximum inactive interval in seconds - */ - public void setDefaultMaxInactiveInterval(Integer defaultMaxInactiveInterval) { - this.defaultMaxInactiveInterval = defaultMaxInactiveInterval; - } - - public void setLobHandler(LobHandler lobHandler) { - Assert.notNull(lobHandler, "LobHandler must not be null"); - this.lobHandler = lobHandler; - } - - /** - * Sets the {@link ConversionService} to use. - * @param conversionService the converter to set - */ - public void setConversionService(ConversionService conversionService) { - Assert.notNull(conversionService, "conversionService must not be null"); - this.conversionService = conversionService; - } - - /** - * Set the flush mode. Default is {@link FlushMode#ON_SAVE}. - * @param flushMode the flush mode - */ - public void setFlushMode(FlushMode flushMode) { - Assert.notNull(flushMode, "flushMode must not be null"); - this.flushMode = flushMode; - } - - /** - * Set the save mode. - * @param saveMode the save mode - */ - public void setSaveMode(SaveMode saveMode) { - Assert.notNull(saveMode, "saveMode must not be null"); - this.saveMode = saveMode; - } - - @Override - public JdbcSession createSession() { - MapSession delegate = new MapSession(); - if (this.defaultMaxInactiveInterval != null) { - delegate.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval)); - } - JdbcSession session = new JdbcSession(delegate, UUID.randomUUID().toString(), true); - session.flushIfRequired(); - return session; - } - - @Override - public void save(final JdbcSession session) { - session.save(); - } - - @Override - public JdbcSession findById(final String id) { - final JdbcSession session = this.transactionOperations.execute((status) -> { - List sessions = JdbcOperationsSessionRepository.this.jdbcOperations.query( - JdbcOperationsSessionRepository.this.getSessionQuery, (ps) -> ps.setString(1, id), - JdbcOperationsSessionRepository.this.extractor); - if (sessions.isEmpty()) { - return null; - } - return sessions.get(0); - }); - - if (session != null) { - if (session.isExpired()) { - deleteById(id); - } - else { - return session; - } - } - return null; - } - - @Override - public void deleteById(final String id) { - this.transactionOperations.execute(() -> JdbcOperationsSessionRepository.this.jdbcOperations - .update(JdbcOperationsSessionRepository.this.deleteSessionQuery, id)); - } - - @Override - public Map findByIndexNameAndIndexValue(String indexName, final String indexValue) { - if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) { - return Collections.emptyMap(); - } - - List sessions = this.transactionOperations - .execute((status) -> JdbcOperationsSessionRepository.this.jdbcOperations.query( - JdbcOperationsSessionRepository.this.listSessionsByPrincipalNameQuery, - (ps) -> ps.setString(1, indexValue), JdbcOperationsSessionRepository.this.extractor)); - - Map sessionMap = new HashMap<>(sessions.size()); - - for (JdbcSession session : sessions) { - sessionMap.put(session.getId(), session); - } - - return sessionMap; - } - - private void insertSessionAttributes(JdbcSession session, List attributeNames) { - 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, attributeName); - getLobHandler().getLobCreator().setBlobAsBytes(ps, 2, - serialize(session.getAttribute(attributeName))); - ps.setString(3, session.getId()); - } - - @Override - public int getBatchSize() { - return attributeNames.size(); - } - - }); - } - else { - this.jdbcOperations.update(this.createSessionAttributeQuery, (ps) -> { - String attributeName = attributeNames.get(0); - ps.setString(1, attributeName); - getLobHandler().getLobCreator().setBlobAsBytes(ps, 2, serialize(session.getAttribute(attributeName))); - ps.setString(3, session.getId()); - }); - } - } - - private void updateSessionAttributes(JdbcSession session, List attributeNames) { - Assert.notEmpty(attributeNames, "attributeNames must not be null or empty"); - if (attributeNames.size() > 1) { - this.jdbcOperations.batchUpdate(this.updateSessionAttributeQuery, new BatchPreparedStatementSetter() { - - @Override - public void setValues(PreparedStatement ps, int i) throws SQLException { - String attributeName = attributeNames.get(i); - getLobHandler().getLobCreator().setBlobAsBytes(ps, 1, - serialize(session.getAttribute(attributeName))); - ps.setString(2, session.primaryKey); - ps.setString(3, attributeName); - } - - @Override - public int getBatchSize() { - return attributeNames.size(); - } - - }); - } - else { - this.jdbcOperations.update(this.updateSessionAttributeQuery, (ps) -> { - String attributeName = attributeNames.get(0); - getLobHandler().getLobCreator().setBlobAsBytes(ps, 1, serialize(session.getAttribute(attributeName))); - ps.setString(2, session.primaryKey); - ps.setString(3, attributeName); - }); - } - } - - private void deleteSessionAttributes(JdbcSession session, List attributeNames) { - Assert.notEmpty(attributeNames, "attributeNames must not be null or empty"); - if (attributeNames.size() > 1) { - this.jdbcOperations.batchUpdate(this.deleteSessionAttributeQuery, 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); - } - - @Override - public int getBatchSize() { - return attributeNames.size(); - } - - }); - } - else { - this.jdbcOperations.update(this.deleteSessionAttributeQuery, (ps) -> { - String attributeName = attributeNames.get(0); - ps.setString(1, session.primaryKey); - ps.setString(2, attributeName); - }); - } - } - - public void cleanUpExpiredSessions() { - Integer deletedCount = this.transactionOperations - .execute((status) -> JdbcOperationsSessionRepository.this.jdbcOperations.update( - JdbcOperationsSessionRepository.this.deleteSessionsByExpiryTimeQuery, - System.currentTimeMillis())); - - if (logger.isDebugEnabled()) { - logger.debug("Cleaned up " + deletedCount + " expired sessions"); - } + super(jdbcOperations, TransactionOperations.withoutTransaction()); } private static TransactionTemplate createTransactionTemplate(PlatformTransactionManager transactionManager) { @@ -596,316 +88,4 @@ public class JdbcOperationsSessionRepository return transactionTemplate; } - private static GenericConversionService createDefaultConversionService() { - GenericConversionService converter = new GenericConversionService(); - converter.addConverter(Object.class, byte[].class, new SerializingConverter()); - converter.addConverter(byte[].class, Object.class, new DeserializingConverter()); - return converter; - } - - private String getQuery(String base) { - return StringUtils.replace(base, "%TABLE_NAME%", this.tableName); - } - - private void prepareQueries() { - this.createSessionQuery = getQuery(CREATE_SESSION_QUERY); - this.createSessionAttributeQuery = getQuery(CREATE_SESSION_ATTRIBUTE_QUERY); - this.getSessionQuery = getQuery(GET_SESSION_QUERY); - this.updateSessionQuery = getQuery(UPDATE_SESSION_QUERY); - this.updateSessionAttributeQuery = getQuery(UPDATE_SESSION_ATTRIBUTE_QUERY); - this.deleteSessionAttributeQuery = getQuery(DELETE_SESSION_ATTRIBUTE_QUERY); - this.deleteSessionQuery = getQuery(DELETE_SESSION_QUERY); - this.listSessionsByPrincipalNameQuery = getQuery(LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY); - this.deleteSessionsByExpiryTimeQuery = getQuery(DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY); - } - - private LobHandler getLobHandler() { - return this.lobHandler; - } - - 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)); - } - - private enum DeltaValue { - - ADDED, UPDATED, REMOVED - - } - - private static Supplier value(T value) { - return (value != null) ? () -> value : null; - } - - private static Supplier lazily(Supplier supplier) { - Supplier lazySupplier = new Supplier() { - - private T value; - - @Override - public T get() { - if (this.value == null) { - this.value = supplier.get(); - } - return this.value; - } - - }; - - return (supplier != null) ? lazySupplier : null; - } - - /** - * The {@link Session} to use for {@link JdbcOperationsSessionRepository}. - * - * @author Vedran Pavic - */ - final class JdbcSession implements Session { - - private final Session delegate; - - private final String primaryKey; - - private boolean isNew; - - private boolean changed; - - private Map delta = new HashMap<>(); - - JdbcSession(MapSession delegate, String primaryKey, boolean isNew) { - this.delegate = delegate; - this.primaryKey = primaryKey; - this.isNew = isNew; - if (this.isNew || (JdbcOperationsSessionRepository.this.saveMode == SaveMode.ALWAYS)) { - getAttributeNames().forEach((attributeName) -> this.delta.put(attributeName, DeltaValue.UPDATED)); - } - } - - boolean isNew() { - return this.isNew; - } - - boolean isChanged() { - return this.changed; - } - - Map getDelta() { - return this.delta; - } - - void clearChangeFlags() { - this.isNew = false; - this.changed = false; - this.delta.clear(); - } - - Instant getExpiryTime() { - return getLastAccessedTime().plus(getMaxInactiveInterval()); - } - - @Override - public String getId() { - return this.delegate.getId(); - } - - @Override - public String changeSessionId() { - this.changed = true; - return this.delegate.changeSessionId(); - } - - @Override - public T getAttribute(String attributeName) { - Supplier supplier = this.delegate.getAttribute(attributeName); - if (supplier == null) { - return null; - } - T attributeValue = supplier.get(); - if (attributeValue != null - && JdbcOperationsSessionRepository.this.saveMode.equals(SaveMode.ON_GET_ATTRIBUTE)) { - this.delta.put(attributeName, DeltaValue.UPDATED); - } - return attributeValue; - } - - @Override - public Set getAttributeNames() { - return this.delegate.getAttributeNames(); - } - - @Override - public void setAttribute(String attributeName, Object attributeValue) { - boolean attributeExists = (this.delegate.getAttribute(attributeName) != null); - boolean attributeRemoved = (attributeValue == null); - if (!attributeExists && attributeRemoved) { - return; - } - if (attributeExists) { - if (attributeRemoved) { - this.delta.merge(attributeName, DeltaValue.REMOVED, - (oldDeltaValue, deltaValue) -> (oldDeltaValue == DeltaValue.ADDED) ? null : deltaValue); - } - else { - this.delta.merge(attributeName, DeltaValue.UPDATED, (oldDeltaValue, - deltaValue) -> (oldDeltaValue == DeltaValue.ADDED) ? oldDeltaValue : deltaValue); - } - } - else { - this.delta.merge(attributeName, DeltaValue.ADDED, (oldDeltaValue, - deltaValue) -> (oldDeltaValue == DeltaValue.ADDED) ? oldDeltaValue : DeltaValue.UPDATED); - } - this.delegate.setAttribute(attributeName, value(attributeValue)); - if (PRINCIPAL_NAME_INDEX_NAME.equals(attributeName) || SPRING_SECURITY_CONTEXT.equals(attributeName)) { - this.changed = true; - } - flushIfRequired(); - } - - @Override - public void removeAttribute(String attributeName) { - setAttribute(attributeName, null); - } - - @Override - public Instant getCreationTime() { - return this.delegate.getCreationTime(); - } - - @Override - public void setLastAccessedTime(Instant lastAccessedTime) { - this.delegate.setLastAccessedTime(lastAccessedTime); - this.changed = true; - flushIfRequired(); - } - - @Override - public Instant getLastAccessedTime() { - return this.delegate.getLastAccessedTime(); - } - - @Override - public void setMaxInactiveInterval(Duration interval) { - this.delegate.setMaxInactiveInterval(interval); - this.changed = true; - flushIfRequired(); - } - - @Override - public Duration getMaxInactiveInterval() { - return this.delegate.getMaxInactiveInterval(); - } - - @Override - public boolean isExpired() { - return this.delegate.isExpired(); - } - - private void flushIfRequired() { - if (JdbcOperationsSessionRepository.this.flushMode == FlushMode.IMMEDIATE) { - save(); - } - } - - private void save() { - if (this.isNew) { - JdbcOperationsSessionRepository.this.transactionOperations.execute(() -> { - Map indexes = JdbcOperationsSessionRepository.this.indexResolver - .resolveIndexesFor(JdbcSession.this); - JdbcOperationsSessionRepository.this.jdbcOperations - .update(JdbcOperationsSessionRepository.this.createSessionQuery, (ps) -> { - ps.setString(1, JdbcSession.this.primaryKey); - ps.setString(2, getId()); - ps.setLong(3, getCreationTime().toEpochMilli()); - ps.setLong(4, getLastAccessedTime().toEpochMilli()); - ps.setInt(5, (int) getMaxInactiveInterval().getSeconds()); - ps.setLong(6, getExpiryTime().toEpochMilli()); - ps.setString(7, indexes.get(PRINCIPAL_NAME_INDEX_NAME)); - }); - Set attributeNames = getAttributeNames(); - if (!attributeNames.isEmpty()) { - insertSessionAttributes(JdbcSession.this, new ArrayList<>(attributeNames)); - } - }); - } - else { - JdbcOperationsSessionRepository.this.transactionOperations.execute(() -> { - if (JdbcSession.this.changed) { - Map indexes = JdbcOperationsSessionRepository.this.indexResolver - .resolveIndexesFor(JdbcSession.this); - JdbcOperationsSessionRepository.this.jdbcOperations - .update(JdbcOperationsSessionRepository.this.updateSessionQuery, (ps) -> { - ps.setString(1, getId()); - ps.setLong(2, getLastAccessedTime().toEpochMilli()); - ps.setInt(3, (int) getMaxInactiveInterval().getSeconds()); - ps.setLong(4, getExpiryTime().toEpochMilli()); - ps.setString(5, indexes.get(PRINCIPAL_NAME_INDEX_NAME)); - ps.setString(6, JdbcSession.this.primaryKey); - }); - } - List addedAttributeNames = JdbcSession.this.delta.entrySet().stream() - .filter((entry) -> entry.getValue() == DeltaValue.ADDED).map(Map.Entry::getKey) - .collect(Collectors.toList()); - if (!addedAttributeNames.isEmpty()) { - insertSessionAttributes(JdbcSession.this, addedAttributeNames); - } - List updatedAttributeNames = JdbcSession.this.delta.entrySet().stream() - .filter((entry) -> entry.getValue() == DeltaValue.UPDATED).map(Map.Entry::getKey) - .collect(Collectors.toList()); - if (!updatedAttributeNames.isEmpty()) { - updateSessionAttributes(JdbcSession.this, updatedAttributeNames); - } - List removedAttributeNames = JdbcSession.this.delta.entrySet().stream() - .filter((entry) -> entry.getValue() == DeltaValue.REMOVED).map(Map.Entry::getKey) - .collect(Collectors.toList()); - if (!removedAttributeNames.isEmpty()) { - deleteSessionAttributes(JdbcSession.this, removedAttributeNames); - } - }); - } - clearChangeFlags(); - } - - } - - private class SessionResultSetExtractor implements ResultSetExtractor> { - - @Override - public List extractData(ResultSet rs) throws SQLException, DataAccessException { - List sessions = new ArrayList<>(); - while (rs.next()) { - String id = rs.getString("SESSION_ID"); - JdbcSession session; - if (sessions.size() > 0 && getLast(sessions).getId().equals(id)) { - session = getLast(sessions); - } - else { - MapSession delegate = new MapSession(id); - String primaryKey = rs.getString("PRIMARY_ID"); - delegate.setCreationTime(Instant.ofEpochMilli(rs.getLong("CREATION_TIME"))); - delegate.setLastAccessedTime(Instant.ofEpochMilli(rs.getLong("LAST_ACCESS_TIME"))); - delegate.setMaxInactiveInterval(Duration.ofSeconds(rs.getInt("MAX_INACTIVE_INTERVAL"))); - session = new JdbcSession(delegate, primaryKey, false); - } - String attributeName = rs.getString("ATTRIBUTE_NAME"); - if (attributeName != null) { - byte[] bytes = getLobHandler().getBlobAsBytes(rs, "ATTRIBUTE_BYTES"); - session.delegate.setAttribute(attributeName, lazily(() -> deserialize(bytes))); - } - sessions.add(session); - } - return sessions; - } - - private JdbcSession getLast(List sessions) { - return sessions.get(sessions.size() - 1); - } - - } - } diff --git a/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/config/annotation/SpringSessionDataSource.java b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/config/annotation/SpringSessionDataSource.java index e2969e47..2f13fc52 100644 --- a/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/config/annotation/SpringSessionDataSource.java +++ b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/config/annotation/SpringSessionDataSource.java @@ -25,11 +25,11 @@ import java.lang.annotation.Target; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.session.jdbc.JdbcOperationsSessionRepository; +import org.springframework.session.jdbc.JdbcIndexedSessionRepository; /** * Qualifier annotation for a {@link DataSource} to be injected in - * {@link JdbcOperationsSessionRepository}. + * {@link JdbcIndexedSessionRepository}. * * @author Vedran Pavic * @since 2.0.0 diff --git a/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/config/annotation/web/http/EnableJdbcHttpSession.java b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/config/annotation/web/http/EnableJdbcHttpSession.java index 6f0a7140..ff9b42da 100644 --- a/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/config/annotation/web/http/EnableJdbcHttpSession.java +++ b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/config/annotation/web/http/EnableJdbcHttpSession.java @@ -32,7 +32,7 @@ import org.springframework.session.SaveMode; import org.springframework.session.Session; import org.springframework.session.SessionRepository; import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; -import org.springframework.session.jdbc.JdbcOperationsSessionRepository; +import org.springframework.session.jdbc.JdbcIndexedSessionRepository; import org.springframework.session.web.http.SessionRepositoryFilter; /** @@ -91,7 +91,7 @@ public @interface EnableJdbcHttpSession { * The name of database table used by Spring Session to store sessions. * @return the database table name */ - String tableName() default JdbcOperationsSessionRepository.DEFAULT_TABLE_NAME; + String tableName() default JdbcIndexedSessionRepository.DEFAULT_TABLE_NAME; /** * The cron expression for expired session cleanup job. By default runs every minute. diff --git a/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfiguration.java b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfiguration.java index 3bb21a52..23e8bc52 100644 --- a/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfiguration.java +++ b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfiguration.java @@ -49,7 +49,7 @@ import org.springframework.session.MapSession; import org.springframework.session.SaveMode; import org.springframework.session.config.SessionRepositoryCustomizer; import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration; -import org.springframework.session.jdbc.JdbcOperationsSessionRepository; +import org.springframework.session.jdbc.JdbcIndexedSessionRepository; import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource; import org.springframework.session.web.http.SessionRepositoryFilter; import org.springframework.transaction.PlatformTransactionManager; @@ -81,7 +81,7 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration private Integer maxInactiveIntervalInSeconds = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS; - private String tableName = JdbcOperationsSessionRepository.DEFAULT_TABLE_NAME; + private String tableName = JdbcIndexedSessionRepository.DEFAULT_TABLE_NAME; private String cleanupCron = DEFAULT_CLEANUP_CRON; @@ -101,19 +101,19 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration private ConversionService conversionService; - private List> sessionRepositoryCustomizers; + private List> sessionRepositoryCustomizers; private ClassLoader classLoader; private StringValueResolver embeddedValueResolver; @Bean - public JdbcOperationsSessionRepository sessionRepository() { + public JdbcIndexedSessionRepository sessionRepository() { JdbcTemplate jdbcTemplate = createJdbcTemplate(this.dataSource); if (this.transactionOperations == null) { this.transactionOperations = createTransactionTemplate(this.transactionManager); } - JdbcOperationsSessionRepository sessionRepository = new JdbcOperationsSessionRepository(jdbcTemplate, + JdbcIndexedSessionRepository sessionRepository = new JdbcIndexedSessionRepository(jdbcTemplate, this.transactionOperations); if (StringUtils.hasText(this.tableName)) { sessionRepository.setTableName(this.tableName); @@ -214,7 +214,7 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration @Autowired(required = false) public void setSessionRepositoryCustomizer( - ObjectProvider> sessionRepositoryCustomizers) { + ObjectProvider> sessionRepositoryCustomizers) { this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList()); } diff --git a/spring-session-jdbc/src/test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryTests.java b/spring-session-jdbc/src/test/java/org/springframework/session/jdbc/JdbcIndexedSessionRepositoryTests.java similarity index 84% rename from spring-session-jdbc/src/test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryTests.java rename to spring-session-jdbc/src/test/java/org/springframework/session/jdbc/JdbcIndexedSessionRepositoryTests.java index adf4665c..aed37e53 100644 --- a/spring-session-jdbc/src/test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryTests.java +++ b/spring-session-jdbc/src/test/java/org/springframework/session/jdbc/JdbcIndexedSessionRepositoryTests.java @@ -43,8 +43,7 @@ import org.springframework.session.FlushMode; import org.springframework.session.MapSession; import org.springframework.session.SaveMode; import org.springframework.session.Session; -import org.springframework.session.jdbc.JdbcOperationsSessionRepository.JdbcSession; -import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.session.jdbc.JdbcIndexedSessionRepository.JdbcSession; import org.springframework.transaction.support.TransactionOperations; import static org.assertj.core.api.Assertions.assertThat; @@ -59,51 +58,41 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; /** - * Tests for {@link JdbcOperationsSessionRepository}. + * Tests for {@link JdbcIndexedSessionRepository}. * * @author Vedran Pavic * @author Craig Andrews - * @since 1.2.0 */ -class JdbcOperationsSessionRepositoryTests { +class JdbcIndexedSessionRepositoryTests { private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; @Mock private JdbcOperations jdbcOperations; - private JdbcOperationsSessionRepository repository; + private JdbcIndexedSessionRepository repository; @BeforeEach void setUp() { MockitoAnnotations.initMocks(this); - this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations, + this.repository = new JdbcIndexedSessionRepository(this.jdbcOperations, TransactionOperations.withoutTransaction()); } @Test void constructorNullJdbcOperations() { assertThatIllegalArgumentException() - .isThrownBy(() -> new JdbcOperationsSessionRepository(null, TransactionOperations.withoutTransaction())) + .isThrownBy(() -> new JdbcIndexedSessionRepository(null, TransactionOperations.withoutTransaction())) .withMessage("jdbcOperations must not be null"); } @Test void constructorNullTransactionOperations() { assertThatIllegalArgumentException() - .isThrownBy( - () -> new JdbcOperationsSessionRepository(this.jdbcOperations, (TransactionOperations) null)) + .isThrownBy(() -> new JdbcIndexedSessionRepository(this.jdbcOperations, null)) .withMessage("transactionOperations must not be null"); } - @Test - @SuppressWarnings("deprecation") - void constructorNullTransactionManager() { - assertThatIllegalArgumentException().isThrownBy( - () -> new JdbcOperationsSessionRepository(this.jdbcOperations, (PlatformTransactionManager) null)) - .withMessage("transactionManager must not be null"); - } - @Test void setTableNameNull() { assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setTableName(null)) @@ -250,7 +239,7 @@ class JdbcOperationsSessionRepositoryTests { @Test void createSessionDefaultMaxInactiveInterval() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession(); + JdbcSession session = this.repository.createSession(); assertThat(session.isNew()).isTrue(); assertThat(session.getMaxInactiveInterval()).isEqualTo(new MapSession().getMaxInactiveInterval()); @@ -262,7 +251,7 @@ class JdbcOperationsSessionRepositoryTests { int interval = 1; this.repository.setDefaultMaxInactiveInterval(interval); - JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession(); + JdbcSession session = this.repository.createSession(); assertThat(session.isNew()).isTrue(); assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofSeconds(interval)); @@ -280,7 +269,7 @@ class JdbcOperationsSessionRepositoryTests { @Test void saveNewWithoutAttributes() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession(); + JdbcSession session = this.repository.createSession(); this.repository.save(session); @@ -291,7 +280,7 @@ class JdbcOperationsSessionRepositoryTests { @Test void saveNewWithSingleAttribute() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession(); + JdbcSession session = this.repository.createSession(); session.setAttribute("testName", "testValue"); this.repository.save(session); @@ -306,7 +295,7 @@ class JdbcOperationsSessionRepositoryTests { @Test void saveNewWithMultipleAttributes() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession(); + JdbcSession session = this.repository.createSession(); session.setAttribute("testName1", "testValue1"); session.setAttribute("testName2", "testValue2"); @@ -322,8 +311,7 @@ class JdbcOperationsSessionRepositoryTests { @Test void saveUpdatedAddSingleAttribute() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(), - "primaryKey", false); + JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false); session.setAttribute("testName", "testValue"); this.repository.save(session); @@ -336,8 +324,7 @@ class JdbcOperationsSessionRepositoryTests { @Test void saveUpdatedAddMultipleAttributes() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(), - "primaryKey", false); + JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false); session.setAttribute("testName1", "testValue1"); session.setAttribute("testName2", "testValue2"); @@ -351,8 +338,7 @@ class JdbcOperationsSessionRepositoryTests { @Test void saveUpdatedModifySingleAttribute() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(), - "primaryKey", false); + JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false); session.setAttribute("testName", "testValue"); session.clearChangeFlags(); session.setAttribute("testName", "testValue"); @@ -367,8 +353,7 @@ class JdbcOperationsSessionRepositoryTests { @Test void saveUpdatedModifyMultipleAttributes() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(), - "primaryKey", false); + JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false); session.setAttribute("testName1", "testValue1"); session.setAttribute("testName2", "testValue2"); session.clearChangeFlags(); @@ -385,8 +370,7 @@ class JdbcOperationsSessionRepositoryTests { @Test void saveUpdatedRemoveSingleAttribute() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(), - "primaryKey", false); + JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false); session.setAttribute("testName", "testValue"); session.clearChangeFlags(); session.removeAttribute("testName"); @@ -401,8 +385,7 @@ class JdbcOperationsSessionRepositoryTests { @Test void saveUpdatedRemoveNonExistingAttribute() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(), - "primaryKey", false); + JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false); session.removeAttribute("testName"); this.repository.save(session); @@ -413,8 +396,7 @@ class JdbcOperationsSessionRepositoryTests { @Test void saveUpdatedRemoveMultipleAttributes() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(), - "primaryKey", false); + JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false); session.setAttribute("testName1", "testValue1"); session.setAttribute("testName2", "testValue2"); session.clearChangeFlags(); @@ -431,8 +413,7 @@ class JdbcOperationsSessionRepositoryTests { @Test // gh-1070 void saveUpdatedAddAndModifyAttribute() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(), - "primaryKey", false); + JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false); session.setAttribute("testName", "testValue1"); session.setAttribute("testName", "testValue2"); @@ -446,8 +427,7 @@ class JdbcOperationsSessionRepositoryTests { @Test // gh-1070 void saveUpdatedAddAndRemoveAttribute() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(), - "primaryKey", false); + JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false); session.setAttribute("testName", "testValue"); session.removeAttribute("testName"); @@ -459,8 +439,7 @@ class JdbcOperationsSessionRepositoryTests { @Test // gh-1070 void saveUpdatedModifyAndRemoveAttribute() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(), - "primaryKey", false); + JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false); session.setAttribute("testName", "testValue1"); session.clearChangeFlags(); session.setAttribute("testName", "testValue2"); @@ -476,8 +455,7 @@ class JdbcOperationsSessionRepositoryTests { @Test // gh-1070 void saveUpdatedRemoveAndAddAttribute() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(), - "primaryKey", false); + JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false); session.setAttribute("testName", "testValue1"); session.clearChangeFlags(); session.removeAttribute("testName"); @@ -493,8 +471,7 @@ class JdbcOperationsSessionRepositoryTests { @Test void saveUpdatedLastAccessedTime() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(), - "primaryKey", false); + JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false); session.setLastAccessedTime(Instant.now()); this.repository.save(session); @@ -507,8 +484,7 @@ class JdbcOperationsSessionRepositoryTests { @Test void saveUnchanged() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(), - "primaryKey", false); + JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false); this.repository.save(session); @@ -523,7 +499,7 @@ class JdbcOperationsSessionRepositoryTests { given(this.jdbcOperations.query(isA(String.class), isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class))).willReturn(Collections.emptyList()); - JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(sessionId); + JdbcSession session = this.repository.findById(sessionId); assertThat(session).isNull(); verify(this.jdbcOperations, times(1)).query(isA(String.class), isA(PreparedStatementSetter.class), @@ -538,7 +514,7 @@ class JdbcOperationsSessionRepositoryTests { given(this.jdbcOperations.query(isA(String.class), isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class))).willReturn(Collections.singletonList(expired)); - JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(expired.getId()); + JdbcSession session = this.repository.findById(expired.getId()); assertThat(session).isNull(); verify(this.jdbcOperations, times(1)).query(isA(String.class), isA(PreparedStatementSetter.class), @@ -554,7 +530,7 @@ class JdbcOperationsSessionRepositoryTests { given(this.jdbcOperations.query(isA(String.class), isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class))).willReturn(Collections.singletonList(saved)); - JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(saved.getId()); + JdbcSession session = this.repository.findById(saved.getId()); assertThat(session.getId()).isEqualTo(saved.getId()); assertThat(session.isNew()).isFalse(); @@ -576,8 +552,7 @@ class JdbcOperationsSessionRepositoryTests { void findByIndexNameAndIndexValueUnknownIndexName() { String indexValue = "testIndexValue"; - Map sessions = this.repository - .findByIndexNameAndIndexValue("testIndexName", indexValue); + Map sessions = this.repository.findByIndexNameAndIndexValue("testIndexName", indexValue); assertThat(sessions).isEmpty(); verifyNoMoreInteractions(this.jdbcOperations); @@ -590,7 +565,7 @@ class JdbcOperationsSessionRepositoryTests { given(this.jdbcOperations.query(isA(String.class), isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class))).willReturn(Collections.emptyList()); - Map sessions = this.repository + Map sessions = this.repository .findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal); assertThat(sessions).isEmpty(); @@ -614,7 +589,7 @@ class JdbcOperationsSessionRepositoryTests { given(this.jdbcOperations.query(isA(String.class), isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class))).willReturn(saved); - Map sessions = this.repository + Map sessions = this.repository .findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal); assertThat(sessions).hasSize(2); @@ -631,7 +606,7 @@ class JdbcOperationsSessionRepositoryTests { @Test // gh-1120 void getAttributeNamesAndRemove() { - JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession(); + JdbcSession session = this.repository.createSession(); session.setAttribute("attribute1", "value1"); session.setAttribute("attribute2", "value2"); @@ -649,8 +624,7 @@ class JdbcOperationsSessionRepositoryTests { delegate.setAttribute("attribute1", (Supplier) () -> "value1"); delegate.setAttribute("attribute2", (Supplier) () -> "value2"); delegate.setAttribute("attribute3", (Supplier) () -> "value3"); - JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(delegate, - UUID.randomUUID().toString(), false); + JdbcSession session = this.repository.new JdbcSession(delegate, UUID.randomUUID().toString(), false); session.getAttribute("attribute2"); session.setAttribute("attribute3", "value4"); this.repository.save(session); @@ -666,8 +640,7 @@ class JdbcOperationsSessionRepositoryTests { delegate.setAttribute("attribute1", (Supplier) () -> "value1"); delegate.setAttribute("attribute2", (Supplier) () -> "value2"); delegate.setAttribute("attribute3", (Supplier) () -> "value3"); - JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(delegate, - UUID.randomUUID().toString(), false); + JdbcSession session = this.repository.new JdbcSession(delegate, UUID.randomUUID().toString(), false); session.getAttribute("attribute2"); session.setAttribute("attribute3", "value4"); this.repository.save(session); @@ -685,8 +658,7 @@ class JdbcOperationsSessionRepositoryTests { delegate.setAttribute("attribute1", (Supplier) () -> "value1"); delegate.setAttribute("attribute2", (Supplier) () -> "value2"); delegate.setAttribute("attribute3", (Supplier) () -> "value3"); - JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(delegate, - UUID.randomUUID().toString(), false); + JdbcSession session = this.repository.new JdbcSession(delegate, UUID.randomUUID().toString(), false); session.getAttribute("attribute2"); session.setAttribute("attribute3", "value4"); this.repository.save(session); diff --git a/spring-session-jdbc/src/test/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfigurationTests.java b/spring-session-jdbc/src/test/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfigurationTests.java index e7d4f9e3..dbab4df8 100644 --- a/spring-session-jdbc/src/test/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfigurationTests.java +++ b/spring-session-jdbc/src/test/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfigurationTests.java @@ -35,7 +35,7 @@ import org.springframework.mock.env.MockEnvironment; import org.springframework.session.FlushMode; import org.springframework.session.SaveMode; import org.springframework.session.config.SessionRepositoryCustomizer; -import org.springframework.session.jdbc.JdbcOperationsSessionRepository; +import org.springframework.session.jdbc.JdbcIndexedSessionRepository; import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.transaction.PlatformTransactionManager; @@ -51,7 +51,6 @@ import static org.mockito.Mockito.mock; * * @author Vedran Pavic * @author EddĂș MelĂ©ndez - * @since 1.2.0 */ class JdbcHttpSessionConfigurationTests { @@ -81,7 +80,7 @@ class JdbcHttpSessionConfigurationTests { void defaultConfiguration() { registerAndRefresh(DataSourceConfiguration.class, DefaultConfiguration.class); - JdbcOperationsSessionRepository sessionRepository = this.context.getBean(JdbcOperationsSessionRepository.class); + JdbcIndexedSessionRepository sessionRepository = this.context.getBean(JdbcIndexedSessionRepository.class); assertThat(sessionRepository).isNotNull(); assertThat(sessionRepository).extracting("transactionOperations") .hasFieldOrPropertyWithValue("propagationBehavior", TransactionDefinition.PROPAGATION_REQUIRES_NEW); @@ -91,7 +90,7 @@ class JdbcHttpSessionConfigurationTests { void customTableNameAnnotation() { registerAndRefresh(DataSourceConfiguration.class, CustomTableNameAnnotationConfiguration.class); - JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class); + JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class); assertThat(repository).isNotNull(); assertThat(ReflectionTestUtils.getField(repository, "tableName")).isEqualTo(TABLE_NAME); } @@ -100,7 +99,7 @@ class JdbcHttpSessionConfigurationTests { void customTableNameSetter() { registerAndRefresh(DataSourceConfiguration.class, CustomTableNameSetterConfiguration.class); - JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class); + JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class); assertThat(repository).isNotNull(); assertThat(ReflectionTestUtils.getField(repository, "tableName")).isEqualTo(TABLE_NAME); } @@ -110,7 +109,7 @@ class JdbcHttpSessionConfigurationTests { registerAndRefresh(DataSourceConfiguration.class, CustomMaxInactiveIntervalInSecondsAnnotationConfiguration.class); - JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class); + JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class); assertThat(repository).isNotNull(); assertThat(ReflectionTestUtils.getField(repository, "defaultMaxInactiveInterval")) .isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS); @@ -120,7 +119,7 @@ class JdbcHttpSessionConfigurationTests { void customMaxInactiveIntervalInSecondsSetter() { registerAndRefresh(DataSourceConfiguration.class, CustomMaxInactiveIntervalInSecondsSetterConfiguration.class); - JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class); + JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class); assertThat(repository).isNotNull(); assertThat(ReflectionTestUtils.getField(repository, "defaultMaxInactiveInterval")) .isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS); @@ -176,7 +175,7 @@ class JdbcHttpSessionConfigurationTests { void qualifiedDataSourceConfiguration() { registerAndRefresh(DataSourceConfiguration.class, QualifiedDataSourceConfiguration.class); - JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class); + JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class); DataSource dataSource = this.context.getBean("qualifiedDataSource", DataSource.class); assertThat(repository).isNotNull(); assertThat(dataSource).isNotNull(); @@ -189,7 +188,7 @@ class JdbcHttpSessionConfigurationTests { void primaryDataSourceConfiguration() { registerAndRefresh(DataSourceConfiguration.class, PrimaryDataSourceConfiguration.class); - JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class); + JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class); DataSource dataSource = this.context.getBean("primaryDataSource", DataSource.class); assertThat(repository).isNotNull(); assertThat(dataSource).isNotNull(); @@ -202,7 +201,7 @@ class JdbcHttpSessionConfigurationTests { void qualifiedAndPrimaryDataSourceConfiguration() { registerAndRefresh(DataSourceConfiguration.class, QualifiedAndPrimaryDataSourceConfiguration.class); - JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class); + JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class); DataSource dataSource = this.context.getBean("qualifiedDataSource", DataSource.class); assertThat(repository).isNotNull(); assertThat(dataSource).isNotNull(); @@ -215,7 +214,7 @@ class JdbcHttpSessionConfigurationTests { void namedDataSourceConfiguration() { registerAndRefresh(DataSourceConfiguration.class, NamedDataSourceConfiguration.class); - JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class); + JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class); DataSource dataSource = this.context.getBean("dataSource", DataSource.class); assertThat(repository).isNotNull(); assertThat(dataSource).isNotNull(); @@ -236,7 +235,7 @@ class JdbcHttpSessionConfigurationTests { void customTransactionOperationsConfiguration() { registerAndRefresh(DataSourceConfiguration.class, CustomTransactionOperationsConfiguration.class); - JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class); + JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class); TransactionOperations transactionOperations = this.context.getBean(TransactionOperations.class); assertThat(repository).isNotNull(); assertThat(transactionOperations).isNotNull(); @@ -247,7 +246,7 @@ class JdbcHttpSessionConfigurationTests { void customLobHandlerConfiguration() { registerAndRefresh(DataSourceConfiguration.class, CustomLobHandlerConfiguration.class); - JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class); + JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class); LobHandler lobHandler = this.context.getBean(LobHandler.class); assertThat(repository).isNotNull(); assertThat(lobHandler).isNotNull(); @@ -258,7 +257,7 @@ class JdbcHttpSessionConfigurationTests { void customConversionServiceConfiguration() { registerAndRefresh(DataSourceConfiguration.class, CustomConversionServiceConfiguration.class); - JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class); + JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class); ConversionService conversionService = this.context.getBean("springSessionConversionService", ConversionService.class); assertThat(repository).isNotNull(); @@ -279,7 +278,7 @@ class JdbcHttpSessionConfigurationTests { @Test void sessionRepositoryCustomizer() { registerAndRefresh(DataSourceConfiguration.class, SessionRepositoryCustomizerConfiguration.class); - JdbcOperationsSessionRepository sessionRepository = this.context.getBean(JdbcOperationsSessionRepository.class); + JdbcIndexedSessionRepository sessionRepository = this.context.getBean(JdbcIndexedSessionRepository.class); assertThat(sessionRepository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", MAX_INACTIVE_INTERVAL_IN_SECONDS); } @@ -488,13 +487,13 @@ class JdbcHttpSessionConfigurationTests { @Bean @Order(0) - public SessionRepositoryCustomizer sessionRepositoryCustomizerOne() { + public SessionRepositoryCustomizer sessionRepositoryCustomizerOne() { return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(0); } @Bean @Order(1) - public SessionRepositoryCustomizer sessionRepositoryCustomizerTwo() { + public SessionRepositoryCustomizer sessionRepositoryCustomizerTwo() { return (sessionRepository) -> sessionRepository .setDefaultMaxInactiveInterval(MAX_INACTIVE_INTERVAL_IN_SECONDS); } diff --git a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/java/sample/config/SessionConfig.java b/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/java/sample/config/SessionConfig.java index c06f9558..b4152811 100644 --- a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/java/sample/config/SessionConfig.java +++ b/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/java/sample/config/SessionConfig.java @@ -23,7 +23,7 @@ import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; -import org.springframework.session.data.redis.SimpleRedisOperationsSessionRepository; +import org.springframework.session.data.redis.RedisSessionRepository; @EnableSpringHttpSession public class SessionConfig { @@ -44,9 +44,8 @@ public class SessionConfig { } @Bean - public SimpleRedisOperationsSessionRepository sessionRepository( - RedisOperations sessionRedisOperations) { - return new SimpleRedisOperationsSessionRepository(sessionRedisOperations); + public RedisSessionRepository sessionRepository(RedisOperations sessionRedisOperations) { + return new RedisSessionRepository(sessionRedisOperations); } } diff --git a/spring-session-samples/spring-session-sample-javaconfig-hazelcast/src/main/java/sample/SessionConfig.java b/spring-session-samples/spring-session-sample-javaconfig-hazelcast/src/main/java/sample/SessionConfig.java index 961e8807..62452dce 100644 --- a/spring-session-samples/spring-session-sample-javaconfig-hazelcast/src/main/java/sample/SessionConfig.java +++ b/spring-session-samples/spring-session-sample-javaconfig-hazelcast/src/main/java/sample/SessionConfig.java @@ -25,7 +25,7 @@ import com.hazelcast.core.HazelcastInstance; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.session.hazelcast.HazelcastSessionRepository; +import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository; import org.springframework.session.hazelcast.PrincipalNameExtractor; import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession; import org.springframework.util.SocketUtils; @@ -51,11 +51,12 @@ public class SessionConfig { config.getSerializationConfig().addSerializerConfig(serializer); MapAttributeConfig attributeConfig = new MapAttributeConfig() - .setName(HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE) + .setName(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE) .setExtractor(PrincipalNameExtractor.class.getName()); - config.getMapConfig(HazelcastSessionRepository.DEFAULT_SESSION_MAP_NAME).addMapAttributeConfig(attributeConfig) - .addMapIndexConfig(new MapIndexConfig(HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false)); + config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME) + .addMapAttributeConfig(attributeConfig).addMapIndexConfig( + new MapIndexConfig(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false)); return Hazelcast.newHazelcastInstance(config); }