diff --git a/docs/build.gradle b/docs/build.gradle index 472a607..10b5675 100644 --- a/docs/build.gradle +++ b/docs/build.gradle @@ -31,6 +31,7 @@ asciidoctor { 'spring-session-version' : version, 'spring-version' : springVersion, 'docs-test-dir' : rootProject.projectDir.path + '/docs/src/test/java/', + 'docs-test-resources-dir' : rootProject.projectDir.path + '/docs/src/test/resources/', 'samples-dir' : rootProject.projectDir.path + '/samples/', 'source-highlighter' : 'coderay', diff --git a/docs/src/docs/asciidoc/index.adoc b/docs/src/docs/asciidoc/index.adoc index 0b1b20d..8ea48a0 100644 --- a/docs/src/docs/asciidoc/index.adoc +++ b/docs/src/docs/asciidoc/index.adoc @@ -364,13 +364,30 @@ Firing a `SessionDestroyedEvent` is made available through the `SessionMessageLi In order for this to work, Redis Keyspace events for Generic commands and Expired events needs to be enabled. For example: -TIP: If you are using `@EnableRedisHttpSession` the `SessionMessageListener` and enabling the necessary Redis Keyspace events is done automatically. - [source,bash] ---- redis-cli config set notify-keyspace-events Egx ---- +If you are using `@EnableRedisHttpSession` the `SessionMessageListener` and enabling the necessary Redis Keyspace events is done automatically. +However, in a secured Redis enviornment the config command is disabled. +This means that Spring Session cannot configure Redis Keyspace events for you. +To disable the automatic configuration add `ConfigureRedisAction.NO_OP` as a bean. + +For example, Java Configuration can use the following: + +[source,java,indent=0] +---- +include::{docs-test-dir}docs/RedisHttpSessionConfigurationNoOpConfigureRedisActionTests.java[tags=configure-redis-action] +---- + +XML Configuraiton can use the following: + +[source,xml,indent=0] +---- +include::{docs-test-resources-dir}docs/HttpSessionConfigurationNoOpConfigureRedisActionXmlTests-context.xml[tags=configure-redis-action] +---- + [[api-redisoperationssessionrepository-cli]] ==== Viewing the Session in Redis diff --git a/docs/src/test/java/docs/HttpSessionConfigurationNoOpConfigureRedisActionXmlTests.java b/docs/src/test/java/docs/HttpSessionConfigurationNoOpConfigureRedisActionXmlTests.java new file mode 100644 index 0000000..55b97e8 --- /dev/null +++ b/docs/src/test/java/docs/HttpSessionConfigurationNoOpConfigureRedisActionXmlTests.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package docs; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.session.ExpiringSession; +import org.springframework.session.web.http.SessionRepositoryFilter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +/** + * @author Rob Winch + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@WebAppConfiguration +public class HttpSessionConfigurationNoOpConfigureRedisActionXmlTests { + @Autowired + SessionRepositoryFilter filter; + + @Test + public void redisConnectionFactoryNotUsedSinceNoValidation() { + assertThat(filter).isNotNull(); + } + + + static RedisConnectionFactory connectionFactory() { + return mock(RedisConnectionFactory.class); + } +} diff --git a/docs/src/test/java/docs/RedisHttpSessionConfigurationNoOpConfigureRedisActionTests.java b/docs/src/test/java/docs/RedisHttpSessionConfigurationNoOpConfigureRedisActionTests.java new file mode 100644 index 0000000..2c17f2c --- /dev/null +++ b/docs/src/test/java/docs/RedisHttpSessionConfigurationNoOpConfigureRedisActionTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package docs; + +import static org.mockito.Mockito.mock; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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.EnableRedisHttpSession; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +/** + * @author Rob Winch + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@WebAppConfiguration +public class RedisHttpSessionConfigurationNoOpConfigureRedisActionTests { + + @Test + public void redisConnectionFactoryNotUsedSinceNoValidation() {} + + @EnableRedisHttpSession + @Configuration + static class Config { + + // tag::configure-redis-action[] + @Bean + public static ConfigureRedisAction configureRedisAction() { + return ConfigureRedisAction.NO_OP; + } + // end::configure-redis-action[] + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return mock(RedisConnectionFactory.class); + } + } +} diff --git a/docs/src/test/resources/docs/HttpSessionConfigurationNoOpConfigureRedisActionXmlTests-context.xml b/docs/src/test/resources/docs/HttpSessionConfigurationNoOpConfigureRedisActionXmlTests-context.xml new file mode 100644 index 0000000..11f16ec --- /dev/null +++ b/docs/src/test/resources/docs/HttpSessionConfigurationNoOpConfigureRedisActionXmlTests-context.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-session/src/main/java/org/springframework/session/data/redis/config/ConfigureNotifyKeyspaceEventsAction.java b/spring-session/src/main/java/org/springframework/session/data/redis/config/ConfigureNotifyKeyspaceEventsAction.java new file mode 100644 index 0000000..2a6ea15 --- /dev/null +++ b/spring-session/src/main/java/org/springframework/session/data/redis/config/ConfigureNotifyKeyspaceEventsAction.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.session.data.redis.config; + +import java.util.List; + +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.redis.connection.RedisConnection; + +/** + *

+ * Ensures that Redis Keyspace events for Generic commands and Expired events are enabled. + * For example, it might set the following: + *

+ * + *
+ * config set notify-keyspace-events Egx
+ * 
+ * + *

+ * This strategy will not work if the Redis instance has been properly secured. Instead, + * the Redis instance should be configured externally and a Bean of type + * {@link ConfigureRedisAction#NO_OP} should be exposed. + *

+ * + * @author Rob Winch + * @since 1.0.1 + */ +public class ConfigureNotifyKeyspaceEventsAction implements ConfigureRedisAction { + + static final String CONFIG_NOTIFY_KEYSPACE_EVENTS = "notify-keyspace-events"; + + /* (non-Javadoc) + * @see org.springframework.session.data.redis.config.ConfigureRedisAction#configure(org.springframework.data.redis.connection.RedisConnection) + */ + public void configure(RedisConnection connection) { + String notifyOptions = getNotifyOptions(connection); + String customizedNotifyOptions = notifyOptions; + if(!customizedNotifyOptions.contains("E")) { + customizedNotifyOptions += "E"; + } + boolean A = customizedNotifyOptions.contains("A"); + if(!(A || customizedNotifyOptions.contains("g"))) { + customizedNotifyOptions += "g"; + } + if(!(A || customizedNotifyOptions.contains("x"))) { + customizedNotifyOptions += "x"; + } + if(!notifyOptions.equals(customizedNotifyOptions)) { + connection.setConfig(CONFIG_NOTIFY_KEYSPACE_EVENTS, customizedNotifyOptions); + } + } + + private String getNotifyOptions(RedisConnection connection) { + try { + List config = connection.getConfig(CONFIG_NOTIFY_KEYSPACE_EVENTS); + if(config.size() < 2) { + return ""; + } + return config.get(1); + } catch(InvalidDataAccessApiUsageException e) { + throw new IllegalStateException("Unable to configure Redis to keyspace notifications. See http://docs.spring.io/spring-session/docs/current/reference/html5/#api-redisoperationssessionrepository-sessiondestroyedevent", e); + } + } + +} diff --git a/spring-session/src/main/java/org/springframework/session/data/redis/config/ConfigureRedisAction.java b/spring-session/src/main/java/org/springframework/session/data/redis/config/ConfigureRedisAction.java new file mode 100644 index 0000000..28fabbb --- /dev/null +++ b/spring-session/src/main/java/org/springframework/session/data/redis/config/ConfigureRedisAction.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.session.data.redis.config; + +import org.springframework.data.redis.connection.RedisConnection; + +/** + * Allows specifying a strategy for configuring and validating Redis. + * + * @author Rob Winch + * @since 1.0.1 + */ +public interface ConfigureRedisAction { + + void configure(RedisConnection connection); + + /** + * A do nothing implementation of {@link ConfigureRedisAction}. + */ + ConfigureRedisAction NO_OP = new ConfigureRedisAction() { + + public void configure(RedisConnection connection) { + } + }; +} diff --git a/spring-session/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.java b/spring-session/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.java index 42c8555..fdae896 100644 --- a/spring-session/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.java +++ b/spring-session/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.java @@ -16,7 +16,6 @@ package org.springframework.session.data.redis.config.annotation.web.http; import java.util.Arrays; -import java.util.List; import java.util.Map; import javax.servlet.ServletContext; @@ -42,6 +41,8 @@ import org.springframework.session.ExpiringSession; import org.springframework.session.SessionRepository; import org.springframework.session.data.redis.RedisOperationsSessionRepository; import org.springframework.session.data.redis.SessionMessageListener; +import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction; +import org.springframework.session.data.redis.config.ConfigureRedisAction; import org.springframework.session.web.http.HttpSessionStrategy; import org.springframework.session.web.http.SessionRepositoryFilter; import org.springframework.util.ClassUtils; @@ -69,6 +70,8 @@ public class RedisHttpSessionConfiguration implements ImportAware, BeanClassLoad @Autowired private ApplicationEventPublisher eventPublisher; + private ConfigureRedisAction configureRedisAction = new ConfigureNotifyKeyspaceEventsAction(); + @Bean public RedisMessageListenerContainer redisMessageListenerContainer( RedisConnectionFactory connectionFactory) { @@ -141,7 +144,7 @@ public class RedisHttpSessionConfiguration implements ImportAware, BeanClassLoad @Bean public EnableRedisKeyspaceNotificationsInitializer enableRedisKeyspaceNotificationsInitializer(RedisConnectionFactory connectionFactory) { - return new EnableRedisKeyspaceNotificationsInitializer(connectionFactory); + return new EnableRedisKeyspaceNotificationsInitializer(connectionFactory, configureRedisAction); } /** @@ -154,38 +157,28 @@ public class RedisHttpSessionConfiguration implements ImportAware, BeanClassLoad private final RedisConnectionFactory connectionFactory; - EnableRedisKeyspaceNotificationsInitializer(RedisConnectionFactory connectionFactory) { + private ConfigureRedisAction configure; + + EnableRedisKeyspaceNotificationsInitializer(RedisConnectionFactory connectionFactory, ConfigureRedisAction configure) { this.connectionFactory = connectionFactory; + this.configure = configure; } public void afterPropertiesSet() throws Exception { RedisConnection connection = connectionFactory.getConnection(); - String notifyOptions = getNotifyOptions(connection); - String customizedNotifyOptions = notifyOptions; - if(!customizedNotifyOptions.contains("E")) { - customizedNotifyOptions += "E"; - } - boolean A = customizedNotifyOptions.contains("A"); - if(!(A || customizedNotifyOptions.contains("g"))) { - customizedNotifyOptions += "g"; - } - if(!(A || customizedNotifyOptions.contains("x"))) { - customizedNotifyOptions += "x"; - } - if(!notifyOptions.equals(customizedNotifyOptions)) { - connection.setConfig(CONFIG_NOTIFY_KEYSPACE_EVENTS, customizedNotifyOptions); - } - } - - private String getNotifyOptions(RedisConnection connection) { - List config = connection.getConfig(CONFIG_NOTIFY_KEYSPACE_EVENTS); - if(config.size() < 2) { - return ""; - } - return config.get(1); + configure.configure(connection); } } + /** + * 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; + } /* (non-Javadoc) * @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader) diff --git a/spring-session/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisKeyspaceNotificationsInitializerTests.java b/spring-session/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisKeyspaceNotificationsInitializerTests.java index dcf691a..c1ffe26 100644 --- a/spring-session/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisKeyspaceNotificationsInitializerTests.java +++ b/spring-session/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/EnableRedisKeyspaceNotificationsInitializerTests.java @@ -30,6 +30,7 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction; import java.util.Arrays; @@ -50,7 +51,7 @@ public class EnableRedisKeyspaceNotificationsInitializerTests { public void setup() { when(connectionFactory.getConnection()).thenReturn(connection); - initializer = new EnableRedisKeyspaceNotificationsInitializer(connectionFactory); + initializer = new EnableRedisKeyspaceNotificationsInitializer(connectionFactory, new ConfigureNotifyKeyspaceEventsAction()); } @Test diff --git a/spring-session/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationNoOpConfigureRedisActionTests.java b/spring-session/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationNoOpConfigureRedisActionTests.java new file mode 100644 index 0000000..521947d --- /dev/null +++ b/spring-session/src/test/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfigurationNoOpConfigureRedisActionTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.session.data.redis.config.annotation.web.http; + +import static org.mockito.Mockito.mock; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.session.data.redis.config.ConfigureRedisAction; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +/** + * @author Rob Winch + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@WebAppConfiguration +public class RedisHttpSessionConfigurationNoOpConfigureRedisActionTests { + + @Test + public void redisConnectionFactoryNotUsedSinceNoValidation() {} + + @EnableRedisHttpSession + @Configuration + static class Config { + + @Bean + public static ConfigureRedisAction configureRedisAction() { + return ConfigureRedisAction.NO_OP; + } + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return mock(RedisConnectionFactory.class); + } + } +}