diff --git a/spring-session-core/src/main/java/org/springframework/session/CreateWithIdSessionRepository.java b/spring-session-core/src/main/java/org/springframework/session/CreateWithIdSessionRepository.java new file mode 100644 index 000000000..995ee07c0 --- /dev/null +++ b/spring-session-core/src/main/java/org/springframework/session/CreateWithIdSessionRepository.java @@ -0,0 +1,46 @@ +/* + * 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; + +import org.springframework.lang.Nullable; + +/** + * Extends a basic {@link SessionRepository} to allow creating session with specific id. + * + * @param the type of Session being managed by this + * {@link CreateWithIdSessionRepository} + * @author Jakub Maciej + */ +public interface CreateWithIdSessionRepository extends SessionRepository { + + /** + * Creates a new {@link Session} that is capable of being persisted by this + * {@link SessionRepository}. + * + *

+ * This allows optimizations and customizations in how the {@link Session} is + * persisted. For example, the implementation returned might keep track of the changes + * ensuring that only the delta needs to be persisted on a save. + *

+ * @param id a desired session id, if id is null - it will be generated by underlying + * implementation + * @return a new {@link Session} that is capable of being persisted by this + * {@link SessionRepository} + */ + S createSession(@Nullable String id); + +} 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 46442683e..b01ef2429 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 @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.Set; import org.apache.commons.logging.Log; @@ -36,6 +37,7 @@ 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.CreateWithIdSessionRepository; import org.springframework.session.DelegatingIndexResolver; import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.FlushMode; @@ -244,10 +246,12 @@ * * @author Rob Winch * @author Vedran Pavic + * @author Jakub Maciej * @since 2.2.0 */ public class RedisIndexedSessionRepository - implements FindByIndexNameSessionRepository, MessageListener { + implements FindByIndexNameSessionRepository, + CreateWithIdSessionRepository, MessageListener { private static final Log logger = LogFactory.getLog(RedisIndexedSessionRepository.class); @@ -489,7 +493,12 @@ public void deleteById(String sessionId) { @Override public RedisSession createSession() { - MapSession cached = new MapSession(); + return createSession(null); + } + + @Override + public RedisSession createSession(final String id) { + MapSession cached = Optional.ofNullable(id).map(MapSession::new).orElse(new MapSession()); if (this.defaultMaxInactiveInterval != null) { cached.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval)); } 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 658c8a456..3c1f555b3 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 @@ -162,6 +162,19 @@ void createSessionCustomMaxInactiveInterval() { assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofSeconds(interval)); } + @Test + void createSessionWithSpecificId() { + final String desiredSessionId = "desiredId"; + Session session = this.redisRepository.createSession(desiredSessionId); + assertThat(session.getId()).isEqualTo(desiredSessionId); + } + + @Test + void createSessionWithIdEqualNull() { + Session session = this.redisRepository.createSession(null); + assertThat(session.getId()).isNotNull(); + } + @Test void saveNewSession() { RedisSession session = this.redisRepository.createSession(); 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 index 4de118c64..04bf80b73 100644 --- 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 @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -39,6 +40,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.session.CreateWithIdSessionRepository; import org.springframework.session.DelegatingIndexResolver; import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.FlushMode; @@ -108,10 +110,12 @@ * @author Tommy Ludwig * @author Mark Anderson * @author Aleksandar Stojsavljevic + * @author Jakub Maciej * @since 2.2.0 */ public class HazelcastIndexedSessionRepository implements FindByIndexNameSessionRepository, + CreateWithIdSessionRepository, EntryAddedListener, EntryEvictedListener, EntryRemovedListener { @@ -234,7 +238,12 @@ public void setSaveMode(SaveMode saveMode) { @Override public HazelcastSession createSession() { - MapSession cached = new MapSession(); + return createSession(null); + } + + @Override + public HazelcastSession createSession(final String id) { + MapSession cached = Optional.ofNullable(id).map(MapSession::new).orElse(new MapSession()); if (this.defaultMaxInactiveInterval != null) { cached.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval)); } diff --git a/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/HazelcastIndexedSessionRepositoryTests.java b/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/HazelcastIndexedSessionRepositoryTests.java index f648bfe58..f7463dc8e 100644 --- a/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/HazelcastIndexedSessionRepositoryTests.java +++ b/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/HazelcastIndexedSessionRepositoryTests.java @@ -115,6 +115,27 @@ void createSessionCustomMaxInactiveInterval() { verifyZeroInteractions(this.sessions); } + @Test + void createSessionWithSpecificId() { + verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean()); + + final String desiredSessionId = "desiredId"; + HazelcastSession session = this.repository.createSession(desiredSessionId); + + assertThat(session.getId()).isEqualTo(desiredSessionId); + verifyZeroInteractions(this.sessions); + } + + @Test + void createSessionWithIdEqualNull() { + verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean()); + + HazelcastSession session = this.repository.createSession(null); + + assertThat(session.getId()).isNotNull(); + verifyZeroInteractions(this.sessions); + } + @Test void saveNewFlushModeOnSave() { verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean()); 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 index ea6f2efd0..41b7eeec1 100644 --- 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 @@ -26,6 +26,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.function.Supplier; @@ -45,6 +46,7 @@ import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.support.lob.DefaultLobHandler; import org.springframework.jdbc.support.lob.LobHandler; +import org.springframework.session.CreateWithIdSessionRepository; import org.springframework.session.DelegatingIndexResolver; import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.FlushMode; @@ -125,10 +127,12 @@ * * @author Vedran Pavic * @author Craig Andrews + * @author Jakub Maciej * @since 2.2.0 */ public class JdbcIndexedSessionRepository - implements FindByIndexNameSessionRepository { + implements FindByIndexNameSessionRepository, + CreateWithIdSessionRepository { /** * The default name of database table used by Spring Session to store sessions. @@ -395,7 +399,12 @@ public void setSaveMode(SaveMode saveMode) { @Override public JdbcSession createSession() { - MapSession delegate = new MapSession(); + return createSession(null); + } + + @Override + public JdbcSession createSession(final String id) { + MapSession delegate = Optional.ofNullable(id).map(MapSession::new).orElse(new MapSession()); if (this.defaultMaxInactiveInterval != null) { delegate.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval)); } diff --git a/spring-session-jdbc/src/test/java/org/springframework/session/jdbc/JdbcIndexedSessionRepositoryTests.java b/spring-session-jdbc/src/test/java/org/springframework/session/jdbc/JdbcIndexedSessionRepositoryTests.java index aed37e535..da3268699 100644 --- a/spring-session-jdbc/src/test/java/org/springframework/session/jdbc/JdbcIndexedSessionRepositoryTests.java +++ b/spring-session-jdbc/src/test/java/org/springframework/session/jdbc/JdbcIndexedSessionRepositoryTests.java @@ -267,6 +267,23 @@ void createSessionImmediateFlushMode() { verifyNoMoreInteractions(this.jdbcOperations); } + @Test + void createSessionWithSpecificId() { + final String desiredSessionId = "desiredId"; + JdbcSession session = this.repository.createSession(desiredSessionId); + assertThat(session.isNew()).isTrue(); + assertThat(session.getId()).isEqualTo(desiredSessionId); + verifyNoMoreInteractions(this.jdbcOperations); + } + + @Test + void createSessionWithIdEqualNull() { + JdbcSession session = this.repository.createSession(null); + assertThat(session.isNew()).isTrue(); + assertThat(session.getId()).isNotNull(); + verifyNoMoreInteractions(this.jdbcOperations); + } + @Test void saveNewWithoutAttributes() { JdbcSession session = this.repository.createSession();