From 8cf44d34b98e840c99878fc17898052383e33d03 Mon Sep 17 00:00:00 2001 From: artsiom Date: Thu, 20 Sep 2018 09:59:54 +0300 Subject: [PATCH 1/6] adding ReactiveFindByIndexNameSessionRepository --- ...ctiveFindByIndexNameSessionRepository.java | 77 +++++++++++++++++++ ...ctiveRedisOperationsSessionRepository.java | 26 ++++++- 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 spring-session-core/src/main/java/org/springframework/session/ReactiveFindByIndexNameSessionRepository.java diff --git a/spring-session-core/src/main/java/org/springframework/session/ReactiveFindByIndexNameSessionRepository.java b/spring-session-core/src/main/java/org/springframework/session/ReactiveFindByIndexNameSessionRepository.java new file mode 100644 index 000000000..00cf5fd56 --- /dev/null +++ b/spring-session-core/src/main/java/org/springframework/session/ReactiveFindByIndexNameSessionRepository.java @@ -0,0 +1,77 @@ +/* + * Copyright 2014-2018 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 + * + * http://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 reactor.core.publisher.Flux; +import reactor.util.function.Tuple2; + +import java.util.Map; + +/** + * Extends a basic {@link SessionRepository} to allow finding sessions by the specified + * index name and index value. + * + * @param the type of Session being managed by this + * {@link ReactiveFindByIndexNameSessionRepository} + * @author Artsiom Yudovin + */ +public interface ReactiveFindByIndexNameSessionRepository + extends ReactiveSessionRepository { + + /** + * A session index that contains the current principal name (i.e. username). + *

+ * It is the responsibility of the developer to ensure the index is populated since + * Spring Session is not aware of the authentication mechanism being used. + * + * @since 1.1 + */ + String PRINCIPAL_NAME_INDEX_NAME = ReactiveFindByIndexNameSessionRepository.class.getName() + .concat(".PRINCIPAL_NAME_INDEX_NAME"); + + /** + * Find a {@link Map} of the session id to the {@link Session} of all sessions that + * contain the specified index name index value. + * + * @param indexName the name of the index (i.e. + * {@link ReactiveFindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME}) + * @param indexValue the value of the index to search for. + * @return a {@code Map} (never {@code null}) of the session id to the {@code Session} + * of all sessions that contain the specified index name and index value. If no + * results are found, an empty {@code Map} is returned. + */ + Flux> findByIndexNameAndIndexValue(String indexName, String indexValue); + + /** + * Find a {@link Map} of the session id to the {@link Session} of all sessions that + * contain the index with the name + * {@link ReactiveFindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME} and the + * specified principal name. + * + * @param principalName the principal name + * @return a {@code Map} (never {@code null}) of the session id to the {@code Session} + * of all sessions that contain the specified principal name. If no results are found, + * an empty {@code Map} is returned. + * @since 2.1.0 + */ + default Flux> findByPrincipalName(String principalName) { + + return findByIndexNameAndIndexValue(PRINCIPAL_NAME_INDEX_NAME, principalName); + + } + +} 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 f59c7fcac..362d12c3b 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 @@ -24,6 +24,9 @@ import java.util.function.Function; import org.reactivestreams.Publisher; +import org.springframework.session.FindByIndexNameSessionRepository; +import org.springframework.session.ReactiveFindByIndexNameSessionRepository; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.data.redis.core.ReactiveRedisOperations; @@ -31,16 +34,19 @@ import org.springframework.session.ReactiveSessionRepository; import org.springframework.session.Session; import org.springframework.util.Assert; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; /** * A {@link ReactiveSessionRepository} that is implemented using Spring Data's * {@link ReactiveRedisOperations}. * * @author Vedran Pavic + * @author Artsiom Yudovin * @since 2.0 */ public class ReactiveRedisOperationsSessionRepository implements - ReactiveSessionRepository { + ReactiveFindByIndexNameSessionRepository { /** * The default namespace for each key and channel in Redis used by Spring Session. @@ -192,6 +198,24 @@ private String getSessionKey(String sessionId) { return this.namespace + "sessions:" + sessionId; } + private String getPrincipalKey(String principalName) { + return this.namespace + "index:" + + FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME + ":" + + principalName; + } + + @Override + public Flux> findByIndexNameAndIndexValue(String indexName, String indexValue) { + if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) { + return Flux.empty(); + } + String principalKey = getPrincipalKey(indexValue); + return this.sessionRedisOperations.opsForSet() + .scan(principalKey) + .cast(String.class) + .flatMap(id -> findById(id).map(session -> Tuples.of(id, session))); + } + /** * 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 From 406e35b28c703f6e20bdfb2083d81a378c67ff5c Mon Sep 17 00:00:00 2001 From: artsiom Date: Thu, 20 Sep 2018 10:38:53 +0300 Subject: [PATCH 2/6] adding tests for findByIndexNameAndIndexValue --- ...ctiveRedisOperationsSessionRepository.java | 2 +- ...RedisOperationsSessionRepositoryTests.java | 55 ++++++++++++++++++- 2 files changed, 53 insertions(+), 4 deletions(-) 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 362d12c3b..1b8c5e834 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 @@ -213,7 +213,7 @@ public Flux> findByIndexNameAndIndexValue(String in return this.sessionRedisOperations.opsForSet() .scan(principalKey) .cast(String.class) - .flatMap(id -> findById(id).map(session -> Tuples.of(id, session))); + .flatMap(id -> this.findById(id).map(session -> Tuples.of(id, session))); } /** 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/ReactiveRedisOperationsSessionRepositoryTests.java index 81e87dee2..503c3ab99 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/ReactiveRedisOperationsSessionRepositoryTests.java @@ -25,6 +25,8 @@ import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.springframework.data.redis.core.ReactiveSetOperations; +import org.springframework.session.ReactiveFindByIndexNameSessionRepository; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -40,14 +42,13 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.*; /** * Tests for {@link ReactiveRedisOperationsSessionRepository}. * * @author Vedran Pavic + * @author Artsiom Yudovin */ public class ReactiveRedisOperationsSessionRepositoryTests { @@ -59,6 +60,10 @@ public class ReactiveRedisOperationsSessionRepositoryTests { private ReactiveHashOperations hashOperations = mock( ReactiveHashOperations.class); + @SuppressWarnings("unchecked") + private ReactiveSetOperations setOperations = mock( + ReactiveSetOperations.class); + @SuppressWarnings("unchecked") private ArgumentCaptor> delta = ArgumentCaptor .forClass(Map.class); @@ -392,6 +397,50 @@ public void getAttributeNamesAndRemove() { assertThat(session.getAttributeNames()).isEmpty(); } + @Test + public void findByIndexNameAndIndexValue() { + given(this.redisOperations.opsForHash()).willReturn(this.hashOperations); + String attribute1 = "attribute1"; + String attribute2 = "attribute2"; + MapSession expected = new MapSession("test"); + expected.setLastAccessedTime(Instant.now().minusSeconds(60)); + expected.setAttribute(attribute1, "test"); + expected.setAttribute(attribute2, null); + Map map = map( + ReactiveRedisOperationsSessionRepository.ATTRIBUTE_PREFIX + attribute1, + expected.getAttribute(attribute1), + ReactiveRedisOperationsSessionRepository.ATTRIBUTE_PREFIX + attribute2, + expected.getAttribute(attribute2), + ReactiveRedisOperationsSessionRepository.CREATION_TIME_KEY, + expected.getCreationTime().toEpochMilli(), + ReactiveRedisOperationsSessionRepository.MAX_INACTIVE_INTERVAL_KEY, + (int) expected.getMaxInactiveInterval().getSeconds(), + ReactiveRedisOperationsSessionRepository.LAST_ACCESSED_TIME_KEY, + expected.getLastAccessedTime().toEpochMilli()); + given(this.hashOperations.entries(anyString())) + .willReturn(Flux.fromIterable(map.entrySet())); + + Flux ids = Flux.just("id"); + given(this.setOperations.scan(anyString())).willReturn(ids); + given(this.redisOperations.opsForSet()).willReturn(this.setOperations); + + StepVerifier + .create(this.repository + .findByIndexNameAndIndexValue( + ReactiveFindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, + "attribute2")) + .consumeNextWith(tuple -> { + assertThat(tuple.getT1()).isEqualTo("id"); + assertThat(tuple.getT2()) + .isInstanceOf(RedisSession.class); + }).verifyComplete(); + + verify(this.redisOperations).opsForHash(); + verify(this.hashOperations).entries(anyString()); + verify(this.redisOperations).opsForSet(); + verify(this.setOperations).scan(anyString()); + } + private Map map(Object... objects) { Map result = new HashMap<>(); if (objects == null) { From 6c843511697fcf1196c93e1ef85e63b2f21637b8 Mon Sep 17 00:00:00 2001 From: artsiom Date: Thu, 20 Sep 2018 11:53:22 +0300 Subject: [PATCH 3/6] fixing check styles --- .../data/redis/ReactiveRedisOperationsSessionRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1b8c5e834..736af242c 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 @@ -18,8 +18,8 @@ import java.time.Duration; import java.time.Instant; -import java.util.HashMap; import java.util.Map; +import java.util.HashMap; import java.util.Set; import java.util.function.Function; From 31f2b2893df1b7c4d823d7d37c7692c4bbc15580 Mon Sep 17 00:00:00 2001 From: artsiom Date: Thu, 20 Sep 2018 11:59:17 +0300 Subject: [PATCH 4/6] fixing check styles --- .../session/ReactiveFindByIndexNameSessionRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-session-core/src/main/java/org/springframework/session/ReactiveFindByIndexNameSessionRepository.java b/spring-session-core/src/main/java/org/springframework/session/ReactiveFindByIndexNameSessionRepository.java index 00cf5fd56..9e8431c5f 100644 --- a/spring-session-core/src/main/java/org/springframework/session/ReactiveFindByIndexNameSessionRepository.java +++ b/spring-session-core/src/main/java/org/springframework/session/ReactiveFindByIndexNameSessionRepository.java @@ -16,11 +16,11 @@ package org.springframework.session; +import java.util.Map; + import reactor.core.publisher.Flux; import reactor.util.function.Tuple2; -import java.util.Map; - /** * Extends a basic {@link SessionRepository} to allow finding sessions by the specified * index name and index value. From 28dd1f0c43177be40ca56d2f6c3a92d5c54058da Mon Sep 17 00:00:00 2001 From: artsiom Date: Thu, 20 Sep 2018 13:16:59 +0300 Subject: [PATCH 5/6] fixing check styles --- .../redis/ReactiveRedisOperationsSessionRepositoryTests.java | 1 + 1 file changed, 1 insertion(+) 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/ReactiveRedisOperationsSessionRepositoryTests.java index 503c3ab99..dd0803345 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/ReactiveRedisOperationsSessionRepositoryTests.java @@ -398,6 +398,7 @@ public void getAttributeNamesAndRemove() { } @Test + @SuppressWarnings("unchecked") public void findByIndexNameAndIndexValue() { given(this.redisOperations.opsForHash()).willReturn(this.hashOperations); String attribute1 = "attribute1"; From 6da75adae732f644ba40703ca159bede3af511cb Mon Sep 17 00:00:00 2001 From: artsiom Date: Thu, 20 Sep 2018 13:49:41 +0300 Subject: [PATCH 6/6] fixing check styles --- .../ReactiveRedisOperationsSessionRepository.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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 736af242c..e29f4afa6 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 @@ -18,24 +18,24 @@ import java.time.Duration; import java.time.Instant; -import java.util.Map; import java.util.HashMap; +import java.util.Map; import java.util.Set; import java.util.function.Function; import org.reactivestreams.Publisher; -import org.springframework.session.FindByIndexNameSessionRepository; -import org.springframework.session.ReactiveFindByIndexNameSessionRepository; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; import org.springframework.data.redis.core.ReactiveRedisOperations; +import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.MapSession; +import org.springframework.session.ReactiveFindByIndexNameSessionRepository; import org.springframework.session.ReactiveSessionRepository; import org.springframework.session.Session; import org.springframework.util.Assert; -import reactor.util.function.Tuple2; -import reactor.util.function.Tuples; /** * A {@link ReactiveSessionRepository} that is implemented using Spring Data's @@ -212,8 +212,8 @@ public Flux> findByIndexNameAndIndexValue(String in String principalKey = getPrincipalKey(indexValue); return this.sessionRedisOperations.opsForSet() .scan(principalKey) - .cast(String.class) - .flatMap(id -> this.findById(id).map(session -> Tuples.of(id, session))); + .cast(String.class) + .flatMap((id) -> this.findById(id).map((session) -> Tuples.of(id, session))); } /**