Apply eviction to caching-defined Regions using @EnableEviction annotation configuration.

Resolves gh-519.
This commit is contained in:
John Blum
2021-07-14 16:28:48 -07:00
parent 9d65677d1c
commit 999e52f50c
3 changed files with 196 additions and 64 deletions

View File

@@ -14,7 +14,6 @@
* limitations under the License.
*
*/
package org.springframework.data.gemfire.config.annotation;
import java.lang.annotation.Documented;

View File

@@ -17,9 +17,6 @@
package org.springframework.data.gemfire.config.annotation;
import static org.springframework.data.gemfire.config.annotation.EnableEviction.EvictionPolicy;
import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable;
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException;
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
import java.lang.annotation.Annotation;
@@ -30,8 +27,10 @@ import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import org.apache.geode.cache.AttributesMutator;
import org.apache.geode.cache.EvictionAttributes;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionAttributes;
import org.apache.geode.cache.util.ObjectSizer;
import org.springframework.beans.BeansException;
@@ -41,6 +40,8 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.gemfire.PeerRegionFactoryBean;
@@ -51,7 +52,10 @@ import org.springframework.data.gemfire.eviction.EvictingRegionFactoryBean;
import org.springframework.data.gemfire.eviction.EvictionActionType;
import org.springframework.data.gemfire.eviction.EvictionAttributesFactoryBean;
import org.springframework.data.gemfire.eviction.EvictionPolicyType;
import org.springframework.data.gemfire.util.ArrayUtils;
import org.springframework.data.gemfire.util.CollectionUtils;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -60,22 +64,22 @@ import org.springframework.util.StringUtils;
* Eviction policy configuration on cache {@link Region Regions}.
*
* @author John Blum
* @see org.apache.geode.cache.EvictionAttributes
* @see org.apache.geode.cache.Region
* @see org.apache.geode.cache.util.ObjectSizer
* @see org.springframework.beans.factory.config.BeanPostProcessor
* @see org.springframework.context.ApplicationContext
* @see org.springframework.context.ApplicationContextAware
* @see org.springframework.context.annotation.Bean
* @see org.springframework.context.annotation.Configuration
* @see org.springframework.context.annotation.ImportAware
* @see PeerRegionFactoryBean
* @see org.springframework.data.gemfire.PeerRegionFactoryBean
* @see org.springframework.data.gemfire.ResolvableRegionFactoryBean
* @see org.springframework.data.gemfire.client.ClientRegionFactoryBean
* @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport
* @see org.springframework.data.gemfire.eviction.EvictionActionType
* @see org.springframework.data.gemfire.eviction.EvictionAttributesFactoryBean
* @see org.springframework.data.gemfire.eviction.EvictionPolicyType
* @see org.apache.geode.cache.EvictionAttributes
* @see org.apache.geode.cache.Region
* @see org.apache.geode.cache.util.ObjectSizer
* @since 1.9.0
*/
@Configuration
@@ -94,7 +98,7 @@ public class EvictionConfiguration extends AbstractAnnotationConfigSupport
* @see java.lang.Class
*/
@Override
protected Class<? extends Annotation> getAnnotationType() {
protected @NonNull Class<? extends Annotation> getAnnotationType() {
return EnableEviction.class;
}
@@ -115,7 +119,7 @@ public class EvictionConfiguration extends AbstractAnnotationConfigSupport
* @inheritDoc
*/
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
public void setImportMetadata(@NonNull AnnotationMetadata importMetadata) {
if (isAnnotationPresent(importMetadata)) {
@@ -123,7 +127,9 @@ public class EvictionConfiguration extends AbstractAnnotationConfigSupport
AnnotationAttributes[] policies = enableEvictionAttributes.getAnnotationArray("policies");
for (AnnotationAttributes evictionPolicyAttributes : nullSafeArray(policies, AnnotationAttributes.class)) {
for (AnnotationAttributes evictionPolicyAttributes :
ArrayUtils.nullSafeArray(policies, AnnotationAttributes.class)) {
this.evictionPolicyConfigurer =
ComposableEvictionPolicyConfigurer.compose(this.evictionPolicyConfigurer,
EvictionPolicyMetaData.from(evictionPolicyAttributes, this.applicationContext));
@@ -173,23 +179,47 @@ public class EvictionConfiguration extends AbstractAnnotationConfigSupport
};
}
@SuppressWarnings("unused")
@EventListener(ContextRefreshedEvent.class)
public void evictionContextRefreshedListener(@NonNull ContextRefreshedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
for (Region<?, ?> region : applicationContext.getBeansOfType(Region.class).values()) {
getEvictionPolicyConfigurer().configure(region);
}
}
/**
* {@link EvictionPolicyConfigurer} configures the Eviction policy of a GemFire {@link Region}.
* {@link EvictionPolicyConfigurer} configures the Eviction policy of an Apache Geode {@link Region}.
*
* @see java.lang.FunctionalInterface
*/
@FunctionalInterface
protected interface EvictionPolicyConfigurer {
/**
* Configure the Eviction policy on the given SDG {@link PeerRegionFactoryBean} or {@link ClientRegionFactoryBean}
* used to create a GemFire {@link Region}.
* Configure the Eviction policy on the given SDG {@link ClientRegionFactoryBean}
* or {@link PeerRegionFactoryBean} used to create an Apache Geode {@link Region}.
*
* @param regionFactoryBean {@link PeerRegionFactoryBean} or {@link ClientRegionFactoryBean} used to create
* a GemFire {@link Region}.
* @param regionBean {@link ClientRegionFactoryBean} or {@link PeerRegionFactoryBean} used to create
* an Apache Geode {@link Region}.
* @return the given {@code regionFactoryBean}.
* @see PeerRegionFactoryBean
* @see org.springframework.data.gemfire.PeerRegionFactoryBean
* @see org.springframework.data.gemfire.client.ClientRegionFactoryBean
*/
Object configure(Object regionFactoryBean);
Object configure(Object regionBean);
/**
* Configures the Eviction policy of the given Apache Geode {@link Region}.
*
* @param region {@link Region} on which to configure the Eviction policy.
* @return the given {@link Region}.
* @see org.apache.geode.cache.Region
*/
default Region<?, ?> configure(Region<?, ?> region) {
return region;
}
}
/**
@@ -201,9 +231,6 @@ public class EvictionConfiguration extends AbstractAnnotationConfigSupport
*/
protected static class ComposableEvictionPolicyConfigurer implements EvictionPolicyConfigurer {
private final EvictionPolicyConfigurer one;
private final EvictionPolicyConfigurer two;
/**
* Composes the array of {@link EvictionPolicyConfigurer} objects into a single
* {@link EvictionPolicyConfigurer} implementation using the Composite Software Design Pattern.
@@ -215,8 +242,8 @@ public class EvictionConfiguration extends AbstractAnnotationConfigSupport
* @see #compose(Iterable)
*/
@SuppressWarnings("unused")
protected static EvictionPolicyConfigurer compose(EvictionPolicyConfigurer[] array) {
return compose(Arrays.asList(nullSafeArray(array, EvictionPolicyConfigurer.class)));
protected static @Nullable EvictionPolicyConfigurer compose(EvictionPolicyConfigurer[] array) {
return compose(Arrays.asList(ArrayUtils.nullSafeArray(array, EvictionPolicyConfigurer.class)));
}
/**
@@ -229,11 +256,11 @@ public class EvictionConfiguration extends AbstractAnnotationConfigSupport
* @see org.springframework.data.gemfire.config.annotation.EvictionConfiguration.EvictionPolicyConfigurer
* @see #compose(EvictionPolicyConfigurer, EvictionPolicyConfigurer)
*/
protected static EvictionPolicyConfigurer compose(Iterable<EvictionPolicyConfigurer> iterable) {
protected static @Nullable EvictionPolicyConfigurer compose(Iterable<EvictionPolicyConfigurer> iterable) {
EvictionPolicyConfigurer current = null;
for (EvictionPolicyConfigurer evictionPolicyConfigurer : nullSafeIterable(iterable)) {
for (EvictionPolicyConfigurer evictionPolicyConfigurer : CollectionUtils.nullSafeIterable(iterable)) {
current = compose(current, evictionPolicyConfigurer);
}
@@ -249,13 +276,17 @@ public class EvictionConfiguration extends AbstractAnnotationConfigSupport
* @return an {@link EvictionPolicyConfigurer} object implementation composed of
* multiple {@link EvictionPolicyConfigurer} objects using the Composite Software Design Pattern.
*/
protected static EvictionPolicyConfigurer compose(EvictionPolicyConfigurer one, EvictionPolicyConfigurer two) {
protected static @Nullable EvictionPolicyConfigurer compose(@Nullable EvictionPolicyConfigurer one,
@Nullable EvictionPolicyConfigurer two) {
return one == null ? two
: (two == null ? one
: new ComposableEvictionPolicyConfigurer(one, two));
: two == null ? one
: new ComposableEvictionPolicyConfigurer(one, two);
}
private final EvictionPolicyConfigurer one;
private final EvictionPolicyConfigurer two;
/**
* Constructs a new instance of the {@link ComposableEvictionPolicyConfigurer} initialized with the two
* {@link EvictionPolicyConfigurer} objects.
@@ -273,19 +304,24 @@ public class EvictionConfiguration extends AbstractAnnotationConfigSupport
* @inheritDoc
*/
@Override
public Object configure(Object regionFactoryBean) {
return this.two.configure(this.one.configure(regionFactoryBean));
public Object configure(Object regionBean) {
return this.two.configure(this.one.configure(regionBean));
}
/**
* @inheritDoc
*/
@Override
public Region<?, ?> configure(Region<?, ?> region) {
return this.two.configure(this.one.configure(region));
}
}
@SuppressWarnings("unused")
protected static class EvictionPolicyMetaData implements EvictionPolicyConfigurer {
protected static final String[] ALL_REGIONS = new String[0];
private final EvictionAttributes evictionAttributes;
private final Set<String> regionNames = new HashSet<>();
protected static EvictionPolicyMetaData from(@NonNull AnnotationAttributes evictionPolicyAttributes,
@NonNull ApplicationContext applicationContext) {
@@ -347,6 +383,10 @@ public class EvictionConfiguration extends AbstractAnnotationConfigSupport
return EvictionPolicyType.HEAP_PERCENTAGE.equals(type) ? null : maximum;
}
private final EvictionAttributes evictionAttributes;
private final Set<String> regionNames = new HashSet<>();
/**
* Constructs an instance of {@link EvictionPolicyMetaData} initialized with the given
* {@link EvictionAttributes} applying to all {@link Region Regions}.
@@ -371,25 +411,11 @@ public class EvictionConfiguration extends AbstractAnnotationConfigSupport
*/
protected EvictionPolicyMetaData(EvictionAttributes evictionAttributes, String[] regionNames) {
this.evictionAttributes = Optional.ofNullable(evictionAttributes).orElseThrow(() ->
newIllegalArgumentException("EvictionAttributes are required"));
Assert.notNull(evictionAttributes, "EvictionAttributes must not be null");
Collections.addAll(this.regionNames, nullSafeArray(regionNames, String.class));
}
this.evictionAttributes = evictionAttributes;
/**
* Returns an instance of the {@link EvictionAttributes} specifying the Eviction policy configuration
* captured in this Eviction policy meta-data.
*
* @return an instance of the {@link EvictionAttributes} specifying the {@link Region}
* Eviction policy configuration.
* @throws IllegalStateException if the {@link EvictionAttributes} were not properly initialized.
* @see org.apache.geode.cache.EvictionAttributes
*/
protected EvictionAttributes getEvictionAttributes() {
return Optional.ofNullable(this.evictionAttributes).orElseThrow(() ->
newIllegalStateException("EvictionAttributes was not properly configured and initialized"));
Collections.addAll(this.regionNames, ArrayUtils.nullSafeArray(regionNames, String.class));
}
/**
@@ -401,10 +427,23 @@ public class EvictionConfiguration extends AbstractAnnotationConfigSupport
* @see #resolveRegionName(Object)
* @see #accepts(Supplier)
*/
protected boolean accepts(Object regionFactoryBean) {
protected boolean accepts(@Nullable Object regionFactoryBean) {
return isRegionFactoryBean(regionFactoryBean) && accepts(() -> resolveRegionName(regionFactoryBean));
}
/**
* Determines whether the given {@link Region} is accepted for Eviction policy configuration.
*
* @param region {@link Region} evaluated for Eviction policy configuration.
* @return a boolean value indicating whether the given {@link Region} is accepted for
* Eviction policy configuration.
* @see org.apache.geode.cache.Region
* @see #accepts(Supplier)
*/
protected boolean accepts(@Nullable Region<?, ?> region) {
return region != null && accepts(() -> region.getName());
}
/**
* Determine whether the {@link Region} identified by name is accepted for Eviction policy configuration.
*
@@ -425,7 +464,7 @@ public class EvictionConfiguration extends AbstractAnnotationConfigSupport
protected String resolveRegionName(Object regionFactoryBean) {
return regionFactoryBean instanceof ResolvableRegionFactoryBean
? ((ResolvableRegionFactoryBean) regionFactoryBean).resolveRegionName()
? ((ResolvableRegionFactoryBean<?, ?>) regionFactoryBean).resolveRegionName()
: null;
}
@@ -447,12 +486,59 @@ public class EvictionConfiguration extends AbstractAnnotationConfigSupport
return regionFactoryBean;
}
@Override
public Object configure(Object regionFactoryBean) {
/**
* Returns an instance of the {@link EvictionAttributes} specifying the Eviction policy configuration
* captured in this Eviction policy meta-data.
*
* @return an instance of the {@link EvictionAttributes} specifying the {@link Region}
* Eviction policy configuration.
* @throws IllegalStateException if the {@link EvictionAttributes} were not properly initialized.
* @see org.apache.geode.cache.EvictionAttributes
*/
protected EvictionAttributes getEvictionAttributes() {
return accepts(regionFactoryBean)
? setEvictionAttributes((EvictingRegionFactoryBean) regionFactoryBean)
: regionFactoryBean;
return Optional.ofNullable(this.evictionAttributes).orElseThrow(() ->
newIllegalStateException("EvictionAttributes was not properly configured and initialized"));
}
/**
* @inheritDoc
*/
@Override
public Object configure(Object regionBean) {
return accepts(regionBean)
? setEvictionAttributes((EvictingRegionFactoryBean) regionBean)
: regionBean;
}
/**
* @inheritDoc
*/
@Override
public Region<?, ?> configure(Region<?, ?> region) {
Optional.ofNullable(region)
.filter(this::accepts)
.filter(this::isDefaultEvictionEntryMaximum)
.map(Region::getAttributesMutator)
.map(AttributesMutator::getEvictionAttributesMutator)
.ifPresent(evictionAttributesMutator ->
evictionAttributesMutator.setMaximum(getEvictionAttributes().getMaximum()));
return region;
}
private boolean isDefaultEvictionEntryMaximum(Region<?, ?> region) {
return region != null && isDefaultEvictionEntryMaximum(region.getAttributes());
}
private boolean isDefaultEvictionEntryMaximum(RegionAttributes<?, ?> regionAttributes) {
return regionAttributes != null && isDefaultEvictionEntryMaximum(regionAttributes.getEvictionAttributes());
}
private boolean isDefaultEvictionEntryMaximum(EvictionAttributes evictionAttributes) {
return EvictionAttributes.DEFAULT_ENTRIES_MAXIMUM == evictionAttributes.getMaximum();
}
}
}

View File

@@ -13,33 +13,40 @@
* 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 java.util.Optional;
import org.junit.After;
import org.junit.Test;
import org.apache.geode.cache.DataPolicy;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionShortcut;
import org.apache.geode.cache.client.ClientRegionShortcut;
import org.junit.After;
import org.junit.Test;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.data.gemfire.eviction.EvictionActionType;
import org.springframework.data.gemfire.eviction.EvictionPolicyType;
import org.springframework.data.gemfire.test.mock.annotation.EnableGemFireMockObjects;
import org.springframework.data.gemfire.test.model.Person;
import org.springframework.stereotype.Service;
/**
* The EnableEvictionConfigurationIntegrationTests class...
* Integration Tests for {@link EnableEviction} and {@link EvictionConfiguration}.
*
* @author John Blum
* @since 1.0.0
* @see org.apache.geode.cache.Region
* @see org.springframework.data.gemfire.config.annotation.EnableEviction
* @see org.springframework.data.gemfire.config.annotation.EvictionConfiguration
* @since 1.9.0
*/
@SuppressWarnings("unused")
public class EnableEvictionConfigurationIntegrationTests {
private static final String GEMFIRE_LOG_LEVEL = "error";
@@ -81,6 +88,18 @@ public class EnableEvictionConfigurationIntegrationTests {
assertThat(region.getAttributes().getEvictionAttributes().getMaximum()).isEqualTo(expectedEvictionMaximum);
}
@Test
public void assertApplicationCachingDefinedRegionsEvictionPolicyIsCorrect() {
ConfigurableApplicationContext applicationContext = newApplicationContext(ApplicationConfiguration.class);
assertRegionEvictionConfiguration(applicationContext, "CacheOne",
EvictionActionType.LOCAL_DESTROY, 100);
assertRegionEvictionConfiguration(applicationContext, "CacheTwo",
EvictionActionType.LOCAL_DESTROY, 100);
}
@Test
public void assertClientCacheRegionEvictionPolicyIsCorrect() {
assertRegionEvictionConfiguration(newApplicationContext(ClientCacheRegionEvictionConfiguration.class),
@@ -93,15 +112,43 @@ public class EnableEvictionConfigurationIntegrationTests {
"People", EvictionActionType.OVERFLOW_TO_DISK, 10000);
}
@ClientCacheApplication(name = "EnableEvictionConfigurationIntegrationTests", logLevel = GEMFIRE_LOG_LEVEL)
@EnableCachingDefinedRegions(clientRegionShortcut = ClientRegionShortcut.LOCAL)
@EnableEviction(policies = @EnableEviction.EvictionPolicy(maximum = 100))
@EnableGemFireMockObjects
static class ApplicationConfiguration {
@Bean
ApplicationService applicationService() {
return new ApplicationService();
}
}
@Service
static class ApplicationService {
@Cacheable("CacheOne")
public Object someMethod(Object key) {
return null;
}
@Cacheable("CacheTwo")
public Object someOtherMethod(Object key) {
return null;
}
}
@ClientCacheApplication(name = "EnableEvictionConfigurationIntegrationTests", logLevel = GEMFIRE_LOG_LEVEL)
@EnableEntityDefinedRegions(basePackageClasses = Person.class, clientRegionShortcut = ClientRegionShortcut.LOCAL)
@EnableEviction(policies = @EnableEviction.EvictionPolicy(regionNames = "People", maximum = 100))
@EnableGemFireMockObjects
static class ClientCacheRegionEvictionConfiguration { }
@PeerCacheApplication(name = "EnableEvictionConfigurationIntegrationTests", logLevel = GEMFIRE_LOG_LEVEL)
@EnableEntityDefinedRegions(basePackageClasses = Person.class, serverRegionShortcut = RegionShortcut.LOCAL)
@EnableEviction(policies = @EnableEviction.EvictionPolicy(regionNames = "People",
action = EvictionActionType.OVERFLOW_TO_DISK, maximum = 10000))
@EnableGemFireMockObjects
static class PeerCacheRegionEvictionConfiguration { }
}