Skip to content

Commit 9d65677

Browse files
committed
Apply expiration to caching-defined Regions using @EnableExpiration annotation configuration.
Resolves spring-projectsgh-518.
1 parent d07cff9 commit 9d65677

File tree

2 files changed

+157
-25
lines changed

2 files changed

+157
-25
lines changed

Diff for: spring-data-geode/src/main/java/org/springframework/data/gemfire/config/annotation/ExpirationConfiguration.java

+108-23
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import static org.springframework.data.gemfire.config.annotation.EnableExpiration.ExpirationPolicy;
2020
import static org.springframework.data.gemfire.config.annotation.EnableExpiration.ExpirationType;
21-
import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
2221
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable;
2322
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
2423

@@ -29,15 +28,21 @@
2928
import java.util.Set;
3029
import java.util.function.Supplier;
3130

31+
import org.apache.geode.cache.AttributesMutator;
32+
import org.apache.geode.cache.CustomExpiry;
3233
import org.apache.geode.cache.ExpirationAction;
3334
import org.apache.geode.cache.ExpirationAttributes;
3435
import org.apache.geode.cache.Region;
36+
import org.apache.geode.cache.RegionAttributes;
3537

3638
import org.springframework.beans.BeansException;
3739
import org.springframework.beans.factory.config.BeanPostProcessor;
40+
import org.springframework.context.ApplicationContext;
3841
import org.springframework.context.annotation.Bean;
3942
import org.springframework.context.annotation.Configuration;
4043
import org.springframework.context.annotation.ImportAware;
44+
import org.springframework.context.event.ContextRefreshedEvent;
45+
import org.springframework.context.event.EventListener;
4146
import org.springframework.core.annotation.AnnotationAttributes;
4247
import org.springframework.core.type.AnnotationMetadata;
4348
import org.springframework.data.gemfire.PeerRegionFactoryBean;
@@ -49,6 +54,7 @@
4954
import org.springframework.data.gemfire.expiration.ExpiringRegionFactoryBean;
5055
import org.springframework.data.gemfire.util.ArrayUtils;
5156
import org.springframework.data.gemfire.util.CollectionUtils;
57+
import org.springframework.data.gemfire.util.SpringUtils;
5258
import org.springframework.lang.NonNull;
5359
import org.springframework.util.Assert;
5460

@@ -102,7 +108,7 @@ public void setImportMetadata(@NonNull AnnotationMetadata importMetadata) {
102108
AnnotationAttributes[] policies = enableExpirationAttributes.getAnnotationArray("policies");
103109

104110
for (AnnotationAttributes expirationPolicyAttributes :
105-
nullSafeArray(policies, AnnotationAttributes.class)) {
111+
ArrayUtils.nullSafeArray(policies, AnnotationAttributes.class)) {
106112

107113
this.expirationPolicyConfigurer =
108114
ComposableExpirationPolicyConfigurer.compose(this.expirationPolicyConfigurer,
@@ -146,20 +152,44 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) thro
146152
};
147153
}
148154

155+
@SuppressWarnings("unused")
156+
@EventListener(ContextRefreshedEvent.class)
157+
public void expirationContextRefreshedListener(@NonNull ContextRefreshedEvent event) {
158+
159+
ApplicationContext applicationContext = event.getApplicationContext();
160+
161+
for (Region<?, ?> region : applicationContext.getBeansOfType(Region.class).values()) {
162+
getExpirationPolicyConfigurer().configure(region);
163+
}
164+
}
165+
149166
/**
150167
* Interface defining a contract for implementations that configure a {@link Region Region's} expiration policy.
168+
*
169+
* @see java.lang.FunctionalInterface
151170
*/
171+
@FunctionalInterface
152172
protected interface ExpirationPolicyConfigurer {
153173

154174
/**
155175
* Configures the expiration policy for the given {@link Region}.
156176
*
157-
* @param regionFactoryBean {@link Region} object who's expiration policy will be configured.
177+
* @param regionBean {@link Region} object who's expiration policy will be configured.
158178
* @return the given {@link Region} object.
159179
* @see org.apache.geode.cache.Region
160180
*/
161-
Object configure(Object regionFactoryBean);
181+
Object configure(Object regionBean);
162182

183+
/**
184+
* Configures the expiration policy for the given {@link Region}.
185+
*
186+
* @param region {@link Region} who's expiration policy will be configured.
187+
* @return the given {@link Region}.
188+
* @see org.apache.geode.cache.Region
189+
*/
190+
default Region<?, ?> configure(Region<?, ?> region) {
191+
return region;
192+
}
163193
}
164194

