From 07b943354067668f5e7b21e7db24747ad4aeac51 Mon Sep 17 00:00:00 2001 From: Vedran Pavic Date: Fri, 21 Jun 2019 15:49:59 +0200 Subject: [PATCH] Add common flush mode support Resolves: #1465 --- .../springframework/session/FlushMode.java | 44 ++++++++++++ ...ssionRepositoryFlushImmediatelyITests.java | 4 +- .../session/data/redis/RedisFlushMode.java | 17 ++++- .../RedisOperationsSessionRepository.java | 18 ++++- ...impleRedisOperationsSessionRepository.java | 7 +- .../web/http/EnableRedisHttpSession.java | 17 ++++- .../http/RedisHttpSessionConfiguration.java | 21 ++++-- ...RedisOperationsSessionRepositoryTests.java | 19 ++++-- ...RedisOperationsSessionRepositoryTests.java | 8 +-- .../RedisHttpSessionConfigurationTests.java | 68 +++++++++++++++++++ .../src/docs/asciidoc/index.adoc | 8 +-- .../session/hazelcast/HazelcastFlushMode.java | 19 +++++- .../hazelcast/HazelcastSessionRepository.java | 17 ++++- .../web/http/EnableHazelcastHttpSession.java | 16 +++++ .../HazelcastHttpSessionConfiguration.java | 20 ++++-- .../HazelcastSessionRepositoryTests.java | 13 ++-- ...azelcastHttpSessionConfigurationTests.java | 46 ++++++++++--- 17 files changed, 309 insertions(+), 53 deletions(-) create mode 100644 spring-session-core/src/main/java/org/springframework/session/FlushMode.java diff --git a/spring-session-core/src/main/java/org/springframework/session/FlushMode.java b/spring-session-core/src/main/java/org/springframework/session/FlushMode.java new file mode 100644 index 00000000..61a9e5d0 --- /dev/null +++ b/spring-session-core/src/main/java/org/springframework/session/FlushMode.java @@ -0,0 +1,44 @@ +/* + * Copyright 2014-2019 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 + * + * https://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; + +/** + * Supported modes of writing the session to session store. + * + * @author Rob Winch + * @author Vedran Pavic + * @since 2.2.0 + */ +public enum FlushMode { + + /** + * Only writes to session store when {@link SessionRepository#save(Session)} is + * invoked. In a web environment this is typically done as soon as the HTTP response + * is committed. + */ + ON_SAVE, + + /** + * Writes to session store as soon as possible. For example + * {@link SessionRepository#createSession()} will write the session to session store. + * Another example is that setting an attribute using + * {@link Session#setAttribute(String, Object)} will also write to session store + * immediately. + */ + IMMEDIATE + +} diff --git a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/flushimmediately/RedisOperationsSessionRepositoryFlushImmediatelyITests.java b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/flushimmediately/RedisOperationsSessionRepositoryFlushImmediatelyITests.java index a098888d..e9463004 100644 --- a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/flushimmediately/RedisOperationsSessionRepositoryFlushImmediatelyITests.java +++ b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/flushimmediately/RedisOperationsSessionRepositoryFlushImmediatelyITests.java @@ -21,10 +21,10 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; +import org.springframework.session.FlushMode; import org.springframework.session.Session; import org.springframework.session.SessionRepository; import org.springframework.session.data.redis.AbstractRedisITests; -import org.springframework.session.data.redis.RedisFlushMode; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -50,7 +50,7 @@ class RedisOperationsSessionRepositoryFlushImmediatelyITests } @Configuration - @EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE) + @EnableRedisHttpSession(flushMode = FlushMode.IMMEDIATE) static class RedisHttpSessionConfig extends BaseConfig { } diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisFlushMode.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisFlushMode.java index 31b5b48a..50a0addd 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisFlushMode.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisFlushMode.java @@ -16,6 +16,7 @@ package org.springframework.session.data.redis; +import org.springframework.session.FlushMode; import org.springframework.session.SessionRepository; /** @@ -23,7 +24,9 @@ import org.springframework.session.SessionRepository; * * @author Rob Winch * @since 1.1 + * @deprecated since 2.2.0 in favor of {@link FlushMode} */ +@Deprecated public enum RedisFlushMode { /** @@ -31,7 +34,7 @@ public enum RedisFlushMode { * {@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, + ON_SAVE(FlushMode.ON_SAVE), /** * Writes to Redis as soon as possible. For example @@ -39,6 +42,16 @@ public enum RedisFlushMode { * example is that setting an attribute on the session will also write to Redis * immediately. */ - IMMEDIATE + IMMEDIATE(FlushMode.IMMEDIATE); + + private final FlushMode flushMode; + + RedisFlushMode(FlushMode flushMode) { + this.flushMode = flushMode; + } + + public FlushMode getFlushMode() { + return this.flushMode; + } } diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisOperationsSessionRepository.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisOperationsSessionRepository.java index 8b3c7867..1758113c 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisOperationsSessionRepository.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisOperationsSessionRepository.java @@ -38,6 +38,7 @@ import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.session.DelegatingIndexResolver; import org.springframework.session.FindByIndexNameSessionRepository; +import org.springframework.session.FlushMode; import org.springframework.session.IndexResolver; import org.springframework.session.MapSession; import org.springframework.session.PrincipalNameIndexResolver; @@ -300,7 +301,7 @@ public class RedisOperationsSessionRepository private RedisSerializer defaultSerializer = new JdkSerializationRedisSerializer(); - private RedisFlushMode redisFlushMode = RedisFlushMode.ON_SAVE; + private FlushMode flushMode = FlushMode.ON_SAVE; /** * Creates a new instance. For an example, refer to the class level javadoc. @@ -352,10 +353,21 @@ public class RedisOperationsSessionRepository /** * Sets the redis flush mode. Default flush mode is {@link RedisFlushMode#ON_SAVE}. * @param redisFlushMode the new redis flush mode + * @deprecated since 2.2.0 in favor of {@link #setFlushMode(FlushMode)} */ + @Deprecated public void setRedisFlushMode(RedisFlushMode redisFlushMode) { Assert.notNull(redisFlushMode, "redisFlushMode cannot be null"); - this.redisFlushMode = redisFlushMode; + setFlushMode(redisFlushMode.getFlushMode()); + } + + /** + * Sets the redis flush mode. Default flush mode is {@link FlushMode#ON_SAVE}. + * @param flushMode the flush mode + */ + public void setFlushMode(FlushMode flushMode) { + Assert.notNull(flushMode, "flushMode cannot be null"); + this.flushMode = flushMode; } /** @@ -770,7 +782,7 @@ public class RedisOperationsSessionRepository } private void flushImmediateIfNecessary() { - if (RedisOperationsSessionRepository.this.redisFlushMode == RedisFlushMode.IMMEDIATE) { + if (RedisOperationsSessionRepository.this.flushMode == FlushMode.IMMEDIATE) { save(); } } diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/SimpleRedisOperationsSessionRepository.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/SimpleRedisOperationsSessionRepository.java index 75fac9e4..2791f3f2 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/SimpleRedisOperationsSessionRepository.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/SimpleRedisOperationsSessionRepository.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.Set; import org.springframework.data.redis.core.RedisOperations; +import org.springframework.session.FlushMode; import org.springframework.session.MapSession; import org.springframework.session.Session; import org.springframework.session.SessionRepository; @@ -49,7 +50,7 @@ public class SimpleRedisOperationsSessionRepository private String keyNamespace = DEFAULT_KEY_NAMESPACE; - private RedisFlushMode flushMode = RedisFlushMode.ON_SAVE; + private FlushMode flushMode = FlushMode.ON_SAVE; /** * Create a new {@link SimpleRedisOperationsSessionRepository} instance. @@ -83,7 +84,7 @@ public class SimpleRedisOperationsSessionRepository * Set the flush mode. * @param flushMode the flush mode */ - public void setFlushMode(RedisFlushMode flushMode) { + public void setFlushMode(FlushMode flushMode) { Assert.notNull(flushMode, "flushMode must not be null"); this.flushMode = flushMode; } @@ -231,7 +232,7 @@ public class SimpleRedisOperationsSessionRepository } private void flushIfRequired() { - if (SimpleRedisOperationsSessionRepository.this.flushMode == RedisFlushMode.IMMEDIATE) { + if (SimpleRedisOperationsSessionRepository.this.flushMode == FlushMode.IMMEDIATE) { save(); } } diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisHttpSession.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisHttpSession.java index 588dc253..3f8facb5 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisHttpSession.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisHttpSession.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2017 the original author or authors. + * Copyright 2014-2019 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. @@ -25,6 +25,7 @@ import java.lang.annotation.Target; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.session.FlushMode; import org.springframework.session.MapSession; import org.springframework.session.Session; import org.springframework.session.SessionRepository; @@ -94,9 +95,23 @@ public @interface EnableRedisHttpSession { * Session are immediately written to the Redis instance. * @return the {@link RedisFlushMode} to use * @since 1.1 + * @deprecated since 2.2.0 in favor of {@link #flushMode()} */ + @Deprecated RedisFlushMode redisFlushMode() default RedisFlushMode.ON_SAVE; + /** + * Flush mode for the Redis sessions. The default is {@code ON_SAVE} which only + * updates the backing Redis when {@link SessionRepository#save(Session)} is invoked. + * In a web environment this happens just before the HTTP response is committed. + *

