diff --git a/src/main/java/org/springframework/data/gemfire/support/AnnotationBasedExpiration.java b/src/main/java/org/springframework/data/gemfire/support/AnnotationBasedExpiration.java new file mode 100644 index 00000000..31961596 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/support/AnnotationBasedExpiration.java @@ -0,0 +1,336 @@ +/* + * Copyright 2010-2013 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.support; + +import java.lang.annotation.Annotation; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.gemfire.ExpirationActionType; +import org.springframework.util.ObjectUtils; + +import com.gemstone.gemfire.cache.CustomExpiry; +import com.gemstone.gemfire.cache.ExpirationAction; +import com.gemstone.gemfire.cache.ExpirationAttributes; +import com.gemstone.gemfire.cache.Region; + +/** + * 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. + * + * @author John Blum + * @see org.springframework.data.gemfire.support.Expiration + * @see org.springframework.data.gemfire.support.IdleTimeoutExpiration + * @see org.springframework.data.gemfire.support.TimeToLiveExpiration + * @see com.gemstone.gemfire.cache.CustomExpiry + * @see com.gemstone.gemfire.cache.ExpirationAttributes + * @since 1.7.0 + */ +@SuppressWarnings("unused") +public class AnnotationBasedExpiration implements CustomExpiry { + + //private ExpirationAttributes defaultExpirationAttributes = ExpirationAttributes.DEFAULT; + private ExpirationAttributes defaultExpirationAttributes; + + /** + * Constructs a new instance of the AnnotationBasedExpiration class with no default expiration policy. + */ + public AnnotationBasedExpiration() { + } + + /** + * Constructs a new instance of the AnnotationBasedExpiration class initialized with a specific, default + * expiration policy. + * + * @param defaultExpirationAttributes expiration settings used as the default expiration policy. + * @see com.gemstone.gemfire.cache.ExpirationAttributes + */ + public AnnotationBasedExpiration(ExpirationAttributes defaultExpirationAttributes) { + this.defaultExpirationAttributes = defaultExpirationAttributes; + } + + public static AnnotationBasedExpiration forIdleTimeout() { + return forIdleTimeout(null); + } + + public static AnnotationBasedExpiration forIdleTimeout(ExpirationAttributes defaultExpirationAttributes) { + return new AnnotationBasedExpiration(defaultExpirationAttributes) { + @Override protected ExpirationMetaData getExpirationMetaData(final Region.Entry entry) { + return (isIdleTimeoutConfigured(entry) ? ExpirationMetaData.from(getIdleTimeout(entry)) + : super.getExpirationMetaData(entry)); + } + }; + } + + public static AnnotationBasedExpiration forTimeToLive() { + return forTimeToLive(null); + } + + public static AnnotationBasedExpiration forTimeToLive(ExpirationAttributes defaultExpirationAttributes) { + return new AnnotationBasedExpiration(defaultExpirationAttributes) { + @Override protected ExpirationMetaData getExpirationMetaData(final Region.Entry entry) { + return (isTimeToLiveConfigured(entry) ? ExpirationMetaData.from(getTimeToLive(entry)) + : super.getExpirationMetaData(entry)); + } + }; + } + + /** + * Sets the expiration policy to use by default when no application domain object specific expiration meta-data + * has been specified. + * + * @param defaultExpirationAttributes expiration settings used as the default expiration policy. + * @see #getDefaultExpirationAttributes() + * @see com.gemstone.gemfire.cache.ExpirationAttributes + */ + public void setDefaultExpirationAttributes(ExpirationAttributes defaultExpirationAttributes) { + this.defaultExpirationAttributes = defaultExpirationAttributes; + } + + /** + * Gets the expiration policy used by default when no application domain object specific expiration meta-data + * has been specified. + * + * @return an instance of ExpirationAttributes with expiration settings defining the default expiration policy. + * @see #setDefaultExpirationAttributes(com.gemstone.gemfire.cache.ExpirationAttributes) + * @see com.gemstone.gemfire.cache.ExpirationAttributes + */ + protected ExpirationAttributes getDefaultExpirationAttributes() { + //return (defaultExpirationAttributes != null ? defaultExpirationAttributes : ExpirationAttributes.DEFAULT); + return defaultExpirationAttributes; + } + + /** + * 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. + * + * @param entry the entry to calculate the expiration for. + * @return the expiration to be used, null if the Region's defaults should be used. + * @see com.gemstone.gemfire.cache.ExpirationAttributes + */ + @Override + public ExpirationAttributes getExpiry(Region.Entry entry) { + return newExpirationAttributes(getExpirationMetaData(entry)); + } + + /** + * Gets custom expiration (Annotation-based) meta-data for the given 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 org.springframework.data.gemfire.support.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. + * + * @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 org.springframework.data.gemfire.support.AnnotationBasedExpiration.ExpirationMetaData + * @see com.gemstone.gemfire.cache.ExpirationAttributes + * @see #getDefaultExpirationAttributes() + */ + protected ExpirationAttributes newExpirationAttributes(ExpirationMetaData expirationMetaData) { + return (expirationMetaData != null ? expirationMetaData.toExpirationAttributes() + : getDefaultExpirationAttributes()); + } + + /** + * Determines whether the Region Entry has been annotated with the Expiration Annotation. + * + * @param entry the Region.Entry to evaluate for the presence of the Expiration Annotation. + * @return a boolean value indicating whether the Region Entry has been annotated with @Expiration. + * @see org.springframework.data.gemfire.support.Expiration + * @see #isAnnotationPresent(Object, Class) + */ + protected boolean isExpirationConfigured(Region.Entry entry) { + return isAnnotationPresent(entry.getValue(), Expiration.class); + } + + /** + * Gets the Expiration Annotation meta-data from the Region Entry. + * + * @param entry the Region.Entry from which to extract the Expiration Annotation meta-data. + * @return the Expiration Annotation meta-data for the given Region Entry or {@code null} + * if the Region Entry has not been annotated with @Expiration. + * @see org.springframework.data.gemfire.support.Expiration + * @see #getAnnotation(Object, Class) + */ + protected Expiration getExpiration(Region.Entry entry) { + return getAnnotation(entry.getValue(), Expiration.class); + } + + /** + * Determines whether the Region Entry has been annotated with the IdleTimeoutExpiration Annotation. + * + * @param entry the Region.Entry to evaluate for the presence of the IdleTimeoutExpiration Annotation. + * @return a boolean value indicating whether the Region Entry has been annotated with @IdleTimeoutExpiration. + * @see org.springframework.data.gemfire.support.IdleTimeoutExpiration + * @see #isAnnotationPresent(Object, Class) + */ + protected boolean isIdleTimeoutConfigured(Region.Entry entry) { + return isAnnotationPresent(entry.getValue(), IdleTimeoutExpiration.class); + } + + /** + * Gets the IdleTimeoutExpiration Annotation meta-data from the Region Entry. + * + * @param entry the Region.Entry from which to extract the IdleTimeoutExpiration Annotation meta-data. + * @return the IdleTimeoutExpiration Annotation meta-data for the given Region Entry or {@code null} + * if the Region Entry has not been annotated with @IdleTimeoutExpiration. + * @see org.springframework.data.gemfire.support.IdleTimeoutExpiration + * @see #getAnnotation(Object, Class) + */ + protected IdleTimeoutExpiration getIdleTimeout(Region.Entry entry) { + return getAnnotation(entry.getValue(), IdleTimeoutExpiration.class); + } + + /** + * Determines whether the Region Entry has been annotated with the TimeToLiveExpiration Annotation. + * + * @param entry the Region.Entry to evaluate for the presence of the TimeToLiveExpiration Annotation. + * @return a boolean value indicating whether the Region Entry has been annotated with @TimeToLiveExpiration. + * @see org.springframework.data.gemfire.support.TimeToLiveExpiration + * @see #isAnnotationPresent(Object, Class) + */ + protected boolean isTimeToLiveConfigured(Region.Entry entry) { + return isAnnotationPresent(entry.getValue(), TimeToLiveExpiration.class); + } + + /** + * Gets the TimeToLiveExpiration Annotation meta-data from the Region Entry. + * + * @param entry the Region.Entry from which to extract the TimeToLiveExpiration Annotation meta-data. + * @return the TimeToLiveExpiration Annotation meta-data for the given Region Entry or {@code null} + * if the Region Entry has not been annotated with @TimeToLiveExpiration. + * @see org.springframework.data.gemfire.support.TimeToLiveExpiration + * @see #getAnnotation(Object, Class) + */ + protected TimeToLiveExpiration getTimeToLive(Region.Entry entry) { + return getAnnotation(entry.getValue(), TimeToLiveExpiration.class); + } + + /* (non-Javadoc) */ + private boolean isAnnotationPresent(Object obj, Class annotationType) { + return (obj != null && obj.getClass().isAnnotationPresent(annotationType)); + } + + /* (non-Javadoc) */ + private T getAnnotation(Object obj, Class annotationType) { + return AnnotationUtils.getAnnotation(obj.getClass(), annotationType); + } + + /** + * Called when the Region containing this callback is closed or destroyed, when the Cache is closed, + * or when a callback is removed from a Region using an AttributesMutator. + */ + @Override + public void close() { + } + + /** + * The ExpirationMetaData class encapsulates the settings constituting the expiration policy including + * the expiration timeout and the action performed when expiration occurs. + * + * @see com.gemstone.gemfire.cache.ExpirationAttributes + */ + protected static class ExpirationMetaData { + + private final int timeout; + + private final ExpirationActionType expirationAction; + + protected ExpirationMetaData(final int timeout, final ExpirationActionType expirationAction) { + this.timeout = timeout; + this.expirationAction = expirationAction; + } + + protected static ExpirationMetaData from(ExpirationAttributes expirationAttributes) { + return new ExpirationMetaData(expirationAttributes.getTimeout(), ExpirationActionType.valueOf( + expirationAttributes.getAction())); + } + + protected static ExpirationMetaData from(Expiration expiration) { + return new ExpirationMetaData(expiration.timeout(), expiration.action()); + } + + protected static ExpirationMetaData from(IdleTimeoutExpiration expiration) { + return new ExpirationMetaData(expiration.timeout(), expiration.action()); + } + + protected static ExpirationMetaData from(TimeToLiveExpiration expiration) { + return new ExpirationMetaData(expiration.timeout(), expiration.action()); + } + + public ExpirationAttributes toExpirationAttributes() { + return new ExpirationAttributes(timeout(), expirationAction()); + } + + public ExpirationActionType action() { + return expirationAction; + } + + public ExpirationAction expirationAction() { + return action().getExpirationAction(); + } + + public int timeout() { + return timeout; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof ExpirationMetaData)) { + return false; + } + + ExpirationMetaData that = (ExpirationMetaData) obj; + + return (this.timeout() == that.timeout() + && ObjectUtils.nullSafeEquals(this.action(), that.action())); + } + + @Override + public int hashCode() { + int hashValue = 17; + hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(timeout()); + hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(action()); + return hashValue; + } + + @Override + public String toString() { + return String.format("{ @type = %1$s, timeout = %2$d, action = %3$s }", getClass().getName(), + timeout(), action()); + } + } + +} diff --git a/src/main/java/org/springframework/data/gemfire/support/Expiration.java b/src/main/java/org/springframework/data/gemfire/support/Expiration.java new file mode 100644 index 00000000..1bf34db3 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/support/Expiration.java @@ -0,0 +1,46 @@ +/* + * Copyright 2010-2013 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.support; + +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.springframework.data.gemfire.ExpirationActionType; + +/** + * Expiration is an enumerated type encapsulating custom expiration settings for application domain objects + * to express their general expiration policy. + * + * @author John Blum + * @see org.springframework.data.gemfire.ExpirationActionType + * @since 1.7.0 + */ +@Documented +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE }) +public @interface Expiration { + + ExpirationActionType action() default ExpirationActionType.INVALIDATE; + + int timeout() default 0; + +} diff --git a/src/main/java/org/springframework/data/gemfire/support/IdleTimeoutExpiration.java b/src/main/java/org/springframework/data/gemfire/support/IdleTimeoutExpiration.java new file mode 100644 index 00000000..db81522f --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/support/IdleTimeoutExpiration.java @@ -0,0 +1,48 @@ +/* + * Copyright 2010-2013 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.support; + +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.springframework.data.gemfire.ExpirationActionType; + +/** + * IdleTimeoutExpiration is an enumerated type encapsulating custom expiration settings for application domain objects + * to express their idle-timeout (TTI) expiration policy. + * + * @author John Blum + * @see org.springframework.data.gemfire.ExpirationActionType + * @see org.springframework.data.gemfire.support.Expiration + * @since 1.7.0 + */ +@Documented +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE }) +@SuppressWarnings("unused") +public @interface IdleTimeoutExpiration { + + ExpirationActionType action() default ExpirationActionType.INVALIDATE; + + int timeout() default 0; + +} diff --git a/src/main/java/org/springframework/data/gemfire/support/TimeToLiveExpiration.java b/src/main/java/org/springframework/data/gemfire/support/TimeToLiveExpiration.java new file mode 100644 index 00000000..8de7e833 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/support/TimeToLiveExpiration.java @@ -0,0 +1,48 @@ +/* + * Copyright 2010-2013 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.support; + +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.springframework.data.gemfire.ExpirationActionType; + +/** + * TimeToLiveExpiration is an enumerated type encapsulating custom expiration settings for application domain objects + * to express their time-to-live (TTL) expiration policy. + * + * @author John Blum + * @see org.springframework.data.gemfire.ExpirationActionType + * @see org.springframework.data.gemfire.support.Expiration + * @since 1.7.0 + */ +@Documented +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE }) +@SuppressWarnings("unused") +public @interface TimeToLiveExpiration { + + ExpirationActionType action() default ExpirationActionType.INVALIDATE; + + int timeout() default 0; + +} diff --git a/src/test/java/org/springframework/data/gemfire/support/AnnotationBasedExpirationConfigurationIntegrationTest.java b/src/test/java/org/springframework/data/gemfire/support/AnnotationBasedExpirationConfigurationIntegrationTest.java new file mode 100644 index 00000000..0d1aeeb0 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/support/AnnotationBasedExpirationConfigurationIntegrationTest.java @@ -0,0 +1,160 @@ +/* + * Copyright 2010-2013 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.support; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import javax.annotation.Resource; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.data.gemfire.ExpirationActionType; +import org.springframework.data.gemfire.test.GemfireTestApplicationContextInitializer; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.gemstone.gemfire.cache.CustomExpiry; +import com.gemstone.gemfire.cache.ExpirationAction; +import com.gemstone.gemfire.cache.ExpirationAttributes; +import com.gemstone.gemfire.cache.Region; + +/** + * The AnnotationBasedExpirationConfigurationIntegrationTest class is a test suite of test cases testing + * the configuration of Annotation-defined expiration policies on Region Entry TTL and TTI + * custom expiration settings. + * + * @author John Blum + * @see org.junit.Test + * @see org.junit.runner.RunWith + * @see org.mockito.Mockito + * @see org.springframework.data.gemfire.support.AnnotationBasedExpiration + * @see org.springframework.data.gemfire.test.GemfireTestApplicationContextInitializer + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner + * @since 1.7.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(initializers = GemfireTestApplicationContextInitializer.class) +@SuppressWarnings("unused") +public class AnnotationBasedExpirationConfigurationIntegrationTest { + + @Autowired + @Qualifier("ttiExpiration") + private AnnotationBasedExpiration idleTimeoutExpiration; + + @Autowired + @Qualifier("ttlExpiration") + private AnnotationBasedExpiration timeToLiveExpiration; + + @Autowired + private ExpirationAttributes defaultExpirationAttributes; + + @Resource(name = "Example") + private Region example; + + @Before + public void setup() { + assertThat(example, is(not(nullValue()))); + assertThat(example.getName(), is(equalTo("Example"))); + assertThat(example.getFullPath(), is(equalTo(String.format("%1$s%2$s", Region.SEPARATOR, "Example")))); + assertThat(example.getAttributes(), is(not(nullValue()))); + assertThat(defaultExpirationAttributes, is(not(nullValue()))); + assertThat(defaultExpirationAttributes.getTimeout(), is(equalTo(600))); + assertThat(defaultExpirationAttributes.getAction(), is(equalTo(ExpirationAction.DESTROY))); + assertThat(idleTimeoutExpiration, is(instanceOf(CustomExpiry.class))); + assertThat(idleTimeoutExpiration.getDefaultExpirationAttributes(), is(nullValue())); + assertThat(timeToLiveExpiration, is(instanceOf(CustomExpiry.class))); + assertThat(timeToLiveExpiration.getDefaultExpirationAttributes(), is(sameInstance(defaultExpirationAttributes))); + } + + protected void assertExpiration(ExpirationAttributes expected, ExpirationAttributes actual) { + assertExpiration(actual, expected.getTimeout(), expected.getAction()); + } + + protected void assertExpiration(ExpirationAttributes expirationAttributes, int expectedTimeout, + ExpirationAction expectedAction) { + + assertThat(expirationAttributes, is(not(nullValue()))); + assertThat(expirationAttributes.getTimeout(), is(equalTo(expectedTimeout))); + assertThat(expirationAttributes.getAction(), is(equalTo(expectedAction))); + } + + @SuppressWarnings("unchecked") + protected Region.Entry mockRegionEntry(Object value) { + Region.Entry mockRegionEntry = mock(Region.Entry.class, "MockRegionEntry"); + + when(mockRegionEntry.getValue()).thenReturn(value); + + return mockRegionEntry; + } + + @Test + public void exampleRegionIdleTimeoutExpirationPolicy() { + CustomExpiry expiration = example.getAttributes().getCustomEntryIdleTimeout(); + + assertExpiration(expiration.getExpiry(mockRegionEntry( + new ApplicationDomainObjectWithIdleTimeoutExpirationPolicy())), 120, ExpirationAction.INVALIDATE); + assertExpiration(expiration.getExpiry(mockRegionEntry( + new ApplicationDomainObjectWithTimeToLiveGenericExpirationPolicies())), 60, ExpirationAction.INVALIDATE); + assertExpiration(expiration.getExpiry(mockRegionEntry( + new ApplicationDomainObjectWithGenericExpirationPolicy())), 60, ExpirationAction.DESTROY); + assertThat(expiration.getExpiry(mockRegionEntry(new ApplicationDomainObjectWithNoExpirationPolicy())), + is(nullValue())); + } + + @Test + public void exampleRegionTimeToLiveExpirationPolicy() { + CustomExpiry expiration = example.getAttributes().getCustomEntryTimeToLive(); + + assertExpiration(expiration.getExpiry(mockRegionEntry( + new ApplicationDomainObjectWithTimeToLiveGenericExpirationPolicies())), 300, ExpirationAction.DESTROY); + assertExpiration(defaultExpirationAttributes, expiration.getExpiry(mockRegionEntry( + new ApplicationDomainObjectWithIdleTimeoutExpirationPolicy()))); + assertExpiration(expiration.getExpiry(mockRegionEntry( + new ApplicationDomainObjectWithGenericExpirationPolicy())), 60, ExpirationAction.DESTROY); + assertExpiration(defaultExpirationAttributes, expiration.getExpiry(mockRegionEntry( + new ApplicationDomainObjectWithNoExpirationPolicy()))); + } + + @Expiration(timeout = 60, action = ExpirationActionType.INVALIDATE) + @TimeToLiveExpiration(timeout = 300, action = ExpirationActionType.DESTROY) + protected static class ApplicationDomainObjectWithTimeToLiveGenericExpirationPolicies { + } + + @IdleTimeoutExpiration(timeout = 120, action = ExpirationActionType.INVALIDATE) + protected static class ApplicationDomainObjectWithIdleTimeoutExpirationPolicy { + } + + @Expiration(timeout = 60, action = ExpirationActionType.DESTROY) + protected static class ApplicationDomainObjectWithGenericExpirationPolicy { + } + + protected static class ApplicationDomainObjectWithNoExpirationPolicy { + } + +} diff --git a/src/test/java/org/springframework/data/gemfire/support/AnnotationBasedExpirationTest.java b/src/test/java/org/springframework/data/gemfire/support/AnnotationBasedExpirationTest.java new file mode 100644 index 00000000..fe4e9dc4 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/support/AnnotationBasedExpirationTest.java @@ -0,0 +1,389 @@ +/* + * Copyright 2010-2013 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.support; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.isA; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.data.gemfire.support.AnnotationBasedExpiration.ExpirationMetaData; + +import org.junit.Test; +import org.springframework.data.gemfire.ExpirationActionType; + +import com.gemstone.gemfire.cache.ExpirationAction; +import com.gemstone.gemfire.cache.ExpirationAttributes; +import com.gemstone.gemfire.cache.Region; + +/** + * The AnnotationBasedExpirationTest class is a test suite of test cases testing the contract and functionality + * of the AnnotationBasedExpirationTest. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.data.gemfire.support.AnnotationBasedExpiration + * @since 1.7.0 + */ +@SuppressWarnings({ "rawtypes", "unchecked", "unused" }) +public class AnnotationBasedExpirationTest { + + private AnnotationBasedExpiration noDefaultExpiration = new AnnotationBasedExpiration(); + + protected void assertExpiration(ExpirationAttributes expirationAttributes, int expectedTimeout, + ExpirationAction expectedAction) { + + assertThat(expirationAttributes, is(not(nullValue()))); + assertThat(expirationAttributes.getTimeout(), is(equalTo(expectedTimeout))); + assertThat(expirationAttributes.getAction(), is(equalTo(expectedAction))); + } + + protected void assertExpiration(ExpirationMetaData expirationMetaData, int expectedTimeout, + ExpirationActionType expectedExpirationAction) { + + assertThat(expirationMetaData, is(not(nullValue()))); + assertThat(expirationMetaData.timeout(), is(equalTo(expectedTimeout))); + assertThat(expirationMetaData.action(), is(equalTo(expectedExpirationAction))); + } + + @Test + public void constructUninitializedAnnotationBasedExpirationInstance() { + AnnotationBasedExpiration expiration = new AnnotationBasedExpiration(); + + assertThat(expiration.getDefaultExpirationAttributes(), is(nullValue())); + } + + @Test + public void constructInitializedAnnotationBasedExpirationInstance() { + AnnotationBasedExpiration expiration = new AnnotationBasedExpiration(ExpirationAttributes.DEFAULT); + + assertThat(expiration.getDefaultExpirationAttributes(), is(equalTo(ExpirationAttributes.DEFAULT))); + } + + @Test + public void forIdleTimeoutNoDefaultExpiration() { + Region.Entry mockRegionEntry = mock(Region.Entry.class, "MockRegionEntry"); + + AnnotationBasedExpiration expiration = AnnotationBasedExpiration.forIdleTimeout(); + + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithTimeToLiveIdleTimeoutExpiration()); + assertExpiration(expiration.getExpiry(mockRegionEntry), 120, ExpirationAction.LOCAL_INVALIDATE); + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithTimeToLiveGenericExpiration()); + assertExpiration(expiration.getExpiry(mockRegionEntry), 60, ExpirationAction.INVALIDATE); + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithNoExpiration()); + assertThat(expiration.getExpiry(mockRegionEntry), is(nullValue())); + verify(mockRegionEntry, atLeast(3)).getValue(); + } + + @Test + public void forIdleTimeoutWithDefaultExpiration() { + Region.Entry mockRegionEntry = mock(Region.Entry.class, "MockRegionEntry"); + + ExpirationAttributes defaultExpiration = new ExpirationAttributes(300, ExpirationAction.DESTROY); + + AnnotationBasedExpiration expiration = AnnotationBasedExpiration.forIdleTimeout(defaultExpiration); + + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithTimeToLiveIdleTimeoutExpiration()); + assertExpiration(expiration.getExpiry(mockRegionEntry), 120, ExpirationAction.LOCAL_INVALIDATE); + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithTimeToLiveGenericExpiration()); + assertExpiration(expiration.getExpiry(mockRegionEntry), 60, ExpirationAction.INVALIDATE); + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithNoExpiration()); + assertThat(expiration.getExpiry(mockRegionEntry), is(equalTo(defaultExpiration))); + verify(mockRegionEntry, atLeast(3)).getValue(); + } + + @Test + public void forTimeToLiveNoDefaultExpiration() { + Region.Entry mockRegionEntry = mock(Region.Entry.class, "MockRegionEntry"); + + AnnotationBasedExpiration expiration = AnnotationBasedExpiration.forTimeToLive(); + + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithTimeToLiveIdleTimeoutExpiration()); + assertExpiration(expiration.getExpiry(mockRegionEntry), 300, ExpirationAction.LOCAL_DESTROY); + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithIdleTimeoutGenericExpiration()); + assertExpiration(expiration.getExpiry(mockRegionEntry), 60, ExpirationAction.INVALIDATE); + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithNoExpiration()); + assertThat(expiration.getExpiry(mockRegionEntry), is(nullValue())); + verify(mockRegionEntry, atLeast(3)).getValue(); + } + + @Test + public void forTimeToLiveWithDefaultExpiration() { + Region.Entry mockRegionEntry = mock(Region.Entry.class, "MockRegionEntry"); + + ExpirationAttributes defaultExpiration = new ExpirationAttributes(300, ExpirationAction.DESTROY); + + AnnotationBasedExpiration expiration = AnnotationBasedExpiration.forTimeToLive(defaultExpiration); + + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithTimeToLiveIdleTimeoutExpiration()); + assertExpiration(expiration.getExpiry(mockRegionEntry), 300, ExpirationAction.LOCAL_DESTROY); + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithIdleTimeoutGenericExpiration()); + assertExpiration(expiration.getExpiry(mockRegionEntry), 60, ExpirationAction.INVALIDATE); + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithNoExpiration()); + assertThat(expiration.getExpiry(mockRegionEntry), is(equalTo(defaultExpiration))); + verify(mockRegionEntry, atLeast(3)).getValue(); + } + + @Test + public void setAndGetDefaultExpirationAttributes() { + ExpirationAttributes expectedExpirationAttributes = new ExpirationAttributes(120, ExpirationAction.INVALIDATE); + AnnotationBasedExpiration expiration = new AnnotationBasedExpiration(); + + expiration.setDefaultExpirationAttributes(expectedExpirationAttributes); + + assertThat(expiration.getDefaultExpirationAttributes(), is(equalTo(expectedExpirationAttributes))); + + expiration.setDefaultExpirationAttributes(null); + + assertThat(expiration.getDefaultExpirationAttributes(), is(nullValue())); + + expiration.setDefaultExpirationAttributes(ExpirationAttributes.DEFAULT); + + assertThat(expiration.getDefaultExpirationAttributes(), is(equalTo(ExpirationAttributes.DEFAULT))); + } + + @Test + public void getExpiryCallsGetExpirationMetaDataOnRegionEntryFollowedByNewExpirationAttributes() { + final ExpirationAttributes expectedExpirationAttributes = new ExpirationAttributes(60, + ExpirationAction.LOCAL_DESTROY); + + final Region.Entry mockRegionEntry = mock(Region.Entry.class, "MockRegionEntry"); + + AnnotationBasedExpiration expiration = new AnnotationBasedExpiration() { + @Override protected ExpirationMetaData getExpirationMetaData(Region.Entry entry) { + assertThat(entry, is(sameInstance(mockRegionEntry))); + return ExpirationMetaData.from(expectedExpirationAttributes); + } + + @Override protected ExpirationAttributes newExpirationAttributes(ExpirationMetaData expirationMetaData) { + assertThat(expirationMetaData.timeout(), is(equalTo(expectedExpirationAttributes.getTimeout()))); + assertThat(expirationMetaData.expirationAction(), is(equalTo(expectedExpirationAttributes.getAction()))); + return expectedExpirationAttributes; + } + }; + + assertThat(expiration.getExpiry(mockRegionEntry), is(equalTo(expectedExpirationAttributes))); + } + + @Test + public void isExpirationConfiguredWithGenericExpirationBasedRegionEntry() { + Region.Entry mockRegionEntry = mock(Region.Entry.class, "MockRegionEntry"); + + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithGenericExpiration()); + assertThat(noDefaultExpiration.isExpirationConfigured(mockRegionEntry), is(true)); + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithTimeToLiveIdleTimeoutGenericExpiration()); + assertThat(noDefaultExpiration.isExpirationConfigured(mockRegionEntry), is(true)); + verify(mockRegionEntry, times(2)).getValue(); + } + + @Test + public void isExpirationConfiguredWithNoGenericExpirationRegionEntry() { + Region.Entry mockRegionEntry = mock(Region.Entry.class, "MockRegionEntry"); + + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithTimeToLiveIdleTimeoutExpiration()); + assertThat(noDefaultExpiration.isExpirationConfigured(mockRegionEntry), is(false)); + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithNoExpiration()); + assertThat(noDefaultExpiration.isExpirationConfigured(mockRegionEntry), is(false)); + verify(mockRegionEntry, times(2)).getValue(); + } + + @Test + public void isIdleTimeoutConfiguredWithIdleTimeoutExpirationBasedRegionEntry() { + Region.Entry mockRegionEntry = mock(Region.Entry.class, "MockRegionEntry"); + + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithIdleTimeoutExpiration()); + assertThat(noDefaultExpiration.isIdleTimeoutConfigured(mockRegionEntry), is(true)); + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithTimeToLiveIdleTimeoutGenericExpiration()); + assertThat(noDefaultExpiration.isIdleTimeoutConfigured(mockRegionEntry), is(true)); + verify(mockRegionEntry, times(2)).getValue(); + } + + @Test + public void isIdleTimeoutConfiguredWithNoIdleTimeoutExpirationRegionEntry() { + Region.Entry mockRegionEntry = mock(Region.Entry.class, "MockRegionEntry"); + + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithTimeToLiveGenericExpiration()); + assertThat(noDefaultExpiration.isIdleTimeoutConfigured(mockRegionEntry), is(false)); + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithNoExpiration()); + assertThat(noDefaultExpiration.isIdleTimeoutConfigured(mockRegionEntry), is(false)); + verify(mockRegionEntry, times(2)).getValue(); + } + + @Test + public void isTimeToLiveConfiguredWithTimeToLiveExpirationBasedRegionEntry() { + Region.Entry mockRegionEntry = mock(Region.Entry.class, "MockRegionEntry"); + + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithTimeToLiveExpiration()); + assertThat(noDefaultExpiration.isTimeToLiveConfigured(mockRegionEntry), is(true)); + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithTimeToLiveIdleTimeoutGenericExpiration()); + assertThat(noDefaultExpiration.isTimeToLiveConfigured(mockRegionEntry), is(true)); + verify(mockRegionEntry, times(2)).getValue(); + } + + public void isTimeToLiveConfiguredWithNoTimeToLiveExpirationRegionEntry() { + Region.Entry mockRegionEntry = mock(Region.Entry.class, "MockRegionEntry"); + + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithIdleTimeoutGenericExpiration()); + assertThat(noDefaultExpiration.isTimeToLiveConfigured(mockRegionEntry), is(false)); + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithNoExpiration()); + assertThat(noDefaultExpiration.isTimeToLiveConfigured(mockRegionEntry), is(false)); + verify(mockRegionEntry, times(2)).getValue(); + } + + @Test + public void getExpirationWithGenericExpirationBasedRegionEntry() { + Region.Entry mockRegionEntry = mock(Region.Entry.class, "MockRegionEntry"); + + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithGenericExpiration()); + assertThat(noDefaultExpiration.getExpiration(mockRegionEntry), isA(Expiration.class)); + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithTimeToLiveIdleTimeoutGenericExpiration()); + assertThat(noDefaultExpiration.getExpiration(mockRegionEntry), isA(Expiration.class)); + verify(mockRegionEntry, times(2)).getValue(); + } + + @Test + public void getExpirationWithNoGenericExpirationRegionEntry() { + Region.Entry mockRegionEntry = mock(Region.Entry.class, "MockRegionEntry"); + + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithTimeToLiveIdleTimeoutExpiration()); + assertThat(noDefaultExpiration.getExpiration(mockRegionEntry), is(nullValue())); + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithNoExpiration()); + assertThat(noDefaultExpiration.getExpiration(mockRegionEntry), is(nullValue())); + verify(mockRegionEntry, times(2)).getValue(); + } + + @Test + public void getIdleTimeoutWithIdleTimeoutExpirationBasedRegionEntry() { + Region.Entry mockRegionEntry = mock(Region.Entry.class, "MockRegionEntry"); + + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithIdleTimeoutExpiration()); + assertThat(noDefaultExpiration.getIdleTimeout(mockRegionEntry), isA(IdleTimeoutExpiration.class)); + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithTimeToLiveIdleTimeoutGenericExpiration()); + assertThat(noDefaultExpiration.getIdleTimeout(mockRegionEntry), isA(IdleTimeoutExpiration.class)); + verify(mockRegionEntry, times(2)).getValue(); + } + + @Test + public void getIdleTimeoutWithNoIdleTimeoutExpirationRegionEntry() { + Region.Entry mockRegionEntry = mock(Region.Entry.class, "MockRegionEntry"); + + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithTimeToLiveGenericExpiration()); + assertThat(noDefaultExpiration.getIdleTimeout(mockRegionEntry), is(nullValue())); + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithNoExpiration()); + assertThat(noDefaultExpiration.getIdleTimeout(mockRegionEntry), is(nullValue())); + verify(mockRegionEntry, times(2)).getValue(); + } + + @Test + public void getTimeToLiveWithTimeToLiveExpirationBasedRegionEntry() { + Region.Entry mockRegionEntry = mock(Region.Entry.class, "MockRegionEntry"); + + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithTimeToLiveExpiration()); + assertThat(noDefaultExpiration.getTimeToLive(mockRegionEntry), isA(TimeToLiveExpiration.class)); + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithTimeToLiveIdleTimeoutGenericExpiration()); + assertThat(noDefaultExpiration.getTimeToLive(mockRegionEntry), isA(TimeToLiveExpiration.class)); + verify(mockRegionEntry, times(2)).getValue(); + } + + @Test + public void getTimeToLiveWithNoTimeToLiveExpirationRegionEntry() { + Region.Entry mockRegionEntry = mock(Region.Entry.class, "MockRegionEntry"); + + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithIdleTimeoutGenericExpiration()); + assertThat(noDefaultExpiration.getTimeToLive(mockRegionEntry), is(nullValue())); + when(mockRegionEntry.getValue()).thenReturn(new RegionEntryValueWithNoExpiration()); + assertThat(noDefaultExpiration.getTimeToLive(mockRegionEntry), is(nullValue())); + verify(mockRegionEntry, times(2)).getValue(); + } + + @Test + public void fromExpiration() { + ExpirationMetaData expirationMetaData = ExpirationMetaData.from( + RegionEntryValueWithGenericExpiration.class.getAnnotation(Expiration.class)); + + assertExpiration(expirationMetaData, 60, ExpirationActionType.INVALIDATE); + } + + @Test + public void fromExpirationIdleTimeout() { + ExpirationMetaData expirationMetaData = ExpirationMetaData.from( + RegionEntryValueWithIdleTimeoutExpiration.class.getAnnotation(IdleTimeoutExpiration.class)); + + assertExpiration(expirationMetaData, 120, ExpirationActionType.LOCAL_INVALIDATE); + } + + @Test + public void fromExpirationTimeToLive() { + ExpirationMetaData expirationMetaData = ExpirationMetaData.from( + RegionEntryValueWithTimeToLiveExpiration.class.getAnnotation(TimeToLiveExpiration.class)); + + assertExpiration(expirationMetaData, 300, ExpirationActionType.LOCAL_DESTROY); + } + + @Test + public void toExpirationAttributes() { + ExpirationMetaData expirationMetaData = new ExpirationMetaData(90, ExpirationActionType.DESTROY); + + assertExpiration(expirationMetaData.toExpirationAttributes(), expirationMetaData.timeout(), + expirationMetaData.expirationAction()); + } + + @Expiration(timeout = 60, action = ExpirationActionType.INVALIDATE) + @IdleTimeoutExpiration(timeout = 120, action = ExpirationActionType.LOCAL_INVALIDATE) + @TimeToLiveExpiration(timeout = 300, action = ExpirationActionType.LOCAL_DESTROY) + public static class RegionEntryValueWithTimeToLiveIdleTimeoutGenericExpiration { + } + + @IdleTimeoutExpiration(timeout = 120, action = ExpirationActionType.LOCAL_INVALIDATE) + @TimeToLiveExpiration(timeout = 300, action = ExpirationActionType.LOCAL_DESTROY) + public static class RegionEntryValueWithTimeToLiveIdleTimeoutExpiration { + } + + @Expiration(timeout = 60, action = ExpirationActionType.INVALIDATE) + @TimeToLiveExpiration(timeout = 300, action = ExpirationActionType.LOCAL_DESTROY) + public static class RegionEntryValueWithTimeToLiveGenericExpiration { + } + + @TimeToLiveExpiration(timeout = 300, action = ExpirationActionType.LOCAL_DESTROY) + public static class RegionEntryValueWithTimeToLiveExpiration { + } + + @Expiration(timeout = 60, action = ExpirationActionType.INVALIDATE) + @IdleTimeoutExpiration(timeout = 120, action = ExpirationActionType.LOCAL_INVALIDATE) + public static class RegionEntryValueWithIdleTimeoutGenericExpiration { + } + + @IdleTimeoutExpiration(timeout = 120, action = ExpirationActionType.LOCAL_INVALIDATE) + public static class RegionEntryValueWithIdleTimeoutExpiration { + } + + @Expiration(timeout = 60, action = ExpirationActionType.INVALIDATE) + public static class RegionEntryValueWithGenericExpiration { + } + + public static class RegionEntryValueWithNoExpiration { + } + +} diff --git a/src/test/resources/org/springframework/data/gemfire/support/AnnotationBasedExpirationConfigurationIntegrationTest-context.xml b/src/test/resources/org/springframework/data/gemfire/support/AnnotationBasedExpirationConfigurationIntegrationTest-context.xml new file mode 100644 index 00000000..3b26d09a --- /dev/null +++ b/src/test/resources/org/springframework/data/gemfire/support/AnnotationBasedExpirationConfigurationIntegrationTest-context.xml @@ -0,0 +1,40 @@ + + + + + SpringGemFireCachingIntegrationTest + 0 + warning + + + + + + + + + + + + + + + + + + + + +