165195
/**
@@ -242,8 +272,16 @@ private ComposableExpirationPolicyConfigurer(@NonNull ExpirationPolicyConfigurer
242272
* @inheritDoc
243273
*/
244274
@Override
245-
public Object configure(Object regionFactoryBean) {
246-
return this.two.configure(this.one.configure(regionFactoryBean));
275+
public Object configure(Object regionBean) {
276+
return this.two.configure(this.one.configure(regionBean));
277+
}
278+
279+
/**
280+
* @inheritDoc
281+
*/
282+
@Override
283+
public Region<?, ?> configure(Region<?, ?> region) {
284+
return this.two.configure(this.one.configure(region));
247285
}
248286
}
249287

@@ -261,12 +299,6 @@ protected static class ExpirationPolicyMetaData implements ExpirationPolicyConfi
261299

262300
protected static final String[] ALL_REGIONS = new String[0];
263301

264-
private final ExpirationAttributes defaultExpirationAttributes;
265-
266-
private final Set<String> regionNames = new HashSet<>();
267-
268-
private final Set<ExpirationType> types = new HashSet<>();
269-
270302
/**
271303
* Factory method to construct an instance of {@link ExpirationPolicyMetaData} initialized with
272304
* the given {@link AnnotationAttributes} from the nested {@link ExpirationPolicy} annotation
@@ -367,8 +399,8 @@ protected static ExpirationPolicyMetaData newExpirationPolicyMetaData(int timeou
367399
String[] regionNames, ExpirationType[] types) {
368400

369401
return new ExpirationPolicyMetaData(newExpirationAttributes(timeout, action),
370-
CollectionUtils.asSet(nullSafeArray(regionNames, String.class)),
371-
CollectionUtils.asSet(nullSafeArray(types, ExpirationType.class)));
402+
CollectionUtils.asSet(ArrayUtils.nullSafeArray(regionNames, String.class)),
403+
CollectionUtils.asSet(ArrayUtils.nullSafeArray(types, ExpirationType.class)));
372404
}
373405

374406
/**
@@ -380,7 +412,7 @@ protected static ExpirationPolicyMetaData newExpirationPolicyMetaData(int timeou
380412
* @see ExpirationActionType
381413
*/
382414
protected static ExpirationActionType resolveAction(ExpirationActionType action) {
383-
return Optional.ofNullable(action).orElse(DEFAULT_ACTION);
415+
return action != null ? action : DEFAULT_ACTION;
384416
}
385417

