diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableExpiration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableExpiration.java
new file mode 100644
index 00000000..5dda1cfd
--- /dev/null
+++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableExpiration.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2016 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.EnableExpiration.ExpirationType.IDLE_TIMEOUT;
+
+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.Import;
+import org.springframework.data.gemfire.expiration.Expiration;
+import org.springframework.data.gemfire.expiration.ExpirationActionType;
+import org.springframework.data.gemfire.expiration.IdleTimeoutExpiration;
+import org.springframework.data.gemfire.expiration.TimeToLiveExpiration;
+
+/**
+ * The {@link EnableExpiration} annotation marks a Spring {@link org.springframework.context.annotation.Configuration @Configuration}
+ * annotated class to enable {@link Region} entry expiration for individual entries. Note, this annotation does not
+ * cover {@link Region} expiration; {@link Region} expiration must be configure on the {@link Region} definition itself.
+ *
+ * @author John Blum
+ * @see org.springframework.context.annotation.Import
+ * @see org.springframework.data.gemfire.config.annotation.ExpirationConfiguration
+ * @see org.springframework.data.gemfire.expiration.Expiration
+ * @see org.springframework.data.gemfire.expiration.IdleTimeoutExpiration
+ * @see org.springframework.data.gemfire.expiration.TimeToLiveExpiration
+ * @see org.apache.geode.cache.Region
+ * @see Annotation-based Data Expiration
+ * @see GemFire Expiration
+ * @see Geode Expiration
+ * @since 1.9.0
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+@Import(ExpirationConfiguration.class)
+@SuppressWarnings({ "unused" })
+public @interface EnableExpiration {
+
+ /**
+ * Defines individual {@link Region} Expiration policies or customizes the default Expiration policy
+ * for all {@link Region Regions}.
+ *
+ * Defaults to empty.
+ */
+ ExpirationPolicy[] policies() default {};
+
+ /**
+ * Definition for a specific Expiration policy that can be applied to 1 or more {@link Region Regions}.
+ *
+ * An Expiration policy defines the expiration timeout and expiration action to take when
+ * an {@link Region} entry times out.
+ *
+ * Additionally, the Expiration policy defines the algorithm to use (e.g. Idle Timeout (TTI) or Time-To-Live (TTL),
+ * or both) to determine if and when an {@link Region} entry has timed out.
+ */
+ @interface ExpirationPolicy {
+
+ /**
+ * Specifies the timeout used to determine when a {@link Region} entry expires.
+ *
+ * This value of this attribute determines the "default" timeout used if no specific timeout was specified.
+ * A specific timeout is determined by {@link Expiration#timeout()}, {@link IdleTimeoutExpiration#timeout()}
+ * or {@link TimeToLiveExpiration#timeout()} attribute on the application domain object.
+ *
+ * See the SDG Reference Guide for more details...
+ *
+ * @see Annotation-based Data Expiration
+ */
+ int timeout();
+
+ /**
+ * Specifies the action taken when a {@link Region} entry expires.
+ *
+ * This value of this attribute determines the "default" action taken if no specific action was specified.
+ * The specific action is determined by {@link Expiration#action()}, {@link IdleTimeoutExpiration#action()}
+ * or {@link TimeToLiveExpiration#action()} attribute on the application domain object.
+ *
+ * See the SDG Reference Guide for more details...
+ *
+ * @see Annotation-based Data Expiration
+ */
+ ExpirationActionType action();
+
+ /**
+ * Names of specific {@link Region Regions} on which this Expiration policy is applied.
+ *
+ * If no {@link Region} names are specified then this Expiration policy will apply to
+ * all {@link Region Regions} declared in the Spring context.
+ *
+ * Defaults to all {@link Region Regions}.
+ */
+ String[] regionNames() default {};
+
+ /**
+ * Types of Expiration algorithms (Idle Timeout (TTI) or Time to Live (TTL)) configured and used by
+ * {@link Region Region(s)} to expire entries.
+ *
+ * Defaults to both Idle Timeout (TTI).
+ *
+ * @see org.springframework.data.gemfire.config.annotation.EnableExpiration.ExpirationType
+ */
+ ExpirationType[] types() default { IDLE_TIMEOUT };
+
+ }
+
+ /**
+ * {@link ExpirationType} defines different types of GemFire/Geode Expiration policies such as
+ * (Entry) Idle Timeout (TTI) and (Entry) Time to Live (TTL).
+ *
+ * @see GemFire Expiration
+ * @see Geode Expiration
+ */
+ enum ExpirationType {
+ IDLE_TIMEOUT("TTI"),
+ TIME_TO_LIVE("TTL");
+
+ private final String abbreviation;
+
+ /**
+ * Factory method to lookup an appropriate {@link ExpirationType} based on an abbreviation.
+ *
+ * @param abbreviation abbreviation used to lookup the appropriate {@link ExpirationType}.
+ * @return an {@link ExpirationType} matching the abbreviation or {@literal null} if the abbreviation
+ * does not match an {@link ExpirationType}.
+ * @see org.springframework.data.gemfire.config.annotation.EnableExpiration.ExpirationType
+ * @see #values()
+ * @see #abbreviation()
+ */
+ static ExpirationType valueOfAbbreviation(String abbreviation) {
+ for (ExpirationType expirationType : values()) {
+ if (expirationType.abbreviation().equalsIgnoreCase(abbreviation)) {
+ return expirationType;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Constructs a new instance of {@link ExpirationType} initialized with the given abbreviation.
+ *
+ * @param abbreviation {@link String} indicating the {@link ExpirationType} abbreviation.
+ */
+ ExpirationType(String abbreviation) {
+ this.abbreviation = abbreviation;
+ }
+
+ /**
+ * Returns the abbreviation for this {@link ExpirationType}.
+ *
+ * @return a {@link String} with the {@link ExpirationType} abbreviation.
+ */
+ protected String abbreviation() {
+ return this.abbreviation;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public String toString() {
+ return String.format("%1$s (%2$s)", name(), abbreviation());
+ }
+ }
+}
diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/ExpirationConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/ExpirationConfiguration.java
new file mode 100644
index 00000000..243fbecd
--- /dev/null
+++ b/src/main/java/org/springframework/data/gemfire/config/annotation/ExpirationConfiguration.java
@@ -0,0 +1,529 @@
+/*
+ * Copyright 2016 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.EnableExpiration.ExpirationPolicy;
+import static org.springframework.data.gemfire.config.annotation.EnableExpiration.ExpirationType;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.geode.cache.AttributesMutator;
+import org.apache.geode.cache.ExpirationAction;
+import org.apache.geode.cache.ExpirationAttributes;
+import org.apache.geode.cache.Region;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+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.expiration.AnnotationBasedExpiration;
+import org.springframework.data.gemfire.expiration.ExpirationActionType;
+import org.springframework.data.gemfire.util.ArrayUtils;
+import org.springframework.data.gemfire.util.CollectionUtils;
+import org.springframework.data.gemfire.util.SpringUtils;
+import org.springframework.util.Assert;
+
+/**
+ * {@link ExpirationConfiguration} is a Spring {@link Configuration} class used to configure expiration policies
+ * for GemFire/Geode {@link Region Regions}.
+ *
+ * @author John Blum
+ * @see org.springframework.context.annotation.Configuration
+ * @see org.springframework.context.annotation.ImportAware
+ * @see org.springframework.data.gemfire.config.annotation.EnableExpiration
+ * @see org.apache.geode.cache.ExpirationAttributes
+ * @see org.apache.geode.cache.Region
+ * @since 1.9.0
+ */
+@Configuration
+public class ExpirationConfiguration implements ImportAware {
+
+ protected static final int DEFAULT_TIMEOUT = 0;
+
+ protected static final ExpirationActionType DEFAULT_ACTION = ExpirationActionType.DEFAULT;
+
+ protected static final ExpirationType[] DEFAULT_EXPIRATION_TYPES = { ExpirationType.IDLE_TIMEOUT };
+
+ private ExpirationPolicyConfigurer expirationPolicyConfigurer;
+
+ /**
+ * Returns the {@link Annotation} {@link Class type} that enables and configures Expiration.
+ *
+ * @return {@link Annotation} {@link Class type} that enables and configures Expiration.
+ * @see java.lang.annotation.Annotation
+ * @see java.lang.Class
+ */
+ protected Class extends Annotation> getAnnotationType() {
+ return EnableExpiration.class;
+ }
+
+ /**
+ * Returns the name of the {@link Annotation} type that enables and configures Expiration.
+ *
+ * @return the name of the {@link Annotation} type that enables and configures Expiration.
+ * @see java.lang.Class#getName()
+ * @see #getAnnotationType()
+ */
+ protected String getAnnotationTypeName() {
+ return getAnnotationType().getName();
+ }
+
+ /**
+ * Returns the simple name of the {@link Annotation} type that enables and configures Expiration.
+ *
+ * @return the simple name of the {@link Annotation} type that enables and configures Expiration.
+ * @see java.lang.Class#getSimpleName()
+ * @see #getAnnotationType()
+ */
+ @SuppressWarnings("unused")
+ protected String getAnnotationTypeSimpleName() {
+ return getAnnotationType().getSimpleName();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public void setImportMetadata(AnnotationMetadata importMetadata) {
+ if (importMetadata.hasAnnotation(getAnnotationTypeName())) {
+ Map enableExpirationAttributes =
+ importMetadata.getAnnotationAttributes(getAnnotationTypeName());
+
+ AnnotationAttributes[] policies =
+ (AnnotationAttributes[]) enableExpirationAttributes.get("policies");
+
+ for (AnnotationAttributes expirationPolicyAttributes :
+ ArrayUtils.nullSafeArray(policies, AnnotationAttributes.class)) {
+
+ this.expirationPolicyConfigurer = ComposableExpirationPolicyConfigurer.compose(
+ this.expirationPolicyConfigurer, ExpirationPolicyMetaData.from(expirationPolicyAttributes));
+ }
+
+ this.expirationPolicyConfigurer = (this.expirationPolicyConfigurer != null ? this.expirationPolicyConfigurer
+ : ExpirationPolicyMetaData.fromDefaults());
+ }
+ }
+
+ /**
+ * Determines whether the given bean is a {@link Region}.
+ *
+ * @param bean {@link Object} to evaluate.
+ * @return a boolean value indicating whether the given bean is a {@link Region}.
+ * @see org.apache.geode.cache.Region
+ */
+ protected boolean isRegion(Object bean) {
+ return (bean instanceof Region);
+ }
+
+ protected ExpirationPolicyConfigurer getExpirationPolicyConfigurer() {
+ Assert.state(this.expirationPolicyConfigurer != null,
+ "ExpirationPolicyConfigurer was not properly configured and initialized");
+
+ return expirationPolicyConfigurer;
+ }
+
+ @Bean
+ @SuppressWarnings("unused")
+ public BeanPostProcessor expirationBeanPostProcessor() {
+ return new BeanPostProcessor() {
+ @Override
+ public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
+ return bean;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+ return (isRegion(bean) ? getExpirationPolicyConfigurer().configure((Region) bean)
+ : bean);
+ }
+
+ };
+ }
+
+ /**
+ * Interface defining a contract for implementations that configure a {@link Region Region's} expiration policy.
+ */
+ protected interface ExpirationPolicyConfigurer {
+
+ /**
+ * Configures the expiration policy for the given {@link Region}.
+ *
+ * @param {@link Class type} of the {@link Region} keys.
+ * @param {@link Class type} of the {@link Region} values.
+ * @param region {@link Region} who's expiration policy will be configured.
+ * @return the given {@link Region}.
+ * @see org.apache.geode.cache.Region
+ */
+ Region configure(Region region);
+
+ }
+
+ /**
+ * {@link ComposableExpirationPolicyConfigurer} is a {@link ExpirationPolicyConfigurer} implementation
+ * that additionally implements the Composition Software Design Pattern to treat a collection of
+ * {@link ExpirationPolicyConfigurer} objects as a single instace of the {@link ExpirationPolicyConfigurer}.
+ *
+ * @see org.springframework.data.gemfire.config.annotation.ExpirationConfiguration.ExpirationPolicyConfigurer
+ * @see Composition Software Design Pattern
+ */
+ protected static class ComposableExpirationPolicyConfigurer implements ExpirationPolicyConfigurer {
+
+ private final ExpirationPolicyConfigurer one;
+ private final ExpirationPolicyConfigurer two;
+
+ /**
+ * Factory method to compose an array of {@link ExpirationPolicyConfigurer} objects.
+ *
+ * @param array array of {@link ComposableExpirationPolicyConfigurer} objects to compose.
+ * @return a composition containing all the {@link ExpirationPolicyConfigurer} objects in the array.
+ * @see org.springframework.data.gemfire.config.annotation.ExpirationConfiguration.ExpirationPolicyConfigurer
+ * @see #compose(Iterable)
+ */
+ protected static ExpirationPolicyConfigurer compose(ExpirationPolicyConfigurer[] array) {
+ return compose(Arrays.asList(ArrayUtils.nullSafeArray(array, ExpirationPolicyConfigurer.class)));
+ }
+
+ /**
+ * Factory method to compose an {@link Iterable} of {@link ExpirationPolicyConfigurer} objects.
+ *
+ * @param iterable {@link Iterable} of {@link ComposableExpirationPolicyConfigurer} objects to compose.
+ * @return a composition containing all the {@link ExpirationPolicyConfigurer} objects in the {@link Iterable}.
+ * @see org.springframework.data.gemfire.config.annotation.ExpirationConfiguration.ExpirationPolicyConfigurer
+ * @see #compose(ExpirationPolicyConfigurer, ExpirationPolicyConfigurer)
+ */
+ protected static ExpirationPolicyConfigurer compose(Iterable iterable) {
+ ExpirationPolicyConfigurer current = null;
+
+ for (ExpirationPolicyConfigurer configurer : CollectionUtils.nullSafeIterable(iterable)) {
+ current = compose(current, configurer);
+ }
+
+ return current;
+ }
+
+ /**
+ * Factory method to compose 2 {@link ExpirationPolicyConfigurer} objects.
+ *
+ * @param one first {@link ComposableExpirationPolicyConfigurer} to compose.
+ * @param two second {@link ComposableExpirationPolicyConfigurer} to compose.
+ * @return a composition of the 2 {@link ExpirationPolicyConfigurer} objects.
+ * Returns {@code one} if {@code two} is {@literal null} or {@code two} if {@code one} is {@literal null}.
+ * @see org.springframework.data.gemfire.config.annotation.ExpirationConfiguration.ExpirationPolicyConfigurer
+ */
+ protected static ExpirationPolicyConfigurer compose(ExpirationPolicyConfigurer one,
+ ExpirationPolicyConfigurer two) {
+
+ return (one == null ? two : (two == null ? one : new ComposableExpirationPolicyConfigurer(one, two)));
+ }
+
+ /**
+ * Constructs an instance of the {@link ComposableExpirationPolicyConfigurer} initialized with
+ * 2 {@link ExpirationPolicyConfigurer} objects.
+ *
+ * @param one first {@link ComposableExpirationPolicyConfigurer} to compose.
+ * @param two second {@link ComposableExpirationPolicyConfigurer} to compose.
+ * @see org.springframework.data.gemfire.config.annotation.ExpirationConfiguration.ExpirationPolicyConfigurer
+ */
+ private ComposableExpirationPolicyConfigurer(ExpirationPolicyConfigurer one, ExpirationPolicyConfigurer two) {
+ this.one = one;
+ this.two = two;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public Region configure(Region region) {
+ return this.two.configure(this.one.configure(region));
+ }
+ }
+
+ /**
+ * {@link ExpirationPolicyMetaData} is a {@link ExpirationPolicyConfigurer} implementation that encapsulates
+ * the expiration configuration meta-data (e.g. expiration timeout and action) necessary to configure
+ * a {@link Region Regions's} expiration policy and behavior.
+ *
+ * This class is meant to capture the expiration configuration meta-data specified in the {@link ExpirationPolicy}
+ * nested annotation in the application-level {@link EnableExpiration} annotation.
+
+ * @see org.springframework.data.gemfire.config.annotation.ExpirationConfiguration.ExpirationPolicyConfigurer
+ */
+ protected static class ExpirationPolicyMetaData implements ExpirationPolicyConfigurer {
+
+ protected static final String[] ALL_REGIONS = new String[0];
+
+ private final ExpirationAttributes defaultExpirationAttributes;
+
+ private final Set regionNames = new HashSet();
+
+ private final Set types = new HashSet();
+
+ /**
+ * Factory method to construct an instance of {@link ExpirationPolicyMetaData} initialized with
+ * the given {@link AnnotationAttributes} from the nested {@link ExpirationPolicy} annotation
+ * specified in an application-level {@link EnableExpiration} annotation.
+ *
+ * @param expirationPolicyAttributes {@link AnnotationAttributes} from a {@link ExpirationPolicy} annotation.
+ * @return an instance of the {@link ExpirationPolicyMetaData} initialized from
+ * {@link ExpirationPolicy} {@link AnnotationAttributes}.
+ * @throws IllegalArgumentException if {@link AnnotationAttributes#annotationType()} is not assignable to
+ * {@link ExpirationPolicy}.
+ * @see #newExpirationPolicyMetaData(int, ExpirationActionType, String[], ExpirationType[])
+ * @see org.springframework.core.annotation.AnnotationAttributes
+ */
+ protected static ExpirationPolicyMetaData from(AnnotationAttributes expirationPolicyAttributes) {
+ Assert.isAssignable(ExpirationPolicy.class, expirationPolicyAttributes.annotationType());
+
+ return newExpirationPolicyMetaData((Integer) expirationPolicyAttributes.get("timeout"),
+ expirationPolicyAttributes.getEnum("action"),
+ expirationPolicyAttributes.getStringArray("regionNames"),
+ (ExpirationType[]) expirationPolicyAttributes.get("types"));
+ }
+
+ /**
+ * Factory method to construct an instance of {@link ExpirationPolicyMetaData} initialized with
+ * the given attribute values from the nested {@link ExpirationPolicy} annotation specified in
+ * an application-level {@link EnableExpiration} annotation.
+ *
+ * @param expirationPolicy {@link ExpirationPolicy} annotation containing the attribute values
+ * used to initialize the {@link ExpirationPolicyMetaData} instance.
+ * @return an instance of the {@link ExpirationPolicyMetaData} initialized from
+ * {@link ExpirationPolicy} attributes values.
+ * @see org.springframework.data.gemfire.config.annotation.EnableExpiration.ExpirationPolicy
+ * @see #newExpirationPolicyMetaData(int, ExpirationActionType, String[], ExpirationType[])
+ */
+ protected static ExpirationPolicyMetaData from(ExpirationPolicy expirationPolicy) {
+ return newExpirationPolicyMetaData(expirationPolicy.timeout(), expirationPolicy.action(),
+ expirationPolicy.regionNames(), expirationPolicy.types());
+ }
+
+ /**
+ * Factory method to construct an instance of {@link ExpirationPolicyMetaData} using default expiration policy
+ * settings.
+ *
+ * @see #newExpirationPolicyMetaData(int, ExpirationActionType, String[], ExpirationType[])
+ */
+ protected static ExpirationPolicyMetaData fromDefaults() {
+ return newExpirationPolicyMetaData(DEFAULT_TIMEOUT, DEFAULT_ACTION, ALL_REGIONS, DEFAULT_EXPIRATION_TYPES);
+ }
+
+ /**
+ * Factory method used to construct a new instance of the {@link ExpirationAttributes} initialized with
+ * the given expiration timeout and action that is taken when an {@link Region} entry times out.
+ *
+ * @param timeout int value indicating the expiration timeout in seconds.
+ * @param action expiration action to take when the {@link Region} entry times out.
+ * @return a new instance of {@link ExpirationAttributes} initialized with the given expiration timeout
+ * and action.
+ * @see org.apache.geode.cache.ExpirationAttributes
+ * @see #newExpirationAttributes(int, ExpirationAction)
+ */
+ protected static ExpirationAttributes newExpirationAttributes(int timeout, ExpirationActionType action) {
+ return newExpirationAttributes(timeout, action.getExpirationAction());
+ }
+
+ /**
+ * Factory method used to construct a new instance of the {@link ExpirationAttributes} initialized with
+ * the given expiration timeout and action that is taken when an {@link Region} entry times out.
+ *
+ * @param timeout int value indicating the expiration timeout in seconds.
+ * @param action expiration action to take when the {@link Region} entry times out.
+ * @return a new instance of {@link ExpirationAttributes} initialized with the given expiration timeout
+ * and action.
+ * @see org.apache.geode.cache.ExpirationAttributes
+ */
+ protected static ExpirationAttributes newExpirationAttributes(int timeout, ExpirationAction action) {
+ return new ExpirationAttributes(timeout, action);
+ }
+
+ /**
+ * Factory method used to construct an instance of {@link ExpirationPolicyMetaData} initialized with
+ * the given expiration policy meta-data.
+ *
+ * @param timeout int value indicating the expiration timeout in seconds.
+ * @param action expiration action taken when the {@link Region} entry expires.
+ * @param regionNames names of {@link Region Regions} configured with the expiration policy meta-data.
+ * @param types type of expiration algorithm/behavior (TTI/TTL) configured for the {@link Region}.
+ * @return an instance of {@link ExpirationPolicyMetaData} initialized with the given expiration policy
+ * meta-data.
+ * @throws IllegalArgumentException if the {@link ExpirationType} array is empty.
+ * @see org.springframework.data.gemfire.config.annotation.EnableExpiration.ExpirationType
+ * @see org.springframework.data.gemfire.ExpirationActionType
+ * @see #ExpirationPolicyMetaData(ExpirationAttributes, Set, Set)
+ * @see #newExpirationAttributes(int, ExpirationActionType)
+ */
+ protected static ExpirationPolicyMetaData newExpirationPolicyMetaData(int timeout, ExpirationActionType action,
+ String[] regionNames, ExpirationType[] types) {
+
+ return new ExpirationPolicyMetaData(newExpirationAttributes(timeout, action),
+ CollectionUtils.asSet(ArrayUtils.nullSafeArray(regionNames, String.class)),
+ CollectionUtils.asSet(ArrayUtils.nullSafeArray(types, ExpirationType.class)));
+ }
+
+ /**
+ * Resolves the {@link ExpirationAction} used in the expiration policy. Defaults to
+ * {@link ExpirationActionType#INVALIDATE} if {@code action} is {@literal null}.
+ *
+ * @param action given {@link ExpirationActionType} to evaluate.
+ * @return the resolved {@link ExpirationActionType} or the default if {@code action} is {@literal null}.
+ * @see org.springframework.data.gemfire.ExpirationActionType
+ */
+ protected static ExpirationActionType resolveAction(ExpirationActionType action) {
+ return SpringUtils.defaultIfNull(action, DEFAULT_ACTION);
+ }
+
+ /**
+ * Resolves the expiration timeout used in the expiration policy. Defaults to {@literal 0} if {@code timeout}
+ * is less than {@literal 0}.
+ *
+ * @param timeout int value expressing the expiration timeout in seconds.
+ * @return the resolved expiration policy timeout.
+ */
+ protected static int resolveTimeout(int timeout) {
+ return Math.max(timeout, DEFAULT_TIMEOUT);
+ }
+
+ /**
+ * Constructs an instance of {@link ExpirationPolicyMetaData} initialized with the given expiration policy
+ * configuraiton meta-data and {@link Region} expiration settings.
+ *
+ * @param timeout int value indicating the expiration timeout in seconds.
+ * @param action expiration action taken when the {@link Region} entry expires.
+ * @param regionNames names of {@link Region Regions} configured with the expiration policy meta-data.
+ * @param types type of expiration algorithm/behavior (TTI/TTL) configured for the {@link Region}.
+ * @throws IllegalArgumentException if the {@link ExpirationType} {@link Set} is empty.
+ * @see org.springframework.data.gemfire.config.annotation.EnableExpiration.ExpirationType
+ * @see org.springframework.data.gemfire.ExpirationActionType
+ * @see #ExpirationPolicyMetaData(ExpirationAttributes, Set, Set)
+ * @see #newExpirationAttributes(int, ExpirationActionType)
+ * @see #resolveAction(ExpirationActionType)
+ * @see #resolveTimeout(int)
+ */
+ protected ExpirationPolicyMetaData(int timeout, ExpirationActionType action, Set regionNames,
+ Set types) {
+
+ this(newExpirationAttributes(resolveTimeout(timeout), resolveAction(action)), regionNames, types);
+ }
+
+ /**
+ * Constructs an instance of {@link ExpirationPolicyMetaData} initialized with the given expiration policy
+ * configuraiton meta-data and {@link Region} expiration settings.
+ *
+ * @param expirationAttributes {@link ExpirationAttributes} specifying the expiration timeout in seconds
+ * and expiration action taken when the {@link Region} entry expires.
+ * @param regionNames names of {@link Region Regions} configured with the expiration policy meta-data.
+ * @param types type of expiration algorithm/behaviors (TTI/TTL) configured for the {@link Region}.
+ * @throws IllegalArgumentException if the {@link ExpirationType} {@link Set} is empty.
+ * @see org.springframework.data.gemfire.config.annotation.EnableExpiration.ExpirationType
+ * @see org.apache.geode.cache.ExpirationAttributes
+ */
+ protected ExpirationPolicyMetaData(ExpirationAttributes expirationAttributes, Set regionNames,
+ Set types) {
+
+ Assert.notEmpty(types, "At least one ExpirationPolicy type [TTI, TTL] must be specified");
+
+ this.defaultExpirationAttributes = expirationAttributes;
+ this.regionNames.addAll(CollectionUtils.nullSafeSet(regionNames));
+ this.types.addAll(CollectionUtils.nullSafeSet(types));
+ }
+
+ /**
+ * Determines whether to apply this expiration policy to the given {@link Region}.
+ *
+ * @param region {@link Region} to evaluate.
+ * @return a boolean value indicating whether the expiration policy applies to the given {@link Region}.
+ * @see org.apache.geode.cache.Region
+ * @see #accepts(String)
+ */
+ protected boolean accepts(Region region) {
+ return (region != null && accepts(region.getName()));
+ }
+
+ /**
+ * Determines whether to apply this expiration policy to the given {@link Region} identified by name.
+ *
+ * @param regionName name of the {@link Region} to evaluate.
+ * @return a boolean value indicating whether the expiration policy applies to the given {@link Region}
+ * identified by name.
+ */
+ protected boolean accepts(String regionName) {
+ return (this.regionNames.isEmpty() || this.regionNames.contains(regionName));
+ }
+
+ /**
+ * Determines whether Idle Timeout Expiration (TTI) was configured for this expiration policy.
+ *
+ * @return a boolean value indicating whether Idle Timeout Expiration (TTI) was configuration for
+ * this expiration policy.
+ */
+ protected boolean isIdleTimeout() {
+ return this.types.contains(ExpirationType.IDLE_TIMEOUT);
+ }
+
+ /**
+ * Determines whether Time-To-Live Expiration (TTL) was configured for this expiration policy.
+ *
+ * @return a boolean value indicating whether Time-To-Live Expiration (TTL) was configuration for
+ * this expiration policy.
+ */
+ protected boolean isTimeToLive() {
+ return this.types.contains(ExpirationType.TIME_TO_LIVE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public Region configure(Region region) {
+ if (accepts(region)) {
+ AttributesMutator regionAttributesMutator = region.getAttributesMutator();
+
+ ExpirationAttributes defaultExpirationAttributes = defaultExpirationAttributes();
+
+ if (isIdleTimeout()) {
+ regionAttributesMutator.setCustomEntryIdleTimeout(
+ AnnotationBasedExpiration.forIdleTimeout(defaultExpirationAttributes));
+ }
+
+ if (isTimeToLive()) {
+ regionAttributesMutator.setCustomEntryTimeToLive(
+ AnnotationBasedExpiration.forTimeToLive(defaultExpirationAttributes));
+ }
+ }
+
+ return region;
+ }
+
+ /**
+ * Returns the default, fallback {@link ExpirationAttributes}.
+ *
+ * @return an {@link ExpirationAttributes} containing the defaults.
+ * @see org.apache.geode.cache.ExpirationAttributes
+ */
+ protected ExpirationAttributes defaultExpirationAttributes() {
+ return this.defaultExpirationAttributes;
+ }
+ }
+}
diff --git a/src/main/java/org/springframework/data/gemfire/expiration/AnnotationBasedExpiration.java b/src/main/java/org/springframework/data/gemfire/expiration/AnnotationBasedExpiration.java
index f0dba1c4..69f0f3aa 100644
--- a/src/main/java/org/springframework/data/gemfire/expiration/AnnotationBasedExpiration.java
+++ b/src/main/java/org/springframework/data/gemfire/expiration/AnnotationBasedExpiration.java
@@ -45,29 +45,31 @@ import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
- * The AnnotationBasedExpiration class is an implementation of GemFire's CustomExpiry interface that determines
- * the Time-To-Live (TTL) or Idle-Timeout (TTI) expiration policy of a Region entry by introspecting the Region
- * entry's class type and reflecting on any Region entries annotated with Spring Data GemFire's Expiration-based
- * Annotations.
+ * The {@link AnnotationBasedExpiration} class is an implementation of the {@link CustomExpiry} interface
+ * that determines the Time-To-Live (TTL) or Idle-Timeout (TTI) expiration policy of a {@link Region} entry
+ * by introspecting the {@link Region} entry's class type and reflecting on any {@link Region} entries annotated
+ * with SDG's Expiration-based Annotations.
*
* @author John Blum
* @see java.lang.annotation.Annotation
* @see org.springframework.beans.factory.BeanFactory
* @see org.springframework.beans.factory.BeanFactoryAware
- * @see ExpirationActionType
- * @see Expiration
- * @see IdleTimeoutExpiration
- * @see TimeToLiveExpiration
+ * @see org.springframework.data.gemfire.expiration.Expiration
+ * @see org.springframework.data.gemfire.expiration.ExpirationActionType
+ * @see org.springframework.data.gemfire.expiration.IdleTimeoutExpiration
+ * @see org.springframework.data.gemfire.expiration.TimeToLiveExpiration
* @see org.apache.geode.cache.CustomExpiry
* @see org.apache.geode.cache.ExpirationAction
* @see org.apache.geode.cache.ExpirationAttributes
* @see org.apache.geode.cache.Region
+ * @see Annotation-based Data Expiration
* @since 1.7.0
*/
@SuppressWarnings("unused")
public class AnnotationBasedExpiration implements BeanFactoryAware, CustomExpiry {
- protected static final AtomicReference BEAN_FACTORY_REFERENCE = new AtomicReference(null);
+ protected static final AtomicReference BEAN_FACTORY_REFERENCE =
+ new AtomicReference(null);
protected static final AtomicReference EVALUATION_CONTEXT_REFERENCE
= new AtomicReference(null);
@@ -83,7 +85,7 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
}
/**
- * Constructs a new instance of the AnnotationBasedExpiration class initialized with a specific, default
+ * Constructs a new instance of {@link AnnotationBasedExpiration} initialized with a specific, default
* expiration policy.
*
* @param defaultExpirationAttributes expiration settings used as the default expiration policy.
@@ -94,14 +96,16 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
}
/**
- * Constructs an AnnotationBasedExpiration instance with no default ExpirationAttributes to process
- * Idle Timeout (TTI) Expiration annotated Region Entries.
+ * Factory method used to construct an instance of {@link AnnotationBasedExpiration} having no default
+ * {@link ExpirationAttributes} to process expired annotated {@link Region} entries
+ * using Idle Timeout (TTI) Expiration.
*
- * @param the class type of the Region Entry Key.
- * @param the class type of the Region Entry Value.
- * @return an AnnotationBasedExpiration instance to process Idle Timeout Expiration annotated Region Entries.
- * @see AnnotationBasedExpiration
- * @see IdleTimeoutExpiration
+ * @param {@link Class} type of the {@link Region} entry key.
+ * @param {@link Class} type of the {@link Region} entry value.
+ * @return an {@link AnnotationBasedExpiration} instance to process expired annotated {@link Region} entries
+ * using Idle Timeout expiration.
+ * @see org.springframework.data.gemfire.expiration.AnnotationBasedExpiration
+ * @see org.springframework.data.gemfire.expiration.IdleTimeoutExpiration
* @see #forIdleTimeout(org.apache.geode.cache.ExpirationAttributes)
*/
public static AnnotationBasedExpiration forIdleTimeout() {
@@ -109,20 +113,23 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
}
/**
- * Constructs an AnnotationBasedExpiration instance with default ExpirationAttributes to process
- * Idle Timeout (TTI) Expiration annotated Region Entries.
+ * Factory method used to construct an instance of {@link AnnotationBasedExpiration} initialized with
+ * default {@link ExpirationAttributes} to process expired annotated {@link Region} entries
+ * using Idle Timeout (TTI) expiration.
*
- * @param the class type of the Region Entry Key.
- * @param the class type of the Region Entry Value.
- * @param defaultExpirationAttributes ExpirationAttributes used by default if no Expiration policy was specified
- * on the Region Entry.
- * @return an AnnotationBasedExpiration instance to process Idle Timeout Expiration annotated Region Entries.
- * @see AnnotationBasedExpiration
- * @see IdleTimeoutExpiration
+ * @param {@link Class} type of the {@link Region} entry key.
+ * @param {@link Class} type of the {@link Region} entry value.
+ * @param defaultExpirationAttributes {@link ExpirationAttributes} used by default if no expiration policy
+ * was specified on the {@link Region}.
+ * @return an {@link AnnotationBasedExpiration} instance to process expired annotated {@link Region} entries
+ * using Idle Timeout expiration.
+ * @see org.springframework.data.gemfire.expiration.AnnotationBasedExpiration
+ * @see org.springframework.data.gemfire.expiration.IdleTimeoutExpiration
+ * @see #AnnotationBasedExpiration(ExpirationAttributes)
*/
public static AnnotationBasedExpiration forIdleTimeout(ExpirationAttributes defaultExpirationAttributes) {
return new AnnotationBasedExpiration(defaultExpirationAttributes) {
- @Override protected ExpirationMetaData getExpirationMetaData(final Region.Entry entry) {
+ @Override protected ExpirationMetaData getExpirationMetaData(Region.Entry entry) {
return (isIdleTimeoutConfigured(entry) ? ExpirationMetaData.from(getIdleTimeout(entry))
: super.getExpirationMetaData(entry));
}
@@ -130,42 +137,50 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
}
/**
- * Constructs an AnnotationBasedExpiration instance with no default ExpirationAttributes to process
- * Time-To-Live (TTL) Expiration annotated Region Entries.
+ * Factory method used to construct an instance of {@link AnnotationBasedExpiration} having no default
+ * {@link ExpirationAttributes} to process expired annotated {@link Region} entries
+ * using Time-To-Live (TTL) Expiration.
*
- * @param the class type of the Region Entry Key.
- * @param the class type of the Region Entry Value.
- * @return an AnnotationBasedExpiration instance to process Time-To-Live Expiration annotated Region Entries.
- * @see AnnotationBasedExpiration
- * @see TimeToLiveExpiration
- * @see #forTimeToLive(org.apache.geode.cache.ExpirationAttributes)
+ * @param {@link Class} type of the {@link Region} entry key.
+ * @param {@link Class} type of the {@link Region} entry value.
+ * @return an {@link AnnotationBasedExpiration} instance to process expired annotated {@link Region} entries
+ * using Time-To-Live expiration.
+ * @see org.springframework.data.gemfire.expiration.AnnotationBasedExpiration
+ * @see org.springframework.data.gemfire.expiration.TimeToLiveExpiration
+ * @see #forTimeToLive(ExpirationAttributes)
*/
public static AnnotationBasedExpiration forTimeToLive() {
return forTimeToLive(null);
}
/**
- * Constructs an AnnotationBasedExpiration instance with default ExpirationAttributes to process
- * Time-To-Live (TTL) Expiration annotated Region Entries.
+ * Factory method used to construct an instance of {@link AnnotationBasedExpiration} initialized with
+ * default {@link ExpirationAttributes} to process expired annotated {@link Region} entries
+ * using Time-To-Live (TTL) expiration.
*
- * @param the class type of the Region Entry Key.
- * @param the class type of the Region Entry Value.
- * @param defaultExpirationAttributes ExpirationAttributes used by default if no Expiration policy was specified
- * on the Region Entry.
- * @return an AnnotationBasedExpiration instance to process Time-To-Live Expiration annotated Region Entries.
- * @see AnnotationBasedExpiration
- * @see TimeToLiveExpiration
+ * @param {@link Class} type of the {@link Region} entry key.
+ * @param {@link Class} type of the {@link Region} entry value.
+ * @param defaultExpirationAttributes {@link ExpirationAttributes} used by default if no expiration policy
+ * was specified on the {@link Region}.
+ * @return an {@link AnnotationBasedExpiration} instance to process expired annotated {@link Region} entries
+ * using Time-To-Live expiration.
+ * @see org.springframework.data.gemfire.expiration.AnnotationBasedExpiration
+ * @see org.springframework.data.gemfire.expiration.TimeToLiveExpiration
+ * @see #AnnotationBasedExpiration(ExpirationAttributes)
*/
public static AnnotationBasedExpiration forTimeToLive(ExpirationAttributes defaultExpirationAttributes) {
return new AnnotationBasedExpiration(defaultExpirationAttributes) {
- @Override protected ExpirationMetaData getExpirationMetaData(final Region.Entry entry) {
+ @Override protected ExpirationMetaData getExpirationMetaData(Region.Entry entry) {
return (isTimeToLiveConfigured(entry) ? ExpirationMetaData.from(getTimeToLive(entry))
: super.getExpirationMetaData(entry));
}
};
}
- /* (non-Javadoc) */
+ /**
+ * Initializes the Spring Expression Language (SpEL) {@link EvaluationContext} used to parse property placeholder
+ * and SpEL expressions in the Expiration annotation attribute values.
+ */
protected void initEvaluationContext() {
BeanFactory beanFactory = getBeanFactory();
@@ -179,9 +194,11 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
if (beanFactory instanceof ConfigurableBeanFactory) {
ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;
ConversionService conversionService = configurableBeanFactory.getConversionService();
+
if (conversionService != null) {
evaluationContext.setTypeConverter(new StandardTypeConverter(conversionService));
}
+
evaluationContext.setTypeLocator(new StandardTypeLocator(configurableBeanFactory.getBeanClassLoader()));
}
}
@@ -195,10 +212,10 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
}
/**
- * Sets the BeanFactory managing this AnnotationBasedExpiration bean in the Spring context.
+ * Sets the {@link BeanFactory} managing this {@link AnnotationBasedExpiration} bean in the Spring context.
*
- * @param beanFactory the Spring BeanFactory to which this bean belongs.
- * @throws BeansException if the BeanFactory reference cannot be initialized.
+ * @param beanFactory the Spring {@link BeanFactory} to which this bean belongs.
+ * @throws BeansException if the {@link BeanFactory} reference cannot be initialized.
* @see org.springframework.beans.factory.BeanFactory
* @see #initEvaluationContext()
*/
@@ -209,10 +226,11 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
}
/**
- * Gets a reference to the BeanFactory in which this AnnotationBasedExpiration Bean belongs.
+ * Gets a reference to the Spring {@link BeanFactory} in which this {@link AnnotationBasedExpiration} bean
+ * is managed.
*
- * @return a reference to the Spring BeanFactory.
- * @throws java.lang.IllegalStateException if the BeanFactory reference was not properly initialized.
+ * @return a reference to the Spring {@link BeanFactory}.
+ * @throws java.lang.IllegalStateException if the {@link BeanFactory} reference was not properly initialized.
* @see org.springframework.beans.factory.BeanFactory
*/
protected BeanFactory getBeanFactory() {
@@ -247,12 +265,16 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
}
/**
- * Calculate the expiration for a given entry. Returning null indicates that the default for the Region
- * should be used. The entry parameter should not be used after this method invocation completes.
+ * Calculate the expiration for a given entry. Returning {@literal null} indicates that the default
+ * for the {@link Region} should be used. The entry parameter should not be used after this method
+ * invocation completes.
*
- * @param entry the entry to calculate the expiration for.
- * @return the expiration to be used, null if the Region's defaults should be used.
+ * @param entry the entry used to determine the appropriate expiration policy.
+ * @return the expiration configuration to be used or {@literal null} if the Region's defaults should be used.
* @see org.apache.geode.cache.ExpirationAttributes
+ * @see org.apache.geode.cache.Region
+ * @see #getExpirationMetaData(Region.Entry)
+ * @see #newExpirationAttributes(ExpirationMetaData)
*/
@Override
public ExpirationAttributes getExpiry(Region.Entry entry) {
@@ -260,28 +282,28 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
}
/**
- * Gets custom expiration (Annotation-based) meta-data for the given Region entry.
+ * Gets custom expiration (Annotation-based) policy meta-data for the given {@link Region} entry.
*
- * @param entry the Region entry used as the source of the expiration meta-data.
- * @return ExpirationMetaData extracted from the Region entry.
- * @throws NullPointerException if the Region.Entry, Region or the Region's attributes are null.
- * @see AnnotationBasedExpiration.ExpirationMetaData
+ * @param entry {@link Region} entry used as the source of the expiration policy meta-data.
+ * @return {@link ExpirationMetaData} extracted from the {@link Region} entry or {@literal null}
+ * if the expiration policy meta-data could not be determined from the {@link Region} entry.
+ * @see org.springframework.data.gemfire.expiration.AnnotationBasedExpiration.ExpirationMetaData
*/
protected ExpirationMetaData getExpirationMetaData(Region.Entry entry) {
return (isExpirationConfigured(entry) ? ExpirationMetaData.from(getExpiration(entry)) : null);
}
/**
- * Constructs a new instance of ExpirationAttributes configured with the application domain object specific
- * expiration policy. If the application domain object type has not been annotated with custom expiration
- * meta-data, then the default expiration settings are used.
+ * Constructs a new instance of {@link ExpirationAttributes} configured with the application domain object
+ * specific expiration policy. If the application domain object type has not been annotated with
+ * custom expiration meta-data, then the default expiration settings are used.
*
* @param expirationMetaData application domain object specific expiration policy meta-data used to construct
- * the ExpirationAttributes.
- * @return a custom ExpirationAttributes configured with the application domain object specific expiration policy
- * or the default expiration settings if the application domain object has not been annotated with custom
- * expiration meta-data.
- * @see AnnotationBasedExpiration.ExpirationMetaData
+ * the {@link ExpirationAttributes}.
+ * @return custom {@link ExpirationAttributes} configured from the application domain object specific
+ * expiration policy or the default expiration settings if the application domain object has not been
+ * annotated with custom expiration meta-data.
+ * @see org.springframework.data.gemfire.expiration.AnnotationBasedExpiration.ExpirationMetaData
* @see org.apache.geode.cache.ExpirationAttributes
* @see #getDefaultExpirationAttributes()
*/
@@ -299,7 +321,12 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
* @see #isAnnotationPresent(Object, Class)
*/
protected boolean isExpirationConfigured(Region.Entry entry) {
- return isAnnotationPresent(entry.getValue(), Expiration.class);
+ return (entry != null && isExpirationConfigured(entry.getValue()));
+ }
+
+ /* (non-Javadoc) */
+ private boolean isExpirationConfigured(Object obj) {
+ return isAnnotationPresent(obj, Expiration.class);
}
/**
@@ -312,7 +339,12 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
* @see #getAnnotation(Object, Class)
*/
protected Expiration getExpiration(Region.Entry entry) {
- return getAnnotation(entry.getValue(), Expiration.class);
+ return getExpiration(entry.getValue());
+ }
+
+ /* (non-Javadoc) */
+ private Expiration getExpiration(Object obj) {
+ return getAnnotation(obj, Expiration.class);
}
/**
@@ -324,7 +356,12 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
* @see #isAnnotationPresent(Object, Class)
*/
protected boolean isIdleTimeoutConfigured(Region.Entry entry) {
- return isAnnotationPresent(entry.getValue(), IdleTimeoutExpiration.class);
+ return (entry != null && isIdleTimeoutConfigured(entry.getValue()));
+ }
+
+ /* (non-Javadoc) */
+ private boolean isIdleTimeoutConfigured(Object obj) {
+ return isAnnotationPresent(obj, IdleTimeoutExpiration.class);
}
/**
@@ -337,7 +374,12 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
* @see #getAnnotation(Object, Class)
*/
protected IdleTimeoutExpiration getIdleTimeout(Region.Entry entry) {
- return getAnnotation(entry.getValue(), IdleTimeoutExpiration.class);
+ return getIdleTimeout(entry.getValue());
+ }
+
+ /* (non-Javadoc) */
+ private IdleTimeoutExpiration getIdleTimeout(Object obj) {
+ return getAnnotation(obj, IdleTimeoutExpiration.class);
}
/**
@@ -349,7 +391,12 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
* @see #isAnnotationPresent(Object, Class)
*/
protected boolean isTimeToLiveConfigured(Region.Entry entry) {
- return isAnnotationPresent(entry.getValue(), TimeToLiveExpiration.class);
+ return (entry != null && isTimeToLiveConfigured(entry.getValue()));
+ }
+
+ /* (non-Javadoc) */
+ private boolean isTimeToLiveConfigured(Object value) {
+ return isAnnotationPresent(value, TimeToLiveExpiration.class);
}
/**
@@ -362,7 +409,12 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
* @see #getAnnotation(Object, Class)
*/
protected TimeToLiveExpiration getTimeToLive(Region.Entry entry) {
- return getAnnotation(entry.getValue(), TimeToLiveExpiration.class);
+ return getTimeToLive(entry.getValue());
+ }
+
+ /* (non-Javadoc) */
+ private TimeToLiveExpiration getTimeToLive(Object obj) {
+ return getAnnotation(obj, TimeToLiveExpiration.class);
}
/* (non-Javadoc) */
@@ -397,32 +449,39 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
private final ExpirationActionType action;
+ /* (non-Javadoc) */
protected ExpirationMetaData(int timeout, ExpirationActionType action) {
this.timeout = timeout;
this.action = action;
}
+ /* (non-Javadoc) */
protected static ExpirationMetaData from(ExpirationAttributes expirationAttributes) {
return new ExpirationMetaData(expirationAttributes.getTimeout(), ExpirationActionType.valueOf(
expirationAttributes.getAction()));
}
+ /* (non-Javadoc) */
protected static ExpirationMetaData from(Expiration expiration) {
return new ExpirationMetaData(parseTimeout(expiration.timeout()), parseAction(expiration.action()));
}
+ /* (non-Javadoc) */
protected static ExpirationMetaData from(IdleTimeoutExpiration expiration) {
return new ExpirationMetaData(parseTimeout(expiration.timeout()), parseAction(expiration.action()));
}
+ /* (non-Javadoc) */
protected static ExpirationMetaData from(TimeToLiveExpiration expiration) {
return new ExpirationMetaData(parseTimeout(expiration.timeout()), parseAction(expiration.action()));
}
+ /* (non-Javadoc) */
public ExpirationAttributes toExpirationAttributes() {
return new ExpirationAttributes(timeout(), expirationAction());
}
+ /* (non-Javadoc) */
protected static int parseTimeout(String timeout) {
try {
return Integer.parseInt(timeout);
@@ -445,6 +504,7 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
}
}
+ /* (non-Javadoc) */
protected static ExpirationActionType parseAction(String action) {
try {
return ExpirationActionType.valueOf(EXPIRATION_ACTION_CONVERTER.convert(action));
@@ -452,7 +512,7 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
catch (IllegalArgumentException cause) {
// Next, try to parse the 'action' as a Spring Expression using SpEL.
EvaluationException evaluationException = new EvaluationException(String.format(
- "'%1$s' is not resolvable as a valid ExpirationAction(Type)", action), cause);
+ "[%s] is not resolvable as an ExpirationAction(Type)", action), cause);
EvaluationContext evaluationContext = EVALUATION_CONTEXT_REFERENCE.get();
@@ -491,18 +551,24 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
}
}
+ /* (non-Javadoc) */
public ExpirationActionType action() {
return action;
}
+ /* (non-Javadoc) */
public ExpirationAction expirationAction() {
return action().getExpirationAction();
}
+ /* (non-Javadoc) */
public int timeout() {
return timeout;
}
+ /**
+ * @inheritDoc
+ */
@Override
public boolean equals(final Object obj) {
if (obj == this) {
@@ -519,6 +585,9 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
&& ObjectUtils.nullSafeEquals(this.action(), that.action()));
}
+ /**
+ * @inheritDoc
+ */
@Override
public int hashCode() {
int hashValue = 17;
@@ -527,11 +596,13 @@ public class AnnotationBasedExpiration implements BeanFactoryAware, Custom
return hashValue;
}
+ /**
+ * @inheritDoc
+ */
@Override
public String toString() {
return String.format("{ @type = %1$s, timeout = %2$d, action = %3$s }", getClass().getName(),
timeout(), action());
}
}
-
}
diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/EnableExpirationConfigurationUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableExpirationConfigurationUnitTests.java
new file mode 100644
index 00000000..e5be8413
--- /dev/null
+++ b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableExpirationConfigurationUnitTests.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2016 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.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.springframework.data.gemfire.config.annotation.EnableExpiration.ExpirationPolicy;
+import static org.springframework.data.gemfire.config.annotation.EnableExpiration.ExpirationType;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.geode.cache.AttributesMutator;
+import org.apache.geode.cache.CustomExpiry;
+import org.apache.geode.cache.ExpirationAction;
+import org.apache.geode.cache.ExpirationAttributes;
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.RegionAttributes;
+import org.junit.After;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.gemfire.expiration.ExpirationActionType;
+import org.springframework.data.gemfire.util.ArrayUtils;
+
+/**
+ * Unit tests for the {@link EnableExpiration} annotation and {@link ExpirationConfiguration} class.
+ *
+ * @author John Blum
+ * @see org.junit.Test
+ * @see org.mockito.Mockito
+ * @see org.springframework.context.ConfigurableApplicationContext
+ * @see org.springframework.data.gemfire.config.annotation.EnableExpiration
+ * @see org.springframework.data.gemfire.config.annotation.ExpirationConfiguration
+ * @see org.apache.geode.cache.CustomExpiry
+ * @see org.apache.geode.cache.ExpirationAttributes
+ * @see org.apache.geode.cache.Region
+ * @since 1.9.0
+ */
+public class EnableExpirationConfigurationUnitTests {
+
+ private ConfigurableApplicationContext applicationContext;
+
+ @After
+ public void tearDown() {
+ if (applicationContext != null) {
+ applicationContext.close();
+ }
+ }
+
+ protected void assertRegionExpiration(ExpirationAttributes expectedExpirationAttributes,
+ Region region, V... applicationDomainObjects) {
+
+ assertIdleTimeoutExpiration(expectedExpirationAttributes, region, applicationDomainObjects);
+ assertTimeToLiveExpiration(expectedExpirationAttributes, region, applicationDomainObjects);
+ }
+
+ protected void assertIdleTimeoutExpiration(ExpirationAttributes expectedExpirationAttributes,
+ Region region, V... applicationDomainObjects) {
+
+ assertExpiration(expectedExpirationAttributes, region.getAttributes().getCustomEntryIdleTimeout(),
+ applicationDomainObjects);
+ }
+
+ protected void assertNoIdleTimeoutExpiration(Region region) {
+ assertThat(region.getAttributes().getCustomEntryIdleTimeout()).isNull();
+ }
+
+ protected void assertTimeToLiveExpiration(ExpirationAttributes expectedExpirationAttributes,
+ Region region, V... applicationDomainObjects) {
+
+ assertExpiration(expectedExpirationAttributes, region.getAttributes().getCustomEntryTimeToLive(),
+ applicationDomainObjects);
+ }
+
+ protected void assertNoTimeToLiveExpiration(Region region) {
+ assertThat(region.getAttributes().getCustomEntryTimeToLive()).isNull();
+ }
+
+ @SuppressWarnings("unchecked")
+ protected void assertExpiration(ExpirationAttributes expectedExpirationAttributes,
+ CustomExpiry customExpiry, V... applicationDomainObjects) {
+
+ Region.Entry regionEntry = mockRegionEntry(ArrayUtils.getFirst(applicationDomainObjects));
+
+ assertExpiration(customExpiry.getExpiry(regionEntry), expectedExpirationAttributes);
+ }
+
+ @SuppressWarnings({ "unchecked", "unused" })
+ protected void assertExpiration(ExpirationAttributes actualExpirationAttributes,
+ ExpirationAttributes expectedExpirationAttributes) {
+
+ assertThat(actualExpirationAttributes).isEqualTo(expectedExpirationAttributes);
+ }
+
+ @SuppressWarnings("unchecked")
+ protected Region getRegion(String beanName) {
+ return applicationContext.getBean(beanName, Region.class);
+ }
+
+ protected AnnotationConfigApplicationContext newApplicationContext(Class>... annotatedClasses) {
+ return new AnnotationConfigApplicationContext(annotatedClasses);
+ }
+
+ protected ExpirationAttributes newExpirationAttributes(int timeout, ExpirationActionType action) {
+ return newExpirationAttributes(timeout, action.getExpirationAction());
+ }
+
+ protected ExpirationAttributes newExpirationAttributes(int timeout, ExpirationAction action) {
+ return new ExpirationAttributes(timeout, action);
+ }
+
+ @Test
+ public void usesDefaultExpirationPolicyConfiguration() {
+ applicationContext = newApplicationContext(DefaultExpirationPolicyConfiguration.class);
+
+ ExpirationAttributes expectedExpiration = newExpirationAttributes(0, ExpirationActionType.INVALIDATE);
+
+ Region one = getRegion("One");
+ Region two = getRegion("Two");
+
+ assertIdleTimeoutExpiration(expectedExpiration, one);
+ assertIdleTimeoutExpiration(expectedExpiration, two);
+
+ assertNoTimeToLiveExpiration(one);
+ assertNoTimeToLiveExpiration(two);
+ }
+
+ @Test
+ public void usesCustomExpirationPolicyConfiguration() {
+ applicationContext = newApplicationContext(CustomExpirationPolicyConfiguration.class);
+
+ ExpirationAttributes expectedExpiration = newExpirationAttributes(300, ExpirationActionType.LOCAL_DESTROY);
+
+ Region one = getRegion("One");
+ Region two = getRegion("Two");
+
+ assertIdleTimeoutExpiration(expectedExpiration, one);
+ assertIdleTimeoutExpiration(expectedExpiration, two);
+
+ assertNoTimeToLiveExpiration(one);
+ assertNoTimeToLiveExpiration(two);
+ }
+
+ @Test
+ public void usesRegionSpecificExpirationPolicyConfiguration() {
+ applicationContext = newApplicationContext(RegionSpecificExpirationPolicyConfiguration.class);
+
+ ExpirationAttributes expectedIdleTimeout = newExpirationAttributes(180, ExpirationActionType.INVALIDATE);
+ ExpirationAttributes expectedTimeToLive = newExpirationAttributes(360, ExpirationActionType.DESTROY);
+
+ Region one = getRegion("One");
+ Region two = getRegion("Two");
+
+ assertIdleTimeoutExpiration(expectedIdleTimeout, one);
+ assertNoIdleTimeoutExpiration(two);
+
+ assertNoTimeToLiveExpiration(one);
+ assertTimeToLiveExpiration(expectedTimeToLive, two);
+ }
+
+ @Test
+ public void usesMixedExpirationPolicyConfiguration() {
+ applicationContext = newApplicationContext(MixedExpirationPolicyConfiguration.class);
+
+ ExpirationAttributes expectedIdleTimeout = newExpirationAttributes(120, ExpirationActionType.LOCAL_INVALIDATE);
+ ExpirationAttributes expectedTimeToLive = newExpirationAttributes(600, ExpirationActionType.DESTROY);
+
+ Region one = getRegion("One");
+ Region two = getRegion("Two");
+
+ assertIdleTimeoutExpiration(expectedIdleTimeout, one);
+ assertNoIdleTimeoutExpiration(two);
+
+ assertTimeToLiveExpiration(expectedTimeToLive, one);
+ assertTimeToLiveExpiration(expectedTimeToLive, two);
+ }
+
+ @SuppressWarnings("unchecked")
+ static Region mockRegion(String name) {
+ Region mockRegion = mock(Region.class);
+
+ when(mockRegion.getName()).thenReturn(name);
+ when(mockRegion.getFullPath()).thenReturn(String.format("%1$s%2$s", Region.SEPARATOR, name));
+
+ final AtomicReference> entryIdleTimeout = new AtomicReference>(null);
+ final AtomicReference> entryTimeToLive = new AtomicReference>(null);
+
+ AttributesMutator mockAttributesMutator = mock(AttributesMutator.class);
+
+ when(mockRegion.getAttributesMutator()).thenReturn(mockAttributesMutator);
+
+ doAnswer(new Answer>() {
+ @Override
+ public CustomExpiry answer(InvocationOnMock invocation) throws Throwable {
+ CustomExpiry customExpiry = invocation.getArgumentAt(0, CustomExpiry.class);
+ entryIdleTimeout.set(customExpiry);
+ return customExpiry;
+ }
+ }).when(mockAttributesMutator).setCustomEntryIdleTimeout(any(CustomExpiry.class));
+
+ doAnswer(new Answer>() {
+ @Override
+ public CustomExpiry answer(InvocationOnMock invocation) throws Throwable {
+ CustomExpiry customExpiry = invocation.getArgumentAt(0, CustomExpiry.class);
+ entryTimeToLive.set(customExpiry);
+ return customExpiry;
+ }
+ }).when(mockAttributesMutator).setCustomEntryTimeToLive(any(CustomExpiry.class));
+
+ RegionAttributes mockRegionAttributes = mock(RegionAttributes.class);
+
+ when(mockRegion.getAttributes()).thenReturn(mockRegionAttributes);
+
+ when(mockRegionAttributes.getCustomEntryIdleTimeout()).thenAnswer(new Answer>() {
+ @Override public CustomExpiry answer(InvocationOnMock invocation) throws Throwable {
+ return entryIdleTimeout.get();
+ }
+ });
+
+ when(mockRegionAttributes.getCustomEntryTimeToLive()).thenAnswer(new Answer>() {
+ @Override public CustomExpiry answer(InvocationOnMock invocation) throws Throwable {
+ return entryTimeToLive.get();
+ }
+ });
+
+ return mockRegion;
+ }
+
+ @SuppressWarnings("unchecked")
+ static Region.Entry mockRegionEntry(V applicationDomainObject) {
+ Region.Entry mockRegionEntry = mock(Region.Entry.class);
+ when(mockRegionEntry.getValue()).thenReturn(applicationDomainObject);
+ return mockRegionEntry;
+ }
+
+ @Configuration
+ @SuppressWarnings("unused")
+ static class RegionConfiguration {
+
+ @Bean("One")
+ Region regionOne() {
+ return mockRegion("One");
+ }
+
+ @Bean("Two")
+ Region regionTwo() {
+ return mockRegion("Two");
+ }
+ }
+
+ @EnableExpiration
+ static class DefaultExpirationPolicyConfiguration extends RegionConfiguration {
+ }
+
+ @EnableExpiration(policies = { @ExpirationPolicy(timeout = 300, action = ExpirationActionType.LOCAL_DESTROY) })
+ static class CustomExpirationPolicyConfiguration extends RegionConfiguration {
+ }
+
+ @EnableExpiration(policies = {
+ @ExpirationPolicy(timeout = 180, action = ExpirationActionType.INVALIDATE, regionNames = "One"),
+ @ExpirationPolicy(timeout = 360, action = ExpirationActionType.DESTROY, regionNames = "Two", types = ExpirationType.TIME_TO_LIVE)
+ })
+ static class RegionSpecificExpirationPolicyConfiguration extends RegionConfiguration {
+ }
+
+ @EnableExpiration(policies = {
+ @ExpirationPolicy(timeout = 120, action = ExpirationActionType.LOCAL_INVALIDATE, regionNames = "One", types = ExpirationType.IDLE_TIMEOUT),
+ @ExpirationPolicy(timeout = 600, action = ExpirationActionType.DESTROY, types = ExpirationType.TIME_TO_LIVE)
+ })
+ static class MixedExpirationPolicyConfiguration extends RegionConfiguration {
+ }
+}
diff --git a/src/test/java/org/springframework/data/gemfire/expiration/AnnotationBasedExpirationConfigurationIntegrationTest.java b/src/test/java/org/springframework/data/gemfire/expiration/AnnotationBasedExpirationConfigurationIntegrationTest.java
index 0f3c27b2..56a9c97e 100644
--- a/src/test/java/org/springframework/data/gemfire/expiration/AnnotationBasedExpirationConfigurationIntegrationTest.java
+++ b/src/test/java/org/springframework/data/gemfire/expiration/AnnotationBasedExpirationConfigurationIntegrationTest.java
@@ -184,7 +184,7 @@ public class AnnotationBasedExpirationConfigurationIntegrationTest {
public void invalidExpirationAction() {
expectedException.expect(EvaluationException.class);
expectedException.expectCause(isA(IllegalArgumentException.class));
- expectedException.expectMessage(String.format("'%1$s' is not resolvable as a valid ExpirationAction(Type)",
+ expectedException.expectMessage(String.format("[%s] is not resolvable as an ExpirationAction(Type)",
"@expirationProperties['gemfire.region.entry.expiration.invalid.action.string']"));
genericExpiration.getExpiry(mockRegionEntry(new RegionEntryWithInvalidExpirationAction()));
}