Skip to content

Commit 5485907

Browse files
committed
Extract RedisSessionMapper
See: #1408
1 parent 3d03c02 commit 5485907

File tree

6 files changed

+290
-162
lines changed

6 files changed

+290
-162
lines changed

spring-session-data-redis/src/main/java/org/springframework/session/data/redis/ReactiveRedisOperationsSessionRepository.java

Lines changed: 10 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import java.util.HashMap;
2222
import java.util.Map;
2323
import java.util.Set;
24-
import java.util.function.Function;
2524

2625
import org.reactivestreams.Publisher;
2726
import reactor.core.publisher.Mono;
@@ -47,29 +46,6 @@ public class ReactiveRedisOperationsSessionRepository implements
4746
*/
4847
public static final String DEFAULT_NAMESPACE = "spring:session";
4948

50-
/**
51-
* The key in the Hash representing {@link Session#getCreationTime()}.
52-
*/
53-
static final String CREATION_TIME_KEY = "creationTime";
54-
55-
/**
56-
* The key in the Hash representing {@link Session#getLastAccessedTime()}.
57-
*/
58-
static final String LAST_ACCESSED_TIME_KEY = "lastAccessedTime";
59-
60-
/**
61-
* The key in the Hash representing {@link Session#getMaxInactiveInterval()} .
62-
*/
63-
static final String MAX_INACTIVE_INTERVAL_KEY = "maxInactiveInterval";
64-
65-
/**
66-
* The prefix of the key used for session attributes. The suffix is the name of
67-
* the session attribute. For example, if the session contained an attribute named
68-
* attributeName, then there would be an entry in the hash named
69-
* sessionAttr:attributeName that mapped to its value.
70-
*/
71-
static final String ATTRIBUTE_PREFIX = "sessionAttr:";
72-
7349
private final ReactiveRedisOperations<String, Object> sessionRedisOperations;
7450

