Skip to content

Commit d154602

Browse files
Introduce SessionIdGenerationStrategy
Issue gh-11
1 parent 3ead793 commit d154602

File tree

25 files changed

+572
-32
lines changed

25 files changed

+572
-32
lines changed

spring-session-core/src/main/java/org/springframework/session/MapSessionRepository.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,14 @@
3939
*/
4040
public class MapSessionRepository implements SessionRepository<MapSession> {
4141

42+
private static final SessionIdGenerationStrategy DEFAULT_STRATEGY = UuidSessionIdGenerationStrategy.getInstance();
43+
4244
private Duration defaultMaxInactiveInterval = Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
4345

4446
private final Map<String, Session> sessions;
4547

48+
private SessionIdGenerationStrategy sessionIdGenerationStrategy = DEFAULT_STRATEGY;
49+
4650
/**
4751
* Creates a new instance backed by the provided {@link java.util.Map}. This allows
4852
* injecting a distributed {@link java.util.Map}.
@@ -94,9 +98,14 @@ public void deleteById(String id) {
9498

9599
@Override
96100
public MapSession createSession() {
97-
MapSession result = new MapSession();
101+
MapSession result = new MapSession(this.sessionIdGenerationStrategy.generate());
98102
result.setMaxInactiveInterval(this.defaultMaxInactiveInterval);
99103
return result;
100104
}
101105

106+
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
107+
Assert.notNull(sessionIdGenerationStrategy, "sessionIdGenerationStrategy cannot be null");
108+
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
109+
}
110+
102111
}

spring-session-core/src/main/java/org/springframework/session/ReactiveMapSessionRepository.java

+16-1
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,14 @@
4141
*/
4242
public class ReactiveMapSessionRepository implements ReactiveSessionRepository<MapSession> {
4343

44+
private static final SessionIdGenerationStrategy DEFAULT_STRATEGY = UuidSessionIdGenerationStrategy.getInstance();
45+
4446
private Duration defaultMaxInactiveInterval = Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
4547

4648
private final Map<String, Session> sessions;
4749

50+
private SessionIdGenerationStrategy sessionIdGenerationStrategy = DEFAULT_STRATEGY;
51+
4852
/**
4953
* Creates a new instance backed by the provided {@link Map}. This allows injecting a
5054
* distributed {@link Map}.
@@ -96,10 +100,21 @@ public Mono<Void> deleteById(String id) {
96100
@Override
97101
public Mono<MapSession> createSession() {
98102
return Mono.defer(() -> {
99-
MapSession result = new MapSession();
103+
MapSession result = new MapSession(this.sessionIdGenerationStrategy.generate());
100104
result.setMaxInactiveInterval(this.defaultMaxInactiveInterval);
101105
return Mono.just(result);
102106
});
103107
}
104108

109+
/**
110+
* Sets the {@link SessionIdGenerationStrategy} to use.
111+
* @param sessionIdGenerationStrategy the non-null {@link SessionIdGenerationStrategy}
112+
* to use
113+
* @since 3.1
114+
*/
115+
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
116+
Assert.notNull(sessionIdGenerationStrategy, "sessionIdGenerationStrategy cannot be null");
117+
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
118+
}
119+
105120
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2014-2023 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;
18+
19+
import org.springframework.lang.NonNull;
20+
21+
public interface SessionIdGenerationStrategy {
22+
23+
@NonNull
24+
String generate();
25+
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2014-2023 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;
18+
19+
import java.util.UUID;
20+
21+
import org.springframework.lang.NonNull;
22+
23+
public final class UuidSessionIdGenerationStrategy implements SessionIdGenerationStrategy {
24+
25+
private static final UuidSessionIdGenerationStrategy INSTANCE = new UuidSessionIdGenerationStrategy();
26+
27+
private UuidSessionIdGenerationStrategy() {
28+
}
29+
30+
@Override
31+
@NonNull
32+
public String generate() {
33+
return UUID.randomUUID().toString();
34+
}
35+
36+
public static UuidSessionIdGenerationStrategy getInstance() {
37+
return INSTANCE;
38+
}
39+
40+
}

spring-session-data-mongodb/src/main/java/org/springframework/session/data/mongo/MongoIndexedSessionRepository.java

+31-8
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
import org.springframework.lang.Nullable;
3737
import org.springframework.session.FindByIndexNameSessionRepository;
3838
import org.springframework.session.MapSession;
39+
import org.springframework.session.SessionIdGenerationStrategy;
40+
import org.springframework.session.UuidSessionIdGenerationStrategy;
3941
import org.springframework.session.events.SessionCreatedEvent;
4042
import org.springframework.session.events.SessionDeletedEvent;
4143
import org.springframework.session.events.SessionExpiredEvent;
@@ -70,6 +72,8 @@ public class MongoIndexedSessionRepository
7072

7173
private static final Log logger = LogFactory.getLog(MongoIndexedSessionRepository.class);
7274

75+
private static final SessionIdGenerationStrategy DEFAULT_STRATEGY = UuidSessionIdGenerationStrategy.getInstance();
76+
7377
private final MongoOperations mongoOperations;
7478

7579
private Duration defaultMaxInactiveInterval = Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
@@ -81,25 +85,36 @@ public class MongoIndexedSessionRepository
8185

8286
private ApplicationEventPublisher eventPublisher;
8387

88+
private SessionIdGenerationStrategy sessionIdGenerationStrategy = DEFAULT_STRATEGY;
89+
8490
public MongoIndexedSessionRepository(MongoOperations mongoOperations) {
8591
this.mongoOperations = mongoOperations;
8692
}
8793

8894
@Override
8995
public MongoSession createSession() {
9096

91-
MongoSession session = new MongoSession();
97+
String sessionId = this.sessionIdGenerationStrategy.generate();
98+
MongoSession session = new MongoSession(sessionId);
9299

93100
session.setMaxInactiveInterval(this.defaultMaxInactiveInterval);
94101

95102
publishEvent(new SessionCreatedEvent(this, session));
96103

97-
return session;
104+
return wrapSession(session);
105+
}
106+
107+
private SessionIdGenerationStrategyAwareMongoSession wrapSession(MongoSession session) {
108+
return new SessionIdGenerationStrategyAwareMongoSession(session, this.sessionIdGenerationStrategy);
98109
}
99110

100111
@Override
101112
public void save(MongoSession session) {
102-
DBObject dbObject = MongoSessionUtils.convertToDBObject(this.mongoSessionConverter, session);
113+
MongoSession mongoSession = session;
114+
if (session instanceof SessionIdGenerationStrategyAwareMongoSession awareMongoSession) {
115+
mongoSession = awareMongoSession.getDelegate();
116+
}
117+
DBObject dbObject = MongoSessionUtils.convertToDBObject(this.mongoSessionConverter, mongoSession);
103118
Assert.notNull(dbObject, "dbObject must not be null");
104119
this.mongoOperations.save(dbObject, this.collectionName);
105120
}
@@ -116,10 +131,13 @@ public MongoSession findById(String id) {
116131

117132
MongoSession session = MongoSessionUtils.convertToSession(this.mongoSessionConverter, sessionWrapper);
118133

119-
if (session != null && session.isExpired()) {
120-
publishEvent(new SessionExpiredEvent(this, session));
121-
deleteById(id);
122-
return null;
134+
if (session != null) {
135+
if (session.isExpired()) {
136+
publishEvent(new SessionExpiredEvent(this, session));
137+
deleteById(id);
138+
return null;
139+
}
140+
return wrapSession(session);
123141
}
124142

125143
return session;
@@ -140,7 +158,7 @@ public Map<String, MongoSession> findByIndexNameAndIndexValue(String indexName,
140158
.map((query) -> this.mongoOperations.find(query, Document.class, this.collectionName))
141159
.orElse(Collections.emptyList()).stream()
142160
.map((dbSession) -> MongoSessionUtils.convertToSession(this.mongoSessionConverter, dbSession))
143-
.collect(Collectors.toMap(MongoSession::getId, (mapSession) -> mapSession));
161+
.map(this::wrapSession).collect(Collectors.toMap(MongoSession::getId, (mapSession) -> mapSession));
144162
}
145163

146164
@Override
@@ -216,4 +234,9 @@ public void setMongoSessionConverter(final AbstractMongoSessionConverter mongoSe
216234
this.mongoSessionConverter = mongoSessionConverter;
217235
}
218236

237+
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
238+
Assert.notNull(sessionIdGenerationStrategy, "sessionIdGenerationStrategy cannot be null");
239+
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
240+
}
241+
219242
}

spring-session-data-mongodb/src/main/java/org/springframework/session/data/mongo/MongoSession.java

+22-1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ class MongoSession implements Session {
6666

6767
private Map<String, Object> attrs = new HashMap<>();
6868

69+
/**
70+
* Constructs a new instance using the provided session id.
71+
* @param sessionId the session id to use
72+
* @since 3.1
73+
*/
74+
MongoSession(String sessionId) {
75+
this(sessionId, MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
76+
}
77+
6978
MongoSession() {
7079
this(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
7180
}
@@ -79,7 +88,7 @@ class MongoSession implements Session {
7988
this.id = id;
8089
this.originalSessionId = id;
8190
this.intervalSeconds = maxInactiveIntervalInSeconds;
82-
setLastAccessedTime(Instant.ofEpochMilli(this.createdMillis));
91+
setLastAccessedTimeAndUpdateExpireAt(Instant.ofEpochMilli(this.createdMillis));
8392
}
8493

8594
static String coverDot(String attributeName) {
@@ -141,7 +150,10 @@ public Instant getLastAccessedTime() {
141150

142151
@Override
143152
public void setLastAccessedTime(Instant lastAccessedTime) {
153+
setLastAccessedTimeAndUpdateExpireAt(lastAccessedTime);
154+
}
144155

156+
private void setLastAccessedTimeAndUpdateExpireAt(Instant lastAccessedTime) {
145157
this.accessedMillis = lastAccessedTime.toEpochMilli();
146158
this.expireAt = Date.from(lastAccessedTime.plus(Duration.ofSeconds(this.intervalSeconds)));
147159
}
@@ -200,4 +212,13 @@ String getOriginalSessionId() {
200212
return this.originalSessionId;
201213
}
202214

215+
/**
216+
* Sets the session id.
217+
* @param id the id to set
218+
* @since 3.1
219+
*/
220+
void setId(String id) {
221+
this.id = id;
222+
}
223+
203224
}

spring-session-data-mongodb/src/main/java/org/springframework/session/data/mongo/ReactiveMongoSessionRepository.java

+23-7
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
import org.springframework.data.mongodb.core.query.Query;
3535
import org.springframework.session.MapSession;
3636
import org.springframework.session.ReactiveSessionRepository;
37+
import org.springframework.session.SessionIdGenerationStrategy;
38+
import org.springframework.session.UuidSessionIdGenerationStrategy;
3739
import org.springframework.session.events.SessionCreatedEvent;
3840
import org.springframework.session.events.SessionDeletedEvent;
3941
import org.springframework.util.Assert;
@@ -63,6 +65,8 @@ public class ReactiveMongoSessionRepository
6365

6466
private static final Log logger = LogFactory.getLog(ReactiveMongoSessionRepository.class);
6567

68+
private static final SessionIdGenerationStrategy DEFAULT_STRATEGY = UuidSessionIdGenerationStrategy.getInstance();
69+
6670
private final ReactiveMongoOperations mongoOperations;
6771

6872
private Duration defaultMaxInactiveInterval = Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
@@ -76,6 +80,8 @@ public class ReactiveMongoSessionRepository
7680

7781
private ApplicationEventPublisher eventPublisher;
7882

83+
private SessionIdGenerationStrategy sessionIdGenerationStrategy = DEFAULT_STRATEGY;
84+
7985
public ReactiveMongoSessionRepository(ReactiveMongoOperations mongoOperations) {
8086
this.mongoOperations = mongoOperations;
8187
}
@@ -94,17 +100,20 @@ public ReactiveMongoSessionRepository(ReactiveMongoOperations mongoOperations) {
94100
@Override
95101
public Mono<MongoSession> createSession() {
96102

97-
return Mono.justOrEmpty(this.defaultMaxInactiveInterval.toSeconds()) //
98-
.map(MongoSession::new) //
99-
.doOnNext((mongoSession) -> publishEvent(new SessionCreatedEvent(this, mongoSession))) //
100-
.switchIfEmpty(Mono.just(new MongoSession()));
103+
return Mono.justOrEmpty(this.defaultMaxInactiveInterval.toSeconds())
104+
.map((interval) -> new MongoSession(this.sessionIdGenerationStrategy.generate(), interval))
105+
.doOnNext((mongoSession) -> publishEvent(new SessionCreatedEvent(this, mongoSession)))
106+
.switchIfEmpty(Mono.just(new MongoSession(this.sessionIdGenerationStrategy.generate())));
101107
}
102108

103109
@Override
104110
public Mono<Void> save(MongoSession session) {
105111

106-
return Mono //
107-
.justOrEmpty(MongoSessionUtils.convertToDBObject(this.mongoSessionConverter, session)) //
112+
return Mono.justOrEmpty(session)
113+
.filter((mongoSession) -> mongoSession instanceof SessionIdGenerationStrategyAwareMongoSession)
114+
.map((mongoSession) -> ((SessionIdGenerationStrategyAwareMongoSession) mongoSession).getDelegate())
115+
.switchIfEmpty(Mono.justOrEmpty(session))
116+
.map((mongoSession) -> MongoSessionUtils.convertToDBObject(this.mongoSessionConverter, mongoSession))
108117
.flatMap((dbObject) -> {
109118
if (session.hasChangedSessionId()) {
110119

@@ -127,7 +136,9 @@ public Mono<MongoSession> findById(String id) {
127136
return findSession(id) //
128137
.map((document) -> MongoSessionUtils.convertToSession(this.mongoSessionConverter, document)) //
129138
.filter((mongoSession) -> !mongoSession.isExpired()) //
130-
.switchIfEmpty(Mono.defer(() -> this.deleteById(id).then(Mono.empty())));
139+
.map((mongoSession) -> new SessionIdGenerationStrategyAwareMongoSession(mongoSession,
140+
this.sessionIdGenerationStrategy))
141+
.cast(MongoSession.class).switchIfEmpty(Mono.defer(() -> this.deleteById(id).then(Mono.empty())));
131142
}
132143

133144
@Override
@@ -216,4 +227,9 @@ public void setBlockingMongoOperations(final MongoOperations blockingMongoOperat
216227
this.blockingMongoOperations = blockingMongoOperations;
217228
}
218229

230+
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
231+
Assert.notNull(sessionIdGenerationStrategy, "sessionIdGenerationStrategy cannot be null");
232+
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
233+
}
234+
219235
}

0 commit comments

Comments
 (0)