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 08745fab..501b2d22 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 @@ -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. @@ -691,7 +691,7 @@ class RedisIndexedSessionRepositoryITests extends AbstractRedisITests { } @Configuration - @EnableRedisHttpSession(redisNamespace = "RedisIndexedSessionRepositoryITests") + @EnableRedisHttpSession(redisNamespace = "RedisIndexedSessionRepositoryITests", enableIndexingAndEvents = true) 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 cc2c0b51..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 @@ -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. @@ -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/EnableRedisHttpSessionExpireSessionDestroyedTests.java index 47568ebc..10fd1415 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/EnableRedisHttpSessionExpireSessionDestroyedTests.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. @@ -113,7 +113,7 @@ class EnableRedisHttpSessionExpireSessionDestroyedTests exten } @Configuration - @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1) + @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1, enableIndexingAndEvents = true) 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 8bf79e9c..e70f159c 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 @@ -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. @@ -101,7 +101,7 @@ class RedisListenerContainerTaskExecutorITests extends AbstractRedisITests { } @Configuration - @EnableRedisHttpSession(redisNamespace = "RedisListenerContainerTaskExecutorITests") + @EnableRedisHttpSession(redisNamespace = "RedisListenerContainerTaskExecutorITests", enableIndexingAndEvents = true) static class Config extends BaseConfig { @Bean 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 7b358e8d..ac975284 100644 --- a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisHttpSession.java +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisHttpSession.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-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. @@ -33,6 +33,7 @@ import org.springframework.session.SessionRepository; import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; import org.springframework.session.data.redis.RedisFlushMode; import org.springframework.session.data.redis.RedisIndexedSessionRepository; +import org.springframework.session.data.redis.RedisSessionRepository; import org.springframework.session.web.http.SessionRepositoryFilter; /** @@ -54,7 +55,8 @@ import org.springframework.session.web.http.SessionRepositoryFilter; * } * * - * More advanced configurations can extend {@link RedisHttpSessionConfiguration} instead. + * More advanced configurations can extend {@link RedisHttpSessionConfiguration} or + * {@link RedisIndexedHttpSessionConfiguration} instead. * * @author Rob Winch * @author Vedran Pavic @@ -64,7 +66,7 @@ import org.springframework.session.web.http.SessionRepositoryFilter; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented -@Import(RedisHttpSessionConfiguration.class) +@Import(RedisHttpSessionConfigurationSelector.class) @Configuration(proxyBeanMethods = false) public @interface EnableRedisHttpSession { @@ -113,13 +115,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. @@ -128,4 +123,13 @@ public @interface EnableRedisHttpSession { */ SaveMode saveMode() default SaveMode.ON_SET_ATTRIBUTE; + /** + * Indicate whether the {@link SessionRepository} should publish session events and + * support fetching sessions by index. If true, a + * {@link RedisIndexedSessionRepository} will be used in place of + * {@link RedisSessionRepository}. This will result in slower performance. + * @return true if indexing and events should be enabled, false otherwise + */ + boolean enableIndexingAndEvents() default false; + } 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 3bd7cb94..814f4eec 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 @@ -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. @@ -16,55 +16,35 @@ package org.springframework.session.data.redis.config.annotation.web.http; -import java.util.Arrays; -import java.util.Collections; +import java.time.Duration; import java.util.List; 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.RedisFlushMode; -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.RedisSessionRepository; import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory; 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; @@ -83,86 +63,39 @@ import org.springframework.util.StringValueResolver; 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 String redisNamespace = RedisSessionRepository.DEFAULT_KEY_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 List> sessionRepositoryCustomizers; private ClassLoader classLoader; 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); - } - if (this.defaultRedisSerializer != null) { - sessionRepository.setDefaultSerializer(this.defaultRedisSerializer); - } - sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds); + public RedisSessionRepository sessionRepository() { + RedisTemplate redisTemplate = createRedisTemplate(); + RedisSessionRepository sessionRepository = new RedisSessionRepository(redisTemplate); + sessionRepository.setDefaultMaxInactiveInterval(Duration.ofSeconds(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 .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; } @@ -186,20 +119,6 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio 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, @@ -217,31 +136,9 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio 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) { + ObjectProvider> sessionRepositoryCustomizers) { this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList()); } @@ -273,14 +170,10 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio } this.flushMode = 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<>(); + private RedisTemplate createRedisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); if (this.defaultRedisSerializer != null) { @@ -292,77 +185,4 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio 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); - } - - } - } diff --git a/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationSelector.java b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationSelector.java new file mode 100644 index 00000000..704e2628 --- /dev/null +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationSelector.java @@ -0,0 +1,46 @@ +/* + * 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 org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; + +/** + * Dynamically determines which session repository configuration to include using the + * {@link EnableRedisHttpSession} annotation. + * + * @author Eleftheria Stein + * @since 3.0 + */ +final class RedisHttpSessionConfigurationSelector implements ImportSelector { + + @Override + public String[] selectImports(AnnotationMetadata importMetadata) { + if (!importMetadata.hasAnnotation(EnableRedisHttpSession.class.getName())) { + return new String[0]; + } + EnableRedisHttpSession annotation = importMetadata.getAnnotations().get(EnableRedisHttpSession.class) + .synthesize(); + if (annotation.enableIndexingAndEvents()) { + return new String[] { RedisIndexedHttpSessionConfiguration.class.getName() }; + } + else { + return new String[] { RedisHttpSessionConfiguration.class.getName() }; + } + } + +} 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..ad85e01c --- /dev/null +++ b/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/RedisIndexedHttpSessionConfiguration.java @@ -0,0 +1,361 @@ +/* + * 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.List; +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.RedisFlushMode; +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.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. + * + * @author Eleftheria Stein + * @since 3.0 + */ +@Configuration(proxyBeanMethods = false) +public class RedisIndexedHttpSessionConfiguration 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; + + 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); + } + 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 + .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; + } + + @Deprecated + public void setRedisFlushMode(RedisFlushMode redisFlushMode) { + Assert.notNull(redisFlushMode, "redisFlushMode cannot be null"); + setFlushMode(redisFlushMode.getFlushMode()); + } + + 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; + } + + @Override + @SuppressWarnings("deprecation") + public void setImportMetadata(AnnotationMetadata importMetadata) { + Map attributeMap = importMetadata + .getAnnotationAttributes(EnableRedisHttpSession.class.getName()); + AnnotationAttributes attributes = AnnotationAttributes.fromMap(attributeMap); + this.maxInactiveIntervalInSeconds = attributes.getNumber("maxInactiveIntervalInSeconds"); + String redisNamespaceValue = attributes.getString("redisNamespace"); + if (StringUtils.hasText(redisNamespaceValue)) { + this.redisNamespace = this.embeddedValueResolver.resolveStringValue(redisNamespaceValue); + } + FlushMode flushMode = attributes.getEnum("flushMode"); + RedisFlushMode redisFlushMode = attributes.getEnum("redisFlushMode"); + if (flushMode == FlushMode.ON_SAVE && redisFlushMode != RedisFlushMode.ON_SAVE) { + flushMode = redisFlushMode.getFlushMode(); + } + this.flushMode = flushMode; + this.saveMode = attributes.getEnum("saveMode"); + } + + 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, + RedisIndexedHttpSessionConfiguration.this.cleanupCron); + } + + } + +} 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 adc48451..3f863547 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 @@ -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. @@ -49,14 +49,14 @@ class EnableRedisKeyspaceNotificationsInitializerTests { @Captor ArgumentCaptor options; - private RedisHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer initializer; + private RedisIndexedHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer initializer; @BeforeEach void setup() { MockitoAnnotations.initMocks(this); given(this.connectionFactory.getConnection()).willReturn(this.connection); - 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/RedisHttpSessionConfigurationTests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationTests.java index 1f9437ba..26f138c8 100644 --- a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationTests.java +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationTests.java @@ -16,7 +16,7 @@ package org.springframework.session.data.redis.config.annotation.web.http; -import java.util.Map; +import java.time.Duration; import java.util.Properties; import org.junit.jupiter.api.AfterEach; @@ -34,15 +34,12 @@ import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.SubscriptionListener; import org.springframework.data.redis.core.RedisOperations; -import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.mock.env.MockEnvironment; import org.springframework.session.FlushMode; -import org.springframework.session.IndexResolver; import org.springframework.session.SaveMode; -import org.springframework.session.Session; import org.springframework.session.config.SessionRepositoryCustomizer; import org.springframework.session.data.redis.RedisFlushMode; -import org.springframework.session.data.redis.RedisIndexedSessionRepository; +import org.springframework.session.data.redis.RedisSessionRepository; import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory; import org.springframework.test.util.ReflectionTestUtils; @@ -62,9 +59,7 @@ import static org.mockito.Mockito.mock; */ class RedisHttpSessionConfigurationTests { - private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600; - - private static final String CLEANUP_CRON_EXPRESSION = "0 0 * * * *"; + private static final Duration MAX_INACTIVE_INTERVAL_DURATION = Duration.ofSeconds(600); private AnnotationConfigApplicationContext context; @@ -100,7 +95,7 @@ class RedisHttpSessionConfigurationTests { @Test void customFlushImmediately() { registerAndRefresh(RedisConfig.class, CustomFlushImmediatelyConfiguration.class); - RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class); + RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class); assertThat(sessionRepository).isNotNull(); assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); } @@ -108,7 +103,7 @@ class RedisHttpSessionConfigurationTests { @Test void customFlushImmediatelyLegacy() { registerAndRefresh(RedisConfig.class, CustomFlushImmediatelyLegacyConfiguration.class); - RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class); + RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class); assertThat(sessionRepository).isNotNull(); assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); } @@ -116,7 +111,7 @@ class RedisHttpSessionConfigurationTests { @Test void setCustomFlushImmediately() { registerAndRefresh(RedisConfig.class, CustomFlushImmediatelySetConfiguration.class); - RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class); + RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class); assertThat(sessionRepository).isNotNull(); assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); } @@ -124,40 +119,22 @@ class RedisHttpSessionConfigurationTests { @Test void setCustomFlushImmediatelyLegacy() { registerAndRefresh(RedisConfig.class, CustomFlushImmediatelySetLegacyConfiguration.class); - RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class); + RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class); assertThat(sessionRepository).isNotNull(); assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); } - @Test - 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); - assertThat(configuration).isNotNull(); - assertThat(ReflectionTestUtils.getField(configuration, "cleanupCron")).isEqualTo(CLEANUP_CRON_EXPRESSION); - } - @Test void customSaveModeAnnotation() { registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionAnnotationConfiguration.class); - assertThat(this.context.getBean(RedisIndexedSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode", + assertThat(this.context.getBean(RedisSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode", SaveMode.ALWAYS); } @Test void customSaveModeSetter() { registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionSetterConfiguration.class); - assertThat(this.context.getBean(RedisIndexedSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode", + assertThat(this.context.getBean(RedisSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode", SaveMode.ALWAYS); } @@ -165,7 +142,7 @@ class RedisHttpSessionConfigurationTests { void qualifiedConnectionFactoryRedisConfig() { registerAndRefresh(RedisConfig.class, QualifiedConnectionFactoryRedisConfig.class); - RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class); + RedisSessionRepository repository = this.context.getBean(RedisSessionRepository.class); RedisConnectionFactory redisConnectionFactory = this.context.getBean("qualifiedRedisConnectionFactory", RedisConnectionFactory.class); assertThat(repository).isNotNull(); @@ -181,7 +158,7 @@ class RedisHttpSessionConfigurationTests { void primaryConnectionFactoryRedisConfig() { registerAndRefresh(RedisConfig.class, PrimaryConnectionFactoryRedisConfig.class); - RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class); + RedisSessionRepository repository = this.context.getBean(RedisSessionRepository.class); RedisConnectionFactory redisConnectionFactory = this.context.getBean("primaryRedisConnectionFactory", RedisConnectionFactory.class); assertThat(repository).isNotNull(); @@ -197,7 +174,7 @@ class RedisHttpSessionConfigurationTests { void qualifiedAndPrimaryConnectionFactoryRedisConfig() { registerAndRefresh(RedisConfig.class, QualifiedAndPrimaryConnectionFactoryRedisConfig.class); - RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class); + RedisSessionRepository repository = this.context.getBean(RedisSessionRepository.class); RedisConnectionFactory redisConnectionFactory = this.context.getBean("qualifiedRedisConnectionFactory", RedisConnectionFactory.class); assertThat(repository).isNotNull(); @@ -213,7 +190,7 @@ class RedisHttpSessionConfigurationTests { void namedConnectionFactoryRedisConfig() { registerAndRefresh(RedisConfig.class, NamedConnectionFactoryRedisConfig.class); - RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class); + RedisSessionRepository repository = this.context.getBean(RedisSessionRepository.class); RedisConnectionFactory redisConnectionFactory = this.context.getBean("redisConnectionFactory", RedisConnectionFactory.class); assertThat(repository).isNotNull(); @@ -232,32 +209,12 @@ class RedisHttpSessionConfigurationTests { .withMessageContaining("expected single matching bean but found 2"); } - @Test - void customIndexResolverConfiguration() { - registerAndRefresh(RedisConfig.class, CustomIndexResolverConfiguration.class); - RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class); - @SuppressWarnings("unchecked") - IndexResolver indexResolver = this.context.getBean(IndexResolver.class); - assertThat(repository).isNotNull(); - assertThat(indexResolver).isNotNull(); - assertThat(repository).hasFieldOrPropertyWithValue("indexResolver", indexResolver); - } - - @Test // gh-1252 - void customRedisMessageListenerContainerConfig() { - registerAndRefresh(RedisConfig.class, CustomRedisMessageListenerContainerConfig.class); - Map beans = this.context - .getBeansOfType(RedisMessageListenerContainer.class); - assertThat(beans).hasSize(2); - assertThat(beans).containsKeys("springSessionRedisMessageListenerContainer", "redisMessageListenerContainer"); - } - @Test void sessionRepositoryCustomizer() { registerAndRefresh(RedisConfig.class, SessionRepositoryCustomizerConfiguration.class); - RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class); + RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class); assertThat(sessionRepository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", - MAX_INACTIVE_INTERVAL_IN_SECONDS); + MAX_INACTIVE_INTERVAL_DURATION); } private void registerAndRefresh(Class... annotatedClasses) { @@ -338,20 +295,6 @@ class RedisHttpSessionConfigurationTests { } - @EnableRedisHttpSession(cleanupCron = CLEANUP_CRON_EXPRESSION) - static class CustomCleanupCronExpressionAnnotationConfiguration { - - } - - @Configuration - static class CustomCleanupCronExpressionSetterConfiguration extends RedisHttpSessionConfiguration { - - CustomCleanupCronExpressionSetterConfiguration() { - setCleanupCron(CLEANUP_CRON_EXPRESSION); - } - - } - @EnableRedisHttpSession(saveMode = SaveMode.ALWAYS) static class CustomSaveModeExpressionAnnotationConfiguration { @@ -442,43 +385,20 @@ class RedisHttpSessionConfigurationTests { } - @Configuration - @EnableRedisHttpSession - static class CustomIndexResolverConfiguration { - - @Bean - @SuppressWarnings("unchecked") - IndexResolver indexResolver() { - return mock(IndexResolver.class); - } - - } - - @Configuration - @EnableRedisHttpSession - static class CustomRedisMessageListenerContainerConfig { - - @Bean - RedisMessageListenerContainer redisMessageListenerContainer() { - return mock(RedisMessageListenerContainer.class); - } - - } - @EnableRedisHttpSession static class SessionRepositoryCustomizerConfiguration { @Bean @Order(0) - SessionRepositoryCustomizer sessionRepositoryCustomizerOne() { - return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(0); + SessionRepositoryCustomizer sessionRepositoryCustomizerOne() { + return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(Duration.ZERO); } @Bean @Order(1) - SessionRepositoryCustomizer sessionRepositoryCustomizerTwo() { + SessionRepositoryCustomizer sessionRepositoryCustomizerTwo() { return (sessionRepository) -> sessionRepository - .setDefaultMaxInactiveInterval(MAX_INACTIVE_INTERVAL_IN_SECONDS); + .setDefaultMaxInactiveInterval(MAX_INACTIVE_INTERVAL_DURATION); } } 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 93% 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 dd1ce66c..c3cc366d 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 @@ -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. @@ -24,7 +24,7 @@ import org.mockito.MockitoAnnotations; 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; @@ -33,7 +33,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -class RedisHttpSessionConfigurationMockTests { +class RedisIndexedHttpSessionConfigurationMockTests { @Mock RedisConnectionFactory factory; 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 96% 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 fca2d967..2a184218 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 @@ -48,7 +48,7 @@ import static org.mockito.Mockito.verify; @ExtendWith(SpringExtension.class) @ContextConfiguration @WebAppConfiguration -class RedisHttpSessionConfigurationOverrideSessionTaskExecutor { +class RedisIndexedHttpSessionConfigurationOverrideSessionTaskExecutor { @Autowired RedisMessageListenerContainer redisMessageListenerContainer; @@ -61,7 +61,7 @@ class RedisHttpSessionConfigurationOverrideSessionTaskExecutor { verify(this.springSessionRedisTaskExecutor, times(1)).execute(any(Runnable.class)); } - @EnableRedisHttpSession + @EnableRedisHttpSession(enableIndexingAndEvents = true) @Configuration static class Config { 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 96% 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 92bded75..8f2f293b 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 @@ -50,7 +50,7 @@ import static org.mockito.Mockito.verify; @ExtendWith(SpringExtension.class) @ContextConfiguration @WebAppConfiguration -class RedisHttpSessionConfigurationOverrideSessionTaskExecutors { +class RedisIndexedHttpSessionConfigurationOverrideSessionTaskExecutors { @Autowired RedisMessageListenerContainer redisMessageListenerContainer; @@ -67,7 +67,7 @@ class RedisHttpSessionConfigurationOverrideSessionTaskExecutors { verify(this.springSessionRedisTaskExecutor, never()).execute(any(Runnable.class)); } - @EnableRedisHttpSession + @EnableRedisHttpSession(enableIndexingAndEvents = true) @Configuration static class Config { diff --git a/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisIndexedHttpSessionConfigurationTests.java b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisIndexedHttpSessionConfigurationTests.java new file mode 100644 index 00000000..7288067f --- /dev/null +++ b/spring-session-data-redis/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisIndexedHttpSessionConfigurationTests.java @@ -0,0 +1,471 @@ +/* + * 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.Map; +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.SubscriptionListener; +import org.springframework.data.redis.core.RedisOperations; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.mock.env.MockEnvironment; +import org.springframework.session.FlushMode; +import org.springframework.session.IndexResolver; +import org.springframework.session.SaveMode; +import org.springframework.session.Session; +import org.springframework.session.config.SessionRepositoryCustomizer; +import org.springframework.session.data.redis.RedisFlushMode; +import org.springframework.session.data.redis.RedisIndexedSessionRepository; +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 RedisIndexedHttpSessionConfiguration}. + */ +class RedisIndexedHttpSessionConfigurationTests { + + private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600; + + private static final String CLEANUP_CRON_EXPRESSION = "0 0 * * * *"; + + 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); + RedisIndexedHttpSessionConfiguration configuration = this.context + .getBean(RedisIndexedHttpSessionConfiguration.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); + RedisIndexedHttpSessionConfiguration configuration = this.context + .getBean(RedisIndexedHttpSessionConfiguration.class); + assertThat(ReflectionTestUtils.getField(configuration, "redisNamespace")).isEqualTo("customRedisNamespace"); + } + + @Test + void customFlushImmediately() { + registerAndRefresh(RedisConfig.class, CustomFlushImmediatelyConfiguration.class); + RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class); + assertThat(sessionRepository).isNotNull(); + assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); + } + + @Test + void customFlushImmediatelyLegacy() { + registerAndRefresh(RedisConfig.class, CustomFlushImmediatelyLegacyConfiguration.class); + RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class); + assertThat(sessionRepository).isNotNull(); + assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); + } + + @Test + void setCustomFlushImmediately() { + registerAndRefresh(RedisConfig.class, CustomFlushImmediatelySetConfiguration.class); + RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class); + assertThat(sessionRepository).isNotNull(); + assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); + } + + @Test + void setCustomFlushImmediatelyLegacy() { + registerAndRefresh(RedisConfig.class, CustomFlushImmediatelySetLegacyConfiguration.class); + RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class); + assertThat(sessionRepository).isNotNull(); + assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE); + } + + @Test + void customCleanupCronSetter() { + registerAndRefresh(RedisConfig.class, CustomCleanupCronExpressionSetterConfiguration.class); + + RedisIndexedHttpSessionConfiguration configuration = this.context + .getBean(RedisIndexedHttpSessionConfiguration.class); + assertThat(configuration).isNotNull(); + assertThat(ReflectionTestUtils.getField(configuration, "cleanupCron")).isEqualTo(CLEANUP_CRON_EXPRESSION); + } + + @Test + void customSaveModeAnnotation() { + registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionAnnotationConfiguration.class); + assertThat(this.context.getBean(RedisIndexedSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode", + 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); + + RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class); + RedisConnectionFactory redisConnectionFactory = this.context.getBean("qualifiedRedisConnectionFactory", + RedisConnectionFactory.class); + assertThat(repository).isNotNull(); + assertThat(redisConnectionFactory).isNotNull(); + 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); + + RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class); + RedisConnectionFactory redisConnectionFactory = this.context.getBean("primaryRedisConnectionFactory", + RedisConnectionFactory.class); + assertThat(repository).isNotNull(); + assertThat(redisConnectionFactory).isNotNull(); + 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); + + RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class); + RedisConnectionFactory redisConnectionFactory = this.context.getBean("qualifiedRedisConnectionFactory", + RedisConnectionFactory.class); + assertThat(repository).isNotNull(); + assertThat(redisConnectionFactory).isNotNull(); + 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); + + RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class); + RedisConnectionFactory redisConnectionFactory = this.context.getBean("redisConnectionFactory", + RedisConnectionFactory.class); + assertThat(repository).isNotNull(); + assertThat(redisConnectionFactory).isNotNull(); + 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)) + .withMessageContaining("expected single matching bean but found 2"); + } + + @Test + void customIndexResolverConfiguration() { + registerAndRefresh(RedisConfig.class, CustomIndexResolverConfiguration.class); + RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class); + @SuppressWarnings("unchecked") + IndexResolver indexResolver = this.context.getBean(IndexResolver.class); + assertThat(repository).isNotNull(); + assertThat(indexResolver).isNotNull(); + assertThat(repository).hasFieldOrPropertyWithValue("indexResolver", indexResolver); + } + + @Test // gh-1252 + void customRedisMessageListenerContainerConfig() { + registerAndRefresh(RedisConfig.class, CustomRedisMessageListenerContainerConfig.class); + Map beans = this.context + .getBeansOfType(RedisMessageListenerContainer.class); + assertThat(beans).hasSize(2); + assertThat(beans).containsKeys("springSessionRedisMessageListenerContainer", "redisMessageListenerContainer"); + } + + @Test + void sessionRepositoryCustomizer() { + registerAndRefresh(RedisConfig.class, SessionRepositoryCustomizerConfiguration.class); + RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class); + assertThat(sessionRepository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", + 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); + given(connectionFactoryMock.getConnection()).willReturn(connectionMock); + + Properties keyspaceEventsConfig = new Properties(); + keyspaceEventsConfig.put("notify-keyspace-events", "KEA"); + given(connectionMock.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 RedisIndexedHttpSessionConfiguration { + + CustomFlushImmediatelySetConfiguration() { + setFlushMode(FlushMode.IMMEDIATE); + } + + } + + @Configuration + @SuppressWarnings("deprecation") + static class CustomFlushImmediatelySetLegacyConfiguration extends RedisIndexedHttpSessionConfiguration { + + CustomFlushImmediatelySetLegacyConfiguration() { + setRedisFlushMode(RedisFlushMode.IMMEDIATE); + } + + } + + @Configuration + @EnableRedisHttpSession(flushMode = FlushMode.IMMEDIATE, enableIndexingAndEvents = true) + static class CustomFlushImmediatelyConfiguration { + + } + + @Configuration + @EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE, enableIndexingAndEvents = true) + @SuppressWarnings("deprecation") + static class CustomFlushImmediatelyLegacyConfiguration { + + } + + @Configuration + static class CustomCleanupCronExpressionSetterConfiguration extends RedisIndexedHttpSessionConfiguration { + + CustomCleanupCronExpressionSetterConfiguration() { + setCleanupCron(CLEANUP_CRON_EXPRESSION); + } + + } + + @EnableRedisHttpSession(saveMode = SaveMode.ALWAYS, enableIndexingAndEvents = true) + static class CustomSaveModeExpressionAnnotationConfiguration { + + } + + @Configuration + static class CustomSaveModeExpressionSetterConfiguration extends RedisIndexedHttpSessionConfiguration { + + CustomSaveModeExpressionSetterConfiguration() { + setSaveMode(SaveMode.ALWAYS); + } + + } + + @Configuration + @EnableRedisHttpSession(enableIndexingAndEvents = true) + static class QualifiedConnectionFactoryRedisConfig { + + @Bean + @SpringSessionRedisConnectionFactory + RedisConnectionFactory qualifiedRedisConnectionFactory() { + return mockRedisConnectionFactory(); + } + + } + + @Configuration + @EnableRedisHttpSession(enableIndexingAndEvents = true) + static class PrimaryConnectionFactoryRedisConfig { + + @Bean + @Primary + RedisConnectionFactory primaryRedisConnectionFactory() { + return mockRedisConnectionFactory(); + } + + } + + @Configuration + @EnableRedisHttpSession(enableIndexingAndEvents = true) + static class QualifiedAndPrimaryConnectionFactoryRedisConfig { + + @Bean + @SpringSessionRedisConnectionFactory + RedisConnectionFactory qualifiedRedisConnectionFactory() { + return mockRedisConnectionFactory(); + } + + @Bean + @Primary + RedisConnectionFactory primaryRedisConnectionFactory() { + return mockRedisConnectionFactory(); + } + + } + + @Configuration + @EnableRedisHttpSession(enableIndexingAndEvents = true) + static class NamedConnectionFactoryRedisConfig { + + @Bean + RedisConnectionFactory redisConnectionFactory() { + return mockRedisConnectionFactory(); + } + + } + + @Configuration + @EnableRedisHttpSession(enableIndexingAndEvents = true) + static class MultipleConnectionFactoryRedisConfig { + + @Bean + RedisConnectionFactory secondaryRedisConnectionFactory() { + return mockRedisConnectionFactory(); + } + + } + + @Configuration + @EnableRedisHttpSession(redisNamespace = "myRedisNamespace", enableIndexingAndEvents = true) + static class CustomRedisHttpSessionConfiguration { + + } + + @Configuration + @EnableRedisHttpSession(redisNamespace = "${session.redis.namespace}", enableIndexingAndEvents = true) + static class CustomRedisHttpSessionConfiguration2 { + + } + + @Configuration + @EnableRedisHttpSession(enableIndexingAndEvents = true) + static class CustomIndexResolverConfiguration { + + @Bean + @SuppressWarnings("unchecked") + IndexResolver indexResolver() { + return mock(IndexResolver.class); + } + + } + + @Configuration + @EnableRedisHttpSession(enableIndexingAndEvents = true) + static class CustomRedisMessageListenerContainerConfig { + + @Bean + RedisMessageListenerContainer redisMessageListenerContainer() { + return mock(RedisMessageListenerContainer.class); + } + + } + + @EnableRedisHttpSession(enableIndexingAndEvents = true) + static class SessionRepositoryCustomizerConfiguration { + + @Bean + @Order(0) + SessionRepositoryCustomizer sessionRepositoryCustomizerOne() { + return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(0); + } + + @Bean + @Order(1) + SessionRepositoryCustomizer sessionRepositoryCustomizerTwo() { + return (sessionRepository) -> sessionRepository + .setDefaultMaxInactiveInterval(MAX_INACTIVE_INTERVAL_IN_SECONDS); + } + + } + +} diff --git a/spring-session-docs/modules/ROOT/examples/resources/docs/http/HttpSessionListenerXmlTests-context.xml b/spring-session-docs/modules/ROOT/examples/resources/docs/http/HttpSessionListenerXmlTests-context.xml index aafcaa6e..c0f0fdf4 100644 --- a/spring-session-docs/modules/ROOT/examples/resources/docs/http/HttpSessionListenerXmlTests-context.xml +++ b/spring-session-docs/modules/ROOT/examples/resources/docs/http/HttpSessionListenerXmlTests-context.xml @@ -14,7 +14,7 @@ - +