diff --git a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/ReactiveRedisSessionRepositoryKeyMissITests.java b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/ReactiveRedisSessionRepositoryKeyMissITests.java
new file mode 100644
index 00000000..9b5dc26c
--- /dev/null
+++ b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/ReactiveRedisSessionRepositoryKeyMissITests.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2014-2023 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.Instant;
+import java.util.Map;
+import java.util.function.BiFunction;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import reactor.core.publisher.Mono;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.core.ReactiveHashOperations;
+import org.springframework.data.redis.core.ReactiveRedisOperations;
+import org.springframework.session.MapSession;
+import org.springframework.session.config.ReactiveSessionRepositoryCustomizer;
+import org.springframework.session.data.redis.ReactiveRedisSessionRepository.RedisSession;
+import org.springframework.session.data.redis.config.annotation.web.server.EnableRedisWebSession;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.willAnswer;
+import static org.mockito.Mockito.spy;
+
+/**
+ * Key miss error tests for {@link ReactiveRedisSessionRepository}
+ *
+ * @author Marcus da Coregio
+ * @see Related
+ * GitHub Issue
+ */
+@ExtendWith(SpringExtension.class)
+class ReactiveRedisSessionRepositoryKeyMissITests extends AbstractRedisITests {
+
+ private ReactiveRedisSessionRepository sessionRepository;
+
+ private ReactiveRedisOperations spyOperations;
+
+ AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
+
+ @Test
+ void findByIdWhenSessionDeletedWhileSavingDeltaThenThrowIllegalStateException() {
+ this.context.register(Config.class);
+ refreshAndPrepareFields();
+ RedisSession session = createAndSaveSession(Instant.now());
+ session.setAttribute("new", "value");
+
+ ReactiveHashOperations opsForHash = spy(this.spyOperations.opsForHash());
+ given(this.spyOperations.opsForHash()).willReturn(opsForHash);
+ willAnswer((invocation) -> this.sessionRepository.deleteById(session.getId())
+ .then((Mono) invocation.callRealMethod())).given(opsForHash).putAll(any(), any());
+
+ this.sessionRepository.save(session).block();
+ assertThatIllegalStateException().isThrownBy(() -> this.sessionRepository.findById(session.getId()).block())
+ .withMessage("creationTime key must not be null");
+ }
+
+ @Test
+ void findByIdWhenSessionDeletedWhileSavingDeltaAndSafeMapperThenSessionIsNull() {
+ this.context.register(RedisSessionMapperConfig.class);
+ refreshAndPrepareFields();
+ RedisSession session = createAndSaveSession(Instant.now());
+ session.setAttribute("new", "value");
+
+ ReactiveHashOperations opsForHash = spy(this.spyOperations.opsForHash());
+ given(this.spyOperations.opsForHash()).willReturn(opsForHash);
+ willAnswer((invocation) -> this.sessionRepository.deleteById(session.getId())
+ .then((Mono) invocation.callRealMethod())).given(opsForHash).putAll(any(), any());
+
+ this.sessionRepository.save(session).block();
+ assertThat(this.sessionRepository.findById(session.getId()).block()).isNull();
+ }
+
+ @SuppressWarnings("unchecked")
+ private void refreshAndPrepareFields() {
+ this.context.refresh();
+ this.sessionRepository = this.context.getBean(ReactiveRedisSessionRepository.class);
+ ReactiveRedisOperations redisOperations = (ReactiveRedisOperations) ReflectionTestUtils
+ .getField(this.sessionRepository, "sessionRedisOperations");
+ this.spyOperations = spy(redisOperations);
+ ReflectionTestUtils.setField(this.sessionRepository, "sessionRedisOperations", this.spyOperations);
+ }
+
+ private RedisSession createAndSaveSession(Instant lastAccessedTime) {
+ RedisSession session = this.sessionRepository.createSession().block();
+ session.setLastAccessedTime(lastAccessedTime);
+ session.setAttribute("attribute1", "value1");
+ this.sessionRepository.save(session).block();
+ return this.sessionRepository.findById(session.getId()).block();
+ }
+
+ @Configuration
+ @EnableRedisWebSession
+ static class Config extends BaseConfig {
+
+ }
+
+ @Configuration
+ @EnableRedisWebSession
+ static class RedisSessionMapperConfig extends BaseConfig {
+
+ @Bean
+ ReactiveSessionRepositoryCustomizer redisSessionRepositoryCustomizer() {
+ return (redisSessionRepository) -> redisSessionRepository
+ .setRedisSessionMapper(new SafeRedisSessionMapper(redisSessionRepository));
+ }
+
+ }
+
+ static class SafeRedisSessionMapper implements BiFunction, Mono> {
+
+ private final RedisSessionMapper delegate = new RedisSessionMapper();
+
+ private final ReactiveRedisSessionRepository sessionRepository;
+
+ SafeRedisSessionMapper(ReactiveRedisSessionRepository sessionRepository) {
+ this.sessionRepository = sessionRepository;
+ }
+
+ @Override
+ public Mono apply(String sessionId, Map map) {
+ return Mono.fromSupplier(() -> this.delegate.apply(sessionId, map)).onErrorResume(
+ IllegalStateException.class,
+ (ex) -> this.sessionRepository.deleteById(sessionId).then(Mono.empty()));
+ }
+
+ }
+
+}
diff --git a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryKeyMissITests.java b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryKeyMissITests.java
new file mode 100644
index 00000000..774c3d24
--- /dev/null
+++ b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryKeyMissITests.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2014-2023 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.Instant;
+import java.util.Map;
+import java.util.function.BiFunction;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.core.BoundHashOperations;
+import org.springframework.data.redis.core.RedisOperations;
+import org.springframework.session.MapSession;
+import org.springframework.session.config.SessionRepositoryCustomizer;
+import org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession;
+import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisIndexedHttpSession;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.willAnswer;
+import static org.mockito.Mockito.spy;
+
+/**
+ * Key miss error tests for {@link RedisIndexedSessionRepository}
+ *
+ * @author Marcus da Coregio
+ * @see Related
+ * GitHub Issue
+ */
+@ExtendWith(SpringExtension.class)
+class RedisIndexedSessionRepositoryKeyMissITests extends AbstractRedisITests {
+
+ private RedisIndexedSessionRepository sessionRepository;
+
+ private RedisOperations spyOperations;
+
+ AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
+
+ @Test
+ void findByIdWhenSessionDeletedWhileSavingDeltaThenThrowIllegalStateException() {
+ this.context.register(Config.class);
+ refreshAndPrepareFields();
+ RedisSession session = createAndSaveSession(Instant.now());
+ session.setAttribute("new", "value");
+
+ BoundHashOperations opsForHash = spy(this.spyOperations.boundHashOps(anyString()));
+ given(this.spyOperations.boundHashOps(anyString())).willReturn(opsForHash);
+ willAnswer((invocation) -> {
+ this.sessionRepository.deleteById(session.getId());
+ return invocation.callRealMethod();
+ }).given(opsForHash).putAll(any());
+
+ this.sessionRepository.save(session);
+ assertThatIllegalStateException().isThrownBy(() -> this.sessionRepository.findById(session.getId()))
+ .withMessage("creationTime key must not be null");
+ }
+
+ @Test
+ void findByIdWhenSessionDeletedWhileSavingDeltaAndSafeMapperThenSessionIsNull() {
+ this.context.register(RedisSessionMapperConfig.class);
+ refreshAndPrepareFields();
+ RedisSession session = createAndSaveSession(Instant.now());
+ session.setAttribute("new", "value");
+
+ BoundHashOperations opsForHash = spy(this.spyOperations.boundHashOps(anyString()));
+ given(this.spyOperations.boundHashOps(anyString())).willReturn(opsForHash);
+ willAnswer((invocation) -> {
+ this.sessionRepository.deleteById(session.getId());
+ return invocation.callRealMethod();
+ }).given(opsForHash).putAll(any());
+
+ this.sessionRepository.save(session);
+ assertThat(this.sessionRepository.findById(session.getId())).isNull();
+ }
+
+ @SuppressWarnings("unchecked")
+ private void refreshAndPrepareFields() {
+ this.context.refresh();
+ this.sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
+ RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils
+ .getField(this.sessionRepository, "sessionRedisOperations");
+ this.spyOperations = spy(redisOperations);
+ ReflectionTestUtils.setField(this.sessionRepository, "sessionRedisOperations", this.spyOperations);
+ }
+
+ private RedisSession createAndSaveSession(Instant lastAccessedTime) {
+ RedisSession session = this.sessionRepository.createSession();
+ session.setLastAccessedTime(lastAccessedTime);
+ session.setAttribute("attribute1", "value1");
+ this.sessionRepository.save(session);
+ return this.sessionRepository.findById(session.getId());
+ }
+
+ @Configuration
+ @EnableRedisIndexedHttpSession
+ static class Config extends BaseConfig {
+
+ }
+
+ @Configuration
+ @EnableRedisIndexedHttpSession
+ static class RedisSessionMapperConfig extends BaseConfig {
+
+ @Bean
+ SessionRepositoryCustomizer redisSessionRepositoryCustomizer() {
+ return (redisSessionRepository) -> redisSessionRepository.setRedisSessionMapper(
+ new SafeRedisSessionMapper(redisSessionRepository.getSessionRedisOperations()));
+ }
+
+ }
+
+ static class SafeRedisSessionMapper implements BiFunction, MapSession> {
+
+ private final RedisSessionMapper delegate = new RedisSessionMapper();
+
+ private final RedisOperations redisOperations;
+
+ SafeRedisSessionMapper(RedisOperations redisOperations) {
+ this.redisOperations = redisOperations;
+ }
+
+ @Override
+ public MapSession apply(String sessionId, Map map) {
+ try {
+ return this.delegate.apply(sessionId, map);
+ }
+ catch (IllegalStateException ex) {
+ this.redisOperations.delete("spring:session:sessions:" + sessionId);
+ return null;
+ }
+ }
+
+ }
+
+}
diff --git a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisSessionRepositoryKeyMissITests.java b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisSessionRepositoryKeyMissITests.java
new file mode 100644
index 00000000..f636d620
--- /dev/null
+++ b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisSessionRepositoryKeyMissITests.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2014-2023 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.Instant;
+import java.util.Map;
+import java.util.function.BiFunction;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.core.HashOperations;
+import org.springframework.data.redis.core.RedisOperations;
+import org.springframework.session.MapSession;
+import org.springframework.session.config.SessionRepositoryCustomizer;
+import org.springframework.session.data.redis.RedisSessionRepository.RedisSession;
+import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.willAnswer;
+import static org.mockito.Mockito.spy;
+
+/**
+ * Key miss error tests for {@link RedisSessionRepository}
+ *
+ * @author Marcus da Coregio
+ * @see Related
+ * GitHub Issue
+ */
+@ExtendWith(SpringExtension.class)
+class RedisSessionRepositoryKeyMissITests extends AbstractRedisITests {
+
+ private RedisSessionRepository sessionRepository;
+
+ private RedisOperations spyOperations;
+
+ AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
+
+ @Test
+ void findByIdWhenSessionDeletedWhileSavingDeltaThenThrowIllegalStateException() {
+ this.context.register(Config.class);
+ refreshAndPrepareFields();
+ RedisSession session = createAndSaveSession(Instant.now());
+ session.setAttribute("new", "value");
+
+ HashOperations opsForHash = spy(this.spyOperations.opsForHash());
+ given(this.spyOperations.opsForHash()).willReturn(opsForHash);
+ willAnswer((invocation) -> {
+ this.sessionRepository.deleteById(session.getId());
+ return invocation.callRealMethod();
+ }).given(opsForHash).putAll(any(), any());
+
+ this.sessionRepository.save(session);
+ assertThatIllegalStateException().isThrownBy(() -> this.sessionRepository.findById(session.getId()))
+ .withMessage("creationTime key must not be null");
+ }
+
+ @Test
+ void findByIdWhenSessionDeletedWhileSavingDeltaAndSafeMapperThenSessionIsNull() {
+ this.context.register(RedisSessionMapperConfig.class);
+ refreshAndPrepareFields();
+ RedisSession session = createAndSaveSession(Instant.now());
+ session.setAttribute("new", "value");
+
+ HashOperations opsForHash = spy(this.spyOperations.opsForHash());
+ given(this.spyOperations.opsForHash()).willReturn(opsForHash);
+ willAnswer((invocation) -> {
+ this.sessionRepository.deleteById(session.getId());
+ return invocation.callRealMethod();
+ }).given(opsForHash).putAll(any(), any());
+
+ this.sessionRepository.save(session);
+ assertThat(this.sessionRepository.findById(session.getId())).isNull();
+ }
+
+ @SuppressWarnings("unchecked")
+ private void refreshAndPrepareFields() {
+ this.context.refresh();
+ this.sessionRepository = this.context.getBean(RedisSessionRepository.class);
+ RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils
+ .getField(this.sessionRepository, "sessionRedisOperations");
+ this.spyOperations = spy(redisOperations);
+ ReflectionTestUtils.setField(this.sessionRepository, "sessionRedisOperations", this.spyOperations);
+ }
+
+ private RedisSession createAndSaveSession(Instant lastAccessedTime) {
+ RedisSession session = this.sessionRepository.createSession();
+ session.setLastAccessedTime(lastAccessedTime);
+ session.setAttribute("attribute1", "value1");
+ this.sessionRepository.save(session);
+ return this.sessionRepository.findById(session.getId());
+ }
+
+ @Configuration
+ @EnableRedisHttpSession
+ static class Config extends BaseConfig {
+
+ }
+
+ @Configuration
+ @EnableRedisHttpSession
+ static class RedisSessionMapperConfig extends BaseConfig {
+
+ @Bean
+ SessionRepositoryCustomizer redisSessionRepositoryCustomizer() {
+ return (redisSessionRepository) -> redisSessionRepository
+ .setRedisSessionMapper(new SafeRedisSessionMapper(redisSessionRepository));
+ }
+
+ }
+
+ static class SafeRedisSessionMapper implements BiFunction, MapSession> {
+
+ private final RedisSessionMapper delegate = new RedisSessionMapper();
+
+ private final RedisSessionRepository sessionRepository;
+
+ SafeRedisSessionMapper(RedisSessionRepository sessionRepository) {
+ this.sessionRepository = sessionRepository;
+ }
+
+ @Override
+ public MapSession apply(String sessionId, Map map) {
+ try {
+ return this.delegate.apply(sessionId, map);
+ }
+ catch (IllegalStateException ex) {
+ this.sessionRepository.deleteById(sessionId);
+ return null;
+ }
+ }
+
+ }
+
+}
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
index 84460599..a24db32d 100644
--- 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
@@ -21,6 +21,7 @@ import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
+import java.util.function.BiFunction;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
@@ -66,6 +67,8 @@ public class ReactiveRedisSessionRepository
private SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance();
+ private BiFunction, Mono> redisSessionMapper = new RedisSessionMapperAdapter();
+
/**
* Create a new {@link ReactiveRedisSessionRepository} instance.
* @param sessionRedisOperations the {@link ReactiveRedisOperations} to use for
@@ -154,7 +157,7 @@ public class ReactiveRedisSessionRepository
return this.sessionRedisOperations.opsForHash().entries(sessionKey)
.collectMap((e) -> e.getKey().toString(), Map.Entry::getValue)
.filter((map) -> !map.isEmpty())
- .map(new RedisSessionMapper(id))
+ .flatMap((map) -> this.redisSessionMapper.apply(id, map))
.filter((session) -> !session.isExpired())
.map((session) -> new RedisSession(session, false))
.switchIfEmpty(Mono.defer(() -> deleteById(id).then(Mono.empty())));
@@ -186,6 +189,16 @@ public class ReactiveRedisSessionRepository
this.sessionIdGenerator = sessionIdGenerator;
}
+ /**
+ * Set the {@link BiFunction} used to convert a {@link Map} to a {@link MapSession}.
+ * @param redisSessionMapper the mapper to use, cannot be null
+ * @since 3.2
+ */
+ public void setRedisSessionMapper(BiFunction, Mono> redisSessionMapper) {
+ Assert.notNull(redisSessionMapper, "redisSessionMapper cannot be null");
+ this.redisSessionMapper = redisSessionMapper;
+ }
+
/**
* 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
@@ -349,4 +362,16 @@ public class ReactiveRedisSessionRepository
}
+ private static final class RedisSessionMapperAdapter
+ implements BiFunction, Mono> {
+
+ private final RedisSessionMapper mapper = new RedisSessionMapper();
+
+ @Override
+ public Mono apply(String sessionId, Map map) {
+ return Mono.fromSupplier(() -> this.mapper.apply(sessionId, map));
+ }
+
+ }
+
}
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 70fe7982..9b08c482 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
@@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
+import java.util.function.BiFunction;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -326,6 +327,8 @@ public class RedisIndexedSessionRepository
private SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance();
+ private BiFunction, MapSession> redisSessionMapper = new RedisSessionMapper();
+
/**
* Creates a new instance. For an example, refer to the class level javadoc.
* @param sessionRedisOperations the {@link RedisOperations} to use for managing the
@@ -523,8 +526,8 @@ public class RedisIndexedSessionRepository
if ((entries == null) || entries.isEmpty()) {
return null;
}
- MapSession loaded = new RedisSessionMapper(id).apply(entries);
- if (!allowExpired && loaded.isExpired()) {
+ MapSession loaded = this.redisSessionMapper.apply(id, entries);
+ if (loaded == null || (!allowExpired && loaded.isExpired())) {
return null;
}
RedisSession result = new RedisSession(loaded, false);
@@ -568,9 +571,11 @@ public class RedisIndexedSessionRepository
String sessionId = channel.substring(channel.lastIndexOf(":") + 1);
@SuppressWarnings("unchecked")
Map entries = (Map) this.defaultSerializer.deserialize(message.getBody());
- MapSession loaded = new RedisSessionMapper(sessionId).apply(entries);
- RedisSession session = new RedisSession(loaded, false);
- handleCreated(session);
+ MapSession loaded = this.redisSessionMapper.apply(sessionId, entries);
+ if (loaded != null) {
+ RedisSession session = new RedisSession(loaded, false);
+ handleCreated(session);
+ }
return;
}
@@ -730,6 +735,17 @@ public class RedisIndexedSessionRepository
this.sessionIdGenerator = sessionIdGenerator;
}
+ /**
+ * Set the {@link BiFunction} used to map {@link MapSession} to a
+ * {@link ReactiveRedisSessionRepository.RedisSession}.
+ * @param redisSessionMapper the mapper to use, cannot be null
+ * @since 3.2
+ */
+ public void setRedisSessionMapper(BiFunction, MapSession> redisSessionMapper) {
+ Assert.notNull(redisSessionMapper, "redisSessionMapper cannot be null");
+ this.redisSessionMapper = redisSessionMapper;
+ }
+
/**
* 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
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 476d03d3..ab2f926c 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2014-2019 the original author or authors.
+ * Copyright 2014-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package org.springframework.session.data.redis;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
+import java.util.function.BiFunction;
import java.util.function.Function;
import org.springframework.session.MapSession;
@@ -30,9 +31,10 @@ import org.springframework.util.Assert;
* {@link MapSession}.
*
* @author Vedran Pavic
+ * @author Marcus da Coregio
* @since 2.2.0
*/
-final class RedisSessionMapper implements Function