diff --git a/.classpath b/.classpath index 23fb3b56a..2dd69c909 100644 --- a/.classpath +++ b/.classpath @@ -1,10 +1,37 @@ + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.project b/.project index 97c950f78..862bb3d6d 100644 --- a/.project +++ b/.project @@ -1,34 +1,16 @@ spring-data-redis - - - + Spring Data Redis + + + org.eclipse.jdt.core.javanature + org.eclipse.jdt.core.javabuilder - - + - - com.springsource.sts.gradle.core.nature - org.eclipse.jdt.core.javanature - - - - 1309881117241 - - 10 - - org.eclipse.ui.ide.orFilterMatcher - - - org.eclipse.ui.ide.multiFilter - 1.0-projectRelativePath-equals-true-false-E:docs - - - - - + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 429280cd1..953c45070 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,12 +1,13 @@ -#Tue Jul 05 18:51:57 EEST 2011 +# +#Fri Jul 15 15:16:09 EEST 2011 +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.debug.lineNumber=generate eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 -org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.6 -org.eclipse.jdt.core.compiler.debug.lineNumber=generate -org.eclipse.jdt.core.compiler.debug.localVariable=generate -org.eclipse.jdt.core.compiler.debug.sourceFile=generate -org.eclipse.jdt.core.compiler.problem.assertIdentifier=error -org.eclipse.jdt.core.compiler.problem.enumIdentifier=error org.eclipse.jdt.core.compiler.source=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error diff --git a/gradle.properties b/gradle.properties index d3ecb7b24..60bb9953d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,8 +5,8 @@ log4jVersion = 1.2.16 slf4jVersion = 1.6.1 # Common libraries -springVersion = 3.0.5.RELEASE -jacksonVersion = 1.6.4 +springVersion = 3.1.0.M2 +jacksonVersion = 1.8.3 # Testing junitVersion = 4.8.1 @@ -20,7 +20,7 @@ rjcVersion= 0.6.4 # Manifest properties ## OSGi ranges -spring.range = "[3.0.0, 4.0.0)" +spring.range = "[3.1.0, 4.0.0)" jedis.range = "[2.0.0, 2.0.0]" jackson.range = "[1.6, 2.0.0)" rjc.range = "[0.6.4, 0.6.4]" diff --git a/src/main/java/org/springframework/data/redis/cache/RedisCache.java b/src/main/java/org/springframework/data/redis/cache/RedisCache.java new file mode 100644 index 000000000..2e34accf8 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/cache/RedisCache.java @@ -0,0 +1,159 @@ +/* + * Copyright 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.redis.cache; + +import java.util.Arrays; +import java.util.Set; + +import org.springframework.cache.Cache; +import org.springframework.cache.interceptor.DefaultValueWrapper; +import org.springframework.dao.DataAccessException; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.data.redis.support.collections.DefaultRedisSet; +import org.springframework.data.redis.support.collections.RedisSet; +import org.springframework.util.Assert; + +/** + * Cache implementation on top of Redis. + * + * @author Costin Leau + */ +@SuppressWarnings("unchecked") +public class RedisCache implements Cache { + + private final String name; + private final RedisSet keys; + private final RedisTemplate template; + private final byte[] nameAsBytes; + private final byte[] setName; + + /** + * + * Constructs a new RedisCache instance. + * + * @param name cache name + */ + public RedisCache(String name, RedisTemplate template) { + Assert.hasText(name, "non-empty cache name is required"); + this.name = name; + this.template = template; + + StringRedisSerializer stringSerializer = new StringRedisSerializer(); + + this.nameAsBytes = stringSerializer.serialize(name + ":"); + + + // extract connection and create an ops for set + RedisConnectionFactory connectionFactory = template.getConnectionFactory(); + RedisTemplate setTemplate = new RedisTemplate(); + setTemplate.setConnectionFactory(connectionFactory); + setTemplate.setDefaultSerializer(template.getDefaultSerializer()); + setTemplate.setKeySerializer(stringSerializer); + + // the values of the set are the cache keys + setTemplate.setValueSerializer(template.getKeySerializer()); + setTemplate.afterPropertiesSet(); + + String sName = name + "-keys"; + this.keys = new DefaultRedisSet(sName, setTemplate); + this.setName = stringSerializer.serialize(sName); + } + + @Override + public String getName() { + return name; + } + + @Override + /** + * {@inheritDoc} + * + * This implementation simply returns the RedisTemplate used for configuring the cache, giving access + * to the underlying Redis store. + */ + public Object getNativeCache() { + return template; + } + + @Override + public ValueWrapper get(final Object key) { + return (ValueWrapper) template.execute(new RedisCallback() { + @Override + public ValueWrapper doInRedis(RedisConnection connection) throws DataAccessException { + byte[] bs = connection.get(computeKey(key)); + return (bs == null ? null : new DefaultValueWrapper(template.getValueSerializer().deserialize(bs))); + } + }); + } + + @Override + public void put(final Object key, final Object value) { + final byte[] k = computeKey(key); + + template.execute(new RedisCallback() { + public Object doInRedis(RedisConnection connection) throws DataAccessException { + connection.set(k, template.getValueSerializer().serialize(value)); + connection.sAdd(setName, k); + return null; + } + }); + } + + @Override + public void evict(Object key) { + final byte[] k = computeKey(key); + + template.execute(new RedisCallback() { + public Object doInRedis(RedisConnection connection) throws DataAccessException { + connection.del(k); + // remove key from set + connection.sRem(setName, k); + return null; + } + }); + } + + @Override + public void clear() { + // need to del each key individually + template.execute(new RedisCallback() { + public Object doInRedis(RedisConnection connection) throws DataAccessException { + // need to paginate the keys + Set keys = connection.sMembers(setName); + for (byte[] bs : keys) { + connection.del(bs); + } + connection.del(setName); + return null; + } + }); + } + + private byte[] computeKey(Object key) { + RedisSerializer keySerializer = template.getKeySerializer(); + byte[] k = keySerializer.serialize(key); + + byte[] result = Arrays.copyOf(nameAsBytes, nameAsBytes.length + k.length); + System.arraycopy(k, 0, result, nameAsBytes.length, k.length); + return result; + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/redis/cache/RedisCacheManager.java b/src/main/java/org/springframework/data/redis/cache/RedisCacheManager.java new file mode 100644 index 000000000..ece643021 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/cache/RedisCacheManager.java @@ -0,0 +1,58 @@ +/* + * Copyright 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.redis.cache; + +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.data.redis.core.RedisTemplate; + +/** + * CacheManager implementation for Redis. + * + * @author Costin Leau + */ +public class RedisCacheManager implements CacheManager { + + // fast lookup by name map + private final ConcurrentMap caches = new ConcurrentHashMap(); + private final Collection names = Collections.unmodifiableSet(caches.keySet()); + private final RedisTemplate template; + + public RedisCacheManager(RedisTemplate template) { + this.template = template; + } + + public Cache getCache(String name) { + Cache c = caches.get(name); + if (c == null) { + c = new RedisCache(name, template); + caches.put(name, c); + names.add(name); + } + + return c; + } + + public Collection getCacheNames() { + return names; + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/redis/cache/package-info.java b/src/main/java/org/springframework/data/redis/cache/package-info.java new file mode 100644 index 000000000..701a830e4 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/cache/package-info.java @@ -0,0 +1,5 @@ +/** + * Package providing a Redis implementation for Spring cache abstraction. + */ +package org.springframework.data.redis.cache; + diff --git a/src/test/java/org/springframework/data/redis/cache/AbstractNativeCacheTest.java b/src/test/java/org/springframework/data/redis/cache/AbstractNativeCacheTest.java new file mode 100644 index 000000000..14fc6fd6a --- /dev/null +++ b/src/test/java/org/springframework/data/redis/cache/AbstractNativeCacheTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 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.redis.cache; + +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); + + protected abstract Object getObject(); + + @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 = getObject(); + Object value = getObject(); + + assertNull(cache.get(key)); + cache.put(key, value); + assertEquals(value, cache.get(key).get()); + } + + @Test + public void testCacheClear() throws Exception { + Object key1 = getObject(); + Object value1 = getObject(); + + + Object key2 = getObject(); + Object value2 = getObject(); + + assertNull(cache.get(key1)); + cache.put(key1, value1); + assertNull(cache.get(key2)); + cache.put(key2, value2); + cache.clear(); + assertNull(cache.get(key2)); + assertNull(cache.get(key1)); + } +} \ No newline at end of file diff --git a/src/test/java/org/springframework/data/redis/cache/RedisCacheTest.java b/src/test/java/org/springframework/data/redis/cache/RedisCacheTest.java new file mode 100644 index 000000000..5c9920f0d --- /dev/null +++ b/src/test/java/org/springframework/data/redis/cache/RedisCacheTest.java @@ -0,0 +1,79 @@ +/* + * Copyright 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.redis.cache; + +import java.util.Collection; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.springframework.cache.Cache; +import org.springframework.data.redis.ConnectionFactoryTracker; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.support.collections.CollectionTestParams; +import org.springframework.data.redis.support.collections.ObjectFactory; + +/** + * @author Costin Leau + */ +@RunWith(Parameterized.class) +public class RedisCacheTest extends AbstractNativeCacheTest { + + private ObjectFactory objFactory; + private RedisTemplate template; + + + public RedisCacheTest(ObjectFactory objFactory, RedisTemplate template) { + this.objFactory = objFactory; + this.template = template; + ConnectionFactoryTracker.add(template.getConnectionFactory()); + } + + @Parameters + public static Collection testParams() { + return CollectionTestParams.testParams(); + } + + @Override + protected Cache createCache(RedisTemplate nativeCache) { + return new RedisCache(CACHE_NAME, nativeCache); + } + + @Override + protected RedisTemplate createNativeCache() throws Exception { + return template; + } + + @Before + public void setUp() throws Exception { + ConnectionFactoryTracker.add(template.getConnectionFactory()); + super.setUp(); + + } + + @AfterClass + public static void cleanUp() { + ConnectionFactoryTracker.cleanUp(); + } + + @Override + protected Object getObject() { + return objFactory.instance(); + } +} \ No newline at end of file diff --git a/src/test/java/org/springframework/data/redis/core/TemplateTest.java b/src/test/java/org/springframework/data/redis/core/TemplateTest.java index a515edf17..5aac04dc4 100644 --- a/src/test/java/org/springframework/data/redis/core/TemplateTest.java +++ b/src/test/java/org/springframework/data/redis/core/TemplateTest.java @@ -15,7 +15,7 @@ */ package org.springframework.data.redis.core; -import static org.junit.Assert.*; +import static org.junit.Assert.assertTrue; import java.util.Collection; @@ -25,7 +25,6 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import org.springframework.data.redis.ConnectionFactoryTracker; -import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.support.collections.CollectionTestParams; import org.springframework.data.redis.support.collections.ObjectFactory;