From 8a28d24bca04eca6617796115f5db90ae64e4796 Mon Sep 17 00:00:00 2001 From: John Blum Date: Wed, 9 Jun 2021 17:20:55 -0700 Subject: [PATCH] Enable SSDG to dynamically determine (at runtime, per invocation) whether GemFire/Geode Data Serialization and Deltas has been configured and is in use. This is necessary in use cases where SSDG bits are used on the GemFire/Geode server-side but no Spring application context has been bootstrapped to properly configure SSDG on the server(s) in the cluster and the client-side is using Data Serialization. Resolves gh-54. --- ...lizationConfigurationIntegrationTests.java | 198 ++++++++++++++++++ ...actGemFireOperationsSessionRepository.java | 17 +- .../http/GemFireHttpSessionConfiguration.java | 3 + 3 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 spring-session-data-geode/src/integration-test/java/org/springframework/session/data/gemfire/serialization/data/SessionSerializationWithDataSerializationAndNoServerSerializationConfigurationIntegrationTests.java diff --git a/spring-session-data-geode/src/integration-test/java/org/springframework/session/data/gemfire/serialization/data/SessionSerializationWithDataSerializationAndNoServerSerializationConfigurationIntegrationTests.java b/spring-session-data-geode/src/integration-test/java/org/springframework/session/data/gemfire/serialization/data/SessionSerializationWithDataSerializationAndNoServerSerializationConfigurationIntegrationTests.java new file mode 100644 index 0000000..92b5d9f --- /dev/null +++ b/spring-session-data-geode/src/integration-test/java/org/springframework/session/data/gemfire/serialization/data/SessionSerializationWithDataSerializationAndNoServerSerializationConfigurationIntegrationTests.java @@ -0,0 +1,198 @@ +/* + * Copyright 2020 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.gemfire.serialization.data; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.io.Serializable; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.client.ClientCache; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Profile; +import org.springframework.data.gemfire.LocalRegionFactoryBean; +import org.springframework.data.gemfire.config.annotation.CacheServerApplication; +import org.springframework.data.gemfire.config.annotation.ClientCacheApplication; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.session.Session; +import org.springframework.session.SessionRepository; +import org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests; +import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession; +import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +/** + * Integration tests testing the configuration of client-side Data Serialization with no explicit server-side + * Serialization configuration. + * + * @author John Blum + * @see java.io.Serializable + * @see org.junit.Test + * @see org.apache.geode.cache.client.ClientCache + * @see org.springframework.context.annotation.AnnotationConfigApplicationContext + * @see org.springframework.context.annotation.Bean + * @see org.springframework.data.gemfire.config.annotation.CacheServerApplication + * @see org.springframework.data.gemfire.config.annotation.ClientCacheApplication + * @see org.springframework.session.Session + * @see org.springframework.session.SessionRepository + * @see org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests + * @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession + * @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringRunner + * @since 2.6.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = SessionSerializationWithDataSerializationAndNoServerSerializationConfigurationIntegrationTests.ClientTestConfiguration.class) +public class SessionSerializationWithDataSerializationAndNoServerSerializationConfigurationIntegrationTests + extends AbstractGemFireIntegrationTests { + + private static final String SESSIONS_REGION_NAME = "Sessions"; + + @BeforeClass + public static void startGemFireServer() throws IOException { + + String[] arguments = { + //"-Dspring.profiles.active=server-session-config", + "-Dspring.session.data.gemfire.session.serializer.bean-name=SessionDataSerializer" + }; + + startGemFireServer(ServerTestConfiguration.class, arguments); + } + + @Autowired + private ClientCache clientCache; + + @Autowired + private SessionRepository sessionRepository; + + @Before + public void setup() { + + assertThat(this.clientCache).isNotNull(); + assertThat(this.clientCache.getPdxSerializer()).isNull(); + assertThat(this.sessionRepository).isNotNull(); + } + + @Test + public void serializesSessionStateCorrectly() { + + Session session = this.sessionRepository.createSession(); + + assertThat(session).isNotNull(); + assertThat(session.getId()).isNotEmpty(); + assertThat(session.isExpired()).isFalse(); + assertThat(session.getAttributeNames()).isEmpty(); + + User jonDoe = User.newUser("jonDoe").identifiedBy(1); + + session.setAttribute("user", jonDoe); + + assertThat(session.getAttributeNames()).containsExactly("user"); + assertThat(session.getAttribute("user")).isEqualTo(jonDoe); + + // Saves the entire Session object + this.sessionRepository.save(session); + + User janeDoe = User.newUser("janeDoe").identifiedBy(2); + + session.setAttribute("user", janeDoe); + + assertThat(session.getAttributeNames()).containsExactly("user"); + assertThat(session.getAttribute("user")).isEqualTo(janeDoe); + + // Saves only the delta + this.sessionRepository.save(session); + + Session loadedSession = this.sessionRepository.findById(session.getId()); + + assertThat(loadedSession).isNotNull(); + assertThat(loadedSession).isNotSameAs(session); + assertThat(loadedSession).isEqualTo(session); + assertThat(loadedSession.getAttributeNames()).containsExactly("user"); + assertThat(loadedSession.getAttribute("user")).isEqualTo(janeDoe); + } + + @ClientCacheApplication + @EnableGemFireHttpSession( + poolName = "DEFAULT", + regionName = SESSIONS_REGION_NAME, + sessionSerializerBeanName = GemFireHttpSessionConfiguration.SESSION_DATA_SERIALIZER_BEAN_NAME + ) + static class ClientTestConfiguration { } + + @CacheServerApplication + @Import(ServerSessionTestConfiguration.class) + static class ServerTestConfiguration { + + public static void main(String[] args) { + new AnnotationConfigApplicationContext(ServerTestConfiguration.class) + .registerShutdownHook(); + } + + @Bean(SESSIONS_REGION_NAME) + public LocalRegionFactoryBean localRegion(GemFireCache gemfireCache) { + + LocalRegionFactoryBean localRegion = new LocalRegionFactoryBean<>(); + + localRegion.setCache(gemfireCache); + localRegion.setPersistent(false); + + return localRegion; + } + } + + @Configuration + @Profile("server-session-config") + @EnableGemFireHttpSession(sessionSerializerBeanName = + GemFireHttpSessionConfiguration.SESSION_DATA_SERIALIZER_BEAN_NAME) + static class ServerSessionTestConfiguration { } + + @Getter + @ToString(of = "name") + @EqualsAndHashCode(of = "name") + @RequiredArgsConstructor(staticName = "newUser") + static class User implements Serializable { + + private Integer id; + + @lombok.NonNull + private final String name; + + public @NonNull User identifiedBy(@Nullable Integer id) { + this.id = id; + return this; + } + } +} diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepository.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepository.java index 46a436f..284823d 100644 --- a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepository.java +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepository.java @@ -515,9 +515,24 @@ public abstract class AbstractGemFireOperationsSessionRepository * Determines whether the DataSerialization framework has been configured. * * @return a boolean indicating whether the DataSerialization framework has been configured. + * @see #resolveSystemUsingDataSerialization() */ protected static boolean isUsingDataSerialization() { - return usingDataSerialization.get(); + return usingDataSerialization.get() || resolveSystemUsingDataSerialization(); + } + + private static boolean resolveSystemUsingDataSerialization() { + + String configuredSessionSerializerBeanName = + System.getProperty(GemFireHttpSessionConfiguration.SPRING_SESSION_DATA_GEMFIRE_SESSION_SERIALIZER_BEAN_NAME_PROPERTY, + GemFireHttpSessionConfiguration.DEFAULT_SESSION_SERIALIZER_BEAN_NAME); + + boolean systemUsingDataSerialization = GemFireHttpSessionConfiguration.SESSION_DATA_SERIALIZER_BEAN_NAME + .equals(configuredSessionSerializerBeanName); + + usingDataSerialization.set(systemUsingDataSerialization); + + return systemUsingDataSerialization; } /** 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 4d8f403..a7c39b8 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 @@ -240,6 +240,9 @@ public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionC public static final String DEFAULT_SESSION_SERIALIZER_BEAN_NAME = SESSION_PDX_SERIALIZER_BEAN_NAME; + public static final String SPRING_SESSION_DATA_GEMFIRE_SESSION_SERIALIZER_BEAN_NAME_PROPERTY = + "spring.session.data.gemfire.session.serializer.bean-name"; + protected static final String SPRING_SESSION_GEMFIRE_PROPERTY_SOURCE = GemFireHttpSessionConfiguration.class.getName().concat(".PROPERTY_SOURCE");