+ * Setting the value to {@code IMMEDIATE} will ensure that the any updates to the + * Session are immediately written to the Redis instance. + * @return the {@link FlushMode} to use + * @since 2.2.0 + */ + FlushMode flushMode() default FlushMode.ON_SAVE; + /** * The cron expression for expired session cleanup job. By default runs every minute. * @return the session cleanup cron expression diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.java index 5d305206..1d3420a5 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.java @@ -48,6 +48,7 @@ import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar; +import org.springframework.session.FlushMode; import org.springframework.session.MapSession; import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration; import org.springframework.session.data.redis.RedisFlushMode; @@ -83,7 +84,7 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio private String redisNamespace = RedisOperationsSessionRepository.DEFAULT_NAMESPACE; - private RedisFlushMode redisFlushMode = RedisFlushMode.ON_SAVE; + private FlushMode flushMode = FlushMode.ON_SAVE; private String cleanupCron = DEFAULT_CLEANUP_CRON; @@ -115,7 +116,7 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio if (StringUtils.hasText(this.redisNamespace)) { sessionRepository.setRedisKeyNamespace(this.redisNamespace); } - sessionRepository.setRedisFlushMode(this.redisFlushMode); + sessionRepository.setFlushMode(this.flushMode); int database = resolveDatabase(); sessionRepository.setDatabase(database); return sessionRepository; @@ -153,9 +154,15 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio this.redisNamespace = namespace; } + @Deprecated public void setRedisFlushMode(RedisFlushMode redisFlushMode) { Assert.notNull(redisFlushMode, "redisFlushMode cannot be null"); - this.redisFlushMode = redisFlushMode; + setFlushMode(redisFlushMode.getFlushMode()); + } + + public void setFlushMode(FlushMode flushMode) { + Assert.notNull(flushMode, "flushMode cannot be null"); + this.flushMode = flushMode; } public void setCleanupCron(String cleanupCron) { @@ -217,6 +224,7 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio } @Override + @SuppressWarnings("deprecation") public void setImportMetadata(AnnotationMetadata importMetadata) { Map attributeMap = importMetadata .getAnnotationAttributes(EnableRedisHttpSession.class.getName()); @@ -226,7 +234,12 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio if (StringUtils.hasText(redisNamespaceValue)) { this.redisNamespace = this.embeddedValueResolver.resolveStringValue(redisNamespaceValue); } - this.redisFlushMode = attributes.getEnum("redisFlushMode"); + FlushMode flushMode = attributes.getEnum("flushMode"); + RedisFlushMode redisFlushMode = attributes.getEnum("redisFlushMode"); + if (flushMode == FlushMode.ON_SAVE && redisFlushMode != RedisFlushMode.ON_SAVE) { + flushMode = redisFlushMode.getFlushMode(); + } + this.flushMode = flushMode; String cleanupCron = attributes.getString("cleanupCron"); if (StringUtils.hasText(cleanupCron)) { this.cleanupCron = cleanupCron; diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisOperationsSessionRepositoryTests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisOperationsSessionRepositoryTests.java index ce90ce6f..b95765d9 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisOperationsSessionRepositoryTests.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisOperationsSessionRepositoryTests.java @@ -46,6 +46,7 @@ import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.session.FindByIndexNameSessionRepository; +import org.springframework.session.FlushMode; import org.springframework.session.MapSession; import org.springframework.session.Session; import org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession; @@ -636,7 +637,7 @@ class RedisOperationsSessionRepositoryTests { given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); - this.redisRepository.setRedisFlushMode(RedisFlushMode.IMMEDIATE); + this.redisRepository.setFlushMode(FlushMode.IMMEDIATE); RedisSession session = this.redisRepository.createSession(); Map delta = getDelta(); @@ -655,7 +656,7 @@ class RedisOperationsSessionRepositoryTests { given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); this.redisRepository.setDefaultMaxInactiveInterval(60); - this.redisRepository.setRedisFlushMode(RedisFlushMode.IMMEDIATE); + this.redisRepository.setFlushMode(FlushMode.IMMEDIATE); this.redisRepository.createSession(); Map delta = getDelta(); assertThat(delta.size()).isEqualTo(3); @@ -668,7 +669,7 @@ class RedisOperationsSessionRepositoryTests { given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); - this.redisRepository.setRedisFlushMode(RedisFlushMode.IMMEDIATE); + this.redisRepository.setFlushMode(FlushMode.IMMEDIATE); RedisSession session = this.redisRepository.createSession(); String attrName = "someAttribute"; session.setAttribute(attrName, "someValue"); @@ -685,7 +686,7 @@ class RedisOperationsSessionRepositoryTests { given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); - this.redisRepository.setRedisFlushMode(RedisFlushMode.IMMEDIATE); + this.redisRepository.setFlushMode(FlushMode.IMMEDIATE); RedisSession session = this.redisRepository.createSession(); String attrName = "someAttribute"; session.removeAttribute(attrName); @@ -702,7 +703,7 @@ class RedisOperationsSessionRepositoryTests { given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); - this.redisRepository.setRedisFlushMode(RedisFlushMode.IMMEDIATE); + this.redisRepository.setFlushMode(FlushMode.IMMEDIATE); RedisSession session = this.redisRepository.createSession(); reset(this.boundHashOperations); @@ -718,7 +719,7 @@ class RedisOperationsSessionRepositoryTests { given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); - this.redisRepository.setRedisFlushMode(RedisFlushMode.IMMEDIATE); + this.redisRepository.setFlushMode(FlushMode.IMMEDIATE); RedisSession session = this.redisRepository.createSession(); session.setLastAccessedTime(Instant.now()); @@ -729,6 +730,12 @@ class RedisOperationsSessionRepositoryTests { map(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, session.getLastAccessedTime().toEpochMilli())); } + @Test + void setFlushModeNull() { + assertThatIllegalArgumentException().isThrownBy(() -> this.redisRepository.setFlushMode(null)) + .withMessage("flushMode cannot be null"); + } + @Test void setRedisFlushModeNull() { assertThatIllegalArgumentException().isThrownBy(() -> this.redisRepository.setRedisFlushMode(null)) diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/SimpleRedisOperationsSessionRepositoryTests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/SimpleRedisOperationsSessionRepositoryTests.java index 4720756f..65a27f9c 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/SimpleRedisOperationsSessionRepositoryTests.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/SimpleRedisOperationsSessionRepositoryTests.java @@ -33,6 +33,7 @@ import org.mockito.MockitoAnnotations; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisOperations; +import org.springframework.session.FlushMode; import org.springframework.session.MapSession; import org.springframework.session.data.redis.SimpleRedisOperationsSessionRepository.RedisSession; import org.springframework.test.util.ReflectionTestUtils; @@ -114,9 +115,8 @@ class SimpleRedisOperationsSessionRepositoryTests { @Test void setFlushMode_ValidFlushMode_ShouldSetFlushMode() { - this.sessionRepository.setFlushMode(RedisFlushMode.IMMEDIATE); - assertThat(ReflectionTestUtils.getField(this.sessionRepository, "flushMode")) - .isEqualTo(RedisFlushMode.IMMEDIATE); + this.sessionRepository.setFlushMode(FlushMode.IMMEDIATE); + assertThat(ReflectionTestUtils.getField(this.sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); } @Test @@ -145,7 +145,7 @@ class SimpleRedisOperationsSessionRepositoryTests { @Test void createSession_ImmediateFlushMode_ShouldCreateSession() { - this.sessionRepository.setFlushMode(RedisFlushMode.IMMEDIATE); + this.sessionRepository.setFlushMode(FlushMode.IMMEDIATE); RedisSession session = this.sessionRepository.createSession(); String key = getSessionKey(session.getId()); verify(this.sessionRedisOperations).opsForHash(); diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationTests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationTests.java index 5bc7f60c..6bd73110 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationTests.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationTests.java @@ -34,6 +34,8 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.mock.env.MockEnvironment; +import org.springframework.session.FlushMode; +import org.springframework.session.data.redis.RedisFlushMode; import org.springframework.session.data.redis.RedisOperationsSessionRepository; import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory; import org.springframework.test.util.ReflectionTestUtils; @@ -86,6 +88,42 @@ class RedisHttpSessionConfigurationTests { assertThat(ReflectionTestUtils.getField(configuration, "redisNamespace")).isEqualTo("customRedisNamespace"); } + @Test + void customFlushImmediately() { + registerAndRefresh(RedisConfig.class, CustomFlushImmediatelyConfiguration.class); + RedisOperationsSessionRepository sessionRepository = this.context + .getBean(RedisOperationsSessionRepository.class); + assertThat(sessionRepository).isNotNull(); + assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); + } + + @Test + void customFlushImmediatelyLegacy() { + registerAndRefresh(RedisConfig.class, CustomFlushImmediatelyLegacyConfiguration.class); + RedisOperationsSessionRepository sessionRepository = this.context + .getBean(RedisOperationsSessionRepository.class); + assertThat(sessionRepository).isNotNull(); + assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); + } + + @Test + void setCustomFlushImmediately() { + registerAndRefresh(RedisConfig.class, CustomFlushImmediatelySetConfiguration.class); + RedisOperationsSessionRepository sessionRepository = this.context + .getBean(RedisOperationsSessionRepository.class); + assertThat(sessionRepository).isNotNull(); + assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); + } + + @Test + void setCustomFlushImmediatelyLegacy() { + registerAndRefresh(RedisConfig.class, CustomFlushImmediatelySetLegacyConfiguration.class); + RedisOperationsSessionRepository sessionRepository = this.context + .getBean(RedisOperationsSessionRepository.class); + assertThat(sessionRepository).isNotNull(); + assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); + } + @Test void customCleanupCronAnnotation() { registerAndRefresh(RedisConfig.class, CustomCleanupCronExpressionAnnotationConfiguration.class); @@ -217,6 +255,36 @@ class RedisHttpSessionConfigurationTests { } + @Configuration + static class CustomFlushImmediatelySetConfiguration extends RedisHttpSessionConfiguration { + + CustomFlushImmediatelySetConfiguration() { + setFlushMode(FlushMode.IMMEDIATE); + } + + } + + @Configuration + static class CustomFlushImmediatelySetLegacyConfiguration extends RedisHttpSessionConfiguration { + + CustomFlushImmediatelySetLegacyConfiguration() { + setRedisFlushMode(RedisFlushMode.IMMEDIATE); + } + + } + + @Configuration + @EnableRedisHttpSession(flushMode = FlushMode.IMMEDIATE) + static class CustomFlushImmediatelyConfiguration { + + } + + @Configuration + @EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE) + static class CustomFlushImmediatelyLegacyConfiguration { + + } + @EnableRedisHttpSession(cleanupCron = CLEANUP_CRON_EXPRESSION) static class CustomCleanupCronExpressionAnnotationConfiguration { diff --git a/spring-session-docs/src/docs/asciidoc/index.adoc b/spring-session-docs/src/docs/asciidoc/index.adoc index 7b2658fa..d43abc7a 100644 --- a/spring-session-docs/src/docs/asciidoc/index.adoc +++ b/spring-session-docs/src/docs/asciidoc/index.adoc @@ -757,8 +757,8 @@ You can use the following attributes to customize the configuration: * *maxInactiveIntervalInSeconds*: The amount of time before the session expires, in seconds. * *redisNamespace*: Allows configuring an application specific namespace for the sessions. Redis keys and channel IDs start with the prefix of `:`. -* *redisFlushMode*: Allows specifying when data is written to Redis. The default is only when `save` is invoked on `SessionRepository`. -A value of `RedisFlushMode.IMMEDIATE` writes to Redis as soon as possible. +* *flushMode*: Allows specifying when data is written to Redis. The default is only when `save` is invoked on `SessionRepository`. +A value of `FlushMode.IMMEDIATE` writes to Redis as soon as possible. ===== Custom `RedisSerializer` @@ -1008,8 +1008,8 @@ You can use the following attributes to customize the configuration: * *maxInactiveIntervalInSeconds*: The amount of time before the session expires, in seconds * *redisNamespace*: Allows configuring an application specific namespace for the sessions. Redis keys and channel IDs start with q prefix of `:`. -* *redisFlushMode*: Allows specifying when data is written to Redis. The default is only when `save` is invoked on `ReactiveSessionRepository`. -A value of `RedisFlushMode.IMMEDIATE` writes to Redis as soon as possible. +* *flushMode*: Allows specifying when data is written to Redis. The default is only when `save` is invoked on `ReactiveSessionRepository`. +A value of `FlushMode.IMMEDIATE` writes to Redis as soon as possible. [[api-reactiveredisoperationssessionrepository-writes]] ===== Optimized Writes diff --git a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/HazelcastFlushMode.java b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/HazelcastFlushMode.java index 0461970c..eddd843c 100644 --- a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/HazelcastFlushMode.java +++ b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/HazelcastFlushMode.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2019 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. @@ -16,6 +16,7 @@ package org.springframework.session.hazelcast; +import org.springframework.session.FlushMode; import org.springframework.session.SessionRepository; /** @@ -23,7 +24,9 @@ import org.springframework.session.SessionRepository; * * @author Aleksandar Stojsavljevic * @since 1.3.0 + * @deprecated since 2.2.0 in favor of {@link FlushMode} */ +@Deprecated public enum HazelcastFlushMode { /** @@ -31,7 +34,7 @@ public enum HazelcastFlushMode { * {@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, + ON_SAVE(FlushMode.ON_SAVE), /** * Writes to Hazelcast as soon as possible. For example @@ -39,6 +42,16 @@ public enum HazelcastFlushMode { * Another example is that setting an attribute on the session will also write to * Hazelcast immediately. */ - IMMEDIATE + IMMEDIATE(FlushMode.IMMEDIATE); + + private final FlushMode flushMode; + + HazelcastFlushMode(FlushMode flushMode) { + this.flushMode = flushMode; + } + + public FlushMode getFlushMode() { + return this.flushMode; + } } 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 71a5e33d..15978b73 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 @@ -42,6 +42,7 @@ import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.session.DelegatingIndexResolver; import org.springframework.session.FindByIndexNameSessionRepository; +import org.springframework.session.FlushMode; import org.springframework.session.IndexResolver; import org.springframework.session.MapSession; import org.springframework.session.PrincipalNameIndexResolver; @@ -154,7 +155,7 @@ public class HazelcastSessionRepository private String sessionMapName = DEFAULT_SESSION_MAP_NAME; - private HazelcastFlushMode hazelcastFlushMode = HazelcastFlushMode.ON_SAVE; + private FlushMode flushMode = FlushMode.ON_SAVE; private IMap sessions; @@ -212,10 +213,20 @@ public class HazelcastSessionRepository * Sets the Hazelcast flush mode. Default flush mode is * {@link HazelcastFlushMode#ON_SAVE}. * @param hazelcastFlushMode the new Hazelcast flush mode + * @deprecated since 2.2.0 in favor of {@link #setFlushMode(FlushMode)} */ public void setHazelcastFlushMode(HazelcastFlushMode hazelcastFlushMode) { Assert.notNull(hazelcastFlushMode, "HazelcastFlushMode cannot be null"); - this.hazelcastFlushMode = hazelcastFlushMode; + setFlushMode(hazelcastFlushMode.getFlushMode()); + } + + /** + * Sets the Hazelcast flush mode. Default flush mode is {@link FlushMode#ON_SAVE}. + * @param flushMode the new Hazelcast flush mode + */ + public void setFlushMode(FlushMode flushMode) { + Assert.notNull(flushMode, "flushMode cannot be null"); + this.flushMode = flushMode; } @Override @@ -456,7 +467,7 @@ public class HazelcastSessionRepository } private void flushImmediateIfNecessary() { - if (HazelcastSessionRepository.this.hazelcastFlushMode == HazelcastFlushMode.IMMEDIATE) { + if (HazelcastSessionRepository.this.flushMode == FlushMode.IMMEDIATE) { HazelcastSessionRepository.this.save(this); } } diff --git a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/EnableHazelcastHttpSession.java b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/EnableHazelcastHttpSession.java index faeb63bb..971ea71c 100644 --- a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/EnableHazelcastHttpSession.java +++ b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/EnableHazelcastHttpSession.java @@ -26,6 +26,7 @@ import com.hazelcast.core.HazelcastInstance; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.session.FlushMode; import org.springframework.session.MapSession; import org.springframework.session.Session; import org.springframework.session.SessionRepository; @@ -94,7 +95,22 @@ public @interface EnableHazelcastHttpSession { * Session are immediately written to the Hazelcast instance. * @return the {@link HazelcastFlushMode} to use * @since 1.3.0 + * @deprecated since 2.2.0 in favor of {@link #flushMode()} */ + @Deprecated HazelcastFlushMode hazelcastFlushMode() default HazelcastFlushMode.ON_SAVE; + /** + * Flush mode for the Hazelcast sessions. The default is {@code ON_SAVE} which only + * updates the backing Hazelcast when {@link SessionRepository#save(Session)} is + * invoked. In a web environment this happens just before the HTTP response is + * committed. + *

+ * Setting the value to {@code IMMEDIATE} will ensure that the any updates to the + * Session are immediately written to the Hazelcast instance. + * @return the {@link FlushMode} to use + * @since 2.2.0 + */ + FlushMode flushMode() default FlushMode.ON_SAVE; + } diff --git a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfiguration.java b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfiguration.java index a8eb9b74..96abb1d3 100644 --- a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfiguration.java +++ b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfiguration.java @@ -28,6 +28,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportAware; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; +import org.springframework.session.FlushMode; import org.springframework.session.MapSession; import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration; import org.springframework.session.hazelcast.HazelcastFlushMode; @@ -53,7 +54,7 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur private String sessionMapName = HazelcastSessionRepository.DEFAULT_SESSION_MAP_NAME; - private HazelcastFlushMode hazelcastFlushMode = HazelcastFlushMode.ON_SAVE; + private FlushMode flushMode = FlushMode.ON_SAVE; private HazelcastInstance hazelcastInstance; @@ -67,7 +68,7 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur sessionRepository.setSessionMapName(this.sessionMapName); } sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds); - sessionRepository.setHazelcastFlushMode(this.hazelcastFlushMode); + sessionRepository.setFlushMode(this.flushMode); return sessionRepository; } @@ -79,8 +80,13 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur this.sessionMapName = sessionMapName; } + @Deprecated public void setHazelcastFlushMode(HazelcastFlushMode hazelcastFlushMode) { - this.hazelcastFlushMode = hazelcastFlushMode; + setFlushMode(hazelcastFlushMode.getFlushMode()); + } + + public void setFlushMode(FlushMode flushMode) { + this.flushMode = flushMode; } @Autowired @@ -100,6 +106,7 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur } @Override + @SuppressWarnings("deprecation") public void setImportMetadata(AnnotationMetadata importMetadata) { Map attributeMap = importMetadata .getAnnotationAttributes(EnableHazelcastHttpSession.class.getName()); @@ -109,7 +116,12 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur if (StringUtils.hasText(sessionMapNameValue)) { this.sessionMapName = sessionMapNameValue; } - this.hazelcastFlushMode = attributes.getEnum("hazelcastFlushMode"); + FlushMode flushMode = attributes.getEnum("flushMode"); + HazelcastFlushMode hazelcastFlushMode = attributes.getEnum("hazelcastFlushMode"); + if (flushMode == FlushMode.ON_SAVE && hazelcastFlushMode != HazelcastFlushMode.ON_SAVE) { + flushMode = hazelcastFlushMode.getFlushMode(); + } + this.flushMode = flushMode; } } 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 ade6efa9..6742abc3 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 @@ -35,6 +35,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.session.FindByIndexNameSessionRepository; +import org.springframework.session.FlushMode; import org.springframework.session.MapSession; import org.springframework.session.hazelcast.HazelcastSessionRepository.HazelcastSession; @@ -122,7 +123,7 @@ class HazelcastSessionRepositoryTests { void saveNewFlushModeImmediate() { verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean()); - this.repository.setHazelcastFlushMode(HazelcastFlushMode.IMMEDIATE); + this.repository.setFlushMode(FlushMode.IMMEDIATE); HazelcastSession session = this.repository.createSession(); verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class), @@ -148,7 +149,7 @@ class HazelcastSessionRepositoryTests { void saveUpdatedAttributeFlushModeImmediate() { verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean()); - this.repository.setHazelcastFlushMode(HazelcastFlushMode.IMMEDIATE); + this.repository.setFlushMode(FlushMode.IMMEDIATE); HazelcastSession session = this.repository.createSession(); session.setAttribute("testName", "testValue"); @@ -178,7 +179,7 @@ class HazelcastSessionRepositoryTests { void removeAttributeFlushModeImmediate() { verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean()); - this.repository.setHazelcastFlushMode(HazelcastFlushMode.IMMEDIATE); + this.repository.setFlushMode(FlushMode.IMMEDIATE); HazelcastSession session = this.repository.createSession(); session.removeAttribute("testName"); @@ -208,7 +209,7 @@ class HazelcastSessionRepositoryTests { void saveUpdatedLastAccessedTimeFlushModeImmediate() { verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean()); - this.repository.setHazelcastFlushMode(HazelcastFlushMode.IMMEDIATE); + this.repository.setFlushMode(FlushMode.IMMEDIATE); HazelcastSession session = this.repository.createSession(); session.setLastAccessedTime(Instant.now()); @@ -238,7 +239,7 @@ class HazelcastSessionRepositoryTests { void saveUpdatedMaxInactiveIntervalInSecondsFlushModeImmediate() { verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean()); - this.repository.setHazelcastFlushMode(HazelcastFlushMode.IMMEDIATE); + this.repository.setFlushMode(FlushMode.IMMEDIATE); HazelcastSession session = this.repository.createSession(); String sessionId = session.getId(); @@ -269,7 +270,7 @@ class HazelcastSessionRepositoryTests { void saveUnchangedFlushModeImmediate() { verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean()); - this.repository.setHazelcastFlushMode(HazelcastFlushMode.IMMEDIATE); + this.repository.setFlushMode(FlushMode.IMMEDIATE); HazelcastSession session = this.repository.createSession(); verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class), diff --git a/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfigurationTests.java b/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfigurationTests.java index ac4459cb..379d951e 100644 --- a/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfigurationTests.java +++ b/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfigurationTests.java @@ -26,6 +26,7 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import org.springframework.session.FlushMode; import org.springframework.session.hazelcast.HazelcastFlushMode; import org.springframework.session.hazelcast.HazelcastSessionRepository; import org.springframework.session.hazelcast.config.annotation.SpringSessionHazelcastInstance; @@ -49,8 +50,6 @@ class HazelcastHttpSessionConfigurationTests { private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600; - private static final HazelcastFlushMode HAZELCAST_FLUSH_MODE = HazelcastFlushMode.IMMEDIATE; - private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); @AfterEach @@ -120,8 +119,16 @@ class HazelcastHttpSessionConfigurationTests { HazelcastSessionRepository repository = this.context.getBean(HazelcastSessionRepository.class); assertThat(repository).isNotNull(); - assertThat(ReflectionTestUtils.getField(repository, "hazelcastFlushMode")) - .isEqualTo(HazelcastFlushMode.IMMEDIATE); + assertThat(ReflectionTestUtils.getField(repository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); + } + + @Test + void customFlushImmediatelyLegacy() { + registerAndRefresh(CustomFlushImmediatelyLegacyConfiguration.class); + + HazelcastSessionRepository repository = this.context.getBean(HazelcastSessionRepository.class); + assertThat(repository).isNotNull(); + assertThat(ReflectionTestUtils.getField(repository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); } @Test @@ -130,8 +137,16 @@ class HazelcastHttpSessionConfigurationTests { HazelcastSessionRepository repository = this.context.getBean(HazelcastSessionRepository.class); assertThat(repository).isNotNull(); - assertThat(ReflectionTestUtils.getField(repository, "hazelcastFlushMode")) - .isEqualTo(HazelcastFlushMode.IMMEDIATE); + assertThat(ReflectionTestUtils.getField(repository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); + } + + @Test + void setCustomFlushImmediatelyLegacy() { + registerAndRefresh(BaseConfiguration.class, CustomFlushImmediatelySetLegacyConfiguration.class); + + HazelcastSessionRepository repository = this.context.getBean(HazelcastSessionRepository.class); + assertThat(repository).isNotNull(); + assertThat(ReflectionTestUtils.getField(repository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); } @Test @@ -256,17 +271,32 @@ class HazelcastHttpSessionConfigurationTests { static class CustomFlushImmediatelySetConfiguration extends HazelcastHttpSessionConfiguration { CustomFlushImmediatelySetConfiguration() { - setHazelcastFlushMode(HAZELCAST_FLUSH_MODE); + setFlushMode(FlushMode.IMMEDIATE); } } @Configuration - @EnableHazelcastHttpSession(hazelcastFlushMode = HazelcastFlushMode.IMMEDIATE) + static class CustomFlushImmediatelySetLegacyConfiguration extends HazelcastHttpSessionConfiguration { + + CustomFlushImmediatelySetLegacyConfiguration() { + setHazelcastFlushMode(HazelcastFlushMode.IMMEDIATE); + } + + } + + @Configuration + @EnableHazelcastHttpSession(flushMode = FlushMode.IMMEDIATE) static class CustomFlushImmediatelyConfiguration extends BaseConfiguration { } + @Configuration + @EnableHazelcastHttpSession(hazelcastFlushMode = HazelcastFlushMode.IMMEDIATE) + static class CustomFlushImmediatelyLegacyConfiguration extends BaseConfiguration { + + } + @Configuration @EnableHazelcastHttpSession static class QualifiedHazelcastInstanceConfiguration extends BaseConfiguration {