Skip to content

Commit 4e884cd

Browse files
AndreasKasparekeleftherias
authored andcommitted
Always set time-to-live within entry processor
Closes gh-1899
1 parent f0450fa commit 4e884cd

File tree

6 files changed

+206
-23
lines changed

6 files changed

+206
-23
lines changed

spring-session-hazelcast/hazelcast4/src/integration-test/java/org/springframework/session/hazelcast/AbstractHazelcast4IndexedSessionRepositoryITests.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.session.hazelcast;
1818

19+
import java.time.Duration;
20+
import java.time.Instant;
21+
1922
import com.hazelcast.core.HazelcastInstance;
2023
import com.hazelcast.map.IMap;
2124
import org.junit.jupiter.api.Test;
@@ -198,4 +201,51 @@ void createSessionWithSecurityContextAndFindById() {
198201
this.repository.deleteById(sessionId);
199202
}
200203

204+
@Test
205+
void createAndUpdateSessionWhileKeepingOriginalTimeToLiveConfiguredOnRepository() {
206+
final Duration defaultSessionTimeout = Duration.ofSeconds(1800);
207+
208+
final IMap<String, MapSession> hazelcastMap = this.hazelcastInstance
209+
.getMap(Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME);
210+
211+
HazelcastSession session = this.repository.createSession();
212+
String sessionId = session.getId();
213+
this.repository.save(session);
214+
215+
assertThat(session.getMaxInactiveInterval()).isEqualTo(defaultSessionTimeout);
216+
assertThat(hazelcastMap.getEntryView(sessionId).getTtl()).isEqualTo(defaultSessionTimeout.toMillis());
217+
218+
session = this.repository.findById(sessionId);
219+
session.setLastAccessedTime(Instant.now());
220+
this.repository.save(session);
221+
222+
session = this.repository.findById(sessionId);
223+
assertThat(session.getMaxInactiveInterval()).isEqualTo(defaultSessionTimeout);
224+
assertThat(hazelcastMap.getEntryView(sessionId).getTtl()).isEqualTo(defaultSessionTimeout.toMillis());
225+
}
226+
227+
@Test
228+
void createAndUpdateSessionWhileKeepingTimeToLiveSetOnSession() {
229+
final Duration individualSessionTimeout = Duration.ofSeconds(23);
230+
231+
final IMap<String, MapSession> hazelcastMap = this.hazelcastInstance
232+
.getMap(Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME);
233+
234+
HazelcastSession session = this.repository.createSession();
235+
session.setMaxInactiveInterval(individualSessionTimeout);
236+
String sessionId = session.getId();
237+
this.repository.save(session);
238+
239+
assertThat(session.getMaxInactiveInterval()).isEqualTo(individualSessionTimeout);
240+
assertThat(hazelcastMap.getEntryView(sessionId).getTtl()).isEqualTo(individualSessionTimeout.toMillis());
241+
242+
session = this.repository.findById(sessionId);
243+
session.setAttribute("attribute", "value");
244+
this.repository.save(session);
245+
246+
session = this.repository.findById(sessionId);
247+
assertThat(session.getMaxInactiveInterval()).isEqualTo(individualSessionTimeout);
248+
assertThat(hazelcastMap.getEntryView(sessionId).getTtl()).isEqualTo(individualSessionTimeout.toMillis());
249+
}
250+
201251
}

spring-session-hazelcast/hazelcast4/src/integration-test/java/org/springframework/session/hazelcast/SessionEventHazelcast4IndexedSessionRepositoryTests.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2020 the original author or authors.
2+
* Copyright 2014-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -199,6 +199,29 @@ void updateMaxInactiveIntervalTest() throws InterruptedException {
199199
assertThat(this.repository.findById(sessionToUpdate.getId())).isNull();
200200
}
201201

