Add support for configuring Apache Geode/Pivotal GemFire's DataSerialization framework server-side when using Spring Session.

Resovles gh-11.
This commit is contained in:
John Blum
2018-11-09 14:58:44 -08:00
parent 15f856c6ae
commit ffe3571a74
4 changed files with 435 additions and 0 deletions

View File

@@ -0,0 +1,165 @@
/*
* 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.serialization.data.support;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
import static org.springframework.session.data.gemfire.serialization.data.support.DataSerializableSessionSerializerInitializer.InitializingGemFireOperationsSessionRepository;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.apache.geode.DataSerializer;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.CacheClosedException;
import org.apache.geode.internal.InternalDataSerializer;
import org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests;
import org.springframework.session.data.gemfire.serialization.data.provider.DataSerializableSessionAttributesSerializer;
import org.springframework.session.data.gemfire.serialization.data.provider.DataSerializableSessionSerializer;
/**
* Unit tests for {@link DataSerializableSessionSerializerInitializer}.
*
* @author John Blum
* @see org.junit.Test
* @see org.mockito.Mock
* @see org.mockito.Mockito
* @see org.mockito.Spy
* @see org.mockito.junit.MockitoJUnitRunner
* @see org.apache.geode.DataSerializer
* @see org.apache.geode.internal.InternalDataSerializer
* @see org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests
* @see org.springframework.session.data.gemfire.serialization.data.provider.DataSerializableSessionAttributesSerializer
* @see org.springframework.session.data.gemfire.serialization.data.provider.DataSerializableSessionSerializer
* @see org.springframework.session.data.gemfire.serialization.data.support.DataSerializableSessionSerializerInitializer
* @since 1.0.0
*/
@RunWith(MockitoJUnitRunner.class)
public class DataSerializableSessionSerializerInitializerIntegrationTests extends AbstractGemFireIntegrationTests {
@Mock
private Cache mockCache;
@Test
public void constructDataSerializableSessionSerializerInitializerWithGemFireCache() {
DataSerializableSessionSerializerInitializer initializer =
new DataSerializableSessionSerializerInitializer(this.mockCache);
assertThat(initializer.getGemFireCache().orElse(null)).isSameAs(this.mockCache);
assertThat(initializer.getLogger()).isNotNull();
}
@Test
public void constructDataSerializableSessionSerializerInitializerWithNull() {
DataSerializableSessionSerializerInitializer initializer =
new DataSerializableSessionSerializerInitializer(null);
assertThat(initializer.getGemFireCache().orElse(null)).isNull();
assertThat(initializer.getLogger()).isNotNull();
}
@Test
public void ofFactoryMethodInitializesDataSerializableSessionSerializerInitializer() {
DataSerializableSessionSerializerInitializer initializer =
DataSerializableSessionSerializerInitializer.of(this.mockCache);
assertThat(initializer).isNotNull();
assertThat(initializer.getGemFireCache().orElse(null)).isSameAs(this.mockCache);
assertThat(initializer.getLogger()).isNotNull();
}
@Test
public void initializeWithCacheAndPropertiesParametersSetGemFireCacheReferenceAndCallsDoInitialize() {
DataSerializableSessionSerializerInitializer initializer =
spy(DataSerializableSessionSerializerInitializer.of(null));
doNothing().when(initializer).doInitialization();
assertThat(initializer).isNotNull();
assertThat(initializer.getGemFireCache().orElse(null)).isNull();
initializer.initialize(this.mockCache, new Properties());
assertThat(initializer.getGemFireCache().orElse(null)).isSameAs(this.mockCache);
verify(initializer, times(1)).doInitialization();
}
@Test
public void doInitializationIsCorrect() {
assertThat(InitializingGemFireOperationsSessionRepository.INSTANCE.isDataSerializationConfigured()).isFalse();
DataSerializableSessionSerializerInitializer initializer =
DataSerializableSessionSerializerInitializer.of(this.mockCache);
assertThat(initializer).isNotNull();
assertThat(initializer.getGemFireCache().orElse(null)).isSameAs(this.mockCache);
initializer.doInitialization();
assertThat(InitializingGemFireOperationsSessionRepository.INSTANCE.isDataSerializationConfigured()).isTrue();
List<Class> registeredDataSerializerTypes =
Arrays.stream(nullSafeArray(InternalDataSerializer.getSerializers(), DataSerializer.class))
.map(Object::getClass)
.collect(Collectors.toList());
assertThat(registeredDataSerializerTypes)
.containsExactly(DataSerializableSessionSerializer.class, DataSerializableSessionAttributesSerializer.class);
}
@Test(expected = CacheClosedException.class)
public void doInitializationWhenGemFireCacheIsNotPresentThrowsCacheClosedException() {
DataSerializableSessionSerializerInitializer initializer =
spy(DataSerializableSessionSerializerInitializer.of(null));
assertThat(initializer).isNotNull();
assertThat(initializer.getGemFireCache().orElse(null)).isNull();
try {
initializer.doInitialization();
}
finally {
assertThat(initializer.getGemFireCache().orElse(null)).isNull();
assertThat(InternalDataSerializer.getSerializers()).isEmpty();
assertThat(InitializingGemFireOperationsSessionRepository.INSTANCE.isDataSerializationConfigured()).isFalse();
verify(initializer, times(1)).resolveGemFireCache();
verify(initializer, never()).configureUseDataSerialization();
}
}
}

View File

@@ -121,6 +121,14 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
private String fullyQualifiedRegionName;
/**
* Protected, default constructor used by extensions of {@link AbstractGemFireOperationsSessionRepository}
* in order to affect and assess {@link SessionRepository} configuration and state.
*/
protected AbstractGemFireOperationsSessionRepository() {
this.template = null;
}
/**
* Constructs an instance of {@link AbstractGemFireOperationsSessionRepository}
* with a required {@link GemfireOperations} instance used to perform Pivotal GemFire data access operations

View File

@@ -0,0 +1,196 @@
/*
* 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.serialization.data.support;
import java.util.Optional;
import java.util.Properties;
import org.apache.geode.DataSerializable;
import org.apache.geode.DataSerializer;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.CacheClosedException;
import org.apache.geode.cache.CacheFactory;
import org.apache.geode.cache.Declarable;
import org.apache.geode.cache.GemFireCache;
import org.springframework.lang.Nullable;
import org.springframework.session.Session;
import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
import org.springframework.session.data.gemfire.serialization.data.provider.DataSerializableSessionSerializer;
import org.springframework.session.data.gemfire.support.GemFireOperationsSessionRepositorySupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Register the Spring Session for Apache Geode/Pivotal GemFire {@link DataSerializableSessionSerializer}
* with Apache Geode/Pivotal GemFire's DataSerialization framework as the {@link DataSerializer} used to handle
* de/serialization of the {@link Session}, the {@literal Session Attributes} and any application
* domain model objects contained in the {@link Session} (if necessary).
*
* @author John Blum
* @see java.util.Properties
* @see org.apache.geode.DataSerializable
* @see org.apache.geode.DataSerializer
* @see org.apache.geode.cache.Cache
* @see org.apache.geode.cache.CacheFactory
* @see org.apache.geode.cache.Declarable
* @see org.apache.geode.cache.GemFireCache
* @see org.springframework.session.Session
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
* @see org.springframework.session.data.gemfire.serialization.data.provider.DataSerializableSessionSerializer
* @since 1.0.0
*/
@SuppressWarnings("unused")
public class DataSerializableSessionSerializerInitializer implements Declarable {
private static final String DEFAULT_SESSION_REGION_NAME =
GemFireHttpSessionConfiguration.DEFAULT_SESSION_REGION_NAME;
private static final String SESSION_REGION_NAME_PROPERTY = "spring.session.data.gemfire.session.region.name";
private volatile GemFireCache gemfireCache;
private Logger logger = LoggerFactory.getLogger(getClass());
private String sessionRegionName;
/**
* Factory method used to construct a new instance of {@link DataSerializableSessionSerializerInitializer}
* initialized with the given, non-required {@link GemFireCache}.
*
* @param gemfireCache reference to the {@link GemFireCache} instance.
* @return a new {@link DataSerializableSessionSerializerInitializer} initialized with the given, non-required
* {@link GemFireCache}.
* @see org.apache.geode.cache.GemFireCache
* @see #DataSerializableSessionSerializerInitializer(GemFireCache)
*/
public static DataSerializableSessionSerializerInitializer of(@Nullable GemFireCache gemfireCache) {
return new DataSerializableSessionSerializerInitializer(gemfireCache);
}
/**
* Default constructor used to construct a new, un-initialized instance of
* {@link DataSerializableSessionSerializerInitializer}.
*
* For use in Apache Geode/Pivotal GemFire {@literal cache.xml}.
*/
public DataSerializableSessionSerializerInitializer() {
this(null);
}
/**
* Constructs a new instance of {@link DataSerializableSessionSerializerInitializer} initialized with the given,
* non-required {@link GemFireCache}.
*
* This constructor is meant to be used programmatically and users are encouraged to provide a reference to
* the configured and initialized {@link GemFireCache} instance if available.
*
* @param gemfireCache reference to the {@link GemFireCache} instance. {@link GemFireCache} may be {@literal null}
* in to order to "lazy" initialize or resolve the cache.
* @see org.apache.geode.cache.GemFireCache
*/
public DataSerializableSessionSerializerInitializer(@Nullable GemFireCache gemfireCache) {
this.gemfireCache = gemfireCache;
}
/**
* Returns an {@link Optional} reference to the {@link GemFireCache}.
*
* The {@link GemFireCache} instance may be resolved lazily when the {@link #initialize(Cache, Properties)} method
* is called, such as when processing {@literal cache.xml}.
*
* @return an {@link Optional} reference to the {@link GemFireCache}.
* @see org.apache.geode.cache.GemFireCache
* @see java.util.Optional
*/
protected Optional<GemFireCache> getGemFireCache() {
return Optional.ofNullable(this.gemfireCache);
}
/**
* Returns a reference to the configured {@link Logger} used to capture log events and messages.
*
* @return a reference to the configured {@link Logger} used for logging.
* @see org.slf4j.Logger
*/
protected Logger getLogger() {
return this.logger;
}
/**
* @see #doInitialization()
*/
@Override
public void initialize(Cache cache, Properties parameters) {
this.gemfireCache = cache;
doInitialization();
}
/**
* Resolves the {@link GemFireCache} instance, configures Spring Session (for Apache Geode/Pivotal GemFire) to
* enable and use the DataSerialization framework and finally, registers the {@link DataSerializer DataSerializers}
* used by Spring Session and required by Apache Geode/Pivotal GemFire to de/serialize the {@link Session} objects
* as {@link DataSerializable} {@link Class types}.
*
* @see #resolveGemFireCache()
* @see #configureUseDataSerialization()
* @see org.springframework.session.data.gemfire.serialization.data.provider.DataSerializableSessionSerializer#register()
*/
public void doInitialization() {
resolveGemFireCache();
configureUseDataSerialization();
DataSerializableSessionSerializer.register();
}
/**
* Resolves a reference to the {@link GemFireCache}.
*
* @return a resolved reference to the {@link GemFireCache}.
* @throws CacheClosedException if the {@link GemFireCache} cannot be resolved.
* @see org.apache.geode.cache.GemFireCache
*/
protected GemFireCache resolveGemFireCache() {
return getGemFireCache().orElseGet(CacheFactory::getAnyInstance);
}
/**
* Configures Spring Session (for Apache Geode/Pivotal GemFire) to "use" Apache Geode/Pivotal GemFire's
* DataSerialization framework and Delta capable {@link DataSerializable} Session objects.
*
* @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.DeltaCapableGemFireSession
* @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.DeltaCapableGemFireSessionAttributes
*/
protected void configureUseDataSerialization() {
InitializingGemFireOperationsSessionRepository.INSTANCE.setUseDataSerialization(true);
}
static final class InitializingGemFireOperationsSessionRepository
extends GemFireOperationsSessionRepositorySupport {
static final InitializingGemFireOperationsSessionRepository INSTANCE =
new InitializingGemFireOperationsSessionRepository();
boolean isDataSerializationConfigured() {
return isUsingDataSerialization();
}
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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.support;
import java.util.Map;
import org.springframework.data.gemfire.GemfireOperations;
import org.springframework.session.Session;
import org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository;
/**
* The GemFireOperationsSessionRepositorySupport class...
*
* @author John Blum
* @since 1.0.0
*/
public abstract class GemFireOperationsSessionRepositorySupport
extends AbstractGemFireOperationsSessionRepository {
private static final String OPERATION_NOT_SUPPORTED = "Operation Not Supported";
protected GemFireOperationsSessionRepositorySupport() { }
public GemFireOperationsSessionRepositorySupport(GemfireOperations gemfireOperations) {
super(gemfireOperations);
}
@Override
public Map<String, Session> findByIndexNameAndIndexValue(String indexName, String indexValue) {
throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED);
}
@Override
public Session createSession() {
throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED);
}
@Override
public void deleteById(String id) {
throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED);
}
@Override
public Session findById(String id) {
throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED);
}
@Override
public void save(Session session) {
throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED);
}
}