Gemfire implementations for Spring 3.1 caching abstraction
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -59,7 +59,7 @@ limitations under the License.
|
||||
</licenses>
|
||||
|
||||
<properties>
|
||||
<spring.version>3.0.5.RELEASE</spring.version>
|
||||
<spring.version>3.1.0.M1</spring.version>
|
||||
<junit.version>4.7</junit.version>
|
||||
|
||||
<!-- javadoc configuration -->
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright 2010-2011 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.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
import org.springframework.cache.support.AbstractDelegatingCache;
|
||||
|
||||
import com.gemstone.gemfire.cache.DataPolicy;
|
||||
import com.gemstone.gemfire.cache.GemFireCache;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
|
||||
/**
|
||||
* Spring Framework {@link Cache} implementation using a GemFire {@link Region} underneath.
|
||||
* Supports both Gemfire 6.5 and 6.0.
|
||||
*
|
||||
* @author Costin Leau
|
||||
*/
|
||||
public class GemfireCache<K, V> extends AbstractDelegatingCache<K, V> {
|
||||
|
||||
private static class NoOpLock implements Lock {
|
||||
|
||||
public void lock() {
|
||||
}
|
||||
|
||||
public void lockInterruptibly() throws InterruptedException {
|
||||
}
|
||||
|
||||
public Condition newCondition() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean tryLock() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void unlock() {
|
||||
}
|
||||
};
|
||||
|
||||
private static final Lock NO_OP_LOCK = new NoOpLock();
|
||||
|
||||
private static final boolean hasConcurrentMap = ConcurrentMap.class.isAssignableFrom(Region.class);
|
||||
private final Region<K, V> region;
|
||||
private final boolean canUseLock;
|
||||
private final boolean canUseConcurrentMap;
|
||||
|
||||
/**
|
||||
* Creates a {@link GemFireCache} instance.
|
||||
*
|
||||
* @param region backing GemFire region
|
||||
*/
|
||||
public GemfireCache(Region<K, V> region) {
|
||||
super(region);
|
||||
this.region = region;
|
||||
this.canUseLock = region.getAttributes().getScope().isGlobal();
|
||||
DataPolicy dataPolicy = region.getAttributes().getDataPolicy();
|
||||
this.canUseConcurrentMap = (hasConcurrentMap && (!dataPolicy.isNormal() && !dataPolicy.isEmpty()));
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return region.getName();
|
||||
}
|
||||
|
||||
public Region<K, V> getNativeCache() {
|
||||
return region;
|
||||
}
|
||||
|
||||
public V putIfAbsent(K key, V value) {
|
||||
if (canUseConcurrentMap) {
|
||||
return region.putIfAbsent(key, value);
|
||||
}
|
||||
|
||||
// fall back to pre 6.5 API
|
||||
Lock lock = (canUseLock ? region.getDistributedLock(key) : NO_OP_LOCK);
|
||||
try {
|
||||
lock.lock();
|
||||
if (!region.containsKey(key)) {
|
||||
return region.put(key, value);
|
||||
}
|
||||
else {
|
||||
return region.get(key);
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean remove(Object key, Object value) {
|
||||
if (canUseConcurrentMap) {
|
||||
return region.remove(key, value);
|
||||
}
|
||||
|
||||
// fall back to pre 6.5 API
|
||||
if (region.containsKey(key)) {
|
||||
Lock lock = (canUseLock ? region.getDistributedLock((K) key) : NO_OP_LOCK);
|
||||
try {
|
||||
lock.lock();
|
||||
|
||||
if (region.get(key).equals(value)) {
|
||||
region.remove(key);
|
||||
return true;
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean replace(K key, V oldValue, V newValue) {
|
||||
if (canUseConcurrentMap) {
|
||||
return region.replace(key, oldValue, newValue);
|
||||
}
|
||||
|
||||
if (region.containsKey(key)) {
|
||||
// fall back to pre 6.5 API
|
||||
Lock lock = (canUseLock ? region.getDistributedLock(key) : NO_OP_LOCK);
|
||||
|
||||
try {
|
||||
lock.lock();
|
||||
|
||||
if (region.get(key).equals(oldValue)) {
|
||||
region.put(key, newValue);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public V replace(K key, V value) {
|
||||
if (canUseConcurrentMap) {
|
||||
return region.replace(key, value);
|
||||
}
|
||||
|
||||
// fall back to pre 6.5 API
|
||||
Lock lock = (canUseLock ? region.getDistributedLock(key) : NO_OP_LOCK);
|
||||
|
||||
try {
|
||||
lock.lock();
|
||||
|
||||
if (region.containsKey(key)) {
|
||||
return region.put(key, value);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2010-2011 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.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.support.AbstractCacheManager;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
|
||||
/**
|
||||
* Spring Framework {@link CacheManager} backed by a Gemfire {@link com.gemstone.gemfire.cache.Cache}. Automatically
|
||||
* discovers the created caches (or {@link Region}s in Gemfire terminology).
|
||||
*
|
||||
* @author Costin Leau
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class GemfireCacheManager extends AbstractCacheManager {
|
||||
|
||||
private com.gemstone.gemfire.cache.Cache gemfireCache;
|
||||
|
||||
|
||||
@Override
|
||||
protected Collection<Cache<?, ?>> loadCaches() {
|
||||
Assert.notNull(gemfireCache, "a backing GemFire cache is required");
|
||||
Assert.isTrue(!gemfireCache.isClosed(), "the GemFire cache is closed; an open instance is required");
|
||||
|
||||
Set<Region<?, ?>> regions = gemfireCache.rootRegions();
|
||||
Collection<Cache<?, ?>> caches = new LinkedHashSet<Cache<?, ?>>(regions.size());
|
||||
|
||||
for (Region<?, ?> region : regions) {
|
||||
caches.add(new GemfireCache(region));
|
||||
}
|
||||
|
||||
return caches;
|
||||
}
|
||||
|
||||
public <K, V> Cache<K, V> getCache(String name) {
|
||||
Cache<K, V> cache = super.getCache(name);
|
||||
if (cache == null) {
|
||||
// check the gemfire cache again
|
||||
// in case the cache was added at runtime
|
||||
|
||||
Region<K, V> reg = gemfireCache.getRegion(name);
|
||||
if (reg != null) {
|
||||
cache = new GemfireCache(reg);
|
||||
getCacheMap().put(name, cache);
|
||||
}
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the GemFire Cache backing this {@link CacheManager}.
|
||||
*
|
||||
* @param gemfireCache
|
||||
*/
|
||||
public void setCache(com.gemstone.gemfire.cache.Cache gemfireCache) {
|
||||
this.gemfireCache = gemfireCache;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
/**
|
||||
*
|
||||
* Support package for Spring Gemfire integration.
|
||||
*
|
||||
* <p/>Provides Spring 3.1 caching support (Cache and CacheManager implementations on top of Gemfire APIs).
|
||||
*
|
||||
*/
|
||||
package org.springframework.data.gemfire.support;
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright 2010 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.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.cache.Cache;
|
||||
|
||||
/**
|
||||
* Test for native cache implementations.
|
||||
*
|
||||
* @author Costin Leau
|
||||
*/
|
||||
public abstract class AbstractNativeCacheTest<T> {
|
||||
|
||||
private T nativeCache;
|
||||
private Cache cache;
|
||||
protected final static String CACHE_NAME = "testCache";
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
nativeCache = createNativeCache();
|
||||
cache = createCache(nativeCache);
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
|
||||
protected abstract T createNativeCache() throws Exception;
|
||||
|
||||
protected abstract Cache createCache(T nativeCache);
|
||||
|
||||
|
||||
@Test
|
||||
public void testCacheName() throws Exception {
|
||||
assertEquals(CACHE_NAME, cache.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNativeCache() throws Exception {
|
||||
assertSame(nativeCache, cache.getNativeCache());
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheRemove() throws Exception {
|
||||
Object key = "enescu";
|
||||
Object value = "george";
|
||||
|
||||
assertNull(cache.get(key));
|
||||
cache.put(key, value);
|
||||
assertEquals(value, cache.remove(key));
|
||||
assertNull(cache.get(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheClear() throws Exception {
|
||||
assertNull(cache.get("enescu"));
|
||||
cache.put("enescu", "george");
|
||||
assertNull(cache.get("vlaicu"));
|
||||
cache.put("vlaicu", "aurel");
|
||||
cache.clear();
|
||||
assertNull(cache.get("vlaicu"));
|
||||
assertNull(cache.get("enescu"));
|
||||
}
|
||||
|
||||
// concurrent map tests
|
||||
@Test
|
||||
public void testPutIfAbsent() throws Exception {
|
||||
Object key = "enescu";
|
||||
Object value1 = "george";
|
||||
Object value2 = "geo";
|
||||
|
||||
assertNull(cache.get("enescu"));
|
||||
cache.put(key, value1);
|
||||
cache.putIfAbsent(key, value2);
|
||||
assertEquals(value1, cache.get(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConcurrentRemove() throws Exception {
|
||||
Object key = "enescu";
|
||||
Object value1 = "george";
|
||||
Object value2 = "geo";
|
||||
|
||||
assertNull(cache.get("enescu"));
|
||||
cache.put(key, value1);
|
||||
// no remove
|
||||
cache.remove(key, value2);
|
||||
assertEquals(value1, cache.get(key));
|
||||
// one remove
|
||||
cache.remove(key, value1);
|
||||
assertNull(cache.get("enescu"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConcurrentReplace() throws Exception {
|
||||
Object key = "enescu";
|
||||
Object value1 = "george";
|
||||
Object value2 = "geo";
|
||||
|
||||
assertNull(cache.get("enescu"));
|
||||
cache.put(key, value1);
|
||||
cache.replace(key, value2);
|
||||
assertEquals(value2, cache.get(key));
|
||||
cache.remove(key);
|
||||
cache.replace(key, value1);
|
||||
assertNull(cache.get("enescu"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConcurrentReplaceIfEqual() throws Exception {
|
||||
Object key = "enescu";
|
||||
Object value1 = "george";
|
||||
Object value2 = "geo";
|
||||
|
||||
assertNull(cache.get("enescu"));
|
||||
cache.put(key, value1);
|
||||
assertEquals(value1, cache.get(key));
|
||||
// no replace
|
||||
cache.replace(key, value2, value1);
|
||||
assertEquals(value1, cache.get(key));
|
||||
cache.replace(key, value1, value2);
|
||||
assertEquals(value2, cache.get(key));
|
||||
cache.replace(key, value2, value1);
|
||||
assertEquals(value1, cache.get(key));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2010 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.Properties;
|
||||
|
||||
import org.springframework.cache.Cache;
|
||||
|
||||
import com.gemstone.gemfire.cache.AttributesFactory;
|
||||
import com.gemstone.gemfire.cache.CacheFactory;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.distributed.DistributedSystem;
|
||||
|
||||
/**
|
||||
* @author Costin Leau
|
||||
*/
|
||||
public class GemfireCacheTest extends AbstractNativeCacheTest<Region<Object, Object>> {
|
||||
|
||||
@Override
|
||||
protected Cache createCache(Region<Object, Object> nativeCache) {
|
||||
return new GemfireCache<Object, Object>(nativeCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region<Object, Object> createNativeCache() throws Exception {
|
||||
com.gemstone.gemfire.cache.Cache instance = null;
|
||||
try {
|
||||
instance = CacheFactory.getAnyInstance();
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
|
||||
if (instance == null) {
|
||||
DistributedSystem ds = DistributedSystem.connect(new Properties());
|
||||
instance = CacheFactory.create(ds);
|
||||
}
|
||||
Region reg = instance.getRegion(CACHE_NAME);
|
||||
if (reg == null) {
|
||||
reg = instance.createRegion(CACHE_NAME, new AttributesFactory().create());
|
||||
}
|
||||
|
||||
return reg;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user