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 index 531e61317..b54beaf24 100644 --- 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 @@ -334,7 +334,7 @@ public class RedisIndexedSessionRepository private SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance(); - private BiFunction, MapSession> redisSessionMapper = new RedisSessionMapper(); + private BiFunction, @Nullable MapSession> redisSessionMapper = new RedisSessionMapper(); /** * Creates a new instance. For an example, refer to the class level javadoc. @@ -757,11 +757,15 @@ public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) { /** * Set the {@link BiFunction} used to map {@link MapSession} to a - * {@link ReactiveRedisSessionRepository.RedisSession}. + * {@link ReactiveRedisSessionRepository.RedisSession}. The mapper may return + * {@code null} to indicate that the session could not be loaded (for example when the + * Redis hash is incomplete); callers treat a {@code null} result the same as a + * missing session. * @param redisSessionMapper the mapper to use, cannot be null * @since 3.2 */ - public void setRedisSessionMapper(BiFunction, MapSession> redisSessionMapper) { + public void setRedisSessionMapper( + BiFunction, @Nullable MapSession> redisSessionMapper) { Assert.notNull(redisSessionMapper, "redisSessionMapper cannot be null"); this.redisSessionMapper = redisSessionMapper; } diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisSessionMapper.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisSessionMapper.java index 481040671..8c5a6ee7b 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisSessionMapper.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisSessionMapper.java @@ -22,6 +22,8 @@ import java.util.function.BiFunction; import java.util.function.Function; +import org.jspecify.annotations.Nullable; + import org.springframework.session.MapSession; import org.springframework.session.Session; import org.springframework.util.Assert; @@ -29,12 +31,15 @@ /** * A {@link Function} that converts a {@link Map} representing Redis hash to a * {@link MapSession}. + *

+ * This implementation never returns {@code null}; custom mappers used via + * {@code setRedisSessionMapper} may return {@code null} to signal an unloadable session. * * @author Vedran Pavic * @author Marcus da Coregio * @since 2.2.0 */ -public final class RedisSessionMapper implements BiFunction, MapSession> { +public final class RedisSessionMapper implements BiFunction, @Nullable MapSession> { /** * The key in the hash representing {@link Session#getCreationTime()}. diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisSessionRepository.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisSessionRepository.java index 1a298ce40..072cb2f2a 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisSessionRepository.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisSessionRepository.java @@ -63,7 +63,7 @@ public class RedisSessionRepository implements SessionRepository, MapSession> redisSessionMapper = new RedisSessionMapper(); + private BiFunction, @Nullable MapSession> redisSessionMapper = new RedisSessionMapper(); /** * Create a new {@link RedisSessionRepository} instance. @@ -183,11 +183,15 @@ public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) { /** * Set the {@link BiFunction} used to map {@link MapSession} to a - * {@link ReactiveRedisSessionRepository.RedisSession}. + * {@link ReactiveRedisSessionRepository.RedisSession}. The mapper may return + * {@code null} to indicate that the session could not be loaded (for example when the + * Redis hash is incomplete); callers treat a {@code null} result the same as a + * missing session. * @param redisSessionMapper the mapper to use, cannot be null * @since 3.2 */ - public void setRedisSessionMapper(BiFunction, MapSession> redisSessionMapper) { + public void setRedisSessionMapper( + BiFunction, @Nullable MapSession> redisSessionMapper) { Assert.notNull(redisSessionMapper, "redisSessionMapper cannot be null"); this.redisSessionMapper = redisSessionMapper; } diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryTests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryTests.java index 1ac304e65..41c54857e 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryTests.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryTests.java @@ -960,6 +960,20 @@ void findByIdWhenChangeSessionIdThenUsesSessionIdGenerator() { assertThat(newSessionId).isEqualTo("test"); } + @Test + void findByIdWhenRedisSessionMapperReturnsNullThenReturnsNull() { + String sessionId = "session-id"; + given(this.redisOperations.boundHashOps(getKey(sessionId))) + .willReturn(this.boundHashOperations); + Map map = map(RedisSessionMapper.CREATION_TIME_KEY, Instant.EPOCH.toEpochMilli(), + RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, 1, RedisSessionMapper.LAST_ACCESSED_TIME_KEY, + Instant.now().toEpochMilli()); + given(this.boundHashOperations.entries()).willReturn(map); + this.redisRepository.setRedisSessionMapper((id, entries) -> null); + + assertThat(this.redisRepository.findById(sessionId)).isNull(); + } + private String getKey(String id) { return "spring:session:sessions:" + id; } diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisSessionRepositoryTests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisSessionRepositoryTests.java index e851da723..4db9b02db 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisSessionRepositoryTests.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisSessionRepositoryTests.java @@ -399,6 +399,17 @@ void findByIdWhenChangeSessionIdThenUsesSessionIdGenerator() { assertThat(session.changeSessionId()).isEqualTo("test"); } + @Test + void findByIdWhenRedisSessionMapperReturnsNullThenReturnsNull() { + given(this.sessionHashOperations.entries(eq(TEST_SESSION_KEY))) + .willReturn(mapOf(RedisSessionMapper.CREATION_TIME_KEY, Instant.EPOCH.toEpochMilli(), + RedisSessionMapper.LAST_ACCESSED_TIME_KEY, Instant.now().toEpochMilli(), + RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS)); + this.sessionRepository.setRedisSessionMapper((id, entries) -> null); + + assertThat(this.sessionRepository.findById(TEST_SESSION_ID)).isNull(); + } + private static String getSessionKey(String sessionId) { return "spring:session:sessions:" + sessionId; }