7551
/**
@@ -162,7 +138,7 @@ public Mono<RedisSession> findById(String id) {
162138
return this.sessionRedisOperations.opsForHash().entries(sessionKey)
163139
.collectMap((e) -> e.getKey().toString(), Map.Entry::getValue)
164140
.filter((map) -> !map.isEmpty())
165-
.map(new SessionMapper(id))
141+
.map(new RedisSessionMapper(id))
166142
.filter((session) -> !session.isExpired())
167143
.map(RedisSession::new)
168144
.switchIfEmpty(Mono.defer(() -> deleteById(id).then(Mono.empty())));
@@ -177,7 +153,7 @@ public Mono<Void> deleteById(String id) {
177153
}
178154

179155
private static String getAttributeKey(String attributeName) {
180-
return ATTRIBUTE_PREFIX + attributeName;
156+
return RedisSessionMapper.ATTRIBUTE_PREFIX + attributeName;
181157
}
182158

183159
private String getSessionKey(String sessionId) {
@@ -206,10 +182,12 @@ final class RedisSession implements Session {
206182
*/
207183
RedisSession() {
208184
this(new MapSession());
209-
this.delta.put(CREATION_TIME_KEY, getCreationTime().toEpochMilli());
210-
this.delta.put(MAX_INACTIVE_INTERVAL_KEY,
185+
this.delta.put(RedisSessionMapper.CREATION_TIME_KEY,
186+
getCreationTime().toEpochMilli());
187+
this.delta.put(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY,
211188
(int) getMaxInactiveInterval().getSeconds());
212-
this.delta.put(LAST_ACCESSED_TIME_KEY, getLastAccessedTime().toEpochMilli());
189+
this.delta.put(RedisSessionMapper.LAST_ACCESSED_TIME_KEY,
190+
getLastAccessedTime().toEpochMilli());
213191
this.isNew = true;
214192
this.flushImmediateIfNecessary();
215193
}
@@ -266,7 +244,8 @@ public Instant getCreationTime() {
266244
@Override
267245
public void setLastAccessedTime(Instant lastAccessedTime) {
268246
this.cached.setLastAccessedTime(lastAccessedTime);
269-
putAndFlush(LAST_ACCESSED_TIME_KEY, getLastAccessedTime().toEpochMilli());
247+
putAndFlush(RedisSessionMapper.LAST_ACCESSED_TIME_KEY,
248+
getLastAccessedTime().toEpochMilli());
270249
}
271250

272251
@Override
@@ -277,7 +256,7 @@ public Instant getLastAccessedTime() {
277256
@Override
278257
public void setMaxInactiveInterval(Duration interval) {
279258
this.cached.setMaxInactiveInterval(interval);
280-
putAndFlush(MAX_INACTIVE_INTERVAL_KEY,
259+
putAndFlush(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY,
281260
(int) getMaxInactiveInterval().getSeconds());
282261
}
283262

@@ -354,36 +333,4 @@ private Mono<Void> saveChangeSessionId() {
354333

355334
}
356335

357-
private static final class SessionMapper
358-
implements Function<Map<String, Object>, MapSession> {
359-
360-
private final String id;
361-
362-
private SessionMapper(String id) {
363-
this.id = id;
364-
}
365-
366-
@Override
367-
public MapSession apply(Map<String, Object> map) {
368-
MapSession session = new MapSession(this.id);
369-
370-
session.setCreationTime(
371-
Instant.ofEpochMilli((long) map.get(CREATION_TIME_KEY)));
372-
session.setLastAccessedTime(
373-
Instant.ofEpochMilli((long) map.get(LAST_ACCESSED_TIME_KEY)));
374-
session.setMaxInactiveInterval(
375-
Duration.ofSeconds((int) map.get(MAX_INACTIVE_INTERVAL_KEY)));
376-
377-
map.forEach((name, value) -> {
378-
if (name.startsWith(ATTRIBUTE_PREFIX)) {
379-
session.setAttribute(name.substring(ATTRIBUTE_PREFIX.length()),
380-
value);
381-
}
382-
});
383-
384-
return session;
385-
}
386-
387-
}
388-
389336
}

spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisOperationsSessionRepository.java

Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -264,33 +264,6 @@ public class RedisOperationsSessionRepository implements
264264
*/
265265
public static final String DEFAULT_NAMESPACE = "spring:session";
266266

267-
/**
268-
* The key in the Hash representing
269-
* {@link org.springframework.session.Session#getCreationTime()}.
270-
*/
271-
static final String CREATION_TIME_ATTR = "creationTime";
272-
273-
/**
274-
* The key in the Hash representing
275-
* {@link org.springframework.session.Session#getMaxInactiveInterval()}
276-
* .
277-
*/
278-
static final String MAX_INACTIVE_ATTR = "maxInactiveInterval";
279-
280-
/**
281-
* The key in the Hash representing
282-
* {@link org.springframework.session.Session#getLastAccessedTime()}.
283-
*/
284-
static final String LAST_ACCESSED_ATTR = "lastAccessedTime";
285-
286-
/**
287-
* The prefix of the key used for session attributes. The suffix is the name of
288-
* the session attribute. For example, if the session contained an attribute named
289-
* attributeName, then there would be an entry in the hash named
290-
* sessionAttr:attributeName that mapped to its value.
291-
*/
292-
static final String SESSION_ATTR_PREFIX = "sessionAttr:";
293-
294267
private int database = RedisOperationsSessionRepository.DEFAULT_DATABASE;
295268

296269
/**
@@ -480,17 +453,18 @@ private MapSession loadSession(String id, Map<Object, Object> entries) {
480453
MapSession loaded = new MapSession(id);
481454
for (Map.Entry<Object, Object> entry : entries.entrySet()) {
482455
String key = (String) entry.getKey();
483-
if (CREATION_TIME_ATTR.equals(key)) {
456+
if (RedisSessionMapper.CREATION_TIME_KEY.equals(key)) {
484457
loaded.setCreationTime(Instant.ofEpochMilli((long) entry.getValue()));
485458
}
486-
else if (MAX_INACTIVE_ATTR.equals(key)) {
459+
else if (RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY.equals(key)) {
487460
loaded.setMaxInactiveInterval(Duration.ofSeconds((int) entry.getValue()));
488461
}
489-
else if (LAST_ACCESSED_ATTR.equals(key)) {
462+
else if (RedisSessionMapper.LAST_ACCESSED_TIME_KEY.equals(key)) {
490463
loaded.setLastAccessedTime(Instant.ofEpochMilli((long) entry.getValue()));
491464
}
492-
else if (key.startsWith(SESSION_ATTR_PREFIX)) {
493-
loaded.setAttribute(key.substring(SESSION_ATTR_PREFIX.length()),
465+
else if (key.startsWith(RedisSessionMapper.ATTRIBUTE_PREFIX)) {
466+
loaded.setAttribute(
467+
key.substring(RedisSessionMapper.ATTRIBUTE_PREFIX.length()),
494468
entry.getValue());
495469
}
496470
}
@@ -689,7 +663,7 @@ private BoundHashOperations<Object, Object, Object> getSessionBoundHashOperation
689663
* @return the attribute key name
690664
*/
691665
static String getSessionAttrNameKey(String attributeName) {
692-
return SESSION_ATTR_PREFIX + attributeName;
666+
return RedisSessionMapper.ATTRIBUTE_PREFIX + attributeName;
693667
}
694668

695669
/**
@@ -719,9 +693,12 @@ final class RedisSession implements Session {
719693
RedisSession(Duration maxInactiveInterval) {
720694
this(new MapSession());
721695
this.cached.setMaxInactiveInterval(maxInactiveInterval);
722-
this.delta.put(CREATION_TIME_ATTR, getCreationTime().toEpochMilli());
723-
this.delta.put(MAX_INACTIVE_ATTR, (int) getMaxInactiveInterval().getSeconds());
724-
this.delta.put(LAST_ACCESSED_ATTR, getLastAccessedTime().toEpochMilli());
696+
this.delta.put(RedisSessionMapper.CREATION_TIME_KEY,
697+
getCreationTime().toEpochMilli());
698+
this.delta.put(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY,
699+
(int) getMaxInactiveInterval().getSeconds());
700+
this.delta.put(RedisSessionMapper.LAST_ACCESSED_TIME_KEY,
701+
getLastAccessedTime().toEpochMilli());
725702
this.isNew = true;
726703
}
727704

@@ -745,7 +722,8 @@ public void setNew(boolean isNew) {
745722
@Override
746723
public void setLastAccessedTime(Instant lastAccessedTime) {
747724
this.cached.setLastAccessedTime(lastAccessedTime);
748-
this.putAndFlush(LAST_ACCESSED_ATTR, getLastAccessedTime().toEpochMilli());
725+
this.putAndFlush(RedisSessionMapper.LAST_ACCESSED_TIME_KEY,
726+
getLastAccessedTime().toEpochMilli());
749727
}
750728

751729
@Override
@@ -780,7 +758,8 @@ public Instant getLastAccessedTime() {
780758
@Override
781759
public void setMaxInactiveInterval(Duration interval) {
782760
this.cached.setMaxInactiveInterval(interval);
783-
this.putAndFlush(MAX_INACTIVE_ATTR, (int) getMaxInactiveInterval().getSeconds());
761+
this.putAndFlush(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY,
762+
(int) getMaxInactiveInterval().getSeconds());
784763
}
785764

786765
@Override
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2014-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.session.data.redis;
18+
19+
import java.time.Duration;
20+
import java.time.Instant;
21+
import java.util.Map;
22+
import java.util.function.Function;
23+
24+
import org.springframework.session.MapSession;
25+
import org.springframework.session.Session;
26+
import org.springframework.util.Assert;
27+
28+
/**
29+
* A {@link Function} that converts a {@link Map} representing Redis hash to a
30+
* {@link MapSession}.
31+
*
32+
* @author Vedran Pavic
33+
* @since 2.2.0
34+
*/
35+
final class RedisSessionMapper implements Function<Map<String, Object>, MapSession> {
36+
37+
/**
38+
* The key in the hash representing {@link Session#getCreationTime()}.
39+
*/
40+
static final String CREATION_TIME_KEY = "creationTime";
41+
42+
/**
43+
* The key in the hash representing {@link Session#getLastAccessedTime()}.
44+
*/
45+
static final String LAST_ACCESSED_TIME_KEY = "lastAccessedTime";
46+
47+
/**
48+
* The key in the hash representing {@link Session#getMaxInactiveInterval()}.
49+
*/
50+
static final String MAX_INACTIVE_INTERVAL_KEY = "maxInactiveInterval";
51+
52+
/**
53+
* The prefix of the key in the hash used for session attributes. For example, if the
54+
* session contained an attribute named {@code attributeName}, then there would be an
55+
* entry in the hash named {@code sessionAttr:attributeName} that mapped to its value.
56+
*/
57+
static final String ATTRIBUTE_PREFIX = "sessionAttr:";
58+
59+
private final String sessionId;
60+
61+
RedisSessionMapper(String sessionId) {
62+
Assert.hasText(sessionId, "sessionId must not be empty");
63+
this.sessionId = sessionId;
64+
}
65+
66+
@Override
67+
public MapSession apply(Map<String, Object> map) {
68+
Assert.notEmpty(map, "map must not be empty");
69+
MapSession session = new MapSession(this.sessionId);
70+
Long creationTime = (Long) map.get(CREATION_TIME_KEY);
71+
if (creationTime == null) {
72+
handleMissingKey(CREATION_TIME_KEY);
73+
}
74+
session.setCreationTime(Instant.ofEpochMilli(creationTime));
75+
Long lastAccessedTime = (Long) map.get(LAST_ACCESSED_TIME_KEY);
76+
if (lastAccessedTime == null) {
77+
handleMissingKey(LAST_ACCESSED_TIME_KEY);
78+
}
79+
session.setLastAccessedTime(Instant.ofEpochMilli(lastAccessedTime));
80+
Integer maxInactiveInterval = (Integer) map.get(MAX_INACTIVE_INTERVAL_KEY);
81+
if (maxInactiveInterval == null) {
82+
handleMissingKey(MAX_INACTIVE_INTERVAL_KEY);
83+
}
84+
session.setMaxInactiveInterval(Duration.ofSeconds(maxInactiveInterval));
85+
map.forEach((name, value) -> {
86+
if (name.startsWith(ATTRIBUTE_PREFIX)) {
87+
session.setAttribute(name.substring(ATTRIBUTE_PREFIX.length()), value);
88+
}
89+
});
90+
return session;
91+
}
92+
93+
private static void handleMissingKey(String key) {
94+
throw new IllegalStateException(key + " key must not be null");
95+
}
96+
97+
}

spring-session-data-redis/src/test/java/org/springframework/session/data/redis/ReactiveRedisOperationsSessionRepositoryTests.java

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -167,15 +167,12 @@ public void saveNewSession() {
167167

168168
Map<String, Object> delta = this.delta.getAllValues().get(0);
169169
assertThat(delta.size()).isEqualTo(3);
170-
assertThat(delta.get(ReactiveRedisOperationsSessionRepository.CREATION_TIME_KEY))
170+
assertThat(delta.get(RedisSessionMapper.CREATION_TIME_KEY))
171171
.isEqualTo(newSession.getCreationTime().toEpochMilli());
172-
assertThat(delta
173-
.get(ReactiveRedisOperationsSessionRepository.MAX_INACTIVE_INTERVAL_KEY))
174-
.isEqualTo(
175-
(int) newSession.getMaxInactiveInterval().getSeconds());
176-
assertThat(delta
177-
.get(ReactiveRedisOperationsSessionRepository.LAST_ACCESSED_TIME_KEY))
178-
.isEqualTo(newSession.getLastAccessedTime().toEpochMilli());
172+
assertThat(delta.get(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY))
173+
.isEqualTo((int) newSession.getMaxInactiveInterval().getSeconds());
174+
assertThat(delta.get(RedisSessionMapper.LAST_ACCESSED_TIME_KEY))
175+
.isEqualTo(newSession.getLastAccessedTime().toEpochMilli());
179176
}
180177

181178
@Test
@@ -214,7 +211,7 @@ public void saveLastAccessChanged() {
214211
verifyZeroInteractions(this.hashOperations);
215212

216213
assertThat(this.delta.getAllValues().get(0))
217-
.isEqualTo(map(RedisOperationsSessionRepository.LAST_ACCESSED_ATTR,
214+
.isEqualTo(map(RedisSessionMapper.LAST_ACCESSED_TIME_KEY,
218215
session.getLastAccessedTime().toEpochMilli()));
219216
}
220217

@@ -316,16 +313,14 @@ public void getSessionFound() {
316313
expected.setLastAccessedTime(Instant.now().minusSeconds(60));
317314
expected.setAttribute(attribute1, "test");
318315
expected.setAttribute(attribute2, null);
319-
Map map = map(
320-
ReactiveRedisOperationsSessionRepository.ATTRIBUTE_PREFIX + attribute1,
316+
Map map = map(RedisSessionMapper.ATTRIBUTE_PREFIX + attribute1,
321317
expected.getAttribute(attribute1),
322-
ReactiveRedisOperationsSessionRepository.ATTRIBUTE_PREFIX + attribute2,
323-
expected.getAttribute(attribute2),
324-
ReactiveRedisOperationsSessionRepository.CREATION_TIME_KEY,
318+
RedisSessionMapper.ATTRIBUTE_PREFIX + attribute2,
319+
expected.getAttribute(attribute2), RedisSessionMapper.CREATION_TIME_KEY,
325320
expected.getCreationTime().toEpochMilli(),
326-
ReactiveRedisOperationsSessionRepository.MAX_INACTIVE_INTERVAL_KEY,
321+
RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY,
327322
(int) expected.getMaxInactiveInterval().getSeconds(),
328-
ReactiveRedisOperationsSessionRepository.LAST_ACCESSED_TIME_KEY,
323+
RedisSessionMapper.LAST_ACCESSED_TIME_KEY,
329324
expected.getLastAccessedTime().toEpochMilli());
330325
given(this.hashOperations.entries(anyString()))
331326
.willReturn(Flux.fromIterable(map.entrySet()));
@@ -360,9 +355,9 @@ public void getSessionFound() {
360355
@SuppressWarnings("unchecked")
361356
public void getSessionExpired() {
362357
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
363-
Map map = map(ReactiveRedisOperationsSessionRepository.CREATION_TIME_KEY, 0L,
364-
ReactiveRedisOperationsSessionRepository.MAX_INACTIVE_INTERVAL_KEY, 1,
365-
ReactiveRedisOperationsSessionRepository.LAST_ACCESSED_TIME_KEY,
358+
Map map = map(RedisSessionMapper.CREATION_TIME_KEY, 0L,
359+
RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, 1,
360+
RedisSessionMapper.LAST_ACCESSED_TIME_KEY,
366361
Instant.now().minus(5, ChronoUnit.MINUTES).toEpochMilli());
367362
given(this.hashOperations.entries(anyString()))
368363
.willReturn(Flux.fromIterable(map.entrySet()));

0 commit comments

Comments
 (0)