386418
/**
@@ -394,6 +426,12 @@ protected static int resolveTimeout(int timeout) {
394426
return Math.max(timeout, DEFAULT_TIMEOUT);
395427
}
396428

429+
private final ExpirationAttributes defaultExpirationAttributes;
430+
431+
private final Set<String> regionNames = new HashSet<>();
432+
433+
private final Set<ExpirationType> types = new HashSet<>();
434+
397435
/**
398436
* Constructs an instance of {@link ExpirationPolicyMetaData} initialized with the given expiration policy
399437
* configuraiton meta-data and {@link Region} expiration settings.
@@ -442,14 +480,27 @@ protected ExpirationPolicyMetaData(ExpirationAttributes expirationAttributes, Se
442480
/**
443481
* Determines whether the given {@link Object} (e.g. Spring bean) is accepted for Eviction policy configuration.
444482
*
445-
* @param regionFactoryBean {@link Object} being evaluated as an Eviction policy configuration candidate.
483+
* @param regionBean {@link Object} being evaluated as an Eviction policy configuration candidate.
446484
* @return a boolean value indicating whether the {@link Object} is accepted for Eviction policy configuration.
447485
* @see #isRegionFactoryBean(Object)
448486
* @see #resolveRegionName(Object)
449487
* @see #accepts(Supplier)
450488
*/
451-
protected boolean accepts(Object regionFactoryBean) {
452-
return isRegionFactoryBean(regionFactoryBean) && accepts(() -> resolveRegionName(regionFactoryBean));
489+
protected boolean accepts(Object regionBean) {
490+
return isRegionFactoryBean(regionBean) && accepts(() -> resolveRegionName(regionBean));
491+
}
492+
493+
/**
494+
* Determines whether the given {@link Region} is accepted for Eviction policy configuration.
495+
*
496+
* @param region {@link Region} being evaluated as a Eviction policy configuration candidate.
497+
* @return a boolean value indicated whether the given {@link Region} is accepted as an Expiration policy
498+
* configuration candidate.
499+
* @see org.apache.geode.cache.Region
500+
* @see #accepts(Supplier)
501+
*/
502+
protected boolean accepts(Region<?, ?> region) {
503+
return region != null && accepts(() -> region.getName());
453504
}
454505

455506
/**
@@ -530,15 +581,49 @@ protected String resolveRegionName(Object regionFactoryBean) {
530581
* @inheritDoc
531582
*/
532583
@Override
533-
public Object configure(Object regionFactoryBean) {
584+
public Object configure(Object regionBean) {
585+
586+
return accepts(regionBean)
587+
? setExpirationAttributes((ExpiringRegionFactoryBean<?, ?>) regionBean)
588+
: regionBean;
589+
}
590+
591+
/**
592+
* @inheritDoc
593+
*/
594+
@Override
595+
public Region<?, ?> configure(Region<?, ?> region) {
596+
597+
if (accepts(region)) {
598+
599+
RegionAttributes<?, ?> regionAttributes = region.getAttributes();
600+
601+
ExpirationAttributes expirationAttributes = defaultExpirationAttributes();
602+
603+
AttributesMutator<?, ?> regionAttributesMutator = region.getAttributesMutator();
604+
605+
if (SpringUtils.areNotNull(regionAttributes, regionAttributesMutator)) {
606+
607+
CustomExpiry<?, ?> customEntryIdleTimeout = regionAttributes.getCustomEntryIdleTimeout();
608+
CustomExpiry<?, ?> customEntryTimeToLive = regionAttributes.getCustomEntryTimeToLive();
609+
610+
if (isIdleTimeout() && customEntryIdleTimeout == null) {
611+
regionAttributesMutator.setCustomEntryIdleTimeout(
612+
AnnotationBasedExpiration.forIdleTimeout(expirationAttributes));
613+
}
614+
615+
if (isTimeToLive() && customEntryTimeToLive == null) {
616+
regionAttributesMutator.setCustomEntryTimeToLive(
617+
AnnotationBasedExpiration.forTimeToLive(expirationAttributes));
618+
}
619+
}
620+
}
534621

535-
return accepts(regionFactoryBean)
536-
? setExpirationAttributes((ExpiringRegionFactoryBean<?, ?>) regionFactoryBean)
537-
: regionFactoryBean;
622+
return region;
538623
}
539624

540625
/**
541-
* Returns the default, fallback {@link ExpirationAttributes}.
626+
* Returns the default {@link ExpirationAttributes}.
542627
*
543628
* @return an {@link ExpirationAttributes} containing the defaults.
544629
* @see org.apache.geode.cache.ExpirationAttributes

Diff for: spring-data-geode/src/test/java/org/springframework/data/gemfire/config/annotation/EnableExpirationConfigurationIntegrationTests.java

+49-2
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,26 @@
2727
import org.apache.geode.cache.RegionShortcut;
2828
import org.apache.geode.cache.client.ClientRegionShortcut;
2929

30+
import org.springframework.cache.annotation.Cacheable;
3031
import org.springframework.context.ConfigurableApplicationContext;
3132
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
33+
import org.springframework.context.annotation.Bean;
3234
import org.springframework.data.gemfire.expiration.AnnotationBasedExpiration;
35+
import org.springframework.data.gemfire.test.mock.annotation.EnableGemFireMockObjects;
3336
import org.springframework.data.gemfire.test.model.Person;
37+
import org.springframework.stereotype.Service;
3438

3539
/**
36-
* The EnableExpirationConfigurationIntegrationTests class...
40+
* Integration Tests for {@link EnableExpiration} and {@link ExpirationConfiguration}.
3741
*
3842
* @author John Blum
39-
* @since 1.0.0
43+
* @see org.junit.Test
44+
* @see org.apache.geode.cache.Region
45+
* @see org.springframework.data.gemfire.config.annotation.EnableExpiration
46+
* @see org.springframework.data.gemfire.config.annotation.ExpirationConfiguration
47+
* @since 1.9.0
4048
*/
49+
@SuppressWarnings("unused")
4150
public class EnableExpirationConfigurationIntegrationTests {
4251

4352
private static final String GEMFIRE_LOG_LEVEL = "error";
@@ -75,6 +84,16 @@ private void assertRegionExpirationConfiguration(ConfigurableApplicationContext
7584
.isInstanceOf(AnnotationBasedExpiration.class);
7685
}
7786

87+
@Test
88+
public void assertApplicationCachingDefinedRegionsExpirationPoliciesAreCorrect() {
89+
90+
ConfigurableApplicationContext applicationContext = newApplicationContext(ApplicationConfiguration.class);
91+
92+
assertThat(applicationContext).isNotNull();
93+
assertRegionExpirationConfiguration(applicationContext, "CacheOne");
94+
assertRegionExpirationConfiguration(applicationContext, "CacheTwo");
95+
}
96+
7897
@Test
7998
public void assertClientCacheRegionExpirationPoliciesAreCorrect() {
8099
assertRegionExpirationConfiguration(newApplicationContext(ClientCacheRegionExpirationConfiguration.class),
@@ -87,14 +106,42 @@ public void assertPeerCacheRegionExpirationPoliciesAreCorrect() {
87106
"People");
88107
}
89108

109+
@ClientCacheApplication(name = "EnableExpirationConfigurationIntegrationTests", logLevel = GEMFIRE_LOG_LEVEL)
110+
@EnableCachingDefinedRegions(clientRegionShortcut = ClientRegionShortcut.LOCAL)
111+
@EnableExpiration
112+
@EnableGemFireMockObjects
113+
static class ApplicationConfiguration {
114+
115+
@Bean
116+
ApplicationService applicationService() {
117+
return new ApplicationService();
118+
}
119+
}
120+
121+
@Service
122+
static class ApplicationService {
123+
124+
@Cacheable("CacheOne")
125+
public Object someMethod(Object key) {
126+
return null;
127+
}
128+
129+
@Cacheable("CacheTwo")
130+
public Object someOtherMethod(Object key) {
131+
return null;
132+
}
133+
}
134+
90135
@ClientCacheApplication(name = "EnableExpirationConfigurationIntegrationTests", logLevel = GEMFIRE_LOG_LEVEL)
91136
@EnableEntityDefinedRegions(basePackageClasses = Person.class, clientRegionShortcut = ClientRegionShortcut.LOCAL)
92137
@EnableExpiration
138+
@EnableGemFireMockObjects
93139
static class ClientCacheRegionExpirationConfiguration { }
94140

95141
@PeerCacheApplication(name = "EnableExpirationConfigurationIntegrationTests", logLevel = GEMFIRE_LOG_LEVEL)
96142
@EnableEntityDefinedRegions(basePackageClasses = Person.class, serverRegionShortcut = RegionShortcut.LOCAL)
97143
@EnableExpiration
144+
@EnableGemFireMockObjects
98145
static class PeerCacheRegionExpirationConfiguration { }
99146

100147
}

0 commit comments

Comments
 (0)