From 6a78101db58d4c1d12f3df478f06d4f4c92fff7c Mon Sep 17 00:00:00 2001 From: Aleksandar Stojsavljevic Date: Mon, 26 Sep 2016 11:49:25 +0200 Subject: [PATCH] Optimize `save` operation in `HazelcastSessionRepository` (#516) This commit improves saving of sessions to only execute save operation if something has been changed (e.g. session.setAttribute(String, Object) was called). Further, configurable flush mode that specifies when to write to the backing Hazelcast instance is introduced. It can be 'on save' (default) or 'immediate'. Fixes gh-516, fixes gh-641 --- .../AbstractHazelcastRepositoryITests.java | 3 +- .../session/hazelcast/HazelcastFlushMode.java | 42 +++++ .../hazelcast/HazelcastSessionRepository.java | 143 ++++++++++++++-- .../web/http/EnableHazelcastHttpSession.java | 20 +++ .../HazelcastHttpSessionConfiguration.java | 8 + .../HazelcastSessionRepositoryTests.java | 159 +++++++++++++++--- ...azelcastHttpSessionConfigurationTests.java | 43 +++++ 7 files changed, 381 insertions(+), 37 deletions(-) create mode 100644 spring-session/src/main/java/org/springframework/session/hazelcast/HazelcastFlushMode.java diff --git a/spring-session/src/integration-test/java/org/springframework/session/hazelcast/AbstractHazelcastRepositoryITests.java b/spring-session/src/integration-test/java/org/springframework/session/hazelcast/AbstractHazelcastRepositoryITests.java index d5fba1f..9cd1fea 100644 --- a/spring-session/src/integration-test/java/org/springframework/session/hazelcast/AbstractHazelcastRepositoryITests.java +++ b/spring-session/src/integration-test/java/org/springframework/session/hazelcast/AbstractHazelcastRepositoryITests.java @@ -22,6 +22,7 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.session.MapSession; +import org.springframework.session.hazelcast.HazelcastSessionRepository.HazelcastSession; import static org.assertj.core.api.Assertions.assertThat; @@ -41,7 +42,7 @@ public abstract class AbstractHazelcastRepositoryITests { @Test public void createAndDestroySession() { - MapSession sessionToSave = this.repository.createSession(); + HazelcastSession sessionToSave = this.repository.createSession(); String sessionId = sessionToSave.getId(); IMap hazelcastMap = this.hazelcast.getMap( diff --git a/spring-session/src/main/java/org/springframework/session/hazelcast/HazelcastFlushMode.java b/spring-session/src/main/java/org/springframework/session/hazelcast/HazelcastFlushMode.java new file mode 100644 index 0000000..90fbcbe --- /dev/null +++ b/spring-session/src/main/java/org/springframework/session/hazelcast/HazelcastFlushMode.java @@ -0,0 +1,42 @@ +/* + * Copyright 2014-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.session.hazelcast; + +import org.springframework.session.SessionRepository; + +/** + * Specifies when to write to the backing Hazelcast instance. + * + * @author Aleksandar Stojsavljevic + * @since 1.3 + */ +public enum HazelcastFlushMode { + /** + * Only writes to Hazelcast when + * {@link SessionRepository#save(org.springframework.session.Session)} is invoked. In + * a web environment this is typically done as soon as the HTTP response is committed. + */ + ON_SAVE, + + /** + * Writes to Hazelcast as soon as possible. For example + * {@link SessionRepository#createSession()} will write the session to Hazelcast. Another + * example is that setting an attribute on the session will also write to Hazelcast + * immediately. + */ + IMMEDIATE +} diff --git a/spring-session/src/main/java/org/springframework/session/hazelcast/HazelcastSessionRepository.java b/spring-session/src/main/java/org/springframework/session/hazelcast/HazelcastSessionRepository.java index 7f82066..a267365 100644 --- a/spring-session/src/main/java/org/springframework/session/hazelcast/HazelcastSessionRepository.java +++ b/spring-session/src/main/java/org/springframework/session/hazelcast/HazelcastSessionRepository.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; @@ -36,8 +37,10 @@ import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.session.ExpiringSession; import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.MapSession; +import org.springframework.session.Session; import org.springframework.session.events.AbstractSessionEvent; import org.springframework.session.events.SessionCreatedEvent; import org.springframework.session.events.SessionDeletedEvent; @@ -100,10 +103,11 @@ import org.springframework.util.Assert; * @author Vedran Pavic * @author Tommy Ludwig * @author Mark Anderson + * @author Aleksandar Stojsavljevic * @since 1.3.0 */ public class HazelcastSessionRepository implements - FindByIndexNameSessionRepository, + FindByIndexNameSessionRepository, EntryAddedListener, EntryEvictedListener, EntryRemovedListener { @@ -117,6 +121,8 @@ public class HazelcastSessionRepository implements private final IMap sessions; + private HazelcastFlushMode hazelcastFlushMode = HazelcastFlushMode.ON_SAVE; + private ApplicationEventPublisher eventPublisher = new ApplicationEventPublisher() { public void publishEvent(ApplicationEvent event) { @@ -175,20 +181,33 @@ public class HazelcastSessionRepository implements this.defaultMaxInactiveInterval = defaultMaxInactiveInterval; } - public MapSession createSession() { - MapSession result = new MapSession(); + /** + * Sets the Hazelcast flush mode. Default flush mode is {@link HazelcastFlushMode#ON_SAVE}. + * + * @param hazelcastFlushMode the new Hazelcast flush mode + */ + public void setHazelcastFlushMode(HazelcastFlushMode hazelcastFlushMode) { + Assert.notNull(hazelcastFlushMode, "HazelcastFlushMode cannot be null"); + this.hazelcastFlushMode = hazelcastFlushMode; + } + + public HazelcastSession createSession() { + HazelcastSession result = new HazelcastSession(); if (this.defaultMaxInactiveInterval != null) { result.setMaxInactiveIntervalInSeconds(this.defaultMaxInactiveInterval); } return result; } - public void save(MapSession session) { - this.sessions.put(session.getId(), session, - session.getMaxInactiveIntervalInSeconds(), TimeUnit.SECONDS); + public void save(HazelcastSession session) { + if (session.isChanged()) { + this.sessions.put(session.getId(), session.getDelegate(), + session.getMaxInactiveIntervalInSeconds(), TimeUnit.SECONDS); + session.markUnchanged(); + } } - public MapSession getSession(String id) { + public HazelcastSession getSession(String id) { MapSession saved = this.sessions.get(id); if (saved == null) { return null; @@ -197,24 +216,24 @@ public class HazelcastSessionRepository implements delete(saved.getId()); return null; } - return saved; + return new HazelcastSession(saved); } public void delete(String id) { this.sessions.remove(id); } - public Map findByIndexNameAndIndexValue( + public Map findByIndexNameAndIndexValue( String indexName, String indexValue) { if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) { return Collections.emptyMap(); } Collection sessions = this.sessions.values( Predicates.equal(PRINCIPAL_NAME_ATTRIBUTE, indexValue)); - Map sessionMap = new HashMap( + Map sessionMap = new HashMap( sessions.size()); for (MapSession session : sessions) { - sessionMap.put(session.getId(), session); + sessionMap.put(session.getId(), new HazelcastSession(session)); } return sessionMap; } @@ -242,4 +261,106 @@ public class HazelcastSessionRepository implements .publishEvent(new SessionDeletedEvent(this, event.getOldValue())); } + /** + * A custom implementation of {@link Session} that uses a {@link MapSession} as the + * basis for its mapping. It keeps track if changes have been made since last save. + * + * @author Aleksandar Stojsavljevic + * @since 1.3 + */ + final class HazelcastSession implements ExpiringSession { + private final MapSession delegate; + private boolean changed; + + /** + * 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; + flushImmediateIfNecessary(); + } + + /** + * Creates a new instance from the provided {@link MapSession}. + * + * @param cached the {@link MapSession} that represents the persisted session that was + * retrieved. Cannot be null. + */ + HazelcastSession(MapSession cached) { + Assert.notNull(cached, "MapSession cannot be null"); + this.delegate = cached; + } + + public void setLastAccessedTime(long lastAccessedTime) { + this.delegate.setLastAccessedTime(lastAccessedTime); + this.changed = true; + flushImmediateIfNecessary(); + } + + public boolean isExpired() { + return this.delegate.isExpired(); + } + + public long getCreationTime() { + return this.delegate.getCreationTime(); + } + + public String getId() { + return this.delegate.getId(); + } + + public long getLastAccessedTime() { + return this.delegate.getLastAccessedTime(); + } + + public void setMaxInactiveIntervalInSeconds(int interval) { + this.delegate.setMaxInactiveIntervalInSeconds(interval); + this.changed = true; + flushImmediateIfNecessary(); + } + + public int getMaxInactiveIntervalInSeconds() { + return this.delegate.getMaxInactiveIntervalInSeconds(); + } + + public T getAttribute(String attributeName) { + return this.delegate.getAttribute(attributeName); + } + + public Set getAttributeNames() { + return this.delegate.getAttributeNames(); + } + + public void setAttribute(String attributeName, Object attributeValue) { + this.delegate.setAttribute(attributeName, attributeValue); + this.changed = true; + flushImmediateIfNecessary(); + } + + public void removeAttribute(String attributeName) { + this.delegate.removeAttribute(attributeName); + this.changed = true; + flushImmediateIfNecessary(); + } + + boolean isChanged() { + return this.changed; + } + + void markUnchanged() { + this.changed = false; + } + + MapSession getDelegate() { + return this.delegate; + } + + private void flushImmediateIfNecessary() { + if (HazelcastSessionRepository.this.hazelcastFlushMode == HazelcastFlushMode.IMMEDIATE) { + HazelcastSessionRepository.this.save(this); + } + } + } } diff --git a/spring-session/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/EnableHazelcastHttpSession.java b/spring-session/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/EnableHazelcastHttpSession.java index 702b815..a7b6993 100644 --- a/spring-session/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/EnableHazelcastHttpSession.java +++ b/spring-session/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/EnableHazelcastHttpSession.java @@ -23,7 +23,9 @@ import java.lang.annotation.Target; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.session.MapSession; +import org.springframework.session.SessionRepository; import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; +import org.springframework.session.hazelcast.HazelcastFlushMode; /** * Add this annotation to a {@code @Configuration} class to expose the @@ -48,6 +50,7 @@ import org.springframework.session.config.annotation.web.http.EnableSpringHttpSe * instead. * * @author Tommy Ludwig + * @author Aleksandar Stojsavljevic * @since 1.1 * @see EnableSpringHttpSession */ @@ -73,4 +76,21 @@ public @interface EnableHazelcastHttpSession { */ String sessionMapName() default HazelcastHttpSessionConfiguration.DEFAULT_SESSION_MAP_NAME; + /** + *

+ * Sets the flush mode for the Hazelcast sessions. The default is ON_SAVE which only + * updates the backing Hazelcast when + * {@link SessionRepository#save(org.springframework.session.Session)} is invoked. In + * a web environment this happens just before the HTTP response is committed. + *

+ *

+ * Setting the value to IMMEDIATE will ensure that the any updates to the Session are + * immediately written to the Hazelcast instance. + *

+ * + * @return the {@link HazelcastFlushMode} to use + * @since 1.3 + */ + HazelcastFlushMode hazelcastFlushMode() default HazelcastFlushMode.ON_SAVE; + } diff --git a/spring-session/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfiguration.java b/spring-session/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfiguration.java index b008204..6d06596 100644 --- a/spring-session/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfiguration.java +++ b/spring-session/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfiguration.java @@ -29,6 +29,7 @@ import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.session.MapSession; import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration; +import org.springframework.session.hazelcast.HazelcastFlushMode; import org.springframework.session.hazelcast.HazelcastSessionRepository; import org.springframework.session.web.http.SessionRepositoryFilter; @@ -52,6 +53,8 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur private String sessionMapName = DEFAULT_SESSION_MAP_NAME; + private HazelcastFlushMode hazelcastFlushMode = HazelcastFlushMode.ON_SAVE; + @Bean public HazelcastSessionRepository sessionRepository( HazelcastInstance hazelcastInstance, @@ -63,6 +66,7 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur sessionRepository.setApplicationEventPublisher(eventPublisher); sessionRepository.setDefaultMaxInactiveInterval( this.maxInactiveIntervalInSeconds); + sessionRepository.setHazelcastFlushMode(this.hazelcastFlushMode); return sessionRepository; } @@ -73,6 +77,7 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur setMaxInactiveIntervalInSeconds( (Integer) enableAttrs.getNumber("maxInactiveIntervalInSeconds")); setSessionMapName(enableAttrs.getString("sessionMapName")); + setHazelcastFlushMode((HazelcastFlushMode) enableAttrs.getEnum("hazelcastFlushMode")); } public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) { @@ -83,4 +88,7 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur this.sessionMapName = sessionMapName; } + public void setHazelcastFlushMode(HazelcastFlushMode hazelcastFlushMode) { + this.hazelcastFlushMode = hazelcastFlushMode; + } } diff --git a/spring-session/src/test/java/org/springframework/session/hazelcast/HazelcastSessionRepositoryTests.java b/spring-session/src/test/java/org/springframework/session/hazelcast/HazelcastSessionRepositoryTests.java index fa69ee9..5fdc63f 100644 --- a/spring-session/src/test/java/org/springframework/session/hazelcast/HazelcastSessionRepositoryTests.java +++ b/spring-session/src/test/java/org/springframework/session/hazelcast/HazelcastSessionRepositoryTests.java @@ -36,11 +36,13 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.MapSession; +import org.springframework.session.hazelcast.HazelcastSessionRepository.HazelcastSession; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isA; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; @@ -49,6 +51,7 @@ import static org.mockito.Mockito.verifyZeroInteractions; * Tests for {@link HazelcastSessionRepository}. * * @author Vedran Pavic + * @author Aleksandar Stojsavljevic */ @RunWith(MockitoJUnitRunner.class) public class HazelcastSessionRepositoryTests { @@ -78,7 +81,7 @@ public class HazelcastSessionRepositoryTests { @Test public void createSessionDefaultMaxInactiveInterval() throws Exception { - MapSession session = this.repository.createSession(); + HazelcastSession session = this.repository.createSession(); assertThat(session.getMaxInactiveIntervalInSeconds()) .isEqualTo(new MapSession().getMaxInactiveIntervalInSeconds()); @@ -90,61 +93,167 @@ public class HazelcastSessionRepositoryTests { int interval = 1; this.repository.setDefaultMaxInactiveInterval(interval); - MapSession session = this.repository.createSession(); + HazelcastSession session = this.repository.createSession(); assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(interval); verifyZeroInteractions(this.sessions); } @Test - public void saveNew() { - MapSession session = this.repository.createSession(); + public void saveNewFlushModeOnSave() { + HazelcastSession session = this.repository.createSession(); + verifyZeroInteractions(this.sessions); this.repository.save(session); - - verify(this.sessions, times(1)).put(eq(session.getId()), eq(session), + verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS)); } @Test - public void saveUpdatedAttributes() { - MapSession session = new MapSession(); + public void saveNewFlushModeImmediate() { + this.repository.setHazelcastFlushMode(HazelcastFlushMode.IMMEDIATE); + + HazelcastSession session = this.repository.createSession(); + verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()), + isA(Long.class), eq(TimeUnit.SECONDS)); + } + + @Test + public void saveUpdatedAttributeFlushModeOnSave() { + HazelcastSession session = this.repository.createSession(); session.setAttribute("testName", "testValue"); + verifyZeroInteractions(this.sessions); this.repository.save(session); - - verify(this.sessions, times(1)).put(eq(session.getId()), eq(session), + verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS)); } + @SuppressWarnings("unchecked") @Test - public void saveUpdatedLastAccessedTime() { - MapSession session = new MapSession(); + public void saveUpdatedAttributeFlushModeImmediate() { + this.repository.setHazelcastFlushMode(HazelcastFlushMode.IMMEDIATE); + + HazelcastSession session = this.repository.createSession(); + session.setAttribute("testName", "testValue"); + verify(this.sessions, times(2)).put(eq(session.getId()), eq(session.getDelegate()), + isA(Long.class), eq(TimeUnit.SECONDS)); + reset(this.sessions); + + this.repository.save(session); + verifyZeroInteractions(this.sessions); + } + + @Test + public void removeAttributeFlushModeOnSave() { + HazelcastSession session = this.repository.createSession(); + session.removeAttribute("testName"); + verifyZeroInteractions(this.sessions); + + this.repository.save(session); + verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()), + isA(Long.class), eq(TimeUnit.SECONDS)); + } + + @SuppressWarnings("unchecked") + @Test + public void removeAttributeFlushModeImmediate() { + this.repository.setHazelcastFlushMode(HazelcastFlushMode.IMMEDIATE); + + HazelcastSession session = this.repository.createSession(); + session.removeAttribute("testName"); + verify(this.sessions, times(2)).put(eq(session.getId()), eq(session.getDelegate()), + isA(Long.class), eq(TimeUnit.SECONDS)); + reset(this.sessions); + + this.repository.save(session); + verifyZeroInteractions(this.sessions); + } + + @Test + public void saveUpdatedLastAccessedTimeFlushModeOnSave() { + HazelcastSession session = this.repository.createSession(); session.setLastAccessedTime(System.currentTimeMillis()); + verifyZeroInteractions(this.sessions); this.repository.save(session); - - verify(this.sessions, times(1)).put(eq(session.getId()), eq(session), + verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS)); } + @SuppressWarnings("unchecked") @Test - public void saveUnchanged() { - MapSession session = new MapSession(); + public void saveUpdatedLastAccessedTimeFlushModeImmediate() { + this.repository.setHazelcastFlushMode(HazelcastFlushMode.IMMEDIATE); + + HazelcastSession session = this.repository.createSession(); + session.setLastAccessedTime(System.currentTimeMillis()); + verify(this.sessions, times(2)).put(eq(session.getId()), eq(session.getDelegate()), + isA(Long.class), eq(TimeUnit.SECONDS)); + reset(this.sessions); this.repository.save(session); + verifyZeroInteractions(this.sessions); + } - verify(this.sessions, times(1)).put(eq(session.getId()), eq(session), + @Test + public void saveUpdatedMaxInactiveIntervalInSecondsFlushModeOnSave() { + HazelcastSession session = this.repository.createSession(); + session.setMaxInactiveIntervalInSeconds(1); + verifyZeroInteractions(this.sessions); + + this.repository.save(session); + verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()), isA(Long.class), eq(TimeUnit.SECONDS)); - // TODO - once save optimization is implemented, should be replaced with: - //verifyZeroInteractions(this.sessions); + } + + @SuppressWarnings("unchecked") + @Test + public void saveUpdatedMaxInactiveIntervalInSecondsFlushModeImmediate() { + this.repository.setHazelcastFlushMode(HazelcastFlushMode.IMMEDIATE); + + HazelcastSession session = this.repository.createSession(); + session.setMaxInactiveIntervalInSeconds(1); + verify(this.sessions, times(2)).put(eq(session.getId()), eq(session.getDelegate()), + isA(Long.class), eq(TimeUnit.SECONDS)); + reset(this.sessions); + + this.repository.save(session); + verifyZeroInteractions(this.sessions); + } + + @SuppressWarnings("unchecked") + @Test + public void saveUnchangedFlushModeOnSave() { + HazelcastSession session = this.repository.createSession(); + this.repository.save(session); + verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()), + isA(Long.class), eq(TimeUnit.SECONDS)); + reset(this.sessions); + + this.repository.save(session); + verifyZeroInteractions(this.sessions); + } + + @SuppressWarnings("unchecked") + @Test + public void saveUnchangedFlushModeImmediate() { + this.repository.setHazelcastFlushMode(HazelcastFlushMode.IMMEDIATE); + + HazelcastSession session = this.repository.createSession(); + verify(this.sessions, times(1)).put(eq(session.getId()), eq(session.getDelegate()), + isA(Long.class), eq(TimeUnit.SECONDS)); + reset(this.sessions); + + this.repository.save(session); + verifyZeroInteractions(this.sessions); } @Test public void getSessionNotFound() { String sessionId = "testSessionId"; - MapSession session = this.repository.getSession(sessionId); + HazelcastSession session = this.repository.getSession(sessionId); assertThat(session).isNull(); verify(this.sessions, times(1)).get(eq(sessionId)); @@ -157,7 +266,7 @@ public class HazelcastSessionRepositoryTests { (MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS * 1000 + 1000)); given(this.sessions.get(eq(expired.getId()))).willReturn(expired); - MapSession session = this.repository.getSession(expired.getId()); + HazelcastSession session = this.repository.getSession(expired.getId()); assertThat(session).isNull(); verify(this.sessions, times(1)).get(eq(expired.getId())); @@ -170,7 +279,7 @@ public class HazelcastSessionRepositoryTests { saved.setAttribute("savedName", "savedValue"); given(this.sessions.get(eq(saved.getId()))).willReturn(saved); - MapSession session = this.repository.getSession(saved.getId()); + HazelcastSession session = this.repository.getSession(saved.getId()); assertThat(session.getId()).isEqualTo(saved.getId()); assertThat(session.getAttribute("savedName")).isEqualTo("savedValue"); @@ -190,7 +299,7 @@ public class HazelcastSessionRepositoryTests { public void findByIndexNameAndIndexValueUnknownIndexName() { String indexValue = "testIndexValue"; - Map sessions = this.repository.findByIndexNameAndIndexValue( + Map sessions = this.repository.findByIndexNameAndIndexValue( "testIndexName", indexValue); assertThat(sessions).isEmpty(); @@ -201,7 +310,7 @@ public class HazelcastSessionRepositoryTests { public void findByIndexNameAndIndexValuePrincipalIndexNameNotFound() { String principal = "username"; - Map sessions = this.repository.findByIndexNameAndIndexValue( + Map sessions = this.repository.findByIndexNameAndIndexValue( FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal); assertThat(sessions).isEmpty(); @@ -222,7 +331,7 @@ public class HazelcastSessionRepositoryTests { saved.add(saved2); given(this.sessions.values(isA(EqualPredicate.class))).willReturn(saved); - Map sessions = this.repository.findByIndexNameAndIndexValue( + Map sessions = this.repository.findByIndexNameAndIndexValue( FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal); assertThat(sessions).hasSize(2); diff --git a/spring-session/src/test/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfigurationTests.java b/spring-session/src/test/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfigurationTests.java index e907ea5..7f898fb 100644 --- a/spring-session/src/test/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfigurationTests.java +++ b/spring-session/src/test/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfigurationTests.java @@ -31,6 +31,7 @@ import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.session.hazelcast.HazelcastFlushMode; import org.springframework.session.hazelcast.HazelcastSessionRepository; import org.springframework.test.util.ReflectionTestUtils; @@ -42,6 +43,7 @@ import static org.mockito.Matchers.isA; * Tests for {@link HazelcastHttpSessionConfiguration}. * * @author Vedran Pavic + * @author Aleksandar Stojsavljevic */ @RunWith(MockitoJUnitRunner.class) public class HazelcastHttpSessionConfigurationTests { @@ -50,6 +52,8 @@ public class HazelcastHttpSessionConfigurationTests { private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600; + private static final HazelcastFlushMode HAZELCAST_FLUSH_MODE = HazelcastFlushMode.IMMEDIATE; + @Rule public final ExpectedException thrown = ExpectedException.none(); @@ -139,6 +143,29 @@ public class HazelcastHttpSessionConfigurationTests { .isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS); } + @Test + public void customFlushImmediately() { + registerAndRefresh(CustomFlushImmediatelyConfiguration.class); + + HazelcastSessionRepository repository = this.context + .getBean(HazelcastSessionRepository.class); + assertThat(repository).isNotNull(); + assertThat(ReflectionTestUtils.getField(repository, "hazelcastFlushMode")).isEqualTo( + HazelcastFlushMode.IMMEDIATE); + } + + @Test + public void setCustomFlushImmediately() { + registerAndRefresh(BaseConfiguration.class, + CustomFlushImmediatelySetConfiguration.class); + + HazelcastSessionRepository repository = this.context + .getBean(HazelcastSessionRepository.class); + assertThat(repository).isNotNull(); + assertThat(ReflectionTestUtils.getField(repository, "hazelcastFlushMode")).isEqualTo( + HazelcastFlushMode.IMMEDIATE); + } + private void registerAndRefresh(Class... annotatedClasses) { this.context.register(annotatedClasses); this.context.refresh(); @@ -194,4 +221,20 @@ public class HazelcastHttpSessionConfigurationTests { extends BaseConfiguration { } + @Configuration + static class CustomFlushImmediatelySetConfiguration + extends HazelcastHttpSessionConfiguration { + + CustomFlushImmediatelySetConfiguration() { + setHazelcastFlushMode(HAZELCAST_FLUSH_MODE); + } + + } + + @Configuration + @EnableHazelcastHttpSession(hazelcastFlushMode = HazelcastFlushMode.IMMEDIATE) + static class CustomFlushImmediatelyConfiguration + extends BaseConfiguration { + } + }