202+
@Test // gh-1899
203+
void updateSessionAndExpireAfterOriginalTimeToLiveTest() throws InterruptedException {
204+
S sessionToSave = this.repository.createSession();
205+
this.repository.save(sessionToSave);
206+
207+
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
208+
assertThat(this.registry.<SessionCreatedEvent>getEvent(sessionToSave.getId()))
209+
.isInstanceOf(SessionCreatedEvent.class);
210+
this.registry.clear();
211+
212+
S sessionToUpdate = this.repository.findById(sessionToSave.getId());
213+
sessionToUpdate.setLastAccessedTime(Instant.now());
214+
this.repository.save(sessionToUpdate);
215+
216+
assertThat(this.registry.receivedEvent(sessionToUpdate.getId())).isTrue();
217+
assertThat(this.registry.<SessionExpiredEvent>getEvent(sessionToUpdate.getId()))
218+
.isInstanceOf(SessionExpiredEvent.class);
219+
// Assert this after the expired event was received because it would otherwise do
220+
// its own expiration check and explicitly delete the session from Hazelcast
221+
// regardless of the TTL of the IMap entry.
222+
assertThat(this.repository.findById(sessionToUpdate.getId())).isNull();
223+
}
224+
202225
@Configuration
203226
@EnableHazelcastHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
204227
static class HazelcastSessionConfig {

spring-session-hazelcast/hazelcast4/src/main/java/org/springframework/session/hazelcast/Hazelcast4IndexedSessionRepository.java

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2020 the original author or authors.
2+
* Copyright 2014-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -54,7 +54,6 @@
5454
import org.springframework.session.events.SessionDeletedEvent;
5555
import org.springframework.session.events.SessionExpiredEvent;
5656
import org.springframework.util.Assert;
57-
import org.springframework.util.ClassUtils;
5857

5958
/**
6059
* A {@link org.springframework.session.SessionRepository} implementation using Hazelcast
@@ -128,8 +127,6 @@ public class Hazelcast4IndexedSessionRepository
128127

129128
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
130129

131-
private static final boolean SUPPORTS_SET_TTL = ClassUtils.hasAtLeastOneMethodWithName(IMap.class, "setTtl");
132-
133130
private static final Log logger = LogFactory.getLog(Hazelcast4IndexedSessionRepository.class);
134131

135132
private final HazelcastInstance hazelcastInstance;
@@ -262,9 +259,6 @@ else if (session.hasChanges()) {
262259
entryProcessor.setLastAccessedTime(session.getLastAccessedTime());
263260
}
264261
if (session.maxInactiveIntervalChanged) {
265-
if (SUPPORTS_SET_TTL) {
266-
updateTtl(session);
267-
}
268262
entryProcessor.setMaxInactiveInterval(session.getMaxInactiveInterval());
269263
}
270264
if (!session.delta.isEmpty()) {
@@ -275,10 +269,6 @@ else if (session.hasChanges()) {
275269
session.clearChangeFlags();
276270
}
277271

278-
private void updateTtl(HazelcastSession session) {
279-
this.sessions.setTtl(session.getId(), session.getMaxInactiveInterval().getSeconds(), TimeUnit.SECONDS);
280-
}
281-
282272
@Override
283273
public HazelcastSession findById(String id) {
284274
MapSession saved = this.sessions.get(id);
@@ -416,6 +406,7 @@ public Instant getLastAccessedTime() {
416406

417407
@Override
418408
public void setMaxInactiveInterval(Duration interval) {
409+
Assert.notNull(interval, "interval must not be null");
419410
this.delegate.setMaxInactiveInterval(interval);
420411
this.maxInactiveIntervalChanged = true;
421412
flushImmediateIfNecessary();

spring-session-hazelcast/hazelcast4/src/main/java/org/springframework/session/hazelcast/Hazelcast4SessionUpdateEntryProcessor.java

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2020 the original author or authors.
2+
* Copyright 2014-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -63,13 +63,8 @@ public Object process(Map.Entry<String, MapSession> entry) {
6363
}
6464
}
6565
}
66-
if (this.maxInactiveInterval != null) {
67-
((ExtendedMapEntry<String, MapSession>) entry).setValue(value, this.maxInactiveInterval.getSeconds(),
68-
TimeUnit.SECONDS);
69-
}
70-
else {
71-
entry.setValue(value);
72-
}
66+
((ExtendedMapEntry<String, MapSession>) entry).setValue(value, value.getMaxInactiveInterval().getSeconds(),
67+
TimeUnit.SECONDS);
7368
return Boolean.TRUE;
7469
}
7570

spring-session-hazelcast/hazelcast4/src/test/java/org/springframework/session/hazelcast/Hazelcast4IndexedSessionRepositoryTests.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2020 the original author or authors.
2+
* Copyright 2014-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -46,7 +46,6 @@
4646
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
4747
import static org.mockito.ArgumentMatchers.any;
4848
import static org.mockito.ArgumentMatchers.anyBoolean;
49-
import static org.mockito.ArgumentMatchers.anyLong;
5049
import static org.mockito.ArgumentMatchers.anyString;
5150
import static org.mockito.ArgumentMatchers.eq;
5251
import static org.mockito.ArgumentMatchers.isA;
@@ -254,7 +253,6 @@ void saveUpdatedMaxInactiveIntervalInSecondsFlushModeImmediate() {
254253
session.setMaxInactiveInterval(Duration.ofSeconds(1));
255254
verify(this.sessions, times(1)).set(eq(sessionId), eq(session.getDelegate()), isA(Long.class),
256255
eq(TimeUnit.SECONDS));
257-
verify(this.sessions).setTtl(eq(sessionId), anyLong(), any());
258256
verify(this.sessions, times(1)).executeOnKey(eq(sessionId), any(EntryProcessor.class));
259257

260258
this.repository.save(session);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright 2014-2021 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.hazelcast;
18+
19+
import java.time.Duration;
20+
import java.time.Instant;
21+
import java.util.HashMap;
22+
import java.util.concurrent.TimeUnit;
23+
24+
import com.hazelcast.map.ExtendedMapEntry;
25+
import org.junit.jupiter.api.BeforeEach;
26+
import org.junit.jupiter.api.Test;
27+
28+
import org.springframework.session.MapSession;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
import static org.mockito.BDDMockito.given;
32+
import static org.mockito.Mockito.mock;
33+
import static org.mockito.Mockito.verify;
34+
35+
class Hazelcast4SessionUpdateEntryProcessorTest {
36+
37+
private Hazelcast4SessionUpdateEntryProcessor processor;
38+
39+
@BeforeEach
40+
void setUp() {
41+
this.processor = new Hazelcast4SessionUpdateEntryProcessor();
42+
}
43+
44+
@Test
45+
void shouldReturnFalseIfNoSessionExistsInHazelcastMapEntry() {
46+
@SuppressWarnings("unchecked")
47+
ExtendedMapEntry<String, MapSession> mapEntry = mock(ExtendedMapEntry.class);
48+
49+
Object result = this.processor.process(mapEntry);
50+
51+
assertThat(result).isEqualTo(Boolean.FALSE);
52+
}
53+
54+
@Test
55+
void shouldUpdateMaxInactiveIntervalOnSessionAndSetMapEntryValueWithNewTimeToLive() {
56+
Duration newMaxInactiveInterval = Duration.ofSeconds(123L);
57+
MapSession mapSession = new MapSession();
58+
@SuppressWarnings("unchecked")
59+
ExtendedMapEntry<String, MapSession> mapEntry = mock(ExtendedMapEntry.class);
60+
given(mapEntry.getValue()).willReturn(mapSession);
61+
62+
this.processor.setMaxInactiveInterval(newMaxInactiveInterval);
63+
Object result = this.processor.process(mapEntry);
64+
65+
assertThat(result).isEqualTo(Boolean.TRUE);
66+
assertThat(mapSession.getMaxInactiveInterval()).isEqualTo(newMaxInactiveInterval);
67+
verify(mapEntry).setValue(mapSession, newMaxInactiveInterval.getSeconds(), TimeUnit.SECONDS);
68+
}
69+
70+
@Test
71+
void shouldSetMapEntryValueWithOldTimeToLiveIfNoChangeToMaxInactiveIntervalIsRegistered() {
72+
Duration maxInactiveInterval = Duration.ofSeconds(123L);
73+
MapSession mapSession = new MapSession();
74+
mapSession.setMaxInactiveInterval(maxInactiveInterval);
75+
@SuppressWarnings("unchecked")
76+
ExtendedMapEntry<String, MapSession> mapEntry = mock(ExtendedMapEntry.class);
77+
given(mapEntry.getValue()).willReturn(mapSession);
78+
79+
Object result = this.processor.process(mapEntry);
80+
81+
assertThat(result).isEqualTo(Boolean.TRUE);
82+
assertThat(mapSession.getMaxInactiveInterval()).isEqualTo(maxInactiveInterval);
83+
verify(mapEntry).setValue(mapSession, maxInactiveInterval.getSeconds(), TimeUnit.SECONDS);
84+
}
85+
86+
@Test
87+
void shouldUpdateLastAccessTimeOnSessionAndSetMapEntryValueWithOldTimeToLive() {
88+
Instant lastAccessTime = Instant.ofEpochSecond(1234L);
89+
MapSession mapSession = new MapSession();
90+
@SuppressWarnings("unchecked")
91+
ExtendedMapEntry<String, MapSession> mapEntry = mock(ExtendedMapEntry.class);
92+
given(mapEntry.getValue()).willReturn(mapSession);
93+
94+
this.processor.setLastAccessedTime(lastAccessTime);
95+
Object result = this.processor.process(mapEntry);
96+
97+
assertThat(result).isEqualTo(Boolean.TRUE);
98+
assertThat(mapSession.getLastAccessedTime()).isEqualTo(lastAccessTime);
99+
verify(mapEntry).setValue(mapSession, mapSession.getMaxInactiveInterval().getSeconds(), TimeUnit.SECONDS);
100+
}
101+
102+
@Test
103+
void shouldUpdateSessionAttributesFromDeltaAndSetMapEntryValueWithOldTimeToLive() {
104+
MapSession mapSession = new MapSession();
105+
mapSession.setAttribute("changed", "oldValue");
106+
mapSession.setAttribute("removed", "existingValue");
107+
@SuppressWarnings("unchecked")
108+
ExtendedMapEntry<String, MapSession> mapEntry = mock(ExtendedMapEntry.class);
109+
given(mapEntry.getValue()).willReturn(mapSession);
110+
111+
HashMap<String, Object> delta = new HashMap<>();
112+
delta.put("added", "addedValue");
113+
delta.put("changed", "newValue");
114+
delta.put("removed", null);
115+
this.processor.setDelta(delta);
116+
117+
Object result = this.processor.process(mapEntry);
118+
119+
assertThat(result).isEqualTo(Boolean.TRUE);
120+
assertThat((String) mapSession.getAttribute("added")).isEqualTo("addedValue");
121+
assertThat((String) mapSession.getAttribute("changed")).isEqualTo("newValue");
122+
assertThat((String) mapSession.getAttribute("removed")).isNull();
123+
verify(mapEntry).setValue(mapSession, mapSession.getMaxInactiveInterval().getSeconds(), TimeUnit.SECONDS);
124+
}
125+
126+
}

0 commit comments

Comments
 (0)