From bb26f2d6780443709501857cafbeec4b20d246f6 Mon Sep 17 00:00:00 2001 From: Costin Leau Date: Tue, 22 Feb 2011 19:07:38 +0200 Subject: [PATCH] Gemfire implementations for Spring 3.1 caching abstraction --- pom.xml | 2 +- .../data/gemfire/support/GemfireCache.java | 180 ++++++++++++++++++ .../gemfire/support/GemfireCacheManager.java | 81 ++++++++ .../data/gemfire/support/package-info.java | 10 + .../support/AbstractNativeCacheTest.java | 155 +++++++++++++++ .../gemfire/support/GemfireCacheTest.java | 57 ++++++ 6 files changed, 484 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/springframework/data/gemfire/support/GemfireCache.java create mode 100644 src/main/java/org/springframework/data/gemfire/support/GemfireCacheManager.java create mode 100644 src/main/java/org/springframework/data/gemfire/support/package-info.java create mode 100644 src/test/java/org/springframework/data/gemfire/support/AbstractNativeCacheTest.java create mode 100644 src/test/java/org/springframework/data/gemfire/support/GemfireCacheTest.java diff --git a/pom.xml b/pom.xml index 627892fa..860f2bc4 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ limitations under the License. - 3.0.5.RELEASE + 3.1.0.M1 4.7 diff --git a/src/main/java/org/springframework/data/gemfire/support/GemfireCache.java b/src/main/java/org/springframework/data/gemfire/support/GemfireCache.java new file mode 100644 index 00000000..b2b4d627 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/support/GemfireCache.java @@ -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 extends AbstractDelegatingCache { + + 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 region; + private final boolean canUseLock; + private final boolean canUseConcurrentMap; + + /** + * Creates a {@link GemFireCache} instance. + * + * @param region backing GemFire region + */ + public GemfireCache(Region 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 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(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/gemfire/support/GemfireCacheManager.java b/src/main/java/org/springframework/data/gemfire/support/GemfireCacheManager.java new file mode 100644 index 00000000..4aae64ca --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/support/GemfireCacheManager.java @@ -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> 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> regions = gemfireCache.rootRegions(); + Collection> caches = new LinkedHashSet>(regions.size()); + + for (Region region : regions) { + caches.add(new GemfireCache(region)); + } + + return caches; + } + + public Cache getCache(String name) { + Cache cache = super.getCache(name); + if (cache == null) { + // check the gemfire cache again + // in case the cache was added at runtime + + Region 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; + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/gemfire/support/package-info.java b/src/main/java/org/springframework/data/gemfire/support/package-info.java new file mode 100644 index 00000000..61b59613 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/support/package-info.java @@ -0,0 +1,10 @@ + +/** + * + * Support package for Spring Gemfire integration. + * + *

Provides Spring 3.1 caching support (Cache and CacheManager implementations on top of Gemfire APIs). + * + */ +package org.springframework.data.gemfire.support; + diff --git a/src/test/java/org/springframework/data/gemfire/support/AbstractNativeCacheTest.java b/src/test/java/org/springframework/data/gemfire/support/AbstractNativeCacheTest.java new file mode 100644 index 00000000..d8f863d6 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/support/AbstractNativeCacheTest.java @@ -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 { + + 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)); + } +} \ No newline at end of file diff --git a/src/test/java/org/springframework/data/gemfire/support/GemfireCacheTest.java b/src/test/java/org/springframework/data/gemfire/support/GemfireCacheTest.java new file mode 100644 index 00000000..1180348a --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/support/GemfireCacheTest.java @@ -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> { + + @Override + protected Cache createCache(Region nativeCache) { + return new GemfireCache(nativeCache); + } + + @Override + protected Region 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; + } +}