diff --git a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryITests.java b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryITests.java index 243104fd..fe17d513 100644 --- a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryITests.java +++ b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryITests.java @@ -39,7 +39,7 @@ import org.springframework.session.Session; import org.springframework.session.data.SessionEventRegistry; import org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession; import org.springframework.session.data.redis.config.annotation.SpringSessionRedisOperations; -import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; +import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisIndexedHttpSession; import org.springframework.session.events.SessionCreatedEvent; import org.springframework.session.events.SessionDestroyedEvent; import org.springframework.test.context.ContextConfiguration; @@ -691,7 +691,7 @@ class RedisIndexedSessionRepositoryITests extends AbstractRedisITests { } @Configuration - @EnableRedisHttpSession(redisNamespace = "RedisIndexedSessionRepositoryITests") + @EnableRedisIndexedHttpSession(redisNamespace = "RedisIndexedSessionRepositoryITests") static class Config extends BaseConfig { @Bean diff --git a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisSessionRepositoryITests.java b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisSessionRepositoryITests.java index afe39696..6ea5963a 100644 --- a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisSessionRepositoryITests.java +++ b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisSessionRepositoryITests.java @@ -26,13 +26,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; import org.springframework.session.MapSession; -import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; import org.springframework.session.data.redis.RedisSessionRepository.RedisSession; +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; import org.springframework.test.context.web.WebAppConfiguration; @@ -223,17 +220,9 @@ class RedisSessionRepositoryITests extends AbstractRedisITests { } @Configuration - @EnableSpringHttpSession + @EnableRedisHttpSession static class Config extends BaseConfig { - @Bean - RedisSessionRepository sessionRepository(RedisConnectionFactory redisConnectionFactory) { - RedisTemplate redisTemplate = new RedisTemplate<>(); - redisTemplate.setConnectionFactory(redisConnectionFactory); - redisTemplate.afterPropertiesSet(); - return new RedisSessionRepository(redisTemplate); - } - } } diff --git a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisHttpSessionExpireSessionDestroyedTests.java b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisIndexedHttpSessionExpireSessionDestroyedTests.java similarity index 95% rename from spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisHttpSessionExpireSessionDestroyedTests.java rename to spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisIndexedHttpSessionExpireSessionDestroyedTests.java index bc91c15f..744ba6d7 100644 --- a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisHttpSessionExpireSessionDestroyedTests.java +++ b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisIndexedHttpSessionExpireSessionDestroyedTests.java @@ -44,7 +44,7 @@ import static org.assertj.core.api.Assertions.assertThat; @ExtendWith(SpringExtension.class) @ContextConfiguration @WebAppConfiguration -class EnableRedisHttpSessionExpireSessionDestroyedTests extends AbstractRedisITests { +class EnableRedisIndexedHttpSessionExpireSessionDestroyedTests extends AbstractRedisITests { @Autowired private SessionRepository repository; @@ -113,7 +113,7 @@ class EnableRedisHttpSessionExpireSessionDestroyedTests exten } @Configuration - @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1) + @EnableRedisIndexedHttpSession(maxInactiveIntervalInSeconds = 1) static class Config extends BaseConfig { @Bean diff --git a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/taskexecutor/RedisListenerContainerTaskExecutorITests.java b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/taskexecutor/RedisListenerContainerTaskExecutorITests.java index 8176d2a1..0345e123 100644 --- a/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/taskexecutor/RedisListenerContainerTaskExecutorITests.java +++ b/spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/taskexecutor/RedisListenerContainerTaskExecutorITests.java @@ -32,7 +32,7 @@ import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.RedisOperations; import org.springframework.session.data.redis.AbstractRedisITests; import org.springframework.session.data.redis.config.annotation.SpringSessionRedisOperations; -import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; +import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisIndexedHttpSession; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; @@ -101,7 +101,7 @@ class RedisListenerContainerTaskExecutorITests extends AbstractRedisITests { } @Configuration - @EnableRedisHttpSession(redisNamespace = "RedisListenerContainerTaskExecutorITests") + @EnableRedisIndexedHttpSession(redisNamespace = "RedisListenerContainerTaskExecutorITests") static class Config extends BaseConfig { @Bean diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisIndexedSessionRepository.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisIndexedSessionRepository.java index 19f3e0a2..60e82ed0 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisIndexedSessionRepository.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisIndexedSessionRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-2022 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. @@ -288,7 +288,7 @@ public class RedisIndexedSessionRepository private byte[] expiredKeyPrefixBytes; - private final RedisOperations sessionRedisOperations; + private final RedisOperations sessionRedisOperations; private final RedisSessionExpirationPolicy expirationPolicy; @@ -314,7 +314,7 @@ public class RedisIndexedSessionRepository * @param sessionRedisOperations the {@link RedisOperations} to use for managing the * sessions. Cannot be null. */ - public RedisIndexedSessionRepository(RedisOperations sessionRedisOperations) { + public RedisIndexedSessionRepository(RedisOperations sessionRedisOperations) { Assert.notNull(sessionRedisOperations, "sessionRedisOperations cannot be null"); this.sessionRedisOperations = sessionRedisOperations; this.expirationPolicy = new RedisSessionExpirationPolicy(sessionRedisOperations, this::getExpirationsKey, @@ -406,7 +406,7 @@ public class RedisIndexedSessionRepository * Returns the {@link RedisOperations} used for sessions. * @return the {@link RedisOperations} used for sessions */ - public RedisOperations getSessionRedisOperations() { + public RedisOperations getSessionRedisOperations() { return this.sessionRedisOperations; } @@ -454,7 +454,7 @@ public class RedisIndexedSessionRepository * @return the Redis session */ private RedisSession getSession(String id, boolean allowExpired) { - Map entries = getSessionBoundHashOperations(id).entries(); + Map entries = getSessionBoundHashOperations(id).entries(); if (entries.isEmpty()) { return null; } @@ -467,10 +467,10 @@ public class RedisIndexedSessionRepository return result; } - private MapSession loadSession(String id, Map entries) { + private MapSession loadSession(String id, Map entries) { MapSession loaded = new MapSession(id); - for (Map.Entry entry : entries.entrySet()) { - String key = (String) entry.getKey(); + for (Map.Entry entry : entries.entrySet()) { + String key = entry.getKey(); if (RedisSessionMapper.CREATION_TIME_KEY.equals(key)) { loaded.setCreationTime(Instant.ofEpochMilli((long) entry.getValue())); } @@ -522,7 +522,7 @@ public class RedisIndexedSessionRepository if (ByteUtils.startsWith(messageChannel, this.sessionCreatedChannelPrefixBytes)) { // TODO: is this thread safe? @SuppressWarnings("unchecked") - Map loaded = (Map) this.defaultSerializer.deserialize(message.getBody()); + Map loaded = (Map) this.defaultSerializer.deserialize(message.getBody()); handleCreated(loaded, new String(messageChannel)); return; } @@ -571,7 +571,7 @@ public class RedisIndexedSessionRepository } } - private void handleCreated(Map loaded, String channel) { + private void handleCreated(Map loaded, String channel) { String id = channel.substring(channel.lastIndexOf(":") + 1); Session session = loadSession(id, loaded); publishEvent(new SessionCreatedEvent(this, session)); @@ -661,7 +661,7 @@ public class RedisIndexedSessionRepository * @param sessionId the id of the {@link Session} to work with * @return the {@link BoundHashOperations} to operate on a {@link Session} */ - private BoundHashOperations getSessionBoundHashOperations(String sessionId) { + private BoundHashOperations getSessionBoundHashOperations(String sessionId) { String key = getSessionKey(sessionId); return this.sessionRedisOperations.boundHashOps(key); } diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisSessionExpirationPolicy.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisSessionExpirationPolicy.java index d67fbc83..33097813 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisSessionExpirationPolicy.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisSessionExpirationPolicy.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-2022 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. @@ -52,13 +52,13 @@ final class RedisSessionExpirationPolicy { private static final String SESSION_EXPIRES_PREFIX = "expires:"; - private final RedisOperations redis; + private final RedisOperations redis; private final Function lookupExpirationKey; private final Function lookupSessionKey; - RedisSessionExpirationPolicy(RedisOperations sessionRedisOperations, + RedisSessionExpirationPolicy(RedisOperations sessionRedisOperations, Function lookupExpirationKey, Function lookupSessionKey) { super(); this.redis = sessionRedisOperations; @@ -96,7 +96,7 @@ final class RedisSessionExpirationPolicy { } String expireKey = getExpirationKey(toExpire); - BoundSetOperations expireOperations = this.redis.boundSetOps(expireKey); + BoundSetOperations expireOperations = this.redis.boundSetOps(expireKey); expireOperations.add(keyToExpire); long fiveMinutesAfterExpires = sessionExpireInSeconds + TimeUnit.MINUTES.toSeconds(5); diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/AbstractRedisHttpSessionConfiguration.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/AbstractRedisHttpSessionConfiguration.java new file mode 100644 index 00000000..34a953c3 --- /dev/null +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/AbstractRedisHttpSessionConfiguration.java @@ -0,0 +1,159 @@ +/* + * Copyright 2014-2022 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.data.redis.config.annotation.web.http; + +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.session.FlushMode; +import org.springframework.session.MapSession; +import org.springframework.session.SaveMode; +import org.springframework.session.Session; +import org.springframework.session.SessionRepository; +import org.springframework.session.config.SessionRepositoryCustomizer; +import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration; +import org.springframework.session.data.redis.RedisSessionRepository; +import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory; +import org.springframework.util.Assert; + +/** + * Base configuration class for Redis based {@link SessionRepository} implementations. + * + * @param the {@link SessionRepository} type + * @author Vedran Pavic + * @since 3.0.0 + * @see RedisHttpSessionConfiguration + * @see RedisIndexedHttpSessionConfiguration + * @see SpringSessionRedisConnectionFactory + */ +@Configuration(proxyBeanMethods = false) +public abstract class AbstractRedisHttpSessionConfiguration> + extends SpringHttpSessionConfiguration implements BeanClassLoaderAware { + + private Integer maxInactiveIntervalInSeconds = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS; + + private String redisNamespace = RedisSessionRepository.DEFAULT_KEY_NAMESPACE; + + private FlushMode flushMode = FlushMode.ON_SAVE; + + private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE; + + private RedisConnectionFactory redisConnectionFactory; + + private RedisSerializer defaultRedisSerializer; + + private List> sessionRepositoryCustomizers; + + private ClassLoader classLoader; + + public abstract T sessionRepository(); + + public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) { + this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds; + } + + protected Integer getMaxInactiveIntervalInSeconds() { + return this.maxInactiveIntervalInSeconds; + } + + public void setRedisNamespace(String namespace) { + Assert.hasText(namespace, "namespace must not be empty"); + this.redisNamespace = namespace; + } + + protected String getRedisNamespace() { + return this.redisNamespace; + } + + public void setFlushMode(FlushMode flushMode) { + Assert.notNull(flushMode, "flushMode must not be null"); + this.flushMode = flushMode; + } + + protected FlushMode getFlushMode() { + return this.flushMode; + } + + public void setSaveMode(SaveMode saveMode) { + Assert.notNull(saveMode, "saveMode must not be null"); + this.saveMode = saveMode; + } + + protected SaveMode getSaveMode() { + return this.saveMode; + } + + @Autowired + public void setRedisConnectionFactory( + @SpringSessionRedisConnectionFactory ObjectProvider springSessionRedisConnectionFactory, + ObjectProvider redisConnectionFactory) { + this.redisConnectionFactory = springSessionRedisConnectionFactory + .getIfAvailable(redisConnectionFactory::getObject); + } + + protected RedisConnectionFactory getRedisConnectionFactory() { + return this.redisConnectionFactory; + } + + @Autowired(required = false) + @Qualifier("springSessionDefaultRedisSerializer") + public void setDefaultRedisSerializer(RedisSerializer defaultRedisSerializer) { + this.defaultRedisSerializer = defaultRedisSerializer; + } + + protected RedisSerializer getDefaultRedisSerializer() { + return this.defaultRedisSerializer; + } + + @Autowired(required = false) + public void setSessionRepositoryCustomizer( + ObjectProvider> sessionRepositoryCustomizers) { + this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList()); + } + + protected List> getSessionRepositoryCustomizers() { + return this.sessionRepositoryCustomizers; + } + + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + protected RedisTemplate createRedisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + if (getDefaultRedisSerializer() != null) { + redisTemplate.setDefaultSerializer(getDefaultRedisSerializer()); + } + redisTemplate.setConnectionFactory(getRedisConnectionFactory()); + redisTemplate.setBeanClassLoader(this.classLoader); + redisTemplate.afterPropertiesSet(); + return redisTemplate; + } + +} 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 125ab8aa..df59fc61 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 @@ -31,14 +31,14 @@ import org.springframework.session.SaveMode; import org.springframework.session.Session; import org.springframework.session.SessionRepository; import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; -import org.springframework.session.data.redis.RedisIndexedSessionRepository; +import org.springframework.session.data.redis.RedisSessionRepository; import org.springframework.session.web.http.SessionRepositoryFilter; /** * Add this annotation to an {@code @Configuration} class to expose the * {@link SessionRepositoryFilter} as a bean named {@code springSessionRepositoryFilter} - * and backed by Redis. In order to leverage the annotation, a single - * {@link RedisConnectionFactory} must be provided. For example: + * and backed by {@link RedisSessionRepository}. In order to leverage the annotation, a + * single {@link RedisConnectionFactory} must be provided. For example: * *
  * @Configuration
@@ -84,7 +84,7 @@ public @interface EnableRedisHttpSession {
 	 * the applications and they could function within the same Redis instance.
 	 * @return the unique namespace for keys
 	 */
-	String redisNamespace() default RedisIndexedSessionRepository.DEFAULT_NAMESPACE;
+	String redisNamespace() default RedisSessionRepository.DEFAULT_KEY_NAMESPACE;
 
 	/**
 	 * Flush mode for the Redis sessions. The default is {@code ON_SAVE} which only
@@ -98,13 +98,6 @@ public @interface EnableRedisHttpSession {
 	 */
 	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
-	 * @since 2.0.0
-	 */
-	String cleanupCron() default RedisHttpSessionConfiguration.DEFAULT_CLEANUP_CRON;
-
 	/**
 	 * Save mode for the session. The default is {@link SaveMode#ON_SET_ATTRIBUTE}, which
 	 * only saves changes made to session.
diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisIndexedHttpSession.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisIndexedHttpSession.java
new file mode 100644
index 00000000..f483fe33
--- /dev/null
+++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisIndexedHttpSession.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2014-2022 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.data.redis.config.annotation.web.http;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+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.SaveMode;
+import org.springframework.session.Session;
+import org.springframework.session.SessionRepository;
+import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
+import org.springframework.session.data.redis.RedisIndexedSessionRepository;
+import org.springframework.session.web.http.SessionRepositoryFilter;
+
+/**
+ * Add this annotation to an {@code @Configuration} class to expose the
+ * {@link SessionRepositoryFilter} as a bean named {@code springSessionRepositoryFilter}
+ * and backed by {@link RedisIndexedSessionRepository}. In order to leverage the
+ * annotation, a single {@link RedisConnectionFactory} must be provided. For example:
+ *
+ * 
+ * @Configuration
+ * @EnableRedisIndexedHttpSession
+ * public class RedisHttpSessionConfig {
+ *
+ *     @Bean
+ *     public LettuceConnectionFactory redisConnectionFactory() {
+ *         return new LettuceConnectionFactory();
+ *     }
+ *
+ * }
+ * 
+ * + * More advanced configurations can extend {@link RedisIndexedHttpSessionConfiguration} + * instead. + * + * @author Vedran Pavic + * @since 3.0.0 + * @see EnableSpringHttpSession + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@Import(RedisIndexedHttpSessionConfiguration.class) +@Configuration(proxyBeanMethods = false) +public @interface EnableRedisIndexedHttpSession { + + /** + * The session timeout in seconds. By default, it is set to 1800 seconds (30 minutes). + * This should be a non-negative integer. + * @return the seconds a session can be inactive before expiring + */ + int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS; + + /** + * Defines a unique namespace for keys. The value is used to isolate sessions by + * changing the prefix from default {@code spring:session:} to + * {@code :}. + *

+ * For example, if you had an application named "Application A" that needed to keep + * the sessions isolated from "Application B" you could set two different values for + * the applications and they could function within the same Redis instance. + * @return the unique namespace for keys + */ + String redisNamespace() default RedisIndexedSessionRepository.DEFAULT_NAMESPACE; + + /** + * 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 + */ + FlushMode flushMode() default FlushMode.ON_SAVE; + + /** + * Save mode for the session. The default is {@link SaveMode#ON_SET_ATTRIBUTE}, which + * only saves changes made to session. + * @return the save mode + */ + SaveMode saveMode() default SaveMode.ON_SET_ATTRIBUTE; + + /** + * The cron expression for expired session cleanup job. By default runs every minute. + * @return the session cleanup cron expression + */ + String cleanupCron() default RedisIndexedHttpSessionConfiguration.DEFAULT_CLEANUP_CRON; + +} 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 49dec20c..443095c8 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 @@ -16,61 +16,26 @@ package org.springframework.session.data.redis.config.annotation.web.http; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import java.time.Duration; import java.util.Map; -import java.util.concurrent.Executor; -import java.util.stream.Collectors; -import org.apache.commons.logging.LogFactory; - -import org.springframework.beans.factory.BeanClassLoaderAware; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.context.annotation.Bean; 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.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.listener.ChannelTopic; -import org.springframework.data.redis.listener.PatternTopic; -import org.springframework.data.redis.listener.RedisMessageListenerContainer; -import org.springframework.data.redis.serializer.RedisSerializer; -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.IndexResolver; -import org.springframework.session.MapSession; -import org.springframework.session.SaveMode; -import org.springframework.session.Session; -import org.springframework.session.config.SessionRepositoryCustomizer; -import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration; -import org.springframework.session.data.redis.RedisIndexedSessionRepository; -import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction; -import org.springframework.session.data.redis.config.ConfigureRedisAction; -import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory; +import org.springframework.session.data.redis.RedisSessionRepository; import org.springframework.session.web.http.SessionRepositoryFilter; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import org.springframework.util.StringValueResolver; /** * Exposes the {@link SessionRepositoryFilter} as a bean named - * {@code springSessionRepositoryFilter}. In order to use this a single - * {@link RedisConnectionFactory} must be exposed as a Bean. + * {@code springSessionRepositoryFilter} backed by {@link RedisSessionRepository}. In + * order to use this a single {@link RedisConnectionFactory} must be exposed as a Bean. * * @author Rob Winch * @author Eddú Meléndez @@ -79,170 +44,27 @@ import org.springframework.util.StringValueResolver; * @see EnableRedisHttpSession */ @Configuration(proxyBeanMethods = false) -public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration - implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware { - - static final String DEFAULT_CLEANUP_CRON = "0 * * * * *"; - - private Integer maxInactiveIntervalInSeconds = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS; - - private String redisNamespace = RedisIndexedSessionRepository.DEFAULT_NAMESPACE; - - private FlushMode flushMode = FlushMode.ON_SAVE; - - private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE; - - private String cleanupCron = DEFAULT_CLEANUP_CRON; - - private ConfigureRedisAction configureRedisAction = new ConfigureNotifyKeyspaceEventsAction(); - - private RedisConnectionFactory redisConnectionFactory; - - private IndexResolver indexResolver; - - private RedisSerializer defaultRedisSerializer; - - private ApplicationEventPublisher applicationEventPublisher; - - private Executor redisTaskExecutor; - - private Executor redisSubscriptionExecutor; - - private List> sessionRepositoryCustomizers; - - private ClassLoader classLoader; +public class RedisHttpSessionConfiguration extends AbstractRedisHttpSessionConfiguration + implements EmbeddedValueResolverAware, ImportAware { private StringValueResolver embeddedValueResolver; @Bean - public RedisIndexedSessionRepository sessionRepository() { - RedisTemplate redisTemplate = createRedisTemplate(); - RedisIndexedSessionRepository sessionRepository = new RedisIndexedSessionRepository(redisTemplate); - sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher); - if (this.indexResolver != null) { - sessionRepository.setIndexResolver(this.indexResolver); + @Override + public RedisSessionRepository sessionRepository() { + RedisTemplate redisTemplate = createRedisTemplate(); + RedisSessionRepository sessionRepository = new RedisSessionRepository(redisTemplate); + sessionRepository.setDefaultMaxInactiveInterval(Duration.ofSeconds(getMaxInactiveIntervalInSeconds())); + if (StringUtils.hasText(getRedisNamespace())) { + sessionRepository.setRedisKeyNamespace(getRedisNamespace()); } - if (this.defaultRedisSerializer != null) { - sessionRepository.setDefaultSerializer(this.defaultRedisSerializer); - } - sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds); - if (StringUtils.hasText(this.redisNamespace)) { - sessionRepository.setRedisKeyNamespace(this.redisNamespace); - } - sessionRepository.setFlushMode(this.flushMode); - sessionRepository.setSaveMode(this.saveMode); - int database = resolveDatabase(); - sessionRepository.setDatabase(database); - this.sessionRepositoryCustomizers + sessionRepository.setFlushMode(getFlushMode()); + sessionRepository.setSaveMode(getSaveMode()); + getSessionRepositoryCustomizers() .forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository)); return sessionRepository; } - @Bean - public RedisMessageListenerContainer springSessionRedisMessageListenerContainer( - RedisIndexedSessionRepository sessionRepository) { - RedisMessageListenerContainer container = new RedisMessageListenerContainer(); - container.setConnectionFactory(this.redisConnectionFactory); - if (this.redisTaskExecutor != null) { - container.setTaskExecutor(this.redisTaskExecutor); - } - if (this.redisSubscriptionExecutor != null) { - container.setSubscriptionExecutor(this.redisSubscriptionExecutor); - } - container.addMessageListener(sessionRepository, - Arrays.asList(new ChannelTopic(sessionRepository.getSessionDeletedChannel()), - new ChannelTopic(sessionRepository.getSessionExpiredChannel()))); - container.addMessageListener(sessionRepository, - Collections.singletonList(new PatternTopic(sessionRepository.getSessionCreatedChannelPrefix() + "*"))); - return container; - } - - @Bean - public InitializingBean enableRedisKeyspaceNotificationsInitializer() { - return new EnableRedisKeyspaceNotificationsInitializer(this.redisConnectionFactory, this.configureRedisAction); - } - - public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) { - this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds; - } - - public void setRedisNamespace(String namespace) { - this.redisNamespace = namespace; - } - - public void setFlushMode(FlushMode flushMode) { - Assert.notNull(flushMode, "flushMode cannot be null"); - this.flushMode = flushMode; - } - - public void setSaveMode(SaveMode saveMode) { - this.saveMode = saveMode; - } - - public void setCleanupCron(String cleanupCron) { - this.cleanupCron = cleanupCron; - } - - /** - * Sets the action to perform for configuring Redis. - * @param configureRedisAction the configureRedis to set. The default is - * {@link ConfigureNotifyKeyspaceEventsAction}. - */ - @Autowired(required = false) - public void setConfigureRedisAction(ConfigureRedisAction configureRedisAction) { - this.configureRedisAction = configureRedisAction; - } - - @Autowired - public void setRedisConnectionFactory( - @SpringSessionRedisConnectionFactory ObjectProvider springSessionRedisConnectionFactory, - ObjectProvider redisConnectionFactory) { - RedisConnectionFactory redisConnectionFactoryToUse = springSessionRedisConnectionFactory.getIfAvailable(); - if (redisConnectionFactoryToUse == null) { - redisConnectionFactoryToUse = redisConnectionFactory.getObject(); - } - this.redisConnectionFactory = redisConnectionFactoryToUse; - } - - @Autowired(required = false) - @Qualifier("springSessionDefaultRedisSerializer") - public void setDefaultRedisSerializer(RedisSerializer defaultRedisSerializer) { - this.defaultRedisSerializer = defaultRedisSerializer; - } - - @Autowired - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - this.applicationEventPublisher = applicationEventPublisher; - } - - @Autowired(required = false) - public void setIndexResolver(IndexResolver indexResolver) { - this.indexResolver = indexResolver; - } - - @Autowired(required = false) - @Qualifier("springSessionRedisTaskExecutor") - public void setRedisTaskExecutor(Executor redisTaskExecutor) { - this.redisTaskExecutor = redisTaskExecutor; - } - - @Autowired(required = false) - @Qualifier("springSessionRedisSubscriptionExecutor") - public void setRedisSubscriptionExecutor(Executor redisSubscriptionExecutor) { - this.redisSubscriptionExecutor = redisSubscriptionExecutor; - } - - @Autowired(required = false) - public void setSessionRepositoryCustomizer( - ObjectProvider> sessionRepositoryCustomizers) { - this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList()); - } - - @Override - public void setBeanClassLoader(ClassLoader classLoader) { - this.classLoader = classLoader; - } - @Override public void setEmbeddedValueResolver(StringValueResolver resolver) { this.embeddedValueResolver = resolver; @@ -253,103 +75,16 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio Map attributeMap = importMetadata .getAnnotationAttributes(EnableRedisHttpSession.class.getName()); AnnotationAttributes attributes = AnnotationAttributes.fromMap(attributeMap); - this.maxInactiveIntervalInSeconds = attributes.getNumber("maxInactiveIntervalInSeconds"); + if (attributes == null) { + return; + } + setMaxInactiveIntervalInSeconds(attributes.getNumber("maxInactiveIntervalInSeconds")); String redisNamespaceValue = attributes.getString("redisNamespace"); if (StringUtils.hasText(redisNamespaceValue)) { - this.redisNamespace = this.embeddedValueResolver.resolveStringValue(redisNamespaceValue); + setRedisNamespace(this.embeddedValueResolver.resolveStringValue(redisNamespaceValue)); } - this.flushMode = attributes.getEnum("flushMode"); - this.saveMode = attributes.getEnum("saveMode"); - String cleanupCron = attributes.getString("cleanupCron"); - if (StringUtils.hasText(cleanupCron)) { - this.cleanupCron = cleanupCron; - } - } - - private RedisTemplate createRedisTemplate() { - RedisTemplate redisTemplate = new RedisTemplate<>(); - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setHashKeySerializer(new StringRedisSerializer()); - if (this.defaultRedisSerializer != null) { - redisTemplate.setDefaultSerializer(this.defaultRedisSerializer); - } - redisTemplate.setConnectionFactory(this.redisConnectionFactory); - redisTemplate.setBeanClassLoader(this.classLoader); - redisTemplate.afterPropertiesSet(); - return redisTemplate; - } - - private int resolveDatabase() { - if (ClassUtils.isPresent("io.lettuce.core.RedisClient", null) - && this.redisConnectionFactory instanceof LettuceConnectionFactory) { - return ((LettuceConnectionFactory) this.redisConnectionFactory).getDatabase(); - } - if (ClassUtils.isPresent("redis.clients.jedis.Jedis", null) - && this.redisConnectionFactory instanceof JedisConnectionFactory) { - return ((JedisConnectionFactory) this.redisConnectionFactory).getDatabase(); - } - return RedisIndexedSessionRepository.DEFAULT_DATABASE; - } - - /** - * Ensures that Redis is configured to send keyspace notifications. This is important - * to ensure that expiration and deletion of sessions trigger SessionDestroyedEvents. - * Without the SessionDestroyedEvent resources may not get cleaned up properly. For - * example, the mapping of the Session to WebSocket connections may not get cleaned - * up. - */ - static class EnableRedisKeyspaceNotificationsInitializer implements InitializingBean { - - private final RedisConnectionFactory connectionFactory; - - private ConfigureRedisAction configure; - - EnableRedisKeyspaceNotificationsInitializer(RedisConnectionFactory connectionFactory, - ConfigureRedisAction configure) { - this.connectionFactory = connectionFactory; - this.configure = configure; - } - - @Override - public void afterPropertiesSet() { - if (this.configure == ConfigureRedisAction.NO_OP) { - return; - } - RedisConnection connection = this.connectionFactory.getConnection(); - try { - this.configure.configure(connection); - } - finally { - try { - connection.close(); - } - catch (Exception ex) { - LogFactory.getLog(getClass()).error("Error closing RedisConnection", ex); - } - } - } - - } - - /** - * Configuration of scheduled job for cleaning up expired sessions. - */ - @EnableScheduling - @Configuration(proxyBeanMethods = false) - class SessionCleanupConfiguration implements SchedulingConfigurer { - - private final RedisIndexedSessionRepository sessionRepository; - - SessionCleanupConfiguration(RedisIndexedSessionRepository sessionRepository) { - this.sessionRepository = sessionRepository; - } - - @Override - public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { - taskRegistrar.addCronTask(this.sessionRepository::cleanupExpiredSessions, - RedisHttpSessionConfiguration.this.cleanupCron); - } - + setFlushMode(attributes.getEnum("flushMode")); + setSaveMode(attributes.getEnum("saveMode")); } } diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/RedisIndexedHttpSessionConfiguration.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/RedisIndexedHttpSessionConfiguration.java new file mode 100644 index 00000000..1635c17a --- /dev/null +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/RedisIndexedHttpSessionConfiguration.java @@ -0,0 +1,271 @@ +/* + * Copyright 2014-2022 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.data.redis.config.annotation.web.http; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.Executor; + +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.EmbeddedValueResolverAware; +import org.springframework.context.annotation.Bean; +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.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.listener.ChannelTopic; +import org.springframework.data.redis.listener.PatternTopic; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; +import org.springframework.session.IndexResolver; +import org.springframework.session.Session; +import org.springframework.session.data.redis.RedisIndexedSessionRepository; +import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction; +import org.springframework.session.data.redis.config.ConfigureRedisAction; +import org.springframework.session.web.http.SessionRepositoryFilter; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; +import org.springframework.util.StringValueResolver; + +/** + * Exposes the {@link SessionRepositoryFilter} as a bean named + * {@code springSessionRepositoryFilter} backed by {@link RedisIndexedSessionRepository}. + * In order to use this a single {@link RedisConnectionFactory} must be exposed as a Bean. + * + * @author Vedran Pavic + * @since 3.0.0 + * @see EnableRedisIndexedHttpSession + */ +@Configuration(proxyBeanMethods = false) +public class RedisIndexedHttpSessionConfiguration + extends AbstractRedisHttpSessionConfiguration + implements EmbeddedValueResolverAware, ImportAware { + + static final String DEFAULT_CLEANUP_CRON = "0 * * * * *"; + + private String cleanupCron = DEFAULT_CLEANUP_CRON; + + private ConfigureRedisAction configureRedisAction = new ConfigureNotifyKeyspaceEventsAction(); + + private IndexResolver indexResolver; + + private ApplicationEventPublisher applicationEventPublisher; + + private Executor redisTaskExecutor; + + private Executor redisSubscriptionExecutor; + + private StringValueResolver embeddedValueResolver; + + @Bean + @Override + public RedisIndexedSessionRepository sessionRepository() { + RedisTemplate redisTemplate = createRedisTemplate(); + RedisIndexedSessionRepository sessionRepository = new RedisIndexedSessionRepository(redisTemplate); + sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher); + if (this.indexResolver != null) { + sessionRepository.setIndexResolver(this.indexResolver); + } + if (getDefaultRedisSerializer() != null) { + sessionRepository.setDefaultSerializer(getDefaultRedisSerializer()); + } + sessionRepository.setDefaultMaxInactiveInterval(getMaxInactiveIntervalInSeconds()); + if (StringUtils.hasText(getRedisNamespace())) { + sessionRepository.setRedisKeyNamespace(getRedisNamespace()); + } + sessionRepository.setFlushMode(getFlushMode()); + sessionRepository.setSaveMode(getSaveMode()); + int database = resolveDatabase(); + sessionRepository.setDatabase(database); + getSessionRepositoryCustomizers() + .forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository)); + return sessionRepository; + } + + @Bean + public RedisMessageListenerContainer springSessionRedisMessageListenerContainer( + RedisIndexedSessionRepository sessionRepository) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(getRedisConnectionFactory()); + if (this.redisTaskExecutor != null) { + container.setTaskExecutor(this.redisTaskExecutor); + } + if (this.redisSubscriptionExecutor != null) { + container.setSubscriptionExecutor(this.redisSubscriptionExecutor); + } + container.addMessageListener(sessionRepository, + Arrays.asList(new ChannelTopic(sessionRepository.getSessionDeletedChannel()), + new ChannelTopic(sessionRepository.getSessionExpiredChannel()))); + container.addMessageListener(sessionRepository, + Collections.singletonList(new PatternTopic(sessionRepository.getSessionCreatedChannelPrefix() + "*"))); + return container; + } + + @Bean + public InitializingBean enableRedisKeyspaceNotificationsInitializer() { + return new EnableRedisKeyspaceNotificationsInitializer(getRedisConnectionFactory(), this.configureRedisAction); + } + + public void setCleanupCron(String cleanupCron) { + this.cleanupCron = cleanupCron; + } + + /** + * Sets the action to perform for configuring Redis. + * @param configureRedisAction the configureRedis to set. The default is + * {@link ConfigureNotifyKeyspaceEventsAction}. + */ + @Autowired(required = false) + public void setConfigureRedisAction(ConfigureRedisAction configureRedisAction) { + this.configureRedisAction = configureRedisAction; + } + + @Autowired + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.applicationEventPublisher = applicationEventPublisher; + } + + @Autowired(required = false) + public void setIndexResolver(IndexResolver indexResolver) { + this.indexResolver = indexResolver; + } + + @Autowired(required = false) + @Qualifier("springSessionRedisTaskExecutor") + public void setRedisTaskExecutor(Executor redisTaskExecutor) { + this.redisTaskExecutor = redisTaskExecutor; + } + + @Autowired(required = false) + @Qualifier("springSessionRedisSubscriptionExecutor") + public void setRedisSubscriptionExecutor(Executor redisSubscriptionExecutor) { + this.redisSubscriptionExecutor = redisSubscriptionExecutor; + } + + @Override + public void setEmbeddedValueResolver(StringValueResolver resolver) { + this.embeddedValueResolver = resolver; + } + + @Override + public void setImportMetadata(AnnotationMetadata importMetadata) { + Map attributeMap = importMetadata + .getAnnotationAttributes(EnableRedisIndexedHttpSession.class.getName()); + AnnotationAttributes attributes = AnnotationAttributes.fromMap(attributeMap); + if (attributes == null) { + return; + } + setMaxInactiveIntervalInSeconds(attributes.getNumber("maxInactiveIntervalInSeconds")); + String redisNamespaceValue = attributes.getString("redisNamespace"); + if (StringUtils.hasText(redisNamespaceValue)) { + setRedisNamespace(this.embeddedValueResolver.resolveStringValue(redisNamespaceValue)); + } + setFlushMode(attributes.getEnum("flushMode")); + setSaveMode(attributes.getEnum("saveMode")); + String cleanupCron = attributes.getString("cleanupCron"); + if (StringUtils.hasText(cleanupCron)) { + setCleanupCron(cleanupCron); + } + } + + private int resolveDatabase() { + if (ClassUtils.isPresent("io.lettuce.core.RedisClient", null) + && getRedisConnectionFactory() instanceof LettuceConnectionFactory) { + return ((LettuceConnectionFactory) getRedisConnectionFactory()).getDatabase(); + } + if (ClassUtils.isPresent("redis.clients.jedis.Jedis", null) + && getRedisConnectionFactory() instanceof JedisConnectionFactory) { + return ((JedisConnectionFactory) getRedisConnectionFactory()).getDatabase(); + } + return RedisIndexedSessionRepository.DEFAULT_DATABASE; + } + + /** + * Ensures that Redis is configured to send keyspace notifications. This is important + * to ensure that expiration and deletion of sessions trigger SessionDestroyedEvents. + * Without the SessionDestroyedEvent resources may not get cleaned up properly. For + * example, the mapping of the Session to WebSocket connections may not get cleaned + * up. + */ + static class EnableRedisKeyspaceNotificationsInitializer implements InitializingBean { + + private final RedisConnectionFactory connectionFactory; + + private final ConfigureRedisAction configure; + + EnableRedisKeyspaceNotificationsInitializer(RedisConnectionFactory connectionFactory, + ConfigureRedisAction configure) { + this.connectionFactory = connectionFactory; + this.configure = configure; + } + + @Override + public void afterPropertiesSet() { + if (this.configure == ConfigureRedisAction.NO_OP) { + return; + } + RedisConnection connection = this.connectionFactory.getConnection(); + try { + this.configure.configure(connection); + } + finally { + try { + connection.close(); + } + catch (Exception ex) { + LogFactory.getLog(getClass()).error("Error closing RedisConnection", ex); + } + } + } + + } + + /** + * Configuration of scheduled job for cleaning up expired sessions. + */ + @EnableScheduling + @Configuration(proxyBeanMethods = false) + class SessionCleanupConfiguration implements SchedulingConfigurer { + + private final RedisIndexedSessionRepository sessionRepository; + + SessionCleanupConfiguration(RedisIndexedSessionRepository sessionRepository) { + this.sessionRepository = sessionRepository; + } + + @Override + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { + taskRegistrar.addCronTask(this.sessionRepository::cleanupExpiredSessions, + RedisIndexedHttpSessionConfiguration.this.cleanupCron); + } + + } + +} diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryTests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryTests.java index 07cd09d5..5b6c22fa 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryTests.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryTests.java @@ -70,16 +70,16 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; class RedisIndexedSessionRepositoryTests { @Mock - private RedisOperations redisOperations; + private RedisOperations redisOperations; @Mock - private BoundValueOperations boundValueOperations; + private BoundValueOperations boundValueOperations; @Mock - private BoundHashOperations boundHashOperations; + private BoundHashOperations boundHashOperations; @Mock - private BoundSetOperations boundSetOperations; + private BoundSetOperations boundSetOperations; @Mock private ApplicationEventPublisher publisher; @@ -116,7 +116,7 @@ class RedisIndexedSessionRepositoryTests { @Test void changeSessionIdWhenNotSaved() { - given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); @@ -133,7 +133,7 @@ class RedisIndexedSessionRepositoryTests { @Test void changeSessionIdWhenSaved() { - given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); @@ -166,7 +166,7 @@ class RedisIndexedSessionRepositoryTests { @Test void saveNewSession() { RedisSession session = this.redisRepository.createSession(); - given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); @@ -201,7 +201,7 @@ class RedisIndexedSessionRepositoryTests { .roundUpToNextMinute(RedisSessionExpirationPolicy.expiresInMillis(session)); String destroyedTriggerKey = "spring:session:sessions:expires:" + session.getId(); - given(this.redisOperations.boundHashOps(sessionKey)).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(sessionKey)).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(backgroundExpireKey)).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(destroyedTriggerKey)).willReturn(this.boundValueOperations); @@ -224,7 +224,7 @@ class RedisIndexedSessionRepositoryTests { RedisSession session = this.redisRepository.new RedisSession(this.cached, false); session.setLastAccessedTime(session.getLastAccessedTime()); - given(this.redisOperations.boundHashOps("spring:session:sessions:session-id")) + given(this.redisOperations.boundHashOps("spring:session:sessions:session-id")) .willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps("spring:session:expirations:1404361860000")) .willReturn(this.boundSetOperations); @@ -245,7 +245,7 @@ class RedisIndexedSessionRepositoryTests { void saveLastAccessChanged() { RedisSession session = this.redisRepository.new RedisSession(this.cached, false); session.setLastAccessedTime(Instant.ofEpochMilli(12345678L)); - given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); @@ -260,7 +260,7 @@ class RedisIndexedSessionRepositoryTests { String attrName = "attrName"; RedisSession session = this.redisRepository.new RedisSession(new MapSession(), false); session.setAttribute(attrName, "attrValue"); - given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); @@ -275,7 +275,7 @@ class RedisIndexedSessionRepositoryTests { String attrName = "attrName"; RedisSession session = this.redisRepository.new RedisSession(new MapSession(), false); session.removeAttribute(attrName); - given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); @@ -288,7 +288,7 @@ class RedisIndexedSessionRepositoryTests { void saveExpired() { RedisSession session = this.redisRepository.new RedisSession(new MapSession(), false); session.setMaxInactiveInterval(Duration.ZERO); - given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); this.redisRepository.save(session); @@ -316,7 +316,7 @@ class RedisIndexedSessionRepositoryTests { MapSession expected = new MapSession(); expected.setLastAccessedTime(Instant.now().minusSeconds(60)); expected.setAttribute(attrName, "attrValue"); - given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); Map map = map(RedisIndexedSessionRepository.getSessionAttrNameKey(attrName), expected.getAttribute(attrName), RedisSessionMapper.CREATION_TIME_KEY, expected.getCreationTime().toEpochMilli(), @@ -335,7 +335,7 @@ class RedisIndexedSessionRepositoryTests { @Test void deleteNullSession() { - given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); String id = "abc"; this.redisRepository.deleteById(id); @@ -347,7 +347,7 @@ class RedisIndexedSessionRepositoryTests { @SuppressWarnings("unchecked") void getSessionNotFound() { String id = "abc"; - given(this.redisOperations.boundHashOps(getKey(id))).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(getKey(id))).willReturn(this.boundHashOperations); given(this.boundHashOperations.entries()).willReturn(map()); assertThat(this.redisRepository.findById(id)).isNull(); @@ -362,7 +362,8 @@ class RedisIndexedSessionRepositoryTests { expected.setLastAccessedTime(Instant.now().minusSeconds(60)); expected.setAttribute(attribute1, "test"); expected.setAttribute(attribute2, null); - given(this.redisOperations.boundHashOps(getKey(expected.getId()))).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(getKey(expected.getId()))) + .willReturn(this.boundHashOperations); Map map = map(RedisIndexedSessionRepository.getSessionAttrNameKey(attribute1), expected.getAttribute(attribute1), RedisIndexedSessionRepository.getSessionAttrNameKey(attribute2), expected.getAttribute(attribute2), RedisSessionMapper.CREATION_TIME_KEY, @@ -387,7 +388,8 @@ class RedisIndexedSessionRepositoryTests { @SuppressWarnings("unchecked") void getSessionExpired() { String expiredId = "expired-id"; - given(this.redisOperations.boundHashOps(getKey(expiredId))).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(getKey(expiredId))) + .willReturn(this.boundHashOperations); Map map = map(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, 1, RedisSessionMapper.LAST_ACCESSED_TIME_KEY, Instant.now().minus(5, ChronoUnit.MINUTES).toEpochMilli()); given(this.boundHashOperations.entries()).willReturn(map); @@ -401,7 +403,8 @@ class RedisIndexedSessionRepositoryTests { String expiredId = "expired-id"; given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.boundSetOperations.members()).willReturn(Collections.singleton(expiredId)); - given(this.redisOperations.boundHashOps(getKey(expiredId))).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(getKey(expiredId))) + .willReturn(this.boundHashOperations); Map map = map(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, 1, RedisSessionMapper.LAST_ACCESSED_TIME_KEY, Instant.now().minus(5, ChronoUnit.MINUTES).toEpochMilli()); given(this.boundHashOperations.entries()).willReturn(map); @@ -420,7 +423,8 @@ class RedisIndexedSessionRepositoryTests { String sessionId = "some-id"; given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.boundSetOperations.members()).willReturn(Collections.singleton(sessionId)); - given(this.redisOperations.boundHashOps(getKey(sessionId))).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(getKey(sessionId))) + .willReturn(this.boundHashOperations); Map map = map(RedisSessionMapper.CREATION_TIME_KEY, createdTime.toEpochMilli(), RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, (int) maxInactive.getSeconds(), RedisSessionMapper.LAST_ACCESSED_TIME_KEY, lastAccessed.toEpochMilli()); @@ -495,7 +499,8 @@ class RedisIndexedSessionRepositoryTests { @SuppressWarnings("unchecked") void onMessageDeletedSessionFound() { String deletedId = "deleted-id"; - given(this.redisOperations.boundHashOps(getKey(deletedId))).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(getKey(deletedId))) + .willReturn(this.boundHashOperations); Map map = map(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, 0, RedisSessionMapper.LAST_ACCESSED_TIME_KEY, System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(5)); given(this.boundHashOperations.entries()).willReturn(map); @@ -522,7 +527,8 @@ class RedisIndexedSessionRepositoryTests { @SuppressWarnings("unchecked") void onMessageDeletedSessionNotFound() { String deletedId = "deleted-id"; - given(this.redisOperations.boundHashOps(getKey(deletedId))).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(getKey(deletedId))) + .willReturn(this.boundHashOperations); given(this.boundHashOperations.entries()).willReturn(map()); String channel = "__keyevent@0__:del"; @@ -545,7 +551,8 @@ class RedisIndexedSessionRepositoryTests { @SuppressWarnings("unchecked") void onMessageExpiredSessionFound() { String expiredId = "expired-id"; - given(this.redisOperations.boundHashOps(getKey(expiredId))).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(getKey(expiredId))) + .willReturn(this.boundHashOperations); Map map = map(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, 1, RedisSessionMapper.LAST_ACCESSED_TIME_KEY, System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(5)); given(this.boundHashOperations.entries()).willReturn(map); @@ -572,7 +579,8 @@ class RedisIndexedSessionRepositoryTests { @SuppressWarnings("unchecked") void onMessageExpiredSessionNotFound() { String expiredId = "expired-id"; - given(this.redisOperations.boundHashOps(getKey(expiredId))).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(getKey(expiredId))) + .willReturn(this.boundHashOperations); given(this.boundHashOperations.entries()).willReturn(map()); String channel = "__keyevent@0__:expired"; @@ -632,7 +640,7 @@ class RedisIndexedSessionRepositoryTests { @Test void flushModeImmediateCreate() { - given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); @@ -651,7 +659,7 @@ class RedisIndexedSessionRepositoryTests { @Test // gh-1409 void flushModeImmediateCreateWithCustomMaxInactiveInterval() { - given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); this.redisRepository.setDefaultMaxInactiveInterval(60); @@ -664,7 +672,7 @@ class RedisIndexedSessionRepositoryTests { @Test void flushModeImmediateSetAttribute() { - given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); @@ -681,7 +689,7 @@ class RedisIndexedSessionRepositoryTests { @Test void flushModeImmediateRemoveAttribute() { - given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); @@ -699,7 +707,7 @@ class RedisIndexedSessionRepositoryTests { @Test @SuppressWarnings("unchecked") void flushModeSetMaxInactiveIntervalInSeconds() { - given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); @@ -715,7 +723,7 @@ class RedisIndexedSessionRepositoryTests { @Test void flushModeSetLastAccessedTime() { - given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); @@ -742,7 +750,7 @@ class RedisIndexedSessionRepositoryTests { this.redisRepository.setRedisKeyNamespace(namespace); RedisSession session = this.redisRepository.new RedisSession(new MapSession(), false); session.setMaxInactiveInterval(Duration.ZERO); - given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); this.redisRepository.save(session); @@ -832,7 +840,7 @@ class RedisIndexedSessionRepositoryTests { @Test void saveWithSaveModeOnSetAttribute() { - given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); this.redisRepository.setSaveMode(SaveMode.ON_SET_ATTRIBUTE); @@ -849,7 +857,7 @@ class RedisIndexedSessionRepositoryTests { @Test void saveWithSaveModeOnGetAttribute() { - given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); this.redisRepository.setSaveMode(SaveMode.ON_GET_ATTRIBUTE); @@ -866,7 +874,7 @@ class RedisIndexedSessionRepositoryTests { @Test void saveWithSaveModeAlways() { - given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); + given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations); given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations); given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations); this.redisRepository.setSaveMode(SaveMode.ALWAYS); diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisSessionExpirationPolicyTests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisSessionExpirationPolicyTests.java index c9d57c7e..af471968 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisSessionExpirationPolicyTests.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/RedisSessionExpirationPolicyTests.java @@ -50,16 +50,16 @@ class RedisSessionExpirationPolicyTests { private static final Long ONE_MINUTE_AGO = 1429111652346L; @Mock(lenient = true) - RedisOperations sessionRedisOperations; + RedisOperations sessionRedisOperations; @Mock - BoundSetOperations setOperations; + BoundSetOperations setOperations; @Mock - BoundHashOperations hashOperations; + BoundHashOperations hashOperations; @Mock - BoundValueOperations valueOperations; + BoundValueOperations valueOperations; private RedisSessionExpirationPolicy policy; diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisKeyspaceNotificationsInitializerTests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisKeyspaceNotificationsInitializerTests.java index 80c59f85..9729400b 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisKeyspaceNotificationsInitializerTests.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisKeyspaceNotificationsInitializerTests.java @@ -55,14 +55,14 @@ class EnableRedisKeyspaceNotificationsInitializerTests { @Captor ArgumentCaptor options; - private RedisHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer initializer; + private RedisIndexedHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer initializer; @BeforeEach void setup() { given(this.connectionFactory.getConnection()).willReturn(this.connection); given(this.connection.serverCommands()).willReturn(this.commands); - this.initializer = new RedisHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer( + this.initializer = new RedisIndexedHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer( this.connectionFactory, new ConfigureNotifyKeyspaceEventsAction()); } diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpsSessionConfigurationTests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpsSessionConfigurationTests.java new file mode 100644 index 00000000..1eca91b0 --- /dev/null +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpsSessionConfigurationTests.java @@ -0,0 +1,363 @@ +/* + * Copyright 2014-2022 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.data.redis.config.annotation.web.http; + +import java.time.Duration; +import java.util.Properties; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.BeanCreationException; +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.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.core.annotation.Order; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisServerCommands; +import org.springframework.data.redis.connection.SubscriptionListener; +import org.springframework.data.redis.core.RedisOperations; +import org.springframework.mock.env.MockEnvironment; +import org.springframework.session.FlushMode; +import org.springframework.session.SaveMode; +import org.springframework.session.config.SessionRepositoryCustomizer; +import org.springframework.session.data.redis.RedisSessionRepository; +import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willAnswer; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link RedisHttpSessionConfiguration}. + * + * @author Eddú Meléndez + * @author Mark Paluch + * @author Vedran Pavic + */ +class RedisHttpsSessionConfigurationTests { + + private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600; + + private AnnotationConfigApplicationContext context; + + @BeforeEach + void before() { + this.context = new AnnotationConfigApplicationContext(); + } + + @AfterEach + void after() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + void resolveValue() { + registerAndRefresh(RedisConfig.class, CustomRedisHttpSessionConfiguration.class); + RedisHttpSessionConfiguration configuration = this.context.getBean(RedisHttpSessionConfiguration.class); + assertThat(ReflectionTestUtils.getField(configuration, "redisNamespace")).isEqualTo("myRedisNamespace"); + } + + @Test + void resolveValueByPlaceholder() { + this.context + .setEnvironment(new MockEnvironment().withProperty("session.redis.namespace", "customRedisNamespace")); + registerAndRefresh(RedisConfig.class, PropertySourceConfiguration.class, + CustomRedisHttpSessionConfiguration2.class); + RedisHttpSessionConfiguration configuration = this.context.getBean(RedisHttpSessionConfiguration.class); + assertThat(ReflectionTestUtils.getField(configuration, "redisNamespace")).isEqualTo("customRedisNamespace"); + } + + @Test + void customFlushImmediately() { + registerAndRefresh(RedisConfig.class, CustomFlushImmediatelyConfiguration.class); + RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class); + assertThat(sessionRepository).isNotNull(); + assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); + } + + @Test + void setCustomFlushImmediately() { + registerAndRefresh(RedisConfig.class, CustomFlushImmediatelySetConfiguration.class); + RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class); + assertThat(sessionRepository).isNotNull(); + assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); + } + + @Test + void customSaveModeAnnotation() { + registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionAnnotationConfiguration.class); + assertThat(this.context.getBean(RedisSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode", + SaveMode.ALWAYS); + } + + @Test + void qualifiedConnectionFactoryRedisConfig() { + registerAndRefresh(RedisConfig.class, QualifiedConnectionFactoryRedisConfig.class); + + RedisSessionRepository repository = this.context.getBean(RedisSessionRepository.class); + RedisConnectionFactory redisConnectionFactory = this.context.getBean("qualifiedRedisConnectionFactory", + RedisConnectionFactory.class); + assertThat(repository).isNotNull(); + assertThat(redisConnectionFactory).isNotNull(); + @SuppressWarnings("unchecked") + RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils + .getField(repository, "sessionRedisOperations"); + assertThat(redisOperations).isNotNull(); + assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory")) + .isEqualTo(redisConnectionFactory); + } + + @Test + void primaryConnectionFactoryRedisConfig() { + registerAndRefresh(RedisConfig.class, PrimaryConnectionFactoryRedisConfig.class); + + RedisSessionRepository repository = this.context.getBean(RedisSessionRepository.class); + RedisConnectionFactory redisConnectionFactory = this.context.getBean("primaryRedisConnectionFactory", + RedisConnectionFactory.class); + assertThat(repository).isNotNull(); + assertThat(redisConnectionFactory).isNotNull(); + @SuppressWarnings("unchecked") + RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils + .getField(repository, "sessionRedisOperations"); + assertThat(redisOperations).isNotNull(); + assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory")) + .isEqualTo(redisConnectionFactory); + } + + @Test + void qualifiedAndPrimaryConnectionFactoryRedisConfig() { + registerAndRefresh(RedisConfig.class, QualifiedAndPrimaryConnectionFactoryRedisConfig.class); + + RedisSessionRepository repository = this.context.getBean(RedisSessionRepository.class); + RedisConnectionFactory redisConnectionFactory = this.context.getBean("qualifiedRedisConnectionFactory", + RedisConnectionFactory.class); + assertThat(repository).isNotNull(); + assertThat(redisConnectionFactory).isNotNull(); + @SuppressWarnings("unchecked") + RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils + .getField(repository, "sessionRedisOperations"); + assertThat(redisOperations).isNotNull(); + assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory")) + .isEqualTo(redisConnectionFactory); + } + + @Test + void namedConnectionFactoryRedisConfig() { + registerAndRefresh(RedisConfig.class, NamedConnectionFactoryRedisConfig.class); + + RedisSessionRepository repository = this.context.getBean(RedisSessionRepository.class); + RedisConnectionFactory redisConnectionFactory = this.context.getBean("redisConnectionFactory", + RedisConnectionFactory.class); + assertThat(repository).isNotNull(); + assertThat(redisConnectionFactory).isNotNull(); + @SuppressWarnings("unchecked") + RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils + .getField(repository, "sessionRedisOperations"); + assertThat(redisOperations).isNotNull(); + assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory")) + .isEqualTo(redisConnectionFactory); + } + + @Test + void multipleConnectionFactoryRedisConfig() { + assertThatExceptionOfType(BeanCreationException.class) + .isThrownBy(() -> registerAndRefresh(RedisConfig.class, MultipleConnectionFactoryRedisConfig.class)) + .havingRootCause().withMessageContaining("expected single matching bean but found 2"); + } + + @Test + void sessionRepositoryCustomizer() { + registerAndRefresh(RedisConfig.class, SessionRepositoryCustomizerConfiguration.class); + RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class); + assertThat(sessionRepository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", + Duration.ofSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS)); + } + + private void registerAndRefresh(Class... annotatedClasses) { + this.context.register(annotatedClasses); + this.context.refresh(); + } + + private static RedisConnectionFactory mockRedisConnectionFactory() { + RedisConnectionFactory connectionFactoryMock = mock(RedisConnectionFactory.class); + RedisConnection connectionMock = mock(RedisConnection.class); + RedisServerCommands commandsMock = mock(RedisServerCommands.class); + given(connectionFactoryMock.getConnection()).willReturn(connectionMock); + given(connectionMock.serverCommands()).willReturn(commandsMock); + + Properties keyspaceEventsConfig = new Properties(); + keyspaceEventsConfig.put("notify-keyspace-events", "KEA"); + given(commandsMock.getConfig("notify-keyspace-events")).willReturn(keyspaceEventsConfig); + + willAnswer((it) -> { + SubscriptionListener listener = it.getArgument(0); + listener.onPatternSubscribed(it.getArgument(1), 0); + listener.onChannelSubscribed("__keyevent@0__:del".getBytes(), 0); + listener.onChannelSubscribed("__keyevent@0__:expired".getBytes(), 0); + + return null; + }).given(connectionMock).pSubscribe(any(), any()); + + return connectionFactoryMock; + } + + @Configuration + static class PropertySourceConfiguration { + + @Bean + PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } + + } + + @Configuration + static class RedisConfig { + + @Bean + RedisConnectionFactory defaultRedisConnectionFactory() { + return mockRedisConnectionFactory(); + } + + } + + @Configuration + static class CustomFlushImmediatelySetConfiguration extends RedisHttpSessionConfiguration { + + CustomFlushImmediatelySetConfiguration() { + setFlushMode(FlushMode.IMMEDIATE); + } + + } + + @Configuration + @EnableRedisHttpSession(flushMode = FlushMode.IMMEDIATE) + static class CustomFlushImmediatelyConfiguration { + + } + + @EnableRedisHttpSession(saveMode = SaveMode.ALWAYS) + static class CustomSaveModeExpressionAnnotationConfiguration { + + } + + @Configuration + @EnableRedisHttpSession + static class QualifiedConnectionFactoryRedisConfig { + + @Bean + @SpringSessionRedisConnectionFactory + RedisConnectionFactory qualifiedRedisConnectionFactory() { + return mockRedisConnectionFactory(); + } + + } + + @Configuration + @EnableRedisHttpSession + static class PrimaryConnectionFactoryRedisConfig { + + @Bean + @Primary + RedisConnectionFactory primaryRedisConnectionFactory() { + return mockRedisConnectionFactory(); + } + + } + + @Configuration + @EnableRedisHttpSession + static class QualifiedAndPrimaryConnectionFactoryRedisConfig { + + @Bean + @SpringSessionRedisConnectionFactory + RedisConnectionFactory qualifiedRedisConnectionFactory() { + return mockRedisConnectionFactory(); + } + + @Bean + @Primary + RedisConnectionFactory primaryRedisConnectionFactory() { + return mockRedisConnectionFactory(); + } + + } + + @Configuration + @EnableRedisHttpSession + static class NamedConnectionFactoryRedisConfig { + + @Bean + RedisConnectionFactory redisConnectionFactory() { + return mockRedisConnectionFactory(); + } + + } + + @Configuration + @EnableRedisHttpSession + static class MultipleConnectionFactoryRedisConfig { + + @Bean + RedisConnectionFactory secondaryRedisConnectionFactory() { + return mockRedisConnectionFactory(); + } + + } + + @Configuration + @EnableRedisHttpSession(redisNamespace = "myRedisNamespace") + static class CustomRedisHttpSessionConfiguration { + + } + + @Configuration + @EnableRedisHttpSession(redisNamespace = "${session.redis.namespace}") + static class CustomRedisHttpSessionConfiguration2 { + + } + + @EnableRedisHttpSession + static class SessionRepositoryCustomizerConfiguration { + + @Bean + @Order(0) + SessionRepositoryCustomizer sessionRepositoryCustomizerOne() { + return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(Duration.ZERO); + } + + @Bean + @Order(1) + SessionRepositoryCustomizer sessionRepositoryCustomizerTwo() { + return (sessionRepository) -> sessionRepository + .setDefaultMaxInactiveInterval(Duration.ofSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS)); + } + + } + +} diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationMockTests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisIndexedHttpSessionConfigurationMockTests.java similarity index 95% rename from spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationMockTests.java rename to spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisIndexedHttpSessionConfigurationMockTests.java index ab1edae6..7e2ca4df 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationMockTests.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisIndexedHttpSessionConfigurationMockTests.java @@ -25,7 +25,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.session.data.redis.config.ConfigureRedisAction; -import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer; +import org.springframework.session.data.redis.config.annotation.web.http.RedisIndexedHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; import static org.mockito.BDDMockito.given; @@ -35,7 +35,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @ExtendWith(MockitoExtension.class) -class RedisHttpSessionConfigurationMockTests { +class RedisIndexedHttpSessionConfigurationMockTests { @Mock(lenient = true) RedisConnectionFactory factory; diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationNoOpConfigureRedisActionTests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisIndexedHttpSessionConfigurationNoOpConfigureRedisActionTests.java similarity index 96% rename from spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationNoOpConfigureRedisActionTests.java rename to spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisIndexedHttpSessionConfigurationNoOpConfigureRedisActionTests.java index 280978a6..30c9b0d6 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationNoOpConfigureRedisActionTests.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisIndexedHttpSessionConfigurationNoOpConfigureRedisActionTests.java @@ -40,13 +40,13 @@ import static org.mockito.Mockito.mock; @ExtendWith(SpringExtension.class) @ContextConfiguration @WebAppConfiguration -class RedisHttpSessionConfigurationNoOpConfigureRedisActionTests { +class RedisIndexedHttpSessionConfigurationNoOpConfigureRedisActionTests { @Test void redisConnectionFactoryNotUsedSinceNoValidation() { } - @EnableRedisHttpSession + @EnableRedisIndexedHttpSession @Configuration static class Config { diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationOverrideSessionTaskExecutor.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisIndexedHttpSessionConfigurationOverrideSessionTaskExecutor.java similarity index 97% rename from spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationOverrideSessionTaskExecutor.java rename to spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisIndexedHttpSessionConfigurationOverrideSessionTaskExecutor.java index cbd14b7b..07b727ef 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationOverrideSessionTaskExecutor.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisIndexedHttpSessionConfigurationOverrideSessionTaskExecutor.java @@ -49,7 +49,7 @@ import static org.mockito.Mockito.verify; @ExtendWith(SpringExtension.class) @ContextConfiguration @WebAppConfiguration -class RedisHttpSessionConfigurationOverrideSessionTaskExecutor { +class RedisIndexedHttpSessionConfigurationOverrideSessionTaskExecutor { @Autowired RedisMessageListenerContainer redisMessageListenerContainer; @@ -62,8 +62,8 @@ class RedisHttpSessionConfigurationOverrideSessionTaskExecutor { verify(this.springSessionRedisTaskExecutor, times(1)).execute(any(Runnable.class)); } - @EnableRedisHttpSession @Configuration + @EnableRedisIndexedHttpSession static class Config { @Bean diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationOverrideSessionTaskExecutors.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisIndexedHttpSessionConfigurationOverrideSessionTaskExecutors.java similarity index 97% rename from spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationOverrideSessionTaskExecutors.java rename to spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisIndexedHttpSessionConfigurationOverrideSessionTaskExecutors.java index c79db725..eebb360d 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationOverrideSessionTaskExecutors.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisIndexedHttpSessionConfigurationOverrideSessionTaskExecutors.java @@ -51,7 +51,7 @@ import static org.mockito.Mockito.verify; @ExtendWith(SpringExtension.class) @ContextConfiguration @WebAppConfiguration -class RedisHttpSessionConfigurationOverrideSessionTaskExecutors { +class RedisIndexedHttpSessionConfigurationOverrideSessionTaskExecutors { @Autowired RedisMessageListenerContainer redisMessageListenerContainer; @@ -68,8 +68,8 @@ class RedisHttpSessionConfigurationOverrideSessionTaskExecutors { verify(this.springSessionRedisTaskExecutor, never()).execute(any(Runnable.class)); } - @EnableRedisHttpSession @Configuration + @EnableRedisIndexedHttpSession static class Config { @Bean 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/RedisIndexedHttpSessionConfigurationTests.java similarity index 82% rename from spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationTests.java rename to spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisIndexedHttpSessionConfigurationTests.java index 75fdb116..fca38d7f 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/RedisIndexedHttpSessionConfigurationTests.java @@ -24,7 +24,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -55,13 +54,11 @@ import static org.mockito.BDDMockito.willAnswer; import static org.mockito.Mockito.mock; /** - * Tests for {@link RedisHttpSessionConfiguration}. + * Tests for {@link RedisIndexedHttpSessionConfiguration}. * - * @author Eddú Meléndez - * @author Mark Paluch * @author Vedran Pavic */ -class RedisHttpSessionConfigurationTests { +class RedisIndexedHttpSessionConfigurationTests { private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600; @@ -84,7 +81,8 @@ class RedisHttpSessionConfigurationTests { @Test void resolveValue() { registerAndRefresh(RedisConfig.class, CustomRedisHttpSessionConfiguration.class); - RedisHttpSessionConfiguration configuration = this.context.getBean(RedisHttpSessionConfiguration.class); + RedisIndexedHttpSessionConfiguration configuration = this.context + .getBean(RedisIndexedHttpSessionConfiguration.class); assertThat(ReflectionTestUtils.getField(configuration, "redisNamespace")).isEqualTo("myRedisNamespace"); } @@ -94,7 +92,8 @@ class RedisHttpSessionConfigurationTests { .setEnvironment(new MockEnvironment().withProperty("session.redis.namespace", "customRedisNamespace")); registerAndRefresh(RedisConfig.class, PropertySourceConfiguration.class, CustomRedisHttpSessionConfiguration2.class); - RedisHttpSessionConfiguration configuration = this.context.getBean(RedisHttpSessionConfiguration.class); + RedisIndexedHttpSessionConfiguration configuration = this.context + .getBean(RedisIndexedHttpSessionConfiguration.class); assertThat(ReflectionTestUtils.getField(configuration, "redisNamespace")).isEqualTo("customRedisNamespace"); } @@ -118,16 +117,8 @@ class RedisHttpSessionConfigurationTests { void customCleanupCronAnnotation() { registerAndRefresh(RedisConfig.class, CustomCleanupCronExpressionAnnotationConfiguration.class); - RedisHttpSessionConfiguration configuration = this.context.getBean(RedisHttpSessionConfiguration.class); - assertThat(configuration).isNotNull(); - assertThat(ReflectionTestUtils.getField(configuration, "cleanupCron")).isEqualTo(CLEANUP_CRON_EXPRESSION); - } - - @Test - void customCleanupCronSetter() { - registerAndRefresh(RedisConfig.class, CustomCleanupCronExpressionSetterConfiguration.class); - - RedisHttpSessionConfiguration configuration = this.context.getBean(RedisHttpSessionConfiguration.class); + RedisIndexedHttpSessionConfiguration configuration = this.context + .getBean(RedisIndexedHttpSessionConfiguration.class); assertThat(configuration).isNotNull(); assertThat(ReflectionTestUtils.getField(configuration, "cleanupCron")).isEqualTo(CLEANUP_CRON_EXPRESSION); } @@ -139,13 +130,6 @@ class RedisHttpSessionConfigurationTests { SaveMode.ALWAYS); } - @Test - void customSaveModeSetter() { - registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionSetterConfiguration.class); - assertThat(this.context.getBean(RedisIndexedSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode", - SaveMode.ALWAYS); - } - @Test void qualifiedConnectionFactoryRedisConfig() { registerAndRefresh(RedisConfig.class, QualifiedConnectionFactoryRedisConfig.class); @@ -155,8 +139,9 @@ class RedisHttpSessionConfigurationTests { RedisConnectionFactory.class); assertThat(repository).isNotNull(); assertThat(redisConnectionFactory).isNotNull(); - RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils.getField(repository, - "sessionRedisOperations"); + @SuppressWarnings("unchecked") + RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils + .getField(repository, "sessionRedisOperations"); assertThat(redisOperations).isNotNull(); assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory")) .isEqualTo(redisConnectionFactory); @@ -171,8 +156,9 @@ class RedisHttpSessionConfigurationTests { RedisConnectionFactory.class); assertThat(repository).isNotNull(); assertThat(redisConnectionFactory).isNotNull(); - RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils.getField(repository, - "sessionRedisOperations"); + @SuppressWarnings("unchecked") + RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils + .getField(repository, "sessionRedisOperations"); assertThat(redisOperations).isNotNull(); assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory")) .isEqualTo(redisConnectionFactory); @@ -187,8 +173,9 @@ class RedisHttpSessionConfigurationTests { RedisConnectionFactory.class); assertThat(repository).isNotNull(); assertThat(redisConnectionFactory).isNotNull(); - RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils.getField(repository, - "sessionRedisOperations"); + @SuppressWarnings("unchecked") + RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils + .getField(repository, "sessionRedisOperations"); assertThat(redisOperations).isNotNull(); assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory")) .isEqualTo(redisConnectionFactory); @@ -203,8 +190,9 @@ class RedisHttpSessionConfigurationTests { RedisConnectionFactory.class); assertThat(repository).isNotNull(); assertThat(redisConnectionFactory).isNotNull(); - RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils.getField(repository, - "sessionRedisOperations"); + @SuppressWarnings("unchecked") + RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils + .getField(repository, "sessionRedisOperations"); assertThat(redisOperations).isNotNull(); assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory")) .isEqualTo(redisConnectionFactory); @@ -214,8 +202,7 @@ class RedisHttpSessionConfigurationTests { void multipleConnectionFactoryRedisConfig() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> registerAndRefresh(RedisConfig.class, MultipleConnectionFactoryRedisConfig.class)) - .withCauseInstanceOf(NoUniqueBeanDefinitionException.class).havingCause() - .withMessageContaining("expected single matching bean but found 2"); + .havingRootCause().withMessageContaining("expected single matching bean but found 2"); } @Test @@ -295,7 +282,7 @@ class RedisHttpSessionConfigurationTests { } @Configuration - static class CustomFlushImmediatelySetConfiguration extends RedisHttpSessionConfiguration { + static class CustomFlushImmediatelySetConfiguration extends RedisIndexedHttpSessionConfiguration { CustomFlushImmediatelySetConfiguration() { setFlushMode(FlushMode.IMMEDIATE); @@ -304,41 +291,23 @@ class RedisHttpSessionConfigurationTests { } @Configuration - @EnableRedisHttpSession(flushMode = FlushMode.IMMEDIATE) + @EnableRedisIndexedHttpSession(flushMode = FlushMode.IMMEDIATE) static class CustomFlushImmediatelyConfiguration { } - @EnableRedisHttpSession(cleanupCron = CLEANUP_CRON_EXPRESSION) + @EnableRedisIndexedHttpSession(cleanupCron = CLEANUP_CRON_EXPRESSION) static class CustomCleanupCronExpressionAnnotationConfiguration { } - @Configuration - static class CustomCleanupCronExpressionSetterConfiguration extends RedisHttpSessionConfiguration { - - CustomCleanupCronExpressionSetterConfiguration() { - setCleanupCron(CLEANUP_CRON_EXPRESSION); - } - - } - - @EnableRedisHttpSession(saveMode = SaveMode.ALWAYS) + @EnableRedisIndexedHttpSession(saveMode = SaveMode.ALWAYS) static class CustomSaveModeExpressionAnnotationConfiguration { } @Configuration - static class CustomSaveModeExpressionSetterConfiguration extends RedisHttpSessionConfiguration { - - CustomSaveModeExpressionSetterConfiguration() { - setSaveMode(SaveMode.ALWAYS); - } - - } - - @Configuration - @EnableRedisHttpSession + @EnableRedisIndexedHttpSession static class QualifiedConnectionFactoryRedisConfig { @Bean @@ -350,7 +319,7 @@ class RedisHttpSessionConfigurationTests { } @Configuration - @EnableRedisHttpSession + @EnableRedisIndexedHttpSession static class PrimaryConnectionFactoryRedisConfig { @Bean @@ -362,7 +331,7 @@ class RedisHttpSessionConfigurationTests { } @Configuration - @EnableRedisHttpSession + @EnableRedisIndexedHttpSession static class QualifiedAndPrimaryConnectionFactoryRedisConfig { @Bean @@ -380,7 +349,7 @@ class RedisHttpSessionConfigurationTests { } @Configuration - @EnableRedisHttpSession + @EnableRedisIndexedHttpSession static class NamedConnectionFactoryRedisConfig { @Bean @@ -391,7 +360,7 @@ class RedisHttpSessionConfigurationTests { } @Configuration - @EnableRedisHttpSession + @EnableRedisIndexedHttpSession static class MultipleConnectionFactoryRedisConfig { @Bean @@ -402,19 +371,19 @@ class RedisHttpSessionConfigurationTests { } @Configuration - @EnableRedisHttpSession(redisNamespace = "myRedisNamespace") + @EnableRedisIndexedHttpSession(redisNamespace = "myRedisNamespace") static class CustomRedisHttpSessionConfiguration { } @Configuration - @EnableRedisHttpSession(redisNamespace = "${session.redis.namespace}") + @EnableRedisIndexedHttpSession(redisNamespace = "${session.redis.namespace}") static class CustomRedisHttpSessionConfiguration2 { } @Configuration - @EnableRedisHttpSession + @EnableRedisIndexedHttpSession static class CustomIndexResolverConfiguration { @Bean @@ -426,7 +395,7 @@ class RedisHttpSessionConfigurationTests { } @Configuration - @EnableRedisHttpSession + @EnableRedisIndexedHttpSession static class CustomRedisMessageListenerContainerConfig { @Bean @@ -436,7 +405,7 @@ class RedisHttpSessionConfigurationTests { } - @EnableRedisHttpSession + @EnableRedisIndexedHttpSession static class SessionRepositoryCustomizerConfiguration { @Bean diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/gh109/Gh109Tests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/gh109/Gh109Tests.java index 26797615..aba58ee8 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/gh109/Gh109Tests.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/gh109/Gh109Tests.java @@ -66,7 +66,7 @@ class Gh109Tests { * override sessionRepository construction to set the custom session-timeout */ @Bean - RedisIndexedSessionRepository sessionRepository(RedisOperations sessionRedisTemplate, + RedisIndexedSessionRepository sessionRepository(RedisOperations sessionRedisTemplate, ApplicationEventPublisher applicationEventPublisher) { RedisIndexedSessionRepository sessionRepository = new RedisIndexedSessionRepository(sessionRedisTemplate); sessionRepository.setDefaultMaxInactiveInterval(this.sessionTimeout); diff --git a/spring-session-data-redis/src/test/resources/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationClassPathXmlApplicationContextTests-context.xml b/spring-session-data-redis/src/test/resources/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationClassPathXmlApplicationContextTests-context.xml index f60708c6..efa78fb6 100644 --- a/spring-session-data-redis/src/test/resources/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationClassPathXmlApplicationContextTests-context.xml +++ b/spring-session-data-redis/src/test/resources/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationClassPathXmlApplicationContextTests-context.xml @@ -7,7 +7,7 @@ - + diff --git a/spring-session-data-redis/src/test/resources/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationXmlCustomExpireTests-context.xml b/spring-session-data-redis/src/test/resources/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationXmlCustomExpireTests-context.xml index 8a3d6aa5..ad70969a 100644 --- a/spring-session-data-redis/src/test/resources/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationXmlCustomExpireTests-context.xml +++ b/spring-session-data-redis/src/test/resources/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationXmlCustomExpireTests-context.xml @@ -8,7 +8,7 @@ - - + diff --git a/spring-session-docs/modules/ROOT/examples/java/docs/IndexDocTests.java b/spring-session-docs/modules/ROOT/examples/java/docs/IndexDocTests.java index 1e96061e..3326c1ae 100644 --- a/spring-session-docs/modules/ROOT/examples/java/docs/IndexDocTests.java +++ b/spring-session-docs/modules/ROOT/examples/java/docs/IndexDocTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-2022 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. @@ -130,7 +130,7 @@ class IndexDocTests { @SuppressWarnings("unused") void newRedisIndexedSessionRepository() { // tag::new-redisindexedsessionrepository[] - RedisTemplate redisTemplate = new RedisTemplate<>(); + RedisTemplate redisTemplate = new RedisTemplate<>(); // ... configure redisTemplate ... diff --git a/spring-session-docs/modules/ROOT/examples/resources/docs/HttpSessionConfigurationNoOpConfigureRedisActionXmlTests-context.xml b/spring-session-docs/modules/ROOT/examples/resources/docs/HttpSessionConfigurationNoOpConfigureRedisActionXmlTests-context.xml index 631a86dd..654186f6 100644 --- a/spring-session-docs/modules/ROOT/examples/resources/docs/HttpSessionConfigurationNoOpConfigureRedisActionXmlTests-context.xml +++ b/spring-session-docs/modules/ROOT/examples/resources/docs/HttpSessionConfigurationNoOpConfigureRedisActionXmlTests-context.xml @@ -10,7 +10,7 @@ - + - + diff --git a/spring-session-docs/modules/ROOT/nav.adoc b/spring-session-docs/modules/ROOT/nav.adoc index dfb51712..68e0daa6 100644 --- a/spring-session-docs/modules/ROOT/nav.adoc +++ b/spring-session-docs/modules/ROOT/nav.adoc @@ -4,7 +4,6 @@ *** HttpSession **** Redis ***** {gh-samples-url}spring-session-sample-boot-redis-json[JSON serialization] -***** {gh-samples-url}spring-session-sample-boot-redis-simple[Simple Redis] ***** xref:guides/boot-redis.adoc[Redis with Events] **** xref:guides/boot-mongo.adoc[MongoDB] **** xref:guides/boot-jdbc.adoc[JDBC] diff --git a/spring-session-docs/modules/ROOT/pages/samples.adoc b/spring-session-docs/modules/ROOT/pages/samples.adoc index 9fb8f670..e6e92f51 100644 --- a/spring-session-docs/modules/ROOT/pages/samples.adoc +++ b/spring-session-docs/modules/ROOT/pages/samples.adoc @@ -39,10 +39,6 @@ To get started with Spring Session, the best place to start is our Sample Applic | Demonstrates how to use Spring Session to replace the `HttpSession` with Redis using JSON serialization. | -| {gh-samples-url}spring-session-sample-boot-redis-simple[HttpSession with simple Redis `SessionRepository`] -| Demonstrates how to use Spring Session to replace the `HttpSession` with Redis using `RedisSessionRepository`. -| - | {gh-samples-url}spring-session-sample-boot-mongodb-traditional[Spring Session with MongoDB Repositories (servlet-based)] | Demonstrates how to back Spring Session with traditional MongoDB repositories. | link:guides/boot-mongo.html[Spring Session with MongoDB Repositories] diff --git a/spring-session-samples/spring-session-sample-boot-redis-simple/spring-session-sample-boot-redis-simple.gradle b/spring-session-samples/spring-session-sample-boot-redis-simple/spring-session-sample-boot-redis-simple.gradle deleted file mode 100644 index 701dec3b..00000000 --- a/spring-session-samples/spring-session-sample-boot-redis-simple/spring-session-sample-boot-redis-simple.gradle +++ /dev/null @@ -1,22 +0,0 @@ -apply plugin: 'io.spring.convention.spring-sample-boot' - -dependencies { - implementation project(':spring-session-data-redis') - implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect' - implementation 'org.springframework.boot:spring-boot-starter-data-redis' - implementation 'org.springframework.boot:spring-boot-starter-security' - implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-devtools' - implementation 'org.webjars:bootstrap' - implementation 'org.webjars:html5shiv' - implementation 'org.webjars:webjars-locator-core' - - testImplementation 'org.junit.jupiter:junit-jupiter-api' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - - integrationTestCompile "org.seleniumhq.selenium:htmlunit-driver" - integrationTestCompile "org.seleniumhq.selenium:selenium-support" - integrationTestCompile 'org.testcontainers:testcontainers' -} diff --git a/spring-session-samples/spring-session-sample-boot-redis-simple/src/integration-test/java/sample/BootTests.java b/spring-session-samples/spring-session-sample-boot-redis-simple/src/integration-test/java/sample/BootTests.java deleted file mode 100644 index d7ed614a..00000000 --- a/spring-session-samples/spring-session-sample-boot-redis-simple/src/integration-test/java/sample/BootTests.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2014-2022 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 sample; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.openqa.selenium.WebDriver; -import org.testcontainers.containers.GenericContainer; -import sample.pages.HomePage; -import sample.pages.LoginPage; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDriverBuilder; - -@ExtendWith(SpringExtension.class) -@AutoConfigureMockMvc -@SpringBootTest(webEnvironment = WebEnvironment.MOCK) -class BootTests { - - private static final String DOCKER_IMAGE = "redis:7.0.4-alpine"; - - @Autowired - private MockMvc mockMvc; - - private WebDriver driver; - - @BeforeEach - void setup() { - this.driver = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(this.mockMvc).build(); - } - - @AfterEach - void tearDown() { - this.driver.quit(); - } - - @Test - void home() { - LoginPage login = HomePage.go(this.driver); - login.assertAt(); - } - - @Test - void login() { - LoginPage login = HomePage.go(this.driver); - HomePage home = login.form().login(HomePage.class); - home.assertAt(); - home.containCookie("SESSION"); - home.doesNotContainCookie("JSESSIONID"); - } - - @Test - void logout() { - LoginPage login = HomePage.go(this.driver); - HomePage home = login.form().login(HomePage.class); - home.logout(); - login.assertAt(); - } - - @TestConfiguration - static class Config { - - @Bean - GenericContainer redisContainer() { - GenericContainer redisContainer = new GenericContainer(DOCKER_IMAGE).withExposedPorts(6379); - redisContainer.start(); - return redisContainer; - } - - @Bean - LettuceConnectionFactory redisConnectionFactory() { - return new LettuceConnectionFactory(redisContainer().getHost(), redisContainer().getFirstMappedPort()); - } - - } - -} diff --git a/spring-session-samples/spring-session-sample-boot-redis-simple/src/integration-test/java/sample/pages/BasePage.java b/spring-session-samples/spring-session-sample-boot-redis-simple/src/integration-test/java/sample/pages/BasePage.java deleted file mode 100644 index 9159c078..00000000 --- a/spring-session-samples/spring-session-sample-boot-redis-simple/src/integration-test/java/sample/pages/BasePage.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 sample.pages; - -import org.openqa.selenium.WebDriver; - -public class BasePage { - - private WebDriver driver; - - public BasePage(WebDriver driver) { - this.driver = driver; - } - - public WebDriver getDriver() { - return this.driver; - } - - public static void get(WebDriver driver, String get) { - String baseUrl = "http://localhost"; - driver.get(baseUrl + get); - } - -} diff --git a/spring-session-samples/spring-session-sample-boot-redis-simple/src/integration-test/java/sample/pages/HomePage.java b/spring-session-samples/spring-session-sample-boot-redis-simple/src/integration-test/java/sample/pages/HomePage.java deleted file mode 100644 index ab9aa87e..00000000 --- a/spring-session-samples/spring-session-sample-boot-redis-simple/src/integration-test/java/sample/pages/HomePage.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 sample.pages; - -import java.util.Set; - -import org.openqa.selenium.By; -import org.openqa.selenium.Cookie; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.PageFactory; - -import static org.assertj.core.api.Assertions.assertThat; - -public class HomePage extends BasePage { - - public HomePage(WebDriver driver) { - super(driver); - } - - public static LoginPage go(WebDriver driver) { - get(driver, "/"); - return PageFactory.initElements(driver, LoginPage.class); - } - - public void assertAt() { - assertThat(getDriver().getTitle()).isEqualTo("Spring Session Sample - Secured Content"); - } - - public void containCookie(String cookieName) { - Set cookies = getDriver().manage().getCookies(); - assertThat(cookies).extracting("name").contains(cookieName); - } - - public void doesNotContainCookie(String cookieName) { - Set cookies = getDriver().manage().getCookies(); - assertThat(cookies).extracting("name").doesNotContain(cookieName); - } - - public HomePage logout() { - WebElement logout = getDriver().findElement(By.cssSelector("input[type=\"submit\"]")); - logout.click(); - return PageFactory.initElements(getDriver(), HomePage.class); - } - -} diff --git a/spring-session-samples/spring-session-sample-boot-redis-simple/src/integration-test/java/sample/pages/LoginPage.java b/spring-session-samples/spring-session-sample-boot-redis-simple/src/integration-test/java/sample/pages/LoginPage.java deleted file mode 100644 index e72bba1e..00000000 --- a/spring-session-samples/spring-session-sample-boot-redis-simple/src/integration-test/java/sample/pages/LoginPage.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 sample.pages; - -import org.openqa.selenium.SearchContext; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.FindBy; -import org.openqa.selenium.support.PageFactory; -import org.openqa.selenium.support.pagefactory.DefaultElementLocatorFactory; - -import static org.assertj.core.api.Assertions.assertThat; - -public class LoginPage extends BasePage { - - public LoginPage(WebDriver driver) { - super(driver); - } - - public void assertAt() { - assertThat(getDriver().getTitle()).isEqualTo("Please sign in"); - } - - public Form form() { - return new Form(getDriver()); - } - - public class Form { - - @FindBy(name = "username") - private WebElement username; - - @FindBy(name = "password") - private WebElement password; - - @FindBy(tagName = "button") - private WebElement button; - - public Form(SearchContext context) { - PageFactory.initElements(new DefaultElementLocatorFactory(context), this); - } - - public T login(Class page) { - this.username.sendKeys("user"); - this.password.sendKeys("password"); - this.button.click(); - return PageFactory.initElements(getDriver(), page); - } - - } - -} diff --git a/spring-session-samples/spring-session-sample-boot-redis-simple/src/integration-test/resources/testcontainers.properties b/spring-session-samples/spring-session-sample-boot-redis-simple/src/integration-test/resources/testcontainers.properties deleted file mode 100644 index e3e83419..00000000 --- a/spring-session-samples/spring-session-sample-boot-redis-simple/src/integration-test/resources/testcontainers.properties +++ /dev/null @@ -1 +0,0 @@ -ryuk.container.timeout=120 diff --git a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/java/sample/Application.java b/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/java/sample/Application.java deleted file mode 100644 index e9c3db94..00000000 --- a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/java/sample/Application.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 sample; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class Application { - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - -} diff --git a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/java/sample/IndexController.java b/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/java/sample/IndexController.java deleted file mode 100644 index 25418e26..00000000 --- a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/java/sample/IndexController.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2014-2022 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 sample; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; - -/** - * An index controller. - * - * @author Rob Winch - */ -@Controller -public class IndexController { - - @GetMapping("/") - String index() { - return "index"; - } - -} diff --git a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/java/sample/UserControllerAdvise.java b/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/java/sample/UserControllerAdvise.java deleted file mode 100644 index c7df27e8..00000000 --- a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/java/sample/UserControllerAdvise.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2014-2022 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 sample; - -import java.security.Principal; - -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ModelAttribute; - -/** - * {@link ControllerAdvice} to expose user related attributes. - * - * @author Rob Winch - */ -@ControllerAdvice -public class UserControllerAdvise { - - @ModelAttribute("currentUserName") - String currentUser(Principal principal) { - return (principal != null) ? principal.getName() : null; - } - -} diff --git a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/java/sample/config/SecurityConfig.java b/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/java/sample/config/SecurityConfig.java deleted file mode 100644 index 66e93ea8..00000000 --- a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/java/sample/config/SecurityConfig.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2014-2022 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 sample.config; - -import org.springframework.boot.autoconfigure.security.servlet.PathRequest; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.web.SecurityFilterChain; - -@Configuration -public class SecurityConfig { - - // @formatter:off - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - return http - .authorizeRequests((authorize) -> authorize - .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() - .anyRequest().authenticated() - ) - .formLogin((formLogin) -> formLogin - .permitAll() - ) - .build(); - } - // @formatter:on - -} diff --git a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/java/sample/config/SessionConfig.java b/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/java/sample/config/SessionConfig.java deleted file mode 100644 index aa2eb6dc..00000000 --- a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/java/sample/config/SessionConfig.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2014-2022 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 sample.config; - -import java.time.Duration; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.session.RedisSessionProperties; -import org.springframework.boot.autoconfigure.session.SessionProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisOperations; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; -import org.springframework.session.data.redis.RedisSessionRepository; - -@Configuration(proxyBeanMethods = false) -@EnableConfigurationProperties(RedisSessionProperties.class) -@EnableSpringHttpSession -public class SessionConfig { - - private final SessionProperties sessionProperties; - - private final RedisSessionProperties redisSessionProperties; - - private final RedisConnectionFactory redisConnectionFactory; - - public SessionConfig(SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties, - ObjectProvider redisConnectionFactory) { - this.sessionProperties = sessionProperties; - this.redisSessionProperties = redisSessionProperties; - this.redisConnectionFactory = redisConnectionFactory.getObject(); - } - - @Bean - public RedisOperations sessionRedisOperations() { - RedisTemplate redisTemplate = new RedisTemplate<>(); - redisTemplate.setConnectionFactory(this.redisConnectionFactory); - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setHashKeySerializer(new StringRedisSerializer()); - return redisTemplate; - } - - @Bean - public RedisSessionRepository sessionRepository(RedisOperations sessionRedisOperations) { - RedisSessionRepository sessionRepository = new RedisSessionRepository(sessionRedisOperations); - Duration timeout = this.sessionProperties.getTimeout(); - if (timeout != null) { - sessionRepository.setDefaultMaxInactiveInterval(timeout); - } - sessionRepository.setRedisKeyNamespace(this.redisSessionProperties.getNamespace()); - sessionRepository.setFlushMode(this.redisSessionProperties.getFlushMode()); - sessionRepository.setSaveMode(this.redisSessionProperties.getSaveMode()); - return sessionRepository; - } - -} diff --git a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/resources/application.properties b/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/resources/application.properties deleted file mode 100644 index 1b5271b5..00000000 --- a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.security.user.password=password diff --git a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/resources/static/favicon.ico b/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/resources/static/favicon.ico deleted file mode 100644 index bfb99740..00000000 Binary files a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/resources/static/favicon.ico and /dev/null differ diff --git a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/resources/static/images/logo.png b/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/resources/static/images/logo.png deleted file mode 100644 index 39323088..00000000 Binary files a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/resources/static/images/logo.png and /dev/null differ diff --git a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/resources/templates/index.html b/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/resources/templates/index.html deleted file mode 100644 index 769c73c2..00000000 --- a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/resources/templates/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - Secured Content - - -
-

Secured Page

-

This page is secured using Spring Boot, Spring Session, and Spring Security.

-
- - diff --git a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/resources/templates/layout.html b/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/resources/templates/layout.html deleted file mode 100644 index 140c16e0..00000000 --- a/spring-session-samples/spring-session-sample-boot-redis-simple/src/main/resources/templates/layout.html +++ /dev/null @@ -1,127 +0,0 @@ - - - - Spring Session Sample - - - - - - - - - - - -
- - -
-
- Some Success message -
-
- Fake content -
-
- -
-
- - - -