diff --git a/spring-session-data-geode/src/integration-test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationIntegrationTests.java b/spring-session-data-geode/src/integration-test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationIntegrationTests.java new file mode 100644 index 0000000..5442098 --- /dev/null +++ b/spring-session-data-geode/src/integration-test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationIntegrationTests.java @@ -0,0 +1,192 @@ +/* + * Copyright 2018 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.gemfire.config.annotation.web.http; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; + +import java.io.DataInput; +import java.io.DataOutput; +import java.util.Arrays; + +import org.junit.Test; + +import org.apache.geode.DataSerializer; +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.internal.InternalDataSerializer; + +import org.springframework.context.annotation.Bean; +import org.springframework.data.gemfire.config.annotation.ClientCacheApplication; +import org.springframework.data.gemfire.tests.integration.SpringApplicationContextIntegrationTestsSupport; +import org.springframework.data.gemfire.tests.mock.annotation.EnableGemFireMockObjects; +import org.springframework.session.data.gemfire.serialization.SessionSerializer; +import org.springframework.session.data.gemfire.serialization.data.AbstractDataSerializableSessionSerializer; +import org.springframework.session.data.gemfire.serialization.data.provider.DataSerializableSessionSerializer; +import org.springframework.session.data.gemfire.serialization.pdx.support.PdxSerializerSessionSerializerAdapter; + +/** + * The GemFireHttpSessionConfigurationIntegrationTests class... + * + * @author John Blum + * @since 1.0.0 + */ +@SuppressWarnings("unused") +public class GemFireHttpSessionConfigurationIntegrationTests extends SpringApplicationContextIntegrationTestsSupport { + + private void assertDataSerializerRegistered(DataSerializer dataSerializer) { + assertDataSerializerRegistered(dataSerializer.getClass()); + } + + private void assertDataSerializerRegistered(Class dataSerializerType) { + + assertThat(Arrays.stream(nullSafeArray(InternalDataSerializer.getSerializers(), DataSerializer.class)) + .map(Object::getClass) + .filter(dataSerializerType::isAssignableFrom) + .findFirst() + .orElse(null)).isNotNull(); + } + + private void testUsesDataSerialization(Class expectedDataSerializerType) { + + GemFireHttpSessionConfiguration configuration = + getApplicationContext().getBean(GemFireHttpSessionConfiguration.class); + + assertThat(configuration).isNotNull(); + assertThat(configuration.isUsingDataSerialization()).isTrue(); + + GemFireCache gemfireCache = getApplicationContext().getBean(GemFireCache.class); + + assertThat(gemfireCache).isNotNull(); + assertThat(gemfireCache.getPdxSerializer()).isNull(); + + DataSerializer dataSerializer = + getApplicationContext().getBean(configuration.getSessionSerializerBeanName(), expectedDataSerializerType); + + assertThat(dataSerializer).isInstanceOf(expectedDataSerializerType); + assertDataSerializerRegistered(dataSerializer); + } + + @Test + public void usesDataSerializationWhenDataSerializableSessionSerializerConfigured() { + + newApplicationContext(DataSerializableSessionSerializerConfiguration.class); + + testUsesDataSerialization(DataSerializableSessionSerializer.class); + } + + @Test + public void usesDataSerializationWhenTestDataSerializerConfigured() { + + newApplicationContext(TestDataSerializerConfiguration.class); + + testUsesDataSerialization(TestDataSerializer.class); + } + + @Test + public void notUsingDataSerializationWhenPdxConfigured() { + + newApplicationContext(TestSessionSerializerConfiguration.class); + + GemFireHttpSessionConfiguration configuration = + getApplicationContext().getBean(GemFireHttpSessionConfiguration.class); + + assertThat(configuration).isNotNull(); + assertThat(configuration.isUsingDataSerialization()).isFalse(); + + GemFireCache gemfireCache = getApplicationContext().getBean(GemFireCache.class); + + SessionSerializer testSessionSerializer = + getApplicationContext().getBean("TestSessionSerializer", SessionSerializer.class); + + assertThat(gemfireCache).isNotNull(); + assertThat(gemfireCache.getPdxSerializer()).isInstanceOf(PdxSerializerSessionSerializerAdapter.class); + assertThat(((PdxSerializerSessionSerializerAdapter) gemfireCache.getPdxSerializer()).getSessionSerializer()) + .isEqualTo(testSessionSerializer); + + assertThat(Arrays.stream(nullSafeArray(InternalDataSerializer.getSerializers(), DataSerializer.class)) + .filter(testSessionSerializer::equals) + .findAny() + .orElse(null)).isNull(); + } + + @ClientCacheApplication + @EnableGemFireMockObjects + @EnableGemFireHttpSession( + poolName = "DEFAULT", + sessionSerializerBeanName = GemFireHttpSessionConfiguration.SESSION_DATA_SERIALIZER_BEAN_NAME + ) + static class DataSerializableSessionSerializerConfiguration { } + + @ClientCacheApplication + @EnableGemFireMockObjects + @EnableGemFireHttpSession(poolName = "DEFAULT", sessionSerializerBeanName = "TestDataSerializer") + static class TestDataSerializerConfiguration { + + @Bean("TestDataSerializer") + DataSerializer testDataSerializer() { + return new TestDataSerializer(); + } + } + + @ClientCacheApplication + @EnableGemFireMockObjects + @EnableGemFireHttpSession(poolName = "DEFAULT", sessionSerializerBeanName = "TestSessionSerializer") + static class TestSessionSerializerConfiguration { + + @Bean("TestSessionSerializer") + SessionSerializer testSessionSerializer() { + return mock(SessionSerializer.class); + } + } + + static class TestDataSerializer extends AbstractDataSerializableSessionSerializer { + + @Override + public Class[] getSupportedClasses() { + return new Class[] { Object.class }; + } + + @Override + public void serialize(Object session, DataOutput dataOutput) { + throw new UnsupportedOperationException("Not Implemented"); + } + + @Override + public Object deserialize(DataInput dataInput) { + throw new UnsupportedOperationException("Not Implemented"); + } + } + + static class TestSessionSerializer implements SessionSerializer { + + @Override + public void serialize(Object session, DataOutput dataOutput) { + throw new UnsupportedOperationException("Not Implemented"); + } + + @Override + public Object deserialize(DataInput dataInput) { + throw new UnsupportedOperationException("Not Implemented"); + } + + @Override + public boolean canSerialize(Class type) { + return false; + } + } +} diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfiguration.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfiguration.java index b56acf6..e83907d 100644 --- a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfiguration.java +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfiguration.java @@ -115,6 +115,12 @@ import org.springframework.util.StringUtils; @SuppressWarnings("unused") public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionConfiguration implements ImportAware { + /** + * Indicates whether to employ Apache Geode/Pivotal's DataSerialization framework + * for {@link Session} de/serialization. + */ + public static final boolean DEFAULT_USE_DATA_SERIALIZATION = false; + /** * Default maximum interval in seconds in which a {@link Session} can remain inactive before it expires. */ @@ -187,6 +193,8 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC */ public static final String[] DEFAULT_INDEXABLE_SESSION_ATTRIBUTES = {}; + private boolean usingDataSerialization = DEFAULT_USE_DATA_SERIALIZATION; + private int maxInactiveIntervalInSeconds = DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS; private ClientRegionShortcut clientRegionShortcut = DEFAULT_CLIENT_REGION_SHORTCUT; @@ -423,6 +431,17 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC .orElse(DEFAULT_SESSION_SERIALIZER_BEAN_NAME); } + /** + * Set whether to use Apache Geode / Pivotal GemFire's DataSerialization framework + * for {@link Session} de/serialization. + * + * @param useDataSerialization boolean value indicating whether to use Apache Geode + * / Pivotal GemFire's DataSerialization framework for {@link Session} de/serialization. + */ + private void setUseDataSerialization(boolean useDataSerialization) { + this.usingDataSerialization = useDataSerialization; + } + /** * Determine whether the configured serialization strategy is using Apache Geode / Pivotal GemFire's * DataSerialization framework. @@ -432,7 +451,9 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC * @see #getSessionSerializerBeanName() */ protected boolean isUsingDataSerialization() { - return SESSION_DATA_SERIALIZER_BEAN_NAME.equals(getSessionSerializerBeanName()); + + return this.usingDataSerialization + || SESSION_DATA_SERIALIZER_BEAN_NAME.equals(getSessionSerializerBeanName()); } /** @@ -442,6 +463,7 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC * @param importMetadata {@link AnnotationMetadata} of the application class importing * this {@link Configuration} class. * @see org.springframework.core.type.AnnotationMetadata + * @see #applySpringSessionGemFireConfigurer() */ public void setImportMetadata(AnnotationMetadata importMetadata) { @@ -490,7 +512,7 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC applySpringSessionGemFireConfigurer(); } - private void applySpringSessionGemFireConfigurer() { + void applySpringSessionGemFireConfigurer() { resolveSpringSessionGemFireConfigurer() .map(this::applyClientRegionShortcut) @@ -620,12 +642,15 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC private void configureSerialization(CacheFactoryBean cacheFactoryBean, SessionSerializer sessionSerializer) { if (sessionSerializer instanceof DataSerializer) { + if (sessionSerializer instanceof DataSerializableSessionSerializer) { DataSerializableSessionSerializer.register(); } else { DataSerializer.register(sessionSerializer.getClass()); } + + setUseDataSerialization(true); } else if (sessionSerializer instanceof PdxSerializer) { cacheFactoryBean.setPdxSerializer(ComposablePdxSerializer.compose( diff --git a/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationTests.java b/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationTests.java index a821bfa..6d0abdb 100644 --- a/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationTests.java +++ b/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationTests.java @@ -17,9 +17,13 @@ package org.springframework.session.data.gemfire.config.annotation.web.http; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; @@ -43,6 +47,7 @@ import org.apache.geode.cache.client.ClientCache; import org.apache.geode.cache.client.ClientRegionShortcut; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; @@ -50,6 +55,7 @@ import org.springframework.core.type.AnnotationMetadata; import org.springframework.data.gemfire.GemfireOperations; import org.springframework.data.gemfire.GemfireTemplate; import org.springframework.data.gemfire.RegionAttributesFactoryBean; +import org.springframework.data.gemfire.util.ArrayUtils; import org.springframework.session.Session; import org.springframework.session.data.gemfire.GemFireOperationsSessionRepository; import org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean; @@ -86,8 +92,11 @@ public class GemFireHttpSessionConfigurationTests { protected T getField(Object obj, String fieldName) { try { + Field field = resolveField(obj, fieldName); + field.setAccessible(true); + return (T) field.get(obj); } catch (NoSuchFieldException cause) { @@ -115,11 +124,6 @@ public class GemFireHttpSessionConfigurationTests { return field; } - @SafeVarargs - private static T[] toArray(T... array) { - return array; - } - @Before public void setup() { @@ -321,20 +325,23 @@ public class GemFireHttpSessionConfigurationTests { Map annotationAttributes = new HashMap<>(4); annotationAttributes.put("clientRegionShortcut", ClientRegionShortcut.CACHING_PROXY); - annotationAttributes.put("indexableSessionAttributes", toArray("one", "two", "three")); + annotationAttributes.put("exposeConfigurationAsProperties", Boolean.TRUE); + annotationAttributes.put("indexableSessionAttributes", ArrayUtils.asArray("one", "two", "three")); annotationAttributes.put("maxInactiveIntervalInSeconds", 600); annotationAttributes.put("poolName", "TestPool"); annotationAttributes.put("serverRegionShortcut", RegionShortcut.REPLICATE); annotationAttributes.put("regionName", "TEST"); + annotationAttributes.put("sessionExpirationPolicyBeanName", "testSessionExpirationPolicy"); annotationAttributes.put("sessionSerializerBeanName", "testSessionSerializer"); - given(mockAnnotationMetadata.getAnnotationAttributes(eq(EnableGemFireHttpSession.class.getName()))) - .willReturn(annotationAttributes); + when(mockAnnotationMetadata.getAnnotationAttributes(eq(EnableGemFireHttpSession.class.getName()))) + .thenReturn(annotationAttributes); this.gemfireConfiguration.setImportMetadata(mockAnnotationMetadata); assertThat(this.gemfireConfiguration.getClientRegionShortcut()).isEqualTo(ClientRegionShortcut.CACHING_PROXY); - assertThat(this.gemfireConfiguration.getIndexableSessionAttributes()).isEqualTo(toArray("one", "two", "three")); + assertThat(this.gemfireConfiguration.getIndexableSessionAttributes()) + .isEqualTo(ArrayUtils.asArray("one", "two", "three")); assertThat(this.gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(600); assertThat(this.gemfireConfiguration.getPoolName()).isEqualTo("TestPool"); assertThat(this.gemfireConfiguration.getServerRegionShortcut()).isEqualTo(RegionShortcut.REPLICATE); @@ -342,8 +349,51 @@ public class GemFireHttpSessionConfigurationTests { assertThat(this.gemfireConfiguration.getSessionSerializerBeanName()).isEqualTo("testSessionSerializer"); verify(mockAnnotationMetadata, times(1)) - .getAnnotationAttributes(eq(EnableGemFireHttpSession.class.getName())); + .getAnnotationAttributes(eq(EnableGemFireHttpSession.class.getName())); } + @Test + public void applyConfigurationFromNonExistingSpringSessionGemFireConfigurer() { + + this.gemfireConfiguration.applySpringSessionGemFireConfigurer(); + + verify(this.gemfireConfiguration, never()).setClientRegionShortcut(any(ClientRegionShortcut.class)); + verify(this.gemfireConfiguration, never()).setIndexableSessionAttributes(any(String[].class)); + verify(this.gemfireConfiguration, never()).setMaxInactiveIntervalInSeconds(anyInt()); + verify(this.gemfireConfiguration, never()).setPoolName(anyString()); + verify(this.gemfireConfiguration, never()).setServerRegionShortcut(any(RegionShortcut.class)); + verify(this.gemfireConfiguration, never()).setSessionRegionName(anyString()); + verify(this.gemfireConfiguration, never()).setSessionSerializerBeanName(anyString()); + } + + @Test(expected = NoUniqueBeanDefinitionException.class) + public void applyConfigurationFromMultipleSpringSessionGemFireConfigurersThrowsException() { + + ApplicationContext mockApplicationContext = mock(ApplicationContext.class); + + when(mockApplicationContext.getBean(eq(SpringSessionGemFireConfigurer.class))) + .thenThrow(new NoUniqueBeanDefinitionException(SpringSessionGemFireConfigurer.class, 2, "TEST")); + + this.gemfireConfiguration.setApplicationContext(mockApplicationContext); + + try { + this.gemfireConfiguration.applySpringSessionGemFireConfigurer(); + } + catch (NoUniqueBeanDefinitionException expected) { + + assertThat(expected).hasMessageContaining("TEST"); + assertThat(expected).hasNoCause(); + + throw expected; + } + finally { + verify(this.gemfireConfiguration, never()).setClientRegionShortcut(any(ClientRegionShortcut.class)); + verify(this.gemfireConfiguration, never()).setIndexableSessionAttributes(any(String[].class)); + verify(this.gemfireConfiguration, never()).setMaxInactiveIntervalInSeconds(anyInt()); + verify(this.gemfireConfiguration, never()).setPoolName(anyString()); + verify(this.gemfireConfiguration, never()).setServerRegionShortcut(any(RegionShortcut.class)); + verify(this.gemfireConfiguration, never()).setSessionRegionName(anyString()); + verify(this.gemfireConfiguration, never()).setSessionSerializerBeanName(anyString()); + } } @Test