diff --git a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/HazelcastSessionRepository.java b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/HazelcastSessionRepository.java index 7159fd4b..0078657a 100644 --- a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/HazelcastSessionRepository.java +++ b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/HazelcastSessionRepository.java @@ -30,6 +30,8 @@ import javax.annotation.PreDestroy; import com.hazelcast.core.EntryEvent; import com.hazelcast.core.IMap; +import com.hazelcast.map.AbstractEntryProcessor; +import com.hazelcast.map.EntryProcessor; import com.hazelcast.map.listener.EntryAddedListener; import com.hazelcast.map.listener.EntryEvictedListener; import com.hazelcast.map.listener.EntryRemovedListener; @@ -210,11 +212,15 @@ public class HazelcastSessionRepository implements this.sessions.remove(session.originalId); session.originalId = session.getId(); } - if (session.isChanged()) { - this.sessions.put(session.getId(), session.getDelegate(), + if (session.isNew) { + this.sessions.set(session.getId(), session.getDelegate(), session.getMaxInactiveInterval().getSeconds(), TimeUnit.SECONDS); - session.markUnchanged(); } + else if (session.changed) { + this.sessions.executeOnKey(session.getId(), + new SessionUpdateEntryProcessor(session.getDelegate(), session.delta)); + } + session.clearFlags(); } @Override @@ -286,16 +292,22 @@ public class HazelcastSessionRepository implements final class HazelcastSession implements Session { private final MapSession delegate; + + private boolean isNew; + private boolean changed; + private String originalId; + private Map delta = new HashMap<>(); + /** * Creates a new instance ensuring to mark all of the new attributes to be * persisted in the next save operation. */ HazelcastSession() { this(new MapSession()); - this.changed = true; + this.isNew = true; flushImmediateIfNecessary(); } @@ -334,7 +346,7 @@ public class HazelcastSessionRepository implements @Override public String changeSessionId() { - this.changed = true; + this.isNew = true; return this.delegate.changeSessionId(); } @@ -368,6 +380,7 @@ public class HazelcastSessionRepository implements @Override public void setAttribute(String attributeName, Object attributeValue) { this.delegate.setAttribute(attributeName, attributeValue); + this.delta.put(attributeName, attributeValue); this.changed = true; flushImmediateIfNecessary(); } @@ -375,22 +388,21 @@ public class HazelcastSessionRepository implements @Override public void removeAttribute(String attributeName) { this.delegate.removeAttribute(attributeName); + this.delta.put(attributeName, null); this.changed = true; flushImmediateIfNecessary(); } - boolean isChanged() { - return this.changed; - } - - void markUnchanged() { - this.changed = false; - } - MapSession getDelegate() { return this.delegate; } + void clearFlags() { + this.isNew = false; + this.changed = false; + this.delta.clear(); + } + private void flushImmediateIfNecessary() { if (HazelcastSessionRepository.this.hazelcastFlushMode == HazelcastFlushMode.IMMEDIATE) { HazelcastSessionRepository.this.save(this); @@ -399,4 +411,41 @@ public class HazelcastSessionRepository implements } + /** + * Hazelcast {@link EntryProcessor} responsible for handling updates to session. + * + * @since 2.0.0 + * @see #save(HazelcastSession) + */ + private static final class SessionUpdateEntryProcessor + extends AbstractEntryProcessor { + + private final MapSession session; + + private final Map delta; + + SessionUpdateEntryProcessor(MapSession session, Map delta) { + this.session = session; + this.delta = delta; + } + + @Override + public Object process(Map.Entry entry) { + MapSession value = entry.getValue(); + value.setLastAccessedTime(this.session.getLastAccessedTime()); + value.setMaxInactiveInterval(this.session.getMaxInactiveInterval()); + for (final Map.Entry attribute : this.delta.entrySet()) { + if (attribute.getValue() != null) { + value.setAttribute(attribute.getKey(), attribute.getValue()); + } + else { + value.removeAttribute(attribute.getKey()); + } + } + entry.setValue(value); + return value; + } + + } + } diff --git a/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/HazelcastSessionRepositoryTests.java b/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/HazelcastSessionRepositoryTests.java index 0a877b28..28e97909 100644 --- a/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/HazelcastSessionRepositoryTests.java +++ b/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/HazelcastSessionRepositoryTests.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.concurrent.TimeUnit; import com.hazelcast.core.IMap; +import com.hazelcast.map.EntryProcessor; import com.hazelcast.query.impl.predicates.EqualPredicate; import org.junit.Before; import org.junit.Rule; @@ -41,6 +42,7 @@ import org.springframework.session.MapSession; import org.springframework.session.hazelcast.HazelcastSessionRepository.HazelcastSession; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.BDDMockito.given; @@ -107,7 +109,7 @@ public class HazelcastSessionRepositoryTests { verifyZeroInteractions(this.sessions); this.repository.save(session); - verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()), + verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS)); } @@ -116,7 +118,7 @@ public class HazelcastSessionRepositoryTests { this.repository.setHazelcastFlushMode(HazelcastFlushMode.IMMEDIATE); HazelcastSession session = this.repository.createSession(); - verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()), + verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS)); } @@ -127,7 +129,7 @@ public class HazelcastSessionRepositoryTests { verifyZeroInteractions(this.sessions); this.repository.save(session); - verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()), + verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS)); } @@ -137,8 +139,10 @@ public class HazelcastSessionRepositoryTests { HazelcastSession session = this.repository.createSession(); session.setAttribute("testName", "testValue"); - verify(this.sessions, times(2)).put(eq(session.getId()), eq(session.getDelegate()), + verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS)); + verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), + any(EntryProcessor.class)); this.repository.save(session); verifyZeroInteractions(this.sessions); @@ -151,7 +155,7 @@ public class HazelcastSessionRepositoryTests { verifyZeroInteractions(this.sessions); this.repository.save(session); - verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()), + verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS)); } @@ -161,8 +165,10 @@ public class HazelcastSessionRepositoryTests { HazelcastSession session = this.repository.createSession(); session.removeAttribute("testName"); - verify(this.sessions, times(2)).put(eq(session.getId()), eq(session.getDelegate()), + verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS)); + verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), + any(EntryProcessor.class)); this.repository.save(session); verifyZeroInteractions(this.sessions); @@ -175,7 +181,7 @@ public class HazelcastSessionRepositoryTests { verifyZeroInteractions(this.sessions); this.repository.save(session); - verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()), + verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS)); } @@ -185,8 +191,10 @@ public class HazelcastSessionRepositoryTests { HazelcastSession session = this.repository.createSession(); session.setLastAccessedTime(Instant.now()); - verify(this.sessions, times(2)).put(eq(session.getId()), eq(session.getDelegate()), + verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS)); + verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), + any(EntryProcessor.class)); this.repository.save(session); verifyZeroInteractions(this.sessions); @@ -199,7 +207,7 @@ public class HazelcastSessionRepositoryTests { verifyZeroInteractions(this.sessions); this.repository.save(session); - verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()), + verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS)); } @@ -209,8 +217,10 @@ public class HazelcastSessionRepositoryTests { HazelcastSession session = this.repository.createSession(); session.setMaxInactiveInterval(Duration.ofSeconds(1)); - verify(this.sessions, times(2)).put(eq(session.getId()), eq(session.getDelegate()), + verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS)); + verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), + any(EntryProcessor.class)); this.repository.save(session); verifyZeroInteractions(this.sessions); @@ -220,7 +230,7 @@ public class HazelcastSessionRepositoryTests { public void saveUnchangedFlushModeOnSave() { HazelcastSession session = this.repository.createSession(); this.repository.save(session); - verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()), + verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS)); this.repository.save(session); @@ -232,7 +242,7 @@ public class HazelcastSessionRepositoryTests { this.repository.setHazelcastFlushMode(HazelcastFlushMode.IMMEDIATE); HazelcastSession session = this.repository.createSession(); - verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()), + verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS)); this.repository.save(session);