/*
* 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 <K, V> void assertRegionExpiration(ExpirationAttributes expectedExpirationAttributes,
Region<K, V> region, V... applicationDomainObjects) {
assertIdleTimeoutExpiration(expectedExpirationAttributes, region, applicationDomainObjects);
assertTimeToLiveExpiration(expectedExpirationAttributes, region, applicationDomainObjects);
}
protected <K, V> void assertIdleTimeoutExpiration(ExpirationAttributes expectedExpirationAttributes,
Region<K, V> region, V... applicationDomainObjects) {
assertExpiration(expectedExpirationAttributes, region.getAttributes().getCustomEntryIdleTimeout(),
applicationDomainObjects);
}
protected <K, V> void assertNoIdleTimeoutExpiration(Region<K, V> region) {
assertThat(region.getAttributes().getCustomEntryIdleTimeout()).isNull();
}
protected <K, V> void assertTimeToLiveExpiration(ExpirationAttributes expectedExpirationAttributes,
Region<K, V> region, V... applicationDomainObjects) {
assertExpiration(expectedExpirationAttributes, region.getAttributes().getCustomEntryTimeToLive(),
applicationDomainObjects);
}
protected <K, V> void assertNoTimeToLiveExpiration(Region<K, V> region) {
assertThat(region.getAttributes().getCustomEntryTimeToLive()).isNull();
}
@SuppressWarnings("unchecked")
protected <K, V> void assertExpiration(ExpirationAttributes expectedExpirationAttributes,
CustomExpiry<K, V> customExpiry, V... applicationDomainObjects) {
Region.Entry<K, V> regionEntry = mockRegionEntry(ArrayUtils.getFirst(applicationDomainObjects));
assertExpiration(customExpiry.getExpiry(regionEntry), expectedExpirationAttributes);
}
@SuppressWarnings({ "unchecked", "unused" })
protected <K, V> void assertExpiration(ExpirationAttributes actualExpirationAttributes,
ExpirationAttributes expectedExpirationAttributes) {
assertThat(actualExpirationAttributes).isEqualTo(expectedExpirationAttributes);
}
@SuppressWarnings("unchecked")
protected <K, V> Region<K, V> 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 <K, V> Region<K, V> mockRegion(String name) {
Region<K, V> mockRegion = mock(Region.class);
when(mockRegion.getName()).thenReturn(name);
when(mockRegion.getFullPath()).thenReturn(String.format("%1$s%2$s", Region.SEPARATOR, name));
final AtomicReference<CustomExpiry<K, V>> entryIdleTimeout = new AtomicReference<CustomExpiry<K, V>>(null);
final AtomicReference<CustomExpiry<K, V>> entryTimeToLive = new AtomicReference<CustomExpiry<K, V>>(null);
AttributesMutator<K, V> mockAttributesMutator = mock(AttributesMutator.class);
when(mockRegion.getAttributesMutator()).thenReturn(mockAttributesMutator);
doAnswer(new Answer<CustomExpiry<K, V>>() {
@Override
public CustomExpiry<K, V> answer(InvocationOnMock invocation) throws Throwable {
CustomExpiry<K, V> customExpiry = invocation.getArgument(0);
entryIdleTimeout.set(customExpiry);
return customExpiry;
}
}).when(mockAttributesMutator).setCustomEntryIdleTimeout(any(CustomExpiry.class));
doAnswer(new Answer<CustomExpiry<K, V>>() {
@Override
public CustomExpiry<K, V> answer(InvocationOnMock invocation) throws Throwable {
CustomExpiry<K, V> customExpiry = invocation.getArgument(0);
entryTimeToLive.set(customExpiry);
return customExpiry;
}
}).when(mockAttributesMutator).setCustomEntryTimeToLive(any(CustomExpiry.class));
RegionAttributes<K, V> mockRegionAttributes = mock(RegionAttributes.class);
when(mockRegion.getAttributes()).thenReturn(mockRegionAttributes);
when(mockRegionAttributes.getCustomEntryIdleTimeout()).thenAnswer(new Answer<CustomExpiry<K, V>>() {
@Override public CustomExpiry<K, V> answer(InvocationOnMock invocation) throws Throwable {
return entryIdleTimeout.get();
}
});
when(mockRegionAttributes.getCustomEntryTimeToLive()).thenAnswer(new Answer<CustomExpiry<K, V>>() {
@Override public CustomExpiry<K, V> answer(InvocationOnMock invocation) throws Throwable {
return entryTimeToLive.get();
}
});
return mockRegion;
}
@SuppressWarnings("unchecked")
static <K, V> Region.Entry<K, V> mockRegionEntry(V applicationDomainObject) {
Region.Entry<K, V> mockRegionEntry = mock(Region.Entry.class);
when(mockRegionEntry.getValue()).thenReturn(applicationDomainObject);
return mockRegionEntry;
}
@Configuration
@SuppressWarnings("unused")
static class RegionConfiguration {
@Bean("One")
Region<Object, Object> regionOne() {
return mockRegion("One");
}
@Bean("Two")
Region<Object, Object> 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 {
}
}