From 6e6b280e3b2e9c0c994ab957e1de665d136e1e77 Mon Sep 17 00:00:00 2001 From: John Blum Date: Tue, 19 Mar 2019 14:19:40 -0700 Subject: [PATCH] Add configuration processor to support auto-completion / content-assist of well-known Spring Session properties, especially when using Apache Geode or Pivotal GemFire as the Session state caching provider. Resolves gh-14. --- ...ingSessionPropertiesAutoConfiguration.java | 56 +++++ .../SpringSessionProperties.java | 221 ++++++++++++++++++ .../main/resources/META-INF/spring.factories | 1 + ...ringSessionPropertiesIntegrationTests.java | 147 ++++++++++++ ...application-session-config-test.properties | 9 + 5 files changed, 434 insertions(+) create mode 100644 spring-geode-autoconfigure/src/main/java/org/springframework/geode/boot/autoconfigure/SpringSessionPropertiesAutoConfiguration.java create mode 100644 spring-geode-autoconfigure/src/main/java/org/springframework/geode/boot/autoconfigure/configuration/SpringSessionProperties.java create mode 100644 spring-geode-autoconfigure/src/test/java/org/springframework/geode/boot/autoconfigure/configuration/SpringSessionPropertiesIntegrationTests.java create mode 100644 spring-geode-autoconfigure/src/test/resources/application-session-config-test.properties diff --git a/spring-geode-autoconfigure/src/main/java/org/springframework/geode/boot/autoconfigure/SpringSessionPropertiesAutoConfiguration.java b/spring-geode-autoconfigure/src/main/java/org/springframework/geode/boot/autoconfigure/SpringSessionPropertiesAutoConfiguration.java new file mode 100644 index 00000000..a1fecd7c --- /dev/null +++ b/spring-geode-autoconfigure/src/main/java/org/springframework/geode/boot/autoconfigure/SpringSessionPropertiesAutoConfiguration.java @@ -0,0 +1,56 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.geode.boot.autoconfigure; + +import org.apache.geode.cache.GemFireCache; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.data.gemfire.CacheFactoryBean; +import org.springframework.geode.boot.autoconfigure.configuration.SpringSessionProperties; +import org.springframework.session.SessionRepository; +import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration; + +/** + * Spring Boot {@link EnableAutoConfiguration auto-configuration} class used to configure Spring Boot + * {@link ConfigurationProperties @ConfigurationProperites} classes and beans from the Spring {@link Environment} + * containing Spring Session configuration properties used to configure either Apache Geode or Pivotal GemFire + * to manage (HTTP) Session state. + * + * @author John Blum + * @see org.apache.geode.cache.GemFireCache + * @see org.springframework.boot.autoconfigure.EnableAutoConfiguration + * @see org.springframework.boot.context.properties.ConfigurationProperties + * @see org.springframework.boot.context.properties.EnableConfigurationProperties + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.core.env.Environment + * @see org.springframework.geode.boot.autoconfigure.configuration.SpringSessionProperties + * @see org.springframework.session.SessionRepository + * @since 1.0.0 + */ +@Configuration +@ConditionalOnBean({ GemFireCache.class, SessionRepository.class }) +@ConditionalOnClass({ GemFireCache.class, CacheFactoryBean.class, GemFireHttpSessionConfiguration.class }) +@EnableConfigurationProperties({ SpringSessionProperties.class }) +@SuppressWarnings("unused") +public class SpringSessionPropertiesAutoConfiguration { + +} diff --git a/spring-geode-autoconfigure/src/main/java/org/springframework/geode/boot/autoconfigure/configuration/SpringSessionProperties.java b/spring-geode-autoconfigure/src/main/java/org/springframework/geode/boot/autoconfigure/configuration/SpringSessionProperties.java new file mode 100644 index 00000000..af73a926 --- /dev/null +++ b/spring-geode-autoconfigure/src/main/java/org/springframework/geode/boot/autoconfigure/configuration/SpringSessionProperties.java @@ -0,0 +1,221 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.geode.boot.autoconfigure.configuration; + +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +/** + * Spring Boot {@link ConfigurationProperties} used to configure Spring Session for Apache Geode or Pivotal GemFire + * (SSDG) in order to manage (HTTP) Session state with Spring Session, backed by either Apache Geode + * or Pivotal GemFire. + * + * @author John Blum + * @see org.springframework.boot.context.properties.ConfigurationProperties + * @see org.springframework.boot.context.properties.NestedConfigurationProperty + * @since 1.0.0 + */ +@SuppressWarnings("unused") +@ConfigurationProperties(prefix = "spring.session.data.gemfire") +public class SpringSessionProperties { + + @NestedConfigurationProperty + private final CacheProperties cache = new CacheProperties(); + + @NestedConfigurationProperty + private final SessionProperties session = new SessionProperties(); + + public CacheProperties getCache() { + return this.cache; + } + + public SessionProperties getSession() { + return this.session; + } + + public static class CacheProperties { + + @NestedConfigurationProperty + private final CacheServerProperties server = new CacheServerProperties(); + + @NestedConfigurationProperty + private final ClientCacheProperties client = new ClientCacheProperties(); + + public ClientCacheProperties getClient() { + return this.client; + } + + public CacheServerProperties getServer() { + return this.server; + } + } + + public static class CacheServerProperties { + + @NestedConfigurationProperty + private final ServerRegionProperties region = new ServerRegionProperties(); + + public ServerRegionProperties getRegion() { + return region; + } + } + + public static class ClientCacheProperties { + + @NestedConfigurationProperty + private final ClientRegionProperties region = new ClientRegionProperties(); + + @NestedConfigurationProperty + private final PoolProperties pool = new PoolProperties(); + + public PoolProperties getPool() { + return this.pool; + } + + public ClientRegionProperties getRegion() { + return this.region; + } + } + + public static class ClientRegionProperties { + + public static final ClientRegionShortcut DEFAULT_CLIENT_REGION_SHORTCUT = ClientRegionShortcut.PROXY; + + private ClientRegionShortcut shortcut = ClientRegionShortcut.PROXY; + + public ClientRegionShortcut getShortcut() { + return this.shortcut != null ? this.shortcut : DEFAULT_CLIENT_REGION_SHORTCUT; + } + + public void setShortcut(ClientRegionShortcut shortcut) { + this.shortcut = shortcut; + } + } + + public static class PoolProperties { + + private String name; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + } + + public static class ServerRegionProperties { + + public static final RegionShortcut DEFAULT_SERVER_REGION_SHORTCUT = RegionShortcut.PARTITION; + + private RegionShortcut shortcut; + + public RegionShortcut getShortcut() { + return this.shortcut != null ? this.shortcut : DEFAULT_SERVER_REGION_SHORTCUT; + } + + public void setShortcut(RegionShortcut shortcut) { + this.shortcut = shortcut; + } + } + + public static class SessionProperties { + + @NestedConfigurationProperty + private final SessionAttributesProperties attributes = new SessionAttributesProperties(); + + @NestedConfigurationProperty + private final SessionExpirationProperties expiration = new SessionExpirationProperties(); + + @NestedConfigurationProperty + private final SessionRegionProperties region = new SessionRegionProperties(); + + @NestedConfigurationProperty + private final SessionSerializerProperties serializer = new SessionSerializerProperties(); + + public SessionAttributesProperties getAttributes() { + return this.attributes; + } + + public SessionExpirationProperties getExpiration() { + return this.expiration; + } + + public SessionRegionProperties getRegion() { + return this.region; + } + + public SessionSerializerProperties getSerializer() { + return this.serializer; + } + } + + public static class SessionAttributesProperties { + + private String[] indexable; + + public String[] getIndexable() { + return this.indexable; + } + + public void setIndexable(String[] indexable) { + this.indexable = indexable; + } + } + + public static class SessionExpirationProperties { + + private int maxInactiveIntervalSeconds; + + public int getMaxInactiveIntervalSeconds() { + return this.maxInactiveIntervalSeconds; + } + + public void setMaxInactiveIntervalSeconds(int maxInactiveIntervalSeconds) { + this.maxInactiveIntervalSeconds = maxInactiveIntervalSeconds; + } + } + + public static class SessionRegionProperties { + + private String name; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + } + + public static class SessionSerializerProperties { + + private String beanName; + + public String getBeanName() { + return this.beanName; + } + + public void setBeanName(String beanName) { + this.beanName = beanName; + } + } +} diff --git a/spring-geode-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-geode-autoconfigure/src/main/resources/META-INF/spring.factories index f662f53c..e98b034f 100644 --- a/spring-geode-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-geode-autoconfigure/src/main/resources/META-INF/spring.factories @@ -11,6 +11,7 @@ org.springframework.geode.boot.autoconfigure.PdxSerializationAutoConfiguration,\ org.springframework.geode.boot.autoconfigure.PeerSecurityAutoConfiguration,\ org.springframework.geode.boot.autoconfigure.RepositoriesAutoConfiguration,\ org.springframework.geode.boot.autoconfigure.SpringSessionAutoConfiguration,\ +org.springframework.geode.boot.autoconfigure.SpringSessionPropertiesAutoConfiguration,\ org.springframework.geode.boot.autoconfigure.SslAutoConfiguration # Environment Post Processing diff --git a/spring-geode-autoconfigure/src/test/java/org/springframework/geode/boot/autoconfigure/configuration/SpringSessionPropertiesIntegrationTests.java b/spring-geode-autoconfigure/src/test/java/org/springframework/geode/boot/autoconfigure/configuration/SpringSessionPropertiesIntegrationTests.java new file mode 100644 index 00000000..5dbd56d1 --- /dev/null +++ b/spring-geode-autoconfigure/src/test/java/org/springframework/geode/boot/autoconfigure/configuration/SpringSessionPropertiesIntegrationTests.java @@ -0,0 +1,147 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.geode.boot.autoconfigure.configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import org.apache.geode.cache.DataPolicy; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.cache.client.ClientCache; +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.apache.geode.cache.client.Pool; +import org.apache.geode.pdx.PdxSerializer; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.data.gemfire.tests.mock.annotation.EnableGemFireMockObjects; +import org.springframework.geode.boot.autoconfigure.ContinuousQueryAutoConfiguration; +import org.springframework.geode.boot.autoconfigure.PdxSerializationAutoConfiguration; +import org.springframework.session.data.gemfire.serialization.SessionSerializer; +import org.springframework.session.data.gemfire.serialization.pdx.support.PdxSerializerSessionSerializerAdapter; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Integration tests for {@link SpringSessionProperties}. + * + * @author John Blum + * @see org.junit.Test + * @see org.springframework.boot.test.context.SpringBootTest + * @see org.springframework.geode.boot.autoconfigure.configuration.SpringSessionProperties + * @see org.springframework.data.gemfire.tests.mock.annotation.EnableGemFireMockObjects + * @see org.springframework.test.context.junit4.SpringRunner + * @since 1.0.0 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) +@ActiveProfiles("session-config-test") +@SuppressWarnings("unused") +public class SpringSessionPropertiesIntegrationTests { + + @Autowired + private ClientCache clientCache; + + @Autowired + @Qualifier("MockSessionSerializer") + private SessionSerializer mockSessionSerializer; + + @Autowired + private SpringSessionProperties springSessionProperties; + + @Before + public void setup() { + + assertThat(this.clientCache).isNotNull(); + assertThat(this.springSessionProperties).isNotNull(); + } + + @Test + public void springSessionPropertiesConfigurationIsCorrect() { + + assertThat(this.springSessionProperties.getCache()).isNotNull(); + assertThat(this.springSessionProperties.getCache().getClient().getPool()).isNotNull(); + assertThat(this.springSessionProperties.getCache().getClient().getPool().getName()).isEqualTo("DEAD"); + assertThat(this.springSessionProperties.getCache().getClient().getRegion()).isNotNull(); + assertThat(this.springSessionProperties.getCache().getClient().getRegion().getShortcut()) + .isEqualTo(ClientRegionShortcut.LOCAL); + assertThat(this.springSessionProperties.getCache().getServer()).isNotNull(); + assertThat(this.springSessionProperties.getCache().getServer().getRegion()).isNotNull(); + assertThat(this.springSessionProperties.getCache().getServer().getRegion().getShortcut()) + .isEqualTo(RegionShortcut.REPLICATE); + assertThat(this.springSessionProperties.getSession()).isNotNull(); + assertThat(this.springSessionProperties.getSession().getAttributes()).isNotNull(); + assertThat(this.springSessionProperties.getSession().getAttributes().getIndexable()) + .containsExactly("firstName", "lastName"); + assertThat(this.springSessionProperties.getSession().getExpiration()).isNotNull(); + assertThat(this.springSessionProperties.getSession().getExpiration().getMaxInactiveIntervalSeconds()) + .isEqualTo(300); + assertThat(this.springSessionProperties.getSession().getRegion()).isNotNull(); + assertThat(this.springSessionProperties.getSession().getRegion().getName()).isEqualTo("TestSessions"); + assertThat(this.springSessionProperties.getSession().getSerializer()).isNotNull(); + assertThat(this.springSessionProperties.getSession().getSerializer().getBeanName()) + .isEqualTo("MockSessionSerializer"); + } + + @Test + public void clientCacheConfigurationIsCorrect() { + + PdxSerializer pdxSerializer = this.clientCache.getPdxSerializer(); + + assertThat(pdxSerializer).isInstanceOf(PdxSerializerSessionSerializerAdapter.class); + assertThat(((PdxSerializerSessionSerializerAdapter) pdxSerializer).getSessionSerializer()) + .isEqualTo(mockSessionSerializer); + } + + @Test + public void sessionsRegionConfigurationIsCorrect() { + + Region sessionsRegion = this.clientCache.getRegion("/TestSessions"); + + assertThat(sessionsRegion).isNotNull(); + assertThat(sessionsRegion.getName()).isEqualTo("TestSessions"); + assertThat(sessionsRegion.getAttributes()).isNotNull(); + assertThat(sessionsRegion.getAttributes().getPoolName()).isEqualTo("DEAD"); + assertThat(sessionsRegion.getAttributes().getDataPolicy()).isEqualTo(DataPolicy.NORMAL); + assertThat(sessionsRegion.getAttributes().getEntryIdleTimeout()).isNotNull(); + assertThat(sessionsRegion.getAttributes().getEntryIdleTimeout().getTimeout()).isEqualTo(300); + } + + @EnableGemFireMockObjects + @SpringBootApplication(exclude = { + PdxSerializationAutoConfiguration.class, + ContinuousQueryAutoConfiguration.class + }) + static class TestConfiguration { + + @Bean("DEAD") + Pool deadPool() { + return mock(Pool.class); + } + + @Bean("MockSessionSerializer") + SessionSerializer mockSessionSerializer() { + return mock(SessionSerializer.class); + } + } +} diff --git a/spring-geode-autoconfigure/src/test/resources/application-session-config-test.properties b/spring-geode-autoconfigure/src/test/resources/application-session-config-test.properties new file mode 100644 index 00000000..fd6f2bc6 --- /dev/null +++ b/spring-geode-autoconfigure/src/test/resources/application-session-config-test.properties @@ -0,0 +1,9 @@ +# Spring Session for Apache Geode/Pivotal GemFire (Test) Properties + +spring.session.data.gemfire.cache.client.pool.name=DEAD +spring.session.data.gemfire.cache.client.region.shortcut=LOCAL +spring.session.data.gemfire.cache.server.region.shortcut=REPLICATE +spring.session.data.gemfire.session.attributes.indexable=firstName,lastName +spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds=300 +spring.session.data.gemfire.session.region.name=TestSessions +spring.session.data.gemfire.session.serializer.bean-name=MockSessionSerializer