Skip to content

Commit f18421a

Browse files
committed
In-Memory session
- create base class - code cleanup and refactor
1 parent 51a32f0 commit f18421a

5 files changed

Lines changed: 165 additions & 142 deletions

File tree

jooby/src/main/java/io/jooby/SessionStore.java

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
*/
66
package io.jooby;
77

8-
import io.jooby.internal.SignedSessionStore;
98
import io.jooby.internal.MemorySessionStore;
9+
import io.jooby.internal.SignedSessionStore;
1010

1111
import javax.annotation.Nonnull;
1212
import javax.annotation.Nullable;
1313
import java.time.Duration;
1414
import java.time.Instant;
1515
import java.util.Map;
16+
import java.util.concurrent.ConcurrentHashMap;
1617
import java.util.function.Function;
1718

1819
/**
@@ -26,6 +27,129 @@ public interface SessionStore {
2627
/** Default session timeout in minutes. */
2728
int DEFAULT_TIMEOUT = 30;
2829

30+
/**
31+
* Base class for in-memory session store.
32+
*
33+
* @author edgar.
34+
* @since 2.0.0
35+
*/
36+
abstract class InMemory implements SessionStore {
37+
protected static class Data {
38+
private Instant lastAccessedTime;
39+
private Instant creationTime;
40+
private Map hash;
41+
42+
public Data(Instant creationTime, Instant lastAccessedTime, Map hash) {
43+
this.creationTime = creationTime;
44+
this.lastAccessedTime = lastAccessedTime;
45+
this.hash = hash;
46+
}
47+
48+
public boolean isExpired(Duration timeout) {
49+
Duration timeElapsed = Duration.between(lastAccessedTime, Instant.now());
50+
return timeElapsed.compareTo(timeout) > 0;
51+
}
52+
}
53+
54+
private SessionToken token;
55+
56+
/**
57+
* Creates a new in-memory session store.
58+
*
59+
* @param token Token.
60+
*/
61+
protected InMemory(@Nonnull SessionToken token) {
62+
this.token = token;
63+
}
64+
65+
@Override public @Nonnull Session newSession(@Nonnull Context ctx) {
66+
String sessionId = token.newToken();
67+
Data data = getOrCreate(sessionId,
68+
sid -> new Data(Instant.now(), Instant.now(), new ConcurrentHashMap()));
69+
70+
Session session = restore(ctx, sessionId, data);
71+
72+
token.saveToken(ctx, sessionId);
73+
return session;
74+
}
75+
76+
/**
77+
* Session token.
78+
*
79+
* @return Session token. Uses a cookie by default: {@link SessionToken#SID}.
80+
*/
81+
public @Nonnull SessionToken getToken() {
82+
return token;
83+
}
84+
85+
/**
86+
* Set custom session token.
87+
*
88+
* @param token Session token.
89+
* @return This store.
90+
*/
91+
public @Nonnull SessionStore setToken(@Nonnull SessionToken token) {
92+
this.token = token;
93+
return this;
94+
}
95+
96+
protected abstract @Nonnull Data getOrCreate(@Nonnull String sessionId,
97+
@Nonnull Function<String, Data> factory);
98+
99+
protected abstract @Nullable Data getOrNull(@Nonnull String sessionId);
100+
101+
protected abstract @Nullable Data remove(@Nonnull String sessionId);
102+
103+
protected abstract void put(@Nonnull String sessionId, @Nonnull Data data);
104+
105+
@Override public Session findSession(Context ctx) {
106+
String sessionId = token.findToken(ctx);
107+
if (sessionId == null) {
108+
return null;
109+
}
110+
Data data = getOrNull(sessionId);
111+
if (data != null) {
112+
Session session = restore(ctx, sessionId, data);
113+
token.saveToken(ctx, sessionId);
114+
return session;
115+
}
116+
return null;
117+
}
118+
119+
@Override public void deleteSession(@Nonnull Context ctx, @Nonnull Session session) {
120+
String sessionId = session.getId();
121+
remove(sessionId);
122+
token.deleteToken(ctx, sessionId);
123+
}
124+
125+
@Override public void touchSession(@Nonnull Context ctx, @Nonnull Session session) {
126+
saveSession(ctx, session);
127+
token.saveToken(ctx, session.getId());
128+
}
129+
130+
@Override public void saveSession(Context ctx, @Nonnull Session session) {
131+
String sessionId = session.getId();
132+
put(sessionId, new Data(session.getCreationTime(), Instant.now(), session.toMap()));
133+
}
134+
135+
@Override public void renewSessionId(@Nonnull Context ctx, @Nonnull Session session) {
136+
String oldId = session.getId();
137+
Data data = remove(oldId);
138+
if (data != null) {
139+
String newId = token.newToken();
140+
session.setId(newId);
141+
142+
put(newId, data);
143+
}
144+
}
145+
146+
private Session restore(Context ctx, String sessionId, Data data) {
147+
return Session.create(ctx, sessionId, data.hash)
148+
.setLastAccessedTime(data.lastAccessedTime)
149+
.setCreationTime(data.creationTime);
150+
}
151+
}
152+
29153
/**
30154
* Creates a new session. This method must:
31155
*

jooby/src/main/java/io/jooby/internal/MemorySessionStore.java

Lines changed: 22 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -10,119 +10,68 @@
1010
import io.jooby.SessionStore;
1111
import io.jooby.SessionToken;
1212

13-
import javax.annotation.Nonnull;
1413
import java.time.Duration;
1514
import java.time.Instant;
1615
import java.util.Iterator;
1716
import java.util.Map;
1817
import java.util.Optional;
1918
import java.util.concurrent.ConcurrentHashMap;
19+
import java.util.function.Function;
2020

21-
public class MemorySessionStore implements SessionStore {
22-
private static class SessionData {
23-
private Instant lastAccessedTime;
24-
private Instant creationTime;
25-
private Map hash;
21+
public class MemorySessionStore extends SessionStore.InMemory {
2622

27-
public SessionData(Instant creationTime, Instant lastAccessedTime, Map hash) {
28-
this.creationTime = creationTime;
29-
this.lastAccessedTime = lastAccessedTime;
30-
this.hash = hash;
31-
}
32-
}
33-
34-
private ConcurrentHashMap<String, SessionData> sessions = new ConcurrentHashMap<>();
35-
36-
private SessionToken token;
23+
private ConcurrentHashMap<String, Data> sessions = new ConcurrentHashMap<>();
3724

3825
private Duration timeout;
3926

4027
public MemorySessionStore(SessionToken token, Duration timeout) {
41-
this.token = token;
28+
super(token);
4229
this.timeout = Optional.ofNullable(timeout)
4330
.filter(t -> t.toMillis() > 0)
4431
.orElse(null);
4532
}
4633

47-
@Override public Session newSession(Context ctx) {
48-
String sessionId = token.newToken();
49-
SessionData data = sessions.computeIfAbsent(sessionId, sid -> {
50-
Instant now = Instant.now();
51-
return new SessionData(now, now, new ConcurrentHashMap());
52-
});
34+
@Override protected Data getOrCreate(String sessionId,
35+
Function<String, Data> factory) {
36+
return sessions.computeIfAbsent(sessionId, factory);
37+
}
38+
39+
@Override protected Data getOrNull(String sessionId) {
40+
return sessions.get(sessionId);
41+
}
5342

54-
Session session = restore(ctx, sessionId, data);
43+
@Override protected Data remove(String sessionId) {
44+
return sessions.remove(sessionId);
45+
}
5546

56-
token.saveToken(ctx, sessionId);
57-
return session;
47+
@Override protected void put(String sessionId, Data data) {
48+
sessions.put(sessionId, data);
5849
}
5950

6051
@Override public Session findSession(Context ctx) {
6152
purge();
62-
String sessionId = token.findToken(ctx);
63-
if (sessionId == null) {
64-
return null;
65-
}
66-
SessionData data = sessions.get(sessionId);
67-
if (data != null) {
68-
Session session = restore(ctx, sessionId, data);
69-
token.saveToken(ctx, sessionId);
70-
return session;
71-
}
72-
return null;
53+
return super.findSession(ctx);
7354
}
7455

7556
/**
7657
* Check for expired session and delete them.
7758
*/
7859
private void purge() {
7960
if (timeout != null) {
80-
Iterator<Map.Entry<String, SessionData>> iterator = sessions.entrySet().iterator();
81-
Instant now = Instant.now();
61+
Iterator<Map.Entry<String, Data>> iterator = sessions.entrySet().iterator();
8262
while (iterator.hasNext()) {
83-
Map.Entry<String, SessionData> entry = iterator.next();
84-
SessionData session = entry.getValue();
85-
Duration timeElapsed = Duration.between(session.lastAccessedTime, now);
86-
if (timeElapsed.compareTo(timeout) > 0) {
63+
Map.Entry<String, Data> entry = iterator.next();
64+
Data session = entry.getValue();
65+
if (session.isExpired(timeout)) {
8766
iterator.remove();
8867
}
8968
}
9069
}
9170
}
9271

93-
@Override public void deleteSession(@Nonnull Context ctx, @Nonnull Session session) {
94-
String sessionId = session.getId();
95-
sessions.remove(sessionId);
96-
token.deleteToken(ctx, sessionId);
97-
}
98-
99-
@Override public void touchSession(@Nonnull Context ctx, @Nonnull Session session) {
100-
saveSession(ctx, session);
101-
token.saveToken(ctx, session.getId());
102-
}
103-
104-
@Override public void saveSession(Context ctx, @Nonnull Session session) {
105-
String sessionId = session.getId();
106-
sessions.put(sessionId,
107-
new SessionData(session.getCreationTime(), Instant.now(), session.toMap()));
108-
}
109-
110-
@Override public void renewSessionId(@Nonnull Context ctx, @Nonnull Session session) {
111-
String oldId = session.getId();
112-
String newId = token.newToken();
113-
session.setId(newId);
114-
SessionData data = sessions.remove(oldId);
115-
sessions.put(newId, data);
116-
}
117-
11872
public SessionStore setTimeout(Duration timeout) {
11973
this.timeout = timeout;
12074
return this;
12175
}
12276

123-
private Session restore(Context ctx, String sessionId, SessionData data) {
124-
return Session.create(ctx, sessionId, data.hash)
125-
.setLastAccessedTime(data.lastAccessedTime)
126-
.setCreationTime(data.creationTime);
127-
}
12877
}

0 commit comments

Comments
 (0)