From f5c7e8205aedd4a4ef3c96db2e9975ee2bc84dad Mon Sep 17 00:00:00 2001 From: John Blum Date: Tue, 21 Nov 2017 18:09:50 -0800 Subject: [PATCH] SGF-692 - Add Annotation support for Region Compression. --- pom.xml | 14 +- .../data/gemfire/RegionFactoryBean.java | 15 ++ .../client/ClientRegionFactoryBean.java | 15 ++ .../annotation/CompressionConfiguration.java | 184 ++++++++++++++++++ .../config/annotation/EnableCompression.java | 69 +++++++ .../annotation/OffHeapConfiguration.java | 19 +- .../CacheTypeAwareRegionFactoryBean.java | 37 ++++ .../data/gemfire/util/SpringUtils.java | 19 ++ ...ableCompressionConfigurationUnitTests.java | 178 +++++++++++++++++ ...ableGemFirePropertiesIntegrationTests.java | 2 +- .../AbstractAnnotationConfigSupportTests.java | 3 +- .../gemfire/util/SpringUtilsUnitTests.java | 47 +++++ 12 files changed, 591 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/CompressionConfiguration.java create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/EnableCompression.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/EnableCompressionConfigurationUnitTests.java diff --git a/pom.xml b/pom.xml index 879c7091..02d60fe8 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,9 @@ Spring Data GemFire + SGF + spring.data.gemfire + 1.8 2.7.7 1.3.2 3.6.2 @@ -23,9 +26,9 @@ 9.1.1 2.0.2 1.01 + 0.4 2.1.0.BUILD-SNAPSHOT 1.2.0.RELEASE - spring.data.gemfire @@ -149,7 +152,14 @@ true - + + org.iq80.snappy + snappy + ${snappy.version} + true + + + org.aspectj aspectjweaver diff --git a/src/main/java/org/springframework/data/gemfire/RegionFactoryBean.java b/src/main/java/org/springframework/data/gemfire/RegionFactoryBean.java index 9db2886f..24c67812 100644 --- a/src/main/java/org/springframework/data/gemfire/RegionFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/RegionFactoryBean.java @@ -49,6 +49,7 @@ import org.apache.geode.cache.RegionShortcut; import org.apache.geode.cache.Scope; import org.apache.geode.cache.asyncqueue.AsyncEventQueue; import org.apache.geode.cache.wan.GatewaySender; +import org.apache.geode.compression.Compressor; import org.apache.geode.internal.cache.UserSpecifiedRegionAttributes; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; @@ -125,6 +126,8 @@ public abstract class RegionFactoryBean extends RegionLookupFactoryBean keyConstraint; private Class valueConstraint; + private Compressor compressor; + private DataPolicy dataPolicy; private EvictionAttributes evictionAttributes; @@ -310,6 +313,8 @@ public abstract class RegionFactoryBean extends RegionLookupFactoryBean extends RegionLookupFactoryBean extends RegionLookupFactoryBean private ClientRegionShortcut shortcut; + private Compressor compressor; + private DataPolicy dataPolicy; private EvictionAttributes evictionAttributes; @@ -391,6 +394,8 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean stream(nullSafeArray(this.cacheListeners, CacheListener.class)).forEach(clientRegionFactory::addCacheListener); + Optional.ofNullable(this.compressor).ifPresent(clientRegionFactory::setCompressor); + Optional.ofNullable(this.diskStoreName).filter(StringUtils::hasText) .ifPresent(clientRegionFactory::setDiskStoreName); @@ -560,6 +565,16 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean this.destroy = (this.destroy && !close); // retain previous value iff close is false. } + /** + * Configures the {@link Compressor} used to compress the this {@link Region Region's} data. + * + * @param compressor {@link Compressor} used to compress the this {@link Region Region's} data. + * @see org.apache.geode.compression.Compressor + */ + public void setCompressor(Compressor compressor) { + this.compressor = compressor; + } + /** * Sets the Data Policy. Used only when a new Region is created. * diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/CompressionConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/CompressionConfiguration.java new file mode 100644 index 00000000..bdf9b76c --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/CompressionConfiguration.java @@ -0,0 +1,184 @@ +/* + * Copyright 2017 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.data.gemfire.config.annotation; + +import static java.util.Arrays.stream; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.CollectionUtils.asSet; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable; + +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import org.apache.geode.cache.Region; +import org.apache.geode.compression.Compressor; +import org.apache.geode.compression.SnappyCompressor; +import org.springframework.beans.PropertyValue; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportAware; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.gemfire.RegionLookupFactoryBean; +import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport; +import org.springframework.data.gemfire.util.CollectionUtils; +import org.springframework.data.gemfire.util.SpringUtils; +import org.springframework.util.StringUtils; + +/** + * The {@link CompressionConfiguration} class is a Spring {@link ImportAware} implementation capable of + * enabling Pivotal GemFire/Apache Geode cache {@link Region Regions} data compression. + * + * @author John Blum + * @see org.apache.geode.cache.Region + * @see org.springframework.beans.factory.config.BeanFactoryPostProcessor + * @see org.springframework.context.annotation.Bean + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.context.annotation.ImportAware + * @see org.springframework.data.gemfire.config.annotation.EnableCompression + * @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport + * @since 2.0.2 + */ +@Configuration +@SuppressWarnings("unused") +public class CompressionConfiguration extends AbstractAnnotationConfigSupport implements ImportAware { + + protected static final String SNAPPY_COMPRESSOR_BEAN_NAME = "SnappyCompressor"; + + private String compressorBeanName = SNAPPY_COMPRESSOR_BEAN_NAME; + + private Set regionNames = new HashSet<>(); + + /** + * Returns the {@link EnableCompression} {@link Annotation} {@link Class} type. + * + * @return the {@link EnableCompression} {@link Annotation} {@link Class} type. + * @see org.springframework.data.gemfire.config.annotation.EnableOffHeap + * @see java.lang.annotation.Annotation + */ + @Override + protected Class getAnnotationType() { + return EnableCompression.class; + } + + public void setCompressorBeanName(String compressorBeanName) { + this.compressorBeanName = compressorBeanName; + } + + protected String resolveCompressorBeanName() { + return Optional.ofNullable(this.compressorBeanName).filter(StringUtils::hasText) + .orElse(SNAPPY_COMPRESSOR_BEAN_NAME); + } + + public void setRegionNames(String[] regionNames) { + setRegionNames(asSet(nullSafeArray(regionNames, String.class))); + } + + public void setRegionNames(Iterable regionNames) { + this.regionNames = CollectionUtils.addAll(this.regionNames, nullSafeIterable(regionNames)); + } + + protected Set resolveRegionNames() { + return Collections.unmodifiableSet(this.regionNames); + } + + @Override + public void setImportMetadata(AnnotationMetadata importingClassMetadata) { + + if (isAnnotationPresent(importingClassMetadata)) { + + AnnotationAttributes enableCompressionAttributes = getAnnotationAttributes(importingClassMetadata); + + setCompressorBeanName(resolveProperty(cacheCompressionProperty("compressor-bean-name"), + enableCompressionAttributes.getString("compressorBeanName"))); + + setRegionNames(resolveProperty(cacheCompressionProperty("region-names"), + String[].class, enableCompressionAttributes.getStringArray("regionNames"))); + } + } + + @Bean(SNAPPY_COMPRESSOR_BEAN_NAME) + Compressor snappyCompressor() { + return new SnappyCompressor(); + } + + @Bean + BeanFactoryPostProcessor regionCompressionBeanFactoryPostProcessor() { + + String resolvedCompressorBeanName = resolveCompressorBeanName(); + + return beanFactory -> + stream(nullSafeArray(beanFactory.getBeanDefinitionNames(), String.class)).forEach(beanName -> + Optional.of(beanFactory.getBeanDefinition(beanName)) + .filter(beanDefinition -> isTargetedRegionBean(beanName, beanDefinition, beanFactory)) + .ifPresent(beanDefinition -> SpringUtils.setPropertyReference( + beanDefinition, "compressor", resolvedCompressorBeanName))); + } + + private boolean isTargetedRegionBean(String beanName, BeanDefinition beanDefinition, + ConfigurableListableBeanFactory beanFactory) { + + return isNamedRegion(beanName, beanDefinition, beanFactory) && isRegionBean(beanDefinition, beanFactory); + } + + private boolean isRegionBean(BeanDefinition beanDefinition, ConfigurableListableBeanFactory beanFactory) { + + return Optional.ofNullable(beanDefinition) + .flatMap(it -> resolveBeanClass(it, beanFactory.getBeanClassLoader())) + .filter(beanClass -> RegionLookupFactoryBean.class.isAssignableFrom(beanClass)) + .isPresent(); + } + + private boolean isNamedRegion(String beanName, BeanDefinition beanDefinition, + ConfigurableListableBeanFactory beanFactory) { + + Set resolvedRegionNames = resolveRegionNames(); + + return CollectionUtils.isEmpty(resolvedRegionNames) + || CollectionUtils.containsAny(resolvedRegionNames, resolveBeanNames(beanName, beanDefinition, beanFactory)); + } + + private Collection resolveBeanNames(String beanName, BeanDefinition beanDefinition, + ConfigurableListableBeanFactory beanFactory) { + + Collection beanNames = new HashSet<>(); + + beanNames.add(beanName); + + Collections.addAll(beanNames, beanFactory.getAliases(beanName)); + + PropertyValue regionName = beanDefinition.getPropertyValues().getPropertyValue("regionName"); + + if (regionName != null) { + + Object regionNameValue = regionName.getValue(); + + if (regionNameValue != null) { + beanNames.add(regionNameValue.toString()); + } + } + + return beanNames; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableCompression.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableCompression.java new file mode 100644 index 00000000..ae01b0a8 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableCompression.java @@ -0,0 +1,69 @@ +/* + * Copyright 2017 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.data.gemfire.config.annotation; + +import static org.springframework.data.gemfire.config.annotation.CompressionConfiguration.SNAPPY_COMPRESSOR_BEAN_NAME; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apache.geode.cache.Region; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * The {@link EnableCompression} annotation marks a Spring {@link Configuration @Configuration} annotated application + * {@link Class} to configure and enable Pivotal GemFire/Apache Geode {@link Region} data compression. + * + * @author John Blum + * @see java.lang.annotation.Annotation + * @see org.springframework.context.annotation.Import + * @see org.springframework.data.gemfire.config.annotation.CompressionConfiguration + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Import(CompressionConfiguration.class) +public @interface EnableCompression { + + /** + * Reference to the {@link String name} of a bean having type {@link org.apache.geode.compression.Compressor} + * registered in the Spring container to handle {@link Region} compression. + * + * Defaults to {@literal snappyCompressor}. + * + * Set the {@literal spring.data.gemfire.cache.compression.compressor-bean-name} + * in {@literal application.properties}. + */ + String compressorBeanName() default SNAPPY_COMPRESSOR_BEAN_NAME; + + /** + * Identifies all the {@link Region Regions} by name in which the data compression will be enabled. + * + * Defaults to all {@link Region Regions}. + * + * Set the {@literal spring.data.gemfire.cache.compression.region-names} property + * in {@literal application.properties}. + */ + String[] regionNames() default {}; + +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/OffHeapConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/OffHeapConfiguration.java index ebd9ad68..4666f38a 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/OffHeapConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/OffHeapConfiguration.java @@ -32,7 +32,6 @@ import java.util.Set; import org.apache.geode.cache.Region; import org.springframework.beans.BeansException; import org.springframework.beans.PropertyValue; -import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -48,11 +47,15 @@ import org.springframework.data.gemfire.util.PropertiesBuilder; /** * The {@link OffHeapConfiguration} class is a Spring {@link ImportBeanDefinitionRegistrar} capable of - * enabling Pivotal GemFire/Apache Geode cache {@link Region Regions} to use Off-Heap memory for data storage. + * enabling Pivotal GemFire/Apache Geode cache {@link Region Regions} to use Off-Heap Memory for data storage. * * @author John Blum + * @see java.util.Properties + * @see org.apache.geode.cache.Region + * @see org.springframework.beans.factory.config.BeanFactoryPostProcessor * @see org.springframework.context.annotation.ImportBeanDefinitionRegistrar * @see org.springframework.data.gemfire.config.annotation.EnableOffHeap + * @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport * @see org.springframework.data.gemfire.config.annotation.support.EmbeddedServiceConfigurationSupport * @since 1.9.0 */ @@ -121,13 +124,13 @@ public class OffHeapConfiguration extends EmbeddedServiceConfigurationSupport { .addPropertyValue("offHeap", true))); } - boolean isTargetedRegionBean(String beanName, BeanDefinition beanDefinition, + private boolean isTargetedRegionBean(String beanName, BeanDefinition beanDefinition, ConfigurableListableBeanFactory beanFactory) { return isNamedRegion(beanName, beanDefinition, beanFactory) && isRegionBean(beanDefinition, beanFactory); } - boolean isRegionBean(BeanDefinition beanDefinition, ConfigurableListableBeanFactory beanFactory) { + private boolean isRegionBean(BeanDefinition beanDefinition, ConfigurableListableBeanFactory beanFactory) { return Optional.ofNullable(beanDefinition) .flatMap(it -> resolveBeanClass(it, beanFactory.getBeanClassLoader())) @@ -135,13 +138,15 @@ public class OffHeapConfiguration extends EmbeddedServiceConfigurationSupport { .isPresent(); } - boolean isNamedRegion(String beanName, BeanDefinition beanDefinition, BeanFactory beanFactory) { + private boolean isNamedRegion(String beanName, BeanDefinition beanDefinition, + ConfigurableListableBeanFactory beanFactory) { return CollectionUtils.isEmpty(regionNames) - || CollectionUtils.containsAny(regionNames, getBeanNames(beanName, beanDefinition, beanFactory)); + || CollectionUtils.containsAny(regionNames, resolveBeanNames(beanName, beanDefinition, beanFactory)); } - Collection getBeanNames(String beanName, BeanDefinition beanDefinition, BeanFactory beanFactory) { + private Collection resolveBeanNames(String beanName, BeanDefinition beanDefinition, + ConfigurableListableBeanFactory beanFactory) { Collection beanNames = new HashSet<>(); diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/support/CacheTypeAwareRegionFactoryBean.java b/src/main/java/org/springframework/data/gemfire/config/annotation/support/CacheTypeAwareRegionFactoryBean.java index 1fa850f1..59964abe 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/support/CacheTypeAwareRegionFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/support/CacheTypeAwareRegionFactoryBean.java @@ -33,6 +33,7 @@ import org.apache.geode.cache.RegionShortcut; import org.apache.geode.cache.Scope; import org.apache.geode.cache.client.ClientCache; import org.apache.geode.cache.client.ClientRegionShortcut; +import org.apache.geode.compression.Compressor; import org.springframework.beans.factory.FactoryBean; import org.springframework.data.gemfire.GemfireUtils; import org.springframework.data.gemfire.GenericRegionFactoryBean; @@ -61,6 +62,7 @@ import org.springframework.util.StringUtils; * @see org.apache.geode.cache.Scope * @see org.apache.geode.cache.client.ClientCache * @see org.apache.geode.cache.client.ClientRegionShortcut + * @see org.apache.geode.compression.Compressor * @see org.springframework.data.gemfire.GenericRegionFactoryBean * @see org.springframework.data.gemfire.LocalRegionFactoryBean * @see org.springframework.data.gemfire.PartitionedRegionFactoryBean @@ -84,6 +86,8 @@ public class CacheTypeAwareRegionFactoryBean extends RegionLookupFactoryBe private ClientRegionShortcut clientRegionShortcut = ClientRegionShortcut.PROXY; + private Compressor compressor; + private DataPolicy dataPolicy = DataPolicy.DEFAULT; private List regionConfigurers = Collections.emptyList(); @@ -129,6 +133,7 @@ public class CacheTypeAwareRegionFactoryBean extends RegionLookupFactoryBe clientRegionFactory.setBeanFactory(getBeanFactory()); clientRegionFactory.setCache(gemfireCache); clientRegionFactory.setClose(isClose()); + clientRegionFactory.setCompressor(getCompressor()); clientRegionFactory.setDiskStoreName(getDiskStoreName()); clientRegionFactory.setKeyConstraint(getKeyConstraint()); clientRegionFactory.setLookupEnabled(getLookupEnabled()); @@ -178,6 +183,7 @@ public class CacheTypeAwareRegionFactoryBean extends RegionLookupFactoryBe serverRegionFactory.setBeanFactory(getBeanFactory()); serverRegionFactory.setCache(gemfireCache); serverRegionFactory.setClose(isClose()); + serverRegionFactory.setCompressor(getCompressor()); serverRegionFactory.setDataPolicy(getDataPolicy()); serverRegionFactory.setDiskStoreName(getDiskStoreName()); serverRegionFactory.setKeyConstraint(getKeyConstraint()); @@ -254,6 +260,26 @@ public class CacheTypeAwareRegionFactoryBean extends RegionLookupFactoryBe return Boolean.TRUE.equals(getClose()); } + /** + * Configures the {@link Compressor} used to compress the this {@link Region Region's} data. + * + * @param compressor {@link Compressor} used to compress the this {@link Region Region's} data. + * @see org.apache.geode.compression.Compressor + */ + public void setCompressor(Compressor compressor) { + this.compressor = compressor; + } + + /** + * Returns the configured {@link Compressor} used to compress the this {@link Region Region's} data. + * + * @return the configured {@link Compressor} used to compress the this {@link Region Region's} data. + * @see org.apache.geode.compression.Compressor + */ + protected Compressor getCompressor() { + return this.compressor; + } + public void setDataPolicy(DataPolicy dataPolicy) { this.dataPolicy = dataPolicy; } @@ -278,10 +304,21 @@ public class CacheTypeAwareRegionFactoryBean extends RegionLookupFactoryBe return this.keyConstraint; } + /** + * Configure the {@link Region} to manage data in Off-Heap Memory. + * + * @param offHeap boolean value indicating whether the {@link Region Region's} data + * will be managed in Off-Heap Memory. + */ public void setOffHeap(Boolean offHeap) { this.offHeap = offHeap; } + /** + * Return the configuration setting for whether the {@link Region Region's} data will be managed in Off-Heap Memory. + * + * @return a boolean value indicating whether the {@link Region Region's} data will be managed in Off-Heap Memory. + */ protected Boolean getOffHeap() { return this.offHeap; } diff --git a/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java b/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java index 8d6ede51..4e6ecc24 100644 --- a/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java +++ b/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java @@ -29,6 +29,7 @@ import java.util.function.Supplier; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.util.StringUtils; /** @@ -53,6 +54,24 @@ public abstract class SpringUtils { return bean; } + /* (non-Javadoc) */ + public static BeanDefinition setPropertyReference(BeanDefinition beanDefinition, + String propertyName, String beanName) { + + beanDefinition.getPropertyValues().addPropertyValue(propertyName, new RuntimeBeanReference(beanName)); + + return beanDefinition; + } + + /* (non-Javadoc) */ + public static BeanDefinition setPropertyValue(BeanDefinition beanDefinition, + String propertyName, Object propertyValue) { + + beanDefinition.getPropertyValues().addPropertyValue(propertyName, propertyValue); + + return beanDefinition; + } + /* (non-Javadoc) */ public static String defaultIfEmpty(String value, String defaultValue) { return (StringUtils.hasText(value) ? value : defaultValue); diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/EnableCompressionConfigurationUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableCompressionConfigurationUnitTests.java new file mode 100644 index 00000000..5b72ba6d --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableCompressionConfigurationUnitTests.java @@ -0,0 +1,178 @@ +/* + * Copyright 2017 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.data.gemfire.config.annotation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.springframework.data.gemfire.config.annotation.CompressionConfiguration.SNAPPY_COMPRESSOR_BEAN_NAME; + +import java.util.Arrays; +import java.util.Optional; + +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.apache.geode.compression.Compressor; +import org.apache.geode.compression.SnappyCompressor; +import org.junit.After; +import org.junit.Test; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.data.gemfire.GemfireUtils; +import org.springframework.data.gemfire.LocalRegionFactoryBean; +import org.springframework.data.gemfire.PartitionedRegionFactoryBean; +import org.springframework.data.gemfire.ReplicatedRegionFactoryBean; +import org.springframework.data.gemfire.client.ClientRegionFactoryBean; +import org.springframework.data.gemfire.test.mock.annotation.EnableGemFireMockObjects; +import org.springframework.data.gemfire.test.model.Person; + +/** + * The EnableCompressionConfigurationUnitTests class... + * + * @author John Blum + * @since 1.0.0 + */ +public class EnableCompressionConfigurationUnitTests { + + private ConfigurableApplicationContext applicationContext; + + @After + public void tearDown() { + Optional.ofNullable(this.applicationContext).ifPresent(ConfigurableApplicationContext::close); + } + + private ConfigurableApplicationContext newApplicationContext(Class... annotatedClasses) { + return new AnnotationConfigApplicationContext(annotatedClasses); + } + + private void assertRegionCompressor(Region region, String regionName, Compressor compressor) { + + assertThat(region).isNotNull(); + assertThat(region.getName()).isEqualTo(regionName); + assertThat(region.getFullPath()).isEqualTo(GemfireUtils.toRegionPath(regionName)); + assertThat(region.getAttributes()).isNotNull(); + assertThat(region.getAttributes().getCompressor()).isEqualTo(compressor); + } + + @Test + public void enableCompressionForAllRegions() { + + this.applicationContext = newApplicationContext(EnableCompressionForAllRegionsConfiguration.class); + + assertThat(this.applicationContext).isNotNull(); + + Compressor compressor = this.applicationContext.getBean(Compressor.class); + + assertThat(compressor).isInstanceOf(SnappyCompressor.class); + + Arrays.asList("People", "ExampleLocalRegion", "ExamplePartitionRegion", "ExampleReplicateRegion") + .forEach(regionName -> { + assertThat(this.applicationContext.containsBean(regionName)).isTrue(); + assertRegionCompressor(this.applicationContext.getBean(regionName, Region.class), + regionName, compressor); + }); + } + + @Test + public void enableCompressionForSelectRegions() { + + this.applicationContext = newApplicationContext(EnableCompressionForSelectRegionsConfiguration.class); + + assertThat(this.applicationContext).isNotNull(); + + Compressor compressor = this.applicationContext.getBean("MockCompressor", Compressor.class); + + assertThat(compressor).isNotNull(); + assertThat(this.applicationContext.containsBean(SNAPPY_COMPRESSOR_BEAN_NAME)).isTrue(); + + Arrays.asList("People", "ExampleClientRegion").forEach(regionName -> { + assertThat(this.applicationContext.containsBean(regionName)).isTrue(); + assertRegionCompressor(this.applicationContext.getBean(regionName, Region.class), + regionName, "People".equals(regionName) ? compressor : null); + }); + } + + @PeerCacheApplication + @EnableGemFireMockObjects + @EnableEntityDefinedRegions(basePackageClasses = Person.class) + @EnableCompression + @SuppressWarnings("unused") + static class EnableCompressionForAllRegionsConfiguration { + + @Bean("ExampleLocalRegion") + public LocalRegionFactoryBean localRegion(GemFireCache gemfireCache) { + + LocalRegionFactoryBean localRegion = new LocalRegionFactoryBean<>(); + + localRegion.setCache(gemfireCache); + localRegion.setClose(false); + localRegion.setPersistent(false); + + return localRegion; + } + + @Bean("ExamplePartitionRegion") + public PartitionedRegionFactoryBean partitionRegion(GemFireCache gemfireCache) { + + PartitionedRegionFactoryBean partitionRegion = new PartitionedRegionFactoryBean<>(); + + partitionRegion.setCache(gemfireCache); + partitionRegion.setClose(false); + partitionRegion.setPersistent(false); + + return partitionRegion; + } + + @Bean("ExampleReplicateRegion") + public ReplicatedRegionFactoryBean replicateRegion(GemFireCache gemfireCache) { + + ReplicatedRegionFactoryBean replicateRegion = new ReplicatedRegionFactoryBean<>(); + + replicateRegion.setCache(gemfireCache); + replicateRegion.setClose(false); + replicateRegion.setPersistent(false); + + return replicateRegion; + } + } + + @ClientCacheApplication + @EnableGemFireMockObjects + @EnableEntityDefinedRegions(basePackageClasses = Person.class) + @EnableCompression(compressorBeanName = "MockCompressor", regionNames = "People") + @SuppressWarnings("unused") + static class EnableCompressionForSelectRegionsConfiguration { + + @Bean("ExampleClientRegion") + public ClientRegionFactoryBean clientRegion(GemFireCache gemfireCache) { + + ClientRegionFactoryBean clientRegion = new ClientRegionFactoryBean<>(); + + clientRegion.setCache(gemfireCache); + clientRegion.setClose(false); + clientRegion.setShortcut(ClientRegionShortcut.LOCAL); + + return clientRegion; + } + + @Bean("MockCompressor") + Compressor mockCompressor() { + return mock(Compressor.class); + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/EnableGemFirePropertiesIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableGemFirePropertiesIntegrationTests.java index 200e2734..75a977f4 100644 --- a/src/test/java/org/springframework/data/gemfire/config/annotation/EnableGemFirePropertiesIntegrationTests.java +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableGemFirePropertiesIntegrationTests.java @@ -256,7 +256,7 @@ public class EnableGemFirePropertiesIntegrationTests { public void offHeapGemFirePropertiesConfiguration() { MockPropertySource testPropertySource = new MockPropertySource() - .withProperty("spring.data.gemfire.cache.off-heap-memory-size", "1024g"); + .withProperty("spring.data.gemfire.cache.off-heap.memory-size", "1024g"); this.applicationContext = newApplicationContext(testPropertySource, TestOffHeapGemFirePropertiesConfiguration.class); diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/support/AbstractAnnotationConfigSupportTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/support/AbstractAnnotationConfigSupportTests.java index 43cd4dd2..f4ddb135 100644 --- a/src/test/java/org/springframework/data/gemfire/config/annotation/support/AbstractAnnotationConfigSupportTests.java +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/support/AbstractAnnotationConfigSupportTests.java @@ -370,7 +370,8 @@ public class AbstractAnnotationConfigSupportTests { when(mockBeanDefinition.resolveBeanClass(any(ClassLoader.class))) .thenThrow(new ClassNotFoundException("Class [non.existing.bean.Class] not found")); - assertThat(this.support.resolveBeanClass(mockBeanDefinition, null).orElse(null)).isNull(); + assertThat(this.support.resolveBeanClass(mockBeanDefinition, (BeanDefinitionRegistry) null) + .orElse(null)).isNull(); verify(mockBeanDefinition, times(1)).getBeanClassName(); verify(mockBeanDefinition, never()).getFactoryMethodName(); diff --git a/src/test/java/org/springframework/data/gemfire/util/SpringUtilsUnitTests.java b/src/test/java/org/springframework/data/gemfire/util/SpringUtilsUnitTests.java index c8e509db..ea879590 100644 --- a/src/test/java/org/springframework/data/gemfire/util/SpringUtilsUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/util/SpringUtilsUnitTests.java @@ -34,7 +34,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; /** * Unit tests for {@link SpringUtils}. @@ -56,6 +58,7 @@ public class SpringUtilsUnitTests { @Test public void addDependsOnToExistingDependencies() { + when(mockBeanDefinition.getDependsOn()).thenReturn(asArray("testBeanNameOne", "testBeanNameTwo")); assertThat(SpringUtils.addDependsOn(mockBeanDefinition, "testBeanNameThree")).isSameAs(mockBeanDefinition); @@ -67,6 +70,7 @@ public class SpringUtilsUnitTests { @Test public void addDependsOnToNonExistingDependencies() { + when(mockBeanDefinition.getDependsOn()).thenReturn(null); assertThat(SpringUtils.addDependsOn(mockBeanDefinition, "testBeanName")).isSameAs(mockBeanDefinition); @@ -77,6 +81,7 @@ public class SpringUtilsUnitTests { @Test public void addDependsOnWithMultipleDependenciesWithExistingDependencies() { + when(mockBeanDefinition.getDependsOn()).thenReturn(asArray("testBeanNameOne", "testBeanNameTwo")); assertThat(SpringUtils.addDependsOn(mockBeanDefinition, "testBeanNameThree", "testBeanNameFour")) @@ -87,6 +92,48 @@ public class SpringUtilsUnitTests { .setDependsOn("testBeanNameOne", "testBeanNameTwo", "testBeanNameThree", "testBeanNameFour"); } + @Test + @SuppressWarnings("all") + public void setBeanDefinitionPropertyReference() { + + BeanDefinition mockBeanDefinition = mock(BeanDefinition.class); + + MutablePropertyValues mutablePropertyValues = new MutablePropertyValues(); + + when(mockBeanDefinition.getPropertyValues()).thenReturn(mutablePropertyValues); + + assertThat(mutablePropertyValues.size()).isEqualTo(0); + + SpringUtils.setPropertyReference(mockBeanDefinition, "testProperty", "testBean"); + + assertThat(mutablePropertyValues.size()).isEqualTo(1); + assertThat(mutablePropertyValues.getPropertyValue("testProperty")).isNotNull(); + assertThat(mutablePropertyValues.getPropertyValue("testProperty").getValue()) + .isInstanceOf(RuntimeBeanReference.class); + assertThat(((RuntimeBeanReference) mutablePropertyValues.getPropertyValue("testProperty").getValue()).getBeanName()) + .isEqualTo("testBean"); + } + + @Test + @SuppressWarnings("all") + public void setBeanDefinitionPropertyValue() { + + BeanDefinition mockBeanDefinition = mock(BeanDefinition.class); + + MutablePropertyValues mutablePropertyValues = new MutablePropertyValues(); + + when(mockBeanDefinition.getPropertyValues()).thenReturn(mutablePropertyValues); + + assertThat(mutablePropertyValues.size()).isEqualTo(0); + + SpringUtils.setPropertyValue(mockBeanDefinition, "testProperty", "testValue"); + + assertThat(mutablePropertyValues.size()).isEqualTo(1); + assertThat(mutablePropertyValues.getPropertyValue("testProperty")).isNotNull(); + assertThat(mutablePropertyValues.getPropertyValue("testProperty").getValue()) + .isEqualTo("testValue"); + } + @Test public void defaultIfEmptyReturnsValue() { assertThat(SpringUtils.defaultIfEmpty("test", "DEFAULT")).isEqualTo("test");