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:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user