SGF-459 - Add support for the get(key:Object, valueLoader:Callable) method in Spring Framework 4.3's Cache interface.

(cherry picked from commit 1b11b912f53b90df7b18fc40e070155096d3648e)
Signed-off-by: John Blum <jblum@pivotal.io>
This commit is contained in:
John Blum
2016-02-01 17:20:12 -08:00
parent 0d5554b543
commit 83023a4425
7 changed files with 661 additions and 65 deletions

View File

@@ -16,22 +16,26 @@
package org.springframework.data.gemfire.support;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cache.Cache;
/**
* Test for native cache implementations.
* Abstract base test class for native cache implementations.
*
* @author Costin Leau
* @author John Blum
* @see org.springframework.cache.Cache
*/
public abstract class AbstractNativeCacheTest<T> {
protected static final String CACHE_NAME = "testCache";
protected static final String CACHE_NAME = "Example";
private T nativeCache;
private Cache cache;
@@ -43,54 +47,65 @@ public abstract class AbstractNativeCacheTest<T> {
cache.clear();
}
protected abstract T createNativeCache() throws Exception;
@SuppressWarnings("unchecked")
protected <C extends Cache> C createCache() throws Exception {
return (C) createCache(createNativeCache());
}
protected abstract Cache createCache(T nativeCache);
protected abstract T createNativeCache() throws Exception;
@Test
public void testCacheName() throws Exception {
assertEquals(CACHE_NAME, cache.getName());
public void cacheNameIsEqualToExpected() throws Exception {
assertThat(cache.getName(), is(equalTo(CACHE_NAME)));
}
@Test
public void testNativeCache() throws Exception {
assertSame(nativeCache, cache.getNativeCache());
@SuppressWarnings("unchecked")
public void nativeCacheIsSameAsExpected() throws Exception {
assertThat((T) cache.getNativeCache(), is(sameInstance(nativeCache)));
}
@Test
public void testCachePut() throws Exception {
Object key = "enescu";
Object value = "george";
assertNull(cache.get(key));
cache.put(key, value);
assertEquals(value, cache.get(key).get());
}
@Test
public void testCacheClear() throws Exception {
assertNull(cache.get("enescu"));
public void cachePutIsSuccessful() throws Exception {
assertThat(cache.get("enescu"), is(nullValue()));
cache.put("enescu", "george");
assertNull(cache.get("vlaicu"));
cache.put("vlaicu", "aurel");
cache.clear();
assertNull(cache.get("vlaicu"));
assertNull(cache.get("enescu"));
assertThat(String.valueOf(cache.get("enescu").get()), is(equalTo("george")));
}
@Test
public void testCacheGetForClassType() {
public void cacheGetForClassTypeIsSuccessful() {
cache.put("one", Boolean.TRUE);
cache.put("two", 'X');
cache.put("three", 101);
cache.put("four", Math.PI);
cache.put("five", "TEST");
assertEquals(Boolean.TRUE, cache.get("one", Boolean.class));
assertEquals(new Character('X'), cache.get("two", Character.class));
assertEquals(new Integer(101), cache.get("three", Integer.class));
assertEquals(new Double(Math.PI), cache.get("four", Double.class));
assertEquals("TEST", cache.get("five", String.class));
assertThat(cache.get("one", Boolean.class), is(equalTo(Boolean.TRUE)));
assertThat(cache.get("two", Character.class), is(equalTo('X')));
assertThat(cache.get("three", Integer.class), is(equalTo(101)));
assertThat(cache.get("four", Double.class), is(equalTo(Math.PI)));
assertThat(cache.get("five", String.class), is(equalTo("TEST")));
}
@Test
public void cacheClearIsSuccessful() throws Exception {
assertThat(cache.get("enescu"), is(nullValue()));
cache.put("enescu", "george");
assertThat(cache.get("vlaicu"), is(nullValue()));
cache.put("vlaicu", "aurel");
assertThat(cache.get("enescu", String.class), is(equalTo("george")));
assertThat(cache.get("vlaicu", String.class), is(equalTo("aurel")));
cache.clear();
assertThat(cache.get("vlaicu"), is(nullValue()));
assertThat(cache.get("enescu"), is(nullValue()));
}
}

View File

@@ -0,0 +1,195 @@
/*
* Copyright 2012 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.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import com.gemstone.gemfire.cache.CacheLoader;
import com.gemstone.gemfire.cache.LoaderHelper;
import com.gemstone.gemfire.cache.Region;
/**
* Tests the adaption of the {@link java.util.concurrent.Callable} in to GemFire's {@link com.gemstone.gemfire.cache.CacheLoader} interface.
*
* @author John Blum
* @see org.junit.Rule
* @see org.junit.Test
* @see org.junit.rules.ExpectedException
* @see org.junit.runner.RunWith
* @see org.mockito.Mock
* @see org.mockito.Mockito
* @see org.mockito.runners.MockitoJUnitRunner
* @see java.util.concurrent.Callable
* @see com.gemstone.gemfire.cache.CacheLoader
* @see com.gemstone.gemfire.cache.LoaderHelper
* @see com.gemstone.gemfire.cache.Region
* @since 1.0.0
*/
@RunWith(MockitoJUnitRunner.class)
public class CallableCacheLoaderAdapterTest {
@Mock
private CacheLoader<String, Object> mockCacheLoader;
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Mock
private LoaderHelper<String, Object> mockLoaderHelper;
@Mock
private Region<String, Object> mockRegion;
@Test
public void constructCallableCacheLoaderAdapterWithArgumentKeyAndRegion() {
CallableCacheLoaderAdapter<String, Object> instance = new CallableCacheLoaderAdapter<String, Object>(
mockCacheLoader, "key", mockRegion, "test");
assertThat(instance, is(notNullValue()));
assertThat(instance.getCacheLoader(), is(sameInstance(mockCacheLoader)));
assertThat(instance.getKey(), is(equalTo("key")));
assertThat(instance.getRegion(), is(sameInstance(mockRegion)));
assertThat(String.valueOf(instance.getArgument()), is(equalTo("test")));
}
@Test
public void constructCallableCacheLoaderAdapterWithKeyRegionAndNoArgument() {
CallableCacheLoaderAdapter<String, Object> instance = new CallableCacheLoaderAdapter<String, Object>(
mockCacheLoader, "key", mockRegion);
assertThat(instance, is(notNullValue()));
assertThat(instance.getCacheLoader(), is(sameInstance(mockCacheLoader)));
assertThat(instance.getKey(), is(equalTo("key")));
assertThat(instance.getRegion(), is(sameInstance(mockRegion)));
assertThat(instance.getArgument(), is(nullValue()));
}
@Test
public void constructCallableCacheLoaderAdapterWithNoArgumentKeyOrRegion() {
CallableCacheLoaderAdapter<String, Object> instance =
new CallableCacheLoaderAdapter<String, Object>(mockCacheLoader);
assertThat(instance, is(notNullValue()));
assertThat(instance.getCacheLoader(), is(sameInstance(mockCacheLoader)));
assertThat(instance.getKey(), is(nullValue()));
assertThat(instance.getRegion(), is(nullValue()));
assertThat(instance.getArgument(), is(nullValue()));
}
@Test
public void constructCallableCacheLoaderAdapterWithNullCacheLoader() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectCause(is(nullValue(Throwable.class)));
expectedException.expectMessage("CacheLoader must not be null");
new CallableCacheLoaderAdapter<Object, Object>(null);
}
@Test
@SuppressWarnings("unchecked")
public void callDelegatesToLoad() throws Exception {
CallableCacheLoaderAdapter<String, Object> instance = new CallableCacheLoaderAdapter<String, Object>(
mockCacheLoader, "key", mockRegion, "test");
when(mockCacheLoader.load(any(LoaderHelper.class))).thenAnswer(new Answer<String>() {
public String answer(final InvocationOnMock invocation) throws Throwable {
LoaderHelper<String, Object> loaderHelper = invocation.getArgumentAt(0, LoaderHelper.class);
assertThat(loaderHelper, is(notNullValue()));
assertThat((String) loaderHelper.getArgument(), is(equalTo("test")));
assertThat(loaderHelper.getKey(), is(equalTo("key")));
assertThat(loaderHelper.getRegion(), is(sameInstance(mockRegion)));
return "mockValue";
}
});
assertThat((String) instance.call(), is(equalTo("mockValue")));
verify(mockCacheLoader, times(1)).load(isA(LoaderHelper.class));
}
@Test
public void callThrowsIllegalStateExceptionForNullKey() throws Exception {
CallableCacheLoaderAdapter<String, Object> instance = new CallableCacheLoaderAdapter<String, Object>(
mockCacheLoader, null, mockRegion);
assertThat(instance.getKey(), is(nullValue()));
assertThat(instance.getRegion(), is(sameInstance(mockRegion)));
expectedException.expect(IllegalStateException.class);
expectedException.expectCause(is(nullValue(Throwable.class)));
expectedException.expectMessage("The key for which the value is loaded for cannot be null");
instance.call();
}
@Test
public void callThrowsIllegalStateExceptionForNullRegion() throws Exception {
CallableCacheLoaderAdapter<String, Object> instance = new CallableCacheLoaderAdapter<String, Object>(
mockCacheLoader, "key", null);
assertThat(instance.getKey(), is(equalTo("key")));
assertThat(instance.getRegion(), is(nullValue()));
expectedException.expect(IllegalStateException.class);
expectedException.expectCause(is(nullValue(Throwable.class)));
expectedException.expectMessage("The Region to load cannot be null");
instance.call();
}
@Test
public void closeDelegatesToCacheLoaderClose() {
new CallableCacheLoaderAdapter<String, Object>(mockCacheLoader).close();
verify(mockCacheLoader, times(1)).close();
}
@Test
public void loadDelegatesToCacheLoaderLoad() {
CallableCacheLoaderAdapter<String, Object> instance =
new CallableCacheLoaderAdapter<String, Object>(mockCacheLoader);
when(mockCacheLoader.load(eq(mockLoaderHelper))).thenReturn("test");
assertThat((String) instance.load(mockLoaderHelper), is(equalTo("test")));
verify(mockCacheLoader, times(1)).load(eq(mockLoaderHelper));
}
}

View File

@@ -16,30 +16,44 @@
package org.springframework.data.gemfire.support;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import java.util.Properties;
import java.util.concurrent.Callable;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.cache.Cache;
import org.springframework.data.gemfire.GemfireUtils;
import com.gemstone.gemfire.cache.CacheFactory;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.distributed.DistributedSystem;
import edu.umd.cs.mtc.MultithreadedTestCase;
import edu.umd.cs.mtc.TestFramework;
/**
* Tests the interaction between the Spring Framework Cache Abstraction and GemFire as a provider
* using Spring Data GemFire's extension.
*
* @author Costin Leau
* @author John Blum
* @author Oliver Gierke
* @see org.junit.Rule
* @see org.junit.Test
* @see edu.umd.cs.mtc.MultithreadedTestCase
* @see edu.umd.cs.mtc.TestFramework
* @see org.springframework.cache.Cache
* @see com.gemstone.gemfire.cache.Region
*/
// TODO avoid using actual GemFire Cache and Region instances, thereby creating a distributed system, for this test!
// TODO Use Mocks!
public class GemfireCacheTest extends AbstractNativeCacheTest<Region<Object, Object>> {
@Rule public ExpectedException exception = ExpectedException.none();
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Override
protected Cache createCache(Region<Object, Object> nativeCache) {
@@ -47,67 +61,206 @@ public class GemfireCacheTest extends AbstractNativeCacheTest<Region<Object, Obj
}
@Override
@SuppressWarnings({ "deprecation", "unchecked" })
protected Region<Object, Object> createNativeCache() throws Exception {
com.gemstone.gemfire.cache.Cache instance = null;
Properties gemfireProperties = new Properties();
try {
instance = CacheFactory.getAnyInstance();
}
catch (Exception ignore) {
}
gemfireProperties.setProperty("name", GemfireCacheTest.class.getName());
gemfireProperties.setProperty("mcast-port", "0");
gemfireProperties.setProperty("log-level", "warning");
if (instance == null) {
DistributedSystem ds = DistributedSystem.connect(new Properties());
instance = CacheFactory.create(ds);
}
com.gemstone.gemfire.cache.Cache cache = GemfireUtils.getCache();
Region region = instance.getRegion(CACHE_NAME);
cache = (cache != null ? cache : new CacheFactory(gemfireProperties).create());
if (region == null) {
region = instance.createRegion(CACHE_NAME, new com.gemstone.gemfire.cache.AttributesFactory().create());
}
Region<Object, Object> region = cache.getRegion(CACHE_NAME);
region = (region != null ? region : cache.createRegionFactory().create(CACHE_NAME));
return region;
}
/**
* @see SGF-317
* @see <a href="https://jira.spring.io/browse/SGF-317">Improve GemfireCache implementation to be able to build on Spring 4.1</a>
*/
@Test
public void findsTypedValue() throws Exception {
Cache cache = createCache();
GemfireCache cache = new GemfireCache(createNativeCache());
cache.put("key", "value");
assertThat(cache.get("key", String.class), is("value"));
}
/**
* @see SGF-317
* @see <a href="https://jira.spring.io/browse/SGF-317">Improve GemfireCache implementation to be able to build on Spring 4.1</a>
*/
@Test
public void skipTypeChecksIfTargetTypeIsNull() throws Exception {
Cache cache = createCache();
GemfireCache cache = new GemfireCache(createNativeCache());
cache.put("key", "value");
assertThat(cache.get("key", null), is((Object) "value"));
assertThat(cache.get("key", (Class<String>) null), is("value"));
}
/**
* @see SGF-317
* @see <a href="https://jira.spring.io/browse/SGF-317">Improve GemfireCache implementation to be able to build on Spring 4.1</a>
*/
@Test
public void throwsIllegalStateExceptionIfTypedAccessDoesntFindMatchingType() throws Exception {
public void throwsIllegalStateExceptionIfTypedAccessDoesNotFindMatchingType() throws Exception {
Cache cache = createCache();
GemfireCache cache = new GemfireCache(createNativeCache());
cache.put("key", "value");
exception.expect(IllegalStateException.class);
exception.expectMessage(Integer.class.getName());
exception.expectMessage("value");
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage(Integer.class.getName());
expectedException.expectMessage("value");
cache.get("key", Integer.class);
}
@Test
public void cacheGetWithValueLoaderFindsValue() throws Exception {
GemfireCache cache = createCache();
cache.put("key", "value");
assertThat(String.valueOf(cache.get("key", TestCacheLoader.NULL_VALUE)), is(equalTo("value")));
assertThat(TestCacheLoader.NULL_VALUE.wasCalled(), is(false));
}
@Test
@SuppressWarnings("unchecked")
public void cacheGetWithValueLoaderUsesValueLoader() throws Exception {
GemfireCache cache = createCache();
TestCacheLoader<String> cacheLoader = new TestCacheLoader<String>("test");
assertThat(cache.get("key", cacheLoader), is(equalTo("test")));
assertThat(cacheLoader.wasCalled(), is(true));
assertThat(((Region<Object, String>) cache.getNativeCache()).get("key"), is(equalTo("test")));
}
@Test
@SuppressWarnings("unchecked")
public void cacheGetWithValueLoaderUsesValueLoaderReturningNull() throws Exception {
GemfireCache cache = createCache();
assertThat(cache.get("key", TestCacheLoader.NULL_VALUE), is(nullValue()));
assertThat(TestCacheLoader.NULL_VALUE.wasCalled(), is(true));
assertThat(cache.getNativeCache().containsKey("key"), is(false));
}
@Test
@SuppressWarnings("all")
public void cacheGetWithValueLoaderUsesValueLoaderThrowingException() throws Exception {
GemfireCache cache = createCache();
TestCacheLoader<Exception> exceptionThrowingCacheLoader = new TestCacheLoader<Exception>(
new IllegalStateException("test"));
expectedException.expect(RuntimeException.class);
expectedException.expectCause(is(nullValue(IllegalStateException.class)));
expectedException.expectMessage(String.format("Failed to load value for key [key] using valueLoader [%1$s]",
exceptionThrowingCacheLoader.getClass().getName()));
cache.get("key", exceptionThrowingCacheLoader);
}
@Test
public void cacheGetWithValueLoaderIsThreadSafe() throws Throwable {
TestFramework.runOnce(new CacheGetWithValueLoaderIsThreadSafe());
}
@SuppressWarnings("unused")
protected class CacheGetWithValueLoaderIsThreadSafe extends MultithreadedTestCase {
private GemfireCache cache;
private TestCacheLoader<String> cacheLoader;
@Override
public void initialize(){
super.initialize();
cache = createCacheHandlesCheckedException();
cacheLoader = new TestCacheLoader<String>("test") {
@Override public String call() throws Exception {
waitForTick(2);
return super.call();
}
};
}
<T extends Cache> T createCacheHandlesCheckedException() {
try {
return createCache();
}
catch (Exception e) {
throw new RuntimeException("failed to create Cache", e);
}
}
public void thread1() {
assertTick(0);
Thread.currentThread().setName("Cache Loader Thread");
String value = cache.get("key", cacheLoader);
assertTick(2);
assertThat(value, is(equalTo("test")));
assertThat(cacheLoader.wasCalled(), is(true));
}
public void thread2() {
waitForTick(1);
Thread.currentThread().setName("Cache Reader Thread");
TestCacheLoader<String> illegalCacheLoader = new TestCacheLoader<String>("illegal");
String value = cache.get("key", illegalCacheLoader);
assertTick(2);
assertThat(value, is(equalTo("test")));
assertThat(illegalCacheLoader.wasCalled(), is(false));
}
}
protected static class TestCacheLoader<T> implements Callable<T> {
protected static final TestCacheLoader<Object> NULL_VALUE = new TestCacheLoader<Object>();
private volatile boolean called;
private final T value;
public TestCacheLoader() {
this(null);
}
public TestCacheLoader(T value) {
this.value = value;
}
protected boolean wasCalled() {
boolean called = this.called;
this.called = false;
return called;
}
@Override
public T call() throws Exception {
called = true;
if (value instanceof Exception) {
throw (Exception) value;
}
return value;
}
}
}