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

(cherry picked from commit 1b11b912f5)
Signed-off-by: John Blum <jblum@pivotal.io>
This commit is contained in:
John Blum
2016-02-01 17:20:12 -08:00
parent cc4b3b419c
commit b4d9c6f76f
8 changed files with 683 additions and 66 deletions

View File

@@ -3,11 +3,11 @@ buildscript {
maven { url 'https://repo.spring.io/plugins-release' }
}
dependencies {
classpath 'io.spring.gradle:docbook-reference-plugin:0.3.0'
classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.0'
classpath 'org.asciidoctor:asciidoctorj:1.5.0'
classpath 'org.springframework.build.gradle:bundlor-plugin:0.1.2'
classpath 'org.springframework.build.gradle:propdeps-plugin:0.0.6'
classpath 'io.spring.gradle:docbook-reference-plugin:0.3.0'
classpath 'io.spring.gradle:spring-io-plugin:0.0.4.RELEASE'
}
}
@@ -93,6 +93,7 @@ dependencies {
testCompile "org.hamcrest:hamcrest-core:$hamcrestVersion"
testCompile "org.hamcrest:hamcrest-library:$hamcrestVersion"
testCompile "org.mockito:mockito-core:$mockitoVersion"
testCompile "edu.umd.cs.mtc:multithreadedtc:$multiThreadedtcVersion"
testCompile "javax.annotation:jsr250-api:1.0", optional
testRuntime "log4j:log4j:$log4jVersion"

View File

@@ -6,6 +6,7 @@ jacksonVersion=2.6.4
junitVersion=4.12
log4jVersion=1.2.17
mockitoVersion=1.10.19
multiThreadedtcVersion=1.01
slf4jVersion=1.7.12
spring.range="[4.0.0, 5.0.0)"
springVersion=4.1.9.RELEASE

View File

@@ -21,8 +21,11 @@ import java.util.concurrent.ConcurrentMap;
import org.springframework.data.gemfire.util.DistributedSystemUtils;
import org.springframework.util.ClassUtils;
import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.CacheClosedException;
import com.gemstone.gemfire.cache.CacheFactory;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.client.ClientCache;
import com.gemstone.gemfire.cache.client.ClientCacheFactory;
/**
@@ -58,6 +61,24 @@ public abstract class GemfireUtils extends DistributedSystemUtils {
}
}
public static Cache getCache() {
try {
return CacheFactory.getAnyInstance();
}
catch (CacheClosedException ignore) {
return null;
}
}
public static ClientCache getClientCache() {
try {
return ClientCacheFactory.getAnyInstance();
}
catch (CacheClosedException ignore) {
return null;
}
}
public static boolean isGemfireVersionGreaterThanEqual(double expectedVersion) {
double actualVersion = Double.parseDouble(GEMFIRE_VERSION.substring(0, 3));
return actualVersion >= expectedVersion;

View File

@@ -0,0 +1,201 @@
/*
* 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 java.util.concurrent.Callable;
import org.springframework.util.Assert;
import com.gemstone.gemfire.cache.CacheLoader;
import com.gemstone.gemfire.cache.CacheLoaderException;
import com.gemstone.gemfire.cache.LoaderHelper;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.TimeoutException;
/**
* The CallableCacheLoaderAdapter class is a {@link Callable} and GemFire {@link CacheLoader} implementation that
* adapts the {@link Callable} interface into an instance of the {@link CacheLoader} interface. This class is useful
* in situations where GemFire developers have several {@link CacheLoader} implementations that they wish to use
* with Spring's Cache Abstraction.
*
* @author John Blum
* @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
*/
@SuppressWarnings("unused")
public class CallableCacheLoaderAdapter<K, V> implements Callable<V>, CacheLoader<K, V> {
private final K key;
private final CacheLoader<K, V> cacheLoader;
private final Object argument;
private final Region<K, V> region;
/**
* Constructs an instance of the CallableCacheLoaderAdapter that delegates to the given {@link CacheLoader}.
*
* @param delegate the {@link CacheLoader} delegated to by this adapter.
* @see #CallableCacheLoaderAdapter(CacheLoader, Object, Region, Object)
* @see com.gemstone.gemfire.cache.CacheLoader
*/
public CallableCacheLoaderAdapter(CacheLoader<K, V> delegate) {
this(delegate, null, null, null);
}
/**
* Constructs an instance of the CallableCacheLoaderAdapter that delegates to the given {@link CacheLoader}
* and is initialized with the given key for which the value will be loaded along with the {@link Region}
* in which the entry (key/value) belongs.
*
* @param delegate the {@link CacheLoader} delegated to by this adapter.
* @param key the key for which the value will be loaded.
* @param region the {@link Region} in which the entry (key/value) belongs.
* @see #CallableCacheLoaderAdapter(CacheLoader, Object, Region, Object)
* @see com.gemstone.gemfire.cache.CacheLoader
* @see com.gemstone.gemfire.cache.Region
*/
public CallableCacheLoaderAdapter(CacheLoader<K, V> delegate, K key, Region<K, V> region) {
this(delegate, key, region, null);
}
/**
* Constructs an instance of the CallableCacheLoaderAdapter that delegates to the given {@link CacheLoader}
* and is initialized with the given key for which the value will be loaded along with the {@link Region}
* in which the entry (key/value) belongs. Additionally, an argument may be specified for use with the
* {@link CacheLoader} delegate.
*
* @param delegate the {@link CacheLoader} delegated to by this adapter.
* @param key the key for which the value will be loaded.
* @param region the {@link Region} in which the entry (key/value) belongs.
* @param argument the Object argument used with the {@link CacheLoader} delegate.
* @see #CallableCacheLoaderAdapter(CacheLoader, Object, Region, Object)
* @see com.gemstone.gemfire.cache.CacheLoader
* @see com.gemstone.gemfire.cache.Region
*/
public CallableCacheLoaderAdapter(CacheLoader<K, V> delegate, K key, Region<K, V> region, Object argument) {
Assert.notNull(delegate, "CacheLoader must not be null");
this.cacheLoader = delegate;
this.argument = argument;
this.key = key;
this.region = region;
}
/**
* Gets the argument used by this {@link CacheLoader} to load the value for the specified key.
*
* @return an Object argument used by this {@link CacheLoader} when loading the value for the specified key.
*/
protected Object getArgument() {
return argument;
}
/**
* The {@link CacheLoader} delegate used to actually load the cache value for the specified key.
*
* @return a reference to the actual {@link CacheLoader} used when loading the cache value for the specified key.
* @see com.gemstone.gemfire.cache.CacheLoader
*/
protected CacheLoader<K, V> getCacheLoader() {
return cacheLoader;
}
/**
* The specified key for which a value will be loaded by this {@link CacheLoader}.
*
* @return the specified key for which the value will be loaded.
*/
protected K getKey() {
return key;
}
/**
* Returns the Region to which the entry (key/value) belongs.
*
* @return the Region to which the entry belongs.
* @see com.gemstone.gemfire.cache.Region
*/
protected Region<K, V> getRegion() {
return region;
}
/**
* Invoked to load a cache value for the specified key. Delegates to {@link #load(LoaderHelper)}.
*
* @return the loaded cache value for the specified key.
* @throws java.lang.IllegalStateException if the {@link Region} or key references are null.
* @throws java.lang.Exception if the load operation fails.
* @see #load(LoaderHelper)
*/
public final V call() throws Exception {
Assert.state(getKey() != null, "The key for which the value is loaded for cannot be null");
Assert.state(getRegion() != null, "The Region to load cannot be null");
return load(new LoaderHelper<K, V>() {
public V netSearch(final boolean doNetLoad) throws CacheLoaderException, TimeoutException {
throw new UnsupportedOperationException("not implemented");
}
public K getKey() {
return CallableCacheLoaderAdapter.this.getKey();
}
public Region<K, V> getRegion() {
return CallableCacheLoaderAdapter.this.getRegion();
}
public Object getArgument() {
return CallableCacheLoaderAdapter.this.getArgument();
}
});
}
/**
* Closes any resources used by this {@link CacheLoader}. Delegates to the underlying {@link CacheLoader}.
*
* @see #getCacheLoader()
*/
public void close() {
getCacheLoader().close();
}
/**
* Loads a value for the specified cache (i.e. {@link Region}) and key with the help of the {@link LoaderHelper}.
* Delegates to the underlying {@link CacheLoader}.
*
* @param loaderHelper a {@link LoaderHelper} object passed in from cache service providing access to the key,
* {@link Region}, argument, and <code>netSearch</code>.
* @return the value supplied for the specified key, or null if no value can be supplied. A local loader will
* always be invoked if one exists. Otherwise one remote loader is invoked. Returning <code>null</code> causes
* {@link Region#get(Object, Object)} to return <code>null</code>.
* @throws CacheLoaderException if an error occurs during the load operation. This exception, or any other
* Exception thrown by this method will be propagated back to the application from the
* {@link Region#get(Object)} method.
* @see com.gemstone.gemfire.cache.CacheLoader#load(LoaderHelper)
* @see com.gemstone.gemfire.cache.LoaderHelper
* @see #getCacheLoader()
*/
public V load(LoaderHelper<K, V> loaderHelper) throws CacheLoaderException {
return getCacheLoader().load(loaderHelper);
}
}

View File

@@ -16,8 +16,11 @@
package org.springframework.data.gemfire.support;
import java.util.concurrent.Callable;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.util.ObjectUtils;
import com.gemstone.gemfire.cache.GemFireCache;
import com.gemstone.gemfire.cache.Region;
@@ -81,6 +84,33 @@ public class GemfireCache implements Cache {
return (T) value;
}
@SuppressWarnings("unchecked")
public <T> T get(final Object key, final Callable<T> valueLoader) {
T value = (T) get(key, Object.class);
if (value == null) {
synchronized (region) {
value = (T) get(key, Object.class);
if (value == null) {
try {
value = valueLoader.call();
put(key, value);
}
catch (Exception e) {
throw new RuntimeException(String.format(
"Failed to load value for key [%1$s] using valueLoader [%2$s]", key,
ObjectUtils.nullSafeClassName(valueLoader)));
//TODO throw ValueRetrievalException when SDG is based on Spring Framework 4.3
//throw new ValueRetrievalException(key, valueLoader, e);
}
}
}
}
return value;
}
@SuppressWarnings("unchecked")
public void put(final Object key, final Object value) {
if (value != null) {

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.*;
import static org.junit.Assert.*;
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;
}
}
}