Skip to content

Commit 27d7a02

Browse files
committed
Add SimpleRedisOperationsSessionRepository
1 parent 2845351 commit 27d7a02

File tree

2 files changed

+419
-0
lines changed

2 files changed

+419
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
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.HashMap;
22+
import java.util.Map;
23+
import java.util.Set;
24+
25+
import org.springframework.core.NestedExceptionUtils;
26+
import org.springframework.dao.NonTransientDataAccessException;
27+
import org.springframework.data.redis.core.BoundHashOperations;
28+
import org.springframework.data.redis.core.RedisOperations;
29+
import org.springframework.session.MapSession;
30+
import org.springframework.session.Session;
31+
import org.springframework.session.SessionRepository;
32+
import org.springframework.util.Assert;
33+
34+
/**
35+
* TODO.
36+
*
37+
* @author Vedran Pavic
38+
* @since 2.2.0
39+
*/
40+
public class SimpleRedisOperationsSessionRepository implements
41+
SessionRepository<SimpleRedisOperationsSessionRepository.RedisSession> {
42+
43+
private static final String DEFAULT_KEY_NAMESPACE = "spring:session:";
44+
45+
private final RedisOperations<String, Object> sessionRedisOperations;
46+
47+
private Duration defaultMaxInactiveInterval = Duration
48+
.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
49+
50+
private String keyNamespace = DEFAULT_KEY_NAMESPACE;
51+
52+
private RedisFlushMode flushMode = RedisFlushMode.ON_SAVE;
53+
54+
public SimpleRedisOperationsSessionRepository(
55+
RedisOperations<String, Object> sessionRedisOperations) {
56+
Assert.notNull(sessionRedisOperations, "sessionRedisOperations mut not be null");
57+
this.sessionRedisOperations = sessionRedisOperations;
58+
}
59+
60+
public void setDefaultMaxInactiveInterval(Duration defaultMaxInactiveInterval) {
61+
Assert.notNull(defaultMaxInactiveInterval,
62+
"defaultMaxInactiveInterval must not be null");
63+
this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
64+
}
65+
66+
public void setKeyNamespace(String keyNamespace) {
67+
Assert.hasText(keyNamespace, "keyNamespace must not be empty");
68+
this.keyNamespace = keyNamespace;
69+
}
70+
71+
public void setFlushMode(RedisFlushMode flushMode) {
72+
Assert.notNull(flushMode, "flushMode must not be null");
73+
this.flushMode = flushMode;
74+
}
75+
76+
@Override
77+
public RedisSession createSession() {
78+
RedisSession session = new RedisSession();
79+
session.setMaxInactiveInterval(this.defaultMaxInactiveInterval);
80+
session.flushIfRequired();
81+
return session;
82+
}
83+
84+
@Override
85+
public void save(RedisSession session) {
86+
session.save();
87+
}
88+
89+
@Override
90+
public RedisSession findById(String sessionId) {
91+
Map<String, Object> entries = getSessionBoundHashOperations(sessionId).entries();
92+
if (entries == null || entries.isEmpty()) {
93+
return null;
94+
}
95+
MapSession session = new RedisSessionMapper(sessionId).apply(entries);
96+
if (session.isExpired()) {
97+
deleteById(sessionId);
98+
return null;
99+
}
100+
return new RedisSession(session);
101+
}
102+
103+
@Override
104+
public void deleteById(String sessionId) {
105+
this.sessionRedisOperations.delete(sessionId);
106+
}
107+
108+
/**
109+
* Returns the {@link RedisOperations} used for sessions.
110+
* @return the {@link RedisOperations} used for sessions
111+
*/
112+
public RedisOperations<String, Object> getSessionRedisOperations() {
113+
return this.sessionRedisOperations;
114+
}
115+
116+
private BoundHashOperations<String, String, Object> getSessionBoundHashOperations(
117+
String sessionId) {
118+
String key = getSessionKey(sessionId);
119+
return this.sessionRedisOperations.boundHashOps(key);
120+
}
121+
122+
private String getSessionKey(String sessionId) {
123+
return this.keyNamespace + sessionId;
124+
}
125+
126+
/**
127+
* TODO.
128+
*/
129+
final class RedisSession implements Session {
130+
131+
private final MapSession cached;
132+
133+
private Map<String, Object> delta = new HashMap<>();
134+
135+
private boolean isNew;
136+
137+
private String originalSessionId;
138+
139+
RedisSession() {
140+
this(new MapSession());
141+
this.delta.put(RedisSessionMapper.CREATION_TIME_KEY,
142+
getCreationTime().toEpochMilli());
143+
this.delta.put(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY,
144+
(int) getMaxInactiveInterval().getSeconds());
145+
this.delta.put(RedisSessionMapper.LAST_ACCESSED_TIME_KEY,
146+
getLastAccessedTime().toEpochMilli());
147+
this.isNew = true;
148+
}
149+
150+
RedisSession(MapSession cached) {
151+
this.cached = cached;
152+
this.originalSessionId = cached.getId();
153+
}
154+
155+
@Override
156+
public String getId() {
157+
return this.cached.getId();
158+
}
159+
160+
@Override
161+
public String changeSessionId() {
162+
return this.cached.changeSessionId();
163+
}
164+
165+
@Override
166+
public <T> T getAttribute(String attributeName) {
167+
return this.cached.getAttribute(attributeName);
168+
}
169+
170+
@Override
171+
public Set<String> getAttributeNames() {
172+
return this.cached.getAttributeNames();
173+
}
174+
175+
@Override
176+
public void setAttribute(String attributeName, Object attributeValue) {
177+
this.cached.setAttribute(attributeName, attributeValue);
178+
putAttribute(attributeName, attributeValue);
179+
}
180+
181+
@Override
182+
public void removeAttribute(String attributeName) {
183+
this.cached.removeAttribute(attributeName);
184+
putAttribute(attributeName, null);
185+
}
186+
187+
@Override
188+
public Instant getCreationTime() {
189+
return this.cached.getCreationTime();
190+
}
191+
192+
@Override
193+
public void setLastAccessedTime(Instant lastAccessedTime) {
194+
this.cached.setLastAccessedTime(lastAccessedTime);
195+
putAttribute(RedisSessionMapper.LAST_ACCESSED_TIME_KEY,
196+
getLastAccessedTime().toEpochMilli());
197+
}
198+
199+
@Override
200+
public Instant getLastAccessedTime() {
201+
return this.cached.getLastAccessedTime();
202+
}
203+
204+
@Override
205+
public void setMaxInactiveInterval(Duration interval) {
206+
this.cached.setMaxInactiveInterval(interval);
207+
putAttribute(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY,
208+
(int) getMaxInactiveInterval().getSeconds());
209+
}
210+
211+
@Override
212+
public Duration getMaxInactiveInterval() {
213+
return this.cached.getMaxInactiveInterval();
214+
}
215+
216+
@Override
217+
public boolean isExpired() {
218+
return this.cached.isExpired();
219+
}
220+
221+
private void flushIfRequired() {
222+
if (SimpleRedisOperationsSessionRepository.this.flushMode == RedisFlushMode.IMMEDIATE) {
223+
save();
224+
}
225+
}
226+
227+
private void save() {
228+
saveChangeSessionId();
229+
saveDelta();
230+
if (this.isNew) {
231+
this.isNew = false;
232+
}
233+
}
234+
235+
private void saveChangeSessionId() {
236+
String sessionId = getId();
237+
if (!sessionId.equals(this.originalSessionId)) {
238+
if (!this.isNew) {
239+
String originalSessionIdKey = getSessionKey(this.originalSessionId);
240+
String sessionIdKey = getSessionKey(sessionId);
241+
try {
242+
SimpleRedisOperationsSessionRepository.this.sessionRedisOperations
243+
.rename(originalSessionIdKey, sessionIdKey);
244+
}
245+
catch (NonTransientDataAccessException ex) {
246+
if (!"ERR no such key".equals(NestedExceptionUtils
247+
.getMostSpecificCause(ex).getMessage())) {
248+
throw ex;
249+
}
250+
}
251+
}
252+
this.originalSessionId = sessionId;
253+
}
254+
}
255+
256+
private void saveDelta() {
257+
if (!this.delta.isEmpty()) {
258+
getSessionBoundHashOperations(getId()).putAll(this.delta);
259+
this.delta.clear();
260+
}
261+
}
262+
263+
private void putAttribute(String name, Object value) {
264+
this.delta.put(RedisSessionMapper.ATTRIBUTE_PREFIX + name, value);
265+
flushIfRequired();
266+
}
267+
268+
}
269+
270+
}

0 commit comments

Comments
 (0)