mget(String... keys);
+
+ //TODO mgetAsBytes? Best to have byte[] overloads somewhere else?
+
+ /**
+ * SETNX works exactly like SET with the only difference that if the key already exists no operation is performed.
+ * SETNX actually means "SET if Not eXists".
+ * Time complexity: O(1)
+ * Corresponds to command "SETNX key value"
+ * @see SetnxCommand
+ * @param key key whose associated value is to be set
+ * @param value value to be associated with the specified key
+ * @return 1 if the key was set, 0 if the key was not set
+ */
+ Integer setnx(String key, String value);
+
+ /**
+ * The command is exactly equivalent to the following group of commands:
+ * SET key value
+ * EXPIRE key time
+ *
+ * Time complexity: O(1)
+ * @see SetexCommand
+ * @param key key whose associated value is to be set
+ * @param seconds timeout on the specified key. After the timeout the key will automatically be deleted by the server
+ * @param value timeout in seconds
+ * @return Status reply code, OK is success
+ */
+ String setex(String key, int seconds, String value);
+
+ /**
+ * Set the the respective keys to the respective values.
+ * Time complexity: O(1) to set every key
+ * Corresponds to the command "MSET key1 value1 key2 value2 ... keyN valueN"
+ * @see MsetCommand
+ * @param keysvalues key value sequence
+ * @return OK as MSET can't fail.
+ */
+ //TODO Consider Map here or in template? Map ?
+ String mset(String... keysvalues);
+
+ Integer msetnx(String... keysvalues);
+
+ Integer incrBy(String key, int increment);
+
+ Integer incr(String key);
+
+ Integer decr(String key);
+
+ Integer decrBy(String key, int decrement);
+
+ //TODO incrementByOne,decrementByOne in template
+
+ /**
+ * If the key already exists and is a string, this command appends the provided value at the
+ * end of the string. If the key does not exist it is created and set as an empty string,
+ * so APPEND will be very similar to SET in this special case.
+ * @see AppendCommand
+ * @param key key whose associated value is to be appended
+ * @param value value to be appended to end of current value associated with the specified key
+ * @return the total length of the string after the append operation.
+ */
+ Integer append(String key, String value);
+
+ String substr(String key, int start, int end);
+
+
+ // Commands operating on all value types "KeySpaceOperations"
+
+ Integer exists(String key);
+
+ Integer del(String... keys);
+
+ String type(String key);
+
+ List keys(String pattern);
+
+ String randomKey();
+
+ String rename(String oldkey, String newkey);
+
+ Integer renamenx(String oldkey, String newkey);
+
+ Integer expire(String key, int seconds);
+
+ Integer expireAt(String key, long unixTime);
+
+ Integer ttl(String key);
+
+ Integer persist(String key);
+
+
+
+
+ // Probably not possible to abstract at this level across different providers....
+ // T sendCommand(String commandName, ReplyTypeMapper mapper, String... commandArgs);
+
+ // Commands operating on Sets
+
+ Integer sadd(String key, String member);
+
+ Set smembers(String key);
+
+ Integer srem(String key, String member);
+
+ String spop(String key);
+
+ Integer smove(String srckey, String dstkey, String member);
+
+ Integer scard(String key);
+
+ Integer sismember(String key, String member);
+
+ Set sinter(String... keys);
+
+ Integer sinterstore(String dstkey, String... keys);
+
+ Set sunion(String... keys);
+
+ Integer sunionstore(String dstkey, String... keys);
+
+ Set sdiff(String... keys);
+
+ Integer sdiffstore(String dstkey, String... keys);
+
+ String srandmember(String key);
+
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/RedisClientFactory.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/RedisClientFactory.java
new file mode 100644
index 000000000..3ac6358a0
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/RedisClientFactory.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2002-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.datastore.redis.core;
+
+
+import org.springframework.datastore.redis.support.RedisPersistenceExceptionTranslator;
+
+/**
+ * An interface based ConnectionFactory for creating {@link RedisClient}s.
+ *
+ * @author Mark Pollack
+ *
+ */
+public interface RedisClientFactory {
+
+ RedisClient createClient();
+
+ void setPassword(String password);
+
+ RedisPersistenceExceptionTranslator getExceptionTranslator();
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/RedisOperations.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/RedisOperations.java
new file mode 100644
index 000000000..b677521dd
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/RedisOperations.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-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.datastore.redis.core;
+
+import java.util.List;
+
+import org.springframework.dao.DataAccessException;
+
+/**
+ * Interface specifying a set of Redis operations.
+ * Implemented by {@link RedisTemplate}.
+ *
+ * @author Mark Pollack
+ *
+ */
+public interface RedisOperations extends KeyValueOperations {
+
+ T execute(RedisCallback action) throws DataAccessException;
+
+ ServerOperations getServerOperations();
+
+ ListOperations getListOperations();
+
+ SetOperations getSetOperations();
+
+
+
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/RedisTemplate.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/RedisTemplate.java
new file mode 100644
index 000000000..5594cb431
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/RedisTemplate.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright 2002-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.datastore.redis.core;
+
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.DataRetrievalFailureException;
+import org.springframework.datastore.redis.support.RedisUtils;
+import org.springframework.datastore.redis.support.converter.DefaultRedisConverter;
+import org.springframework.datastore.redis.support.converter.RedisConverter;
+import org.springframework.util.Assert;
+
+/**
+ * This is the central class in the Redis core package.
+ * It simplifies the use of Redis and helps to avoid common errors.
+ *
+ * @author Mark Pollack
+ *
+ */
+public class RedisTemplate extends RedisAccessor implements RedisOperations {
+
+ // TODO perform validation to see if value size > 1GB
+ // TODO perform validation to see if key contains space, newline or whitespace.
+ // TODO warning on key size being large > 1024 bytes?
+
+ private RedisConverter redisConverter = new DefaultRedisConverter();
+
+ private ServerOperations serverOperations;
+
+ public RedisTemplate() {
+ initDefaults();
+ }
+ public RedisTemplate(RedisClientFactory redisClientFactory) {
+ this();
+ this.setRedisClientFactory(redisClientFactory);
+ afterPropertiesSet();
+ }
+
+ public void setRedisConverter(RedisConverter redisConverter) {
+ this.redisConverter = redisConverter;
+ }
+
+ protected void initDefaults() {
+ serverOperations = new DefaultServerOperations(this);
+ }
+
+
+
+ public T execute(RedisCallback action) {
+ Assert.notNull(action, "Callback object must not be null");
+
+ RedisClient clientToClose = null;
+ try {
+ RedisClient clientToUse = null; //ConnectionFactoryUtils.doGetTransacxtionChannel(getConnectionFactory, this.transactionResourceFactory);
+ if (clientToUse == null) {
+ clientToClose = createClient();
+ clientToUse = clientToClose;
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("Executing callback on Redis Client: " + clientToUse);
+ }
+ return action.doInRedis(clientToUse);
+ }
+ catch (Exception e) {
+ throw convertRedisAccessException(e);
+ } finally {
+ RedisUtils.closeClient(clientToClose);
+ }
+ }
+
+ protected DataAccessException convertRedisAccessException(Exception ex) {
+ //TODO
+ return null;
+ }
+
+ public ServerOperations getServerOperations() {
+ return serverOperations;
+ }
+
+ public ListOperations getListOperations() {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+
+ // Key Value Operations
+
+ public String get(final String key) {
+ return execute(new RedisCallback() {
+ public String doInRedis(RedisClient redisClient) throws Exception {
+ return redisClient.get(key);
+ }
+ });
+ }
+
+ public byte[] getAsBytes(final String key) {
+ return execute(new RedisCallback() {
+ public byte[] doInRedis(RedisClient redisClient) throws Exception {
+ return redisClient.getAsBytes(key);
+ }
+ });
+ }
+
+ public T getAndConvert(String key, Class requiredType) {
+ //TODO deserializer exceptions need to be under DAO exception hierarchy.
+ Object object = redisConverter.deserialize(getAsBytes(key));
+ if (requiredType != null && object != null && !requiredType.isAssignableFrom(object.getClass())) {
+ throw new DataRetrievalFailureException("Can not assign from " + requiredType + " to " + object.getClass());
+ }
+ return (T) object;
+ }
+
+ public String getAndSet(String key, String value) {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+
+ public byte[] getAndSetBytes(String key, byte[] value) {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+
+ public T getAndSetObject(String key, T value, Class requiredType) {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+
+ public void set(final String key, final String value) {
+ execute(new RedisCallback() {
+ public Void doInRedis(RedisClient redisClient) throws Exception {
+ redisClient.set(key, value);
+ return null;
+ }
+ });
+ }
+
+ public void set(String key, String value, long expiryInMillis) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void setAsBytes(final String key, final byte[] value) {
+ execute(new RedisCallback() {
+ public Void doInRedis(RedisClient redisClient) throws Exception {
+ redisClient.set(key, value);
+ return null;
+ }
+ });
+
+ }
+
+ public void setAsBytes(String key, byte[] value, long expiryInMillis) {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+
+ public void setIfKeyNonExistent(String key, String value) {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+
+ public void setIfKeyNonExistent(String key, byte[] value) {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+
+ public void setMultiple(Map keysAndValues) {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+
+ public void setMultipleAsBytes(Map keysAndValues) {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+
+ public void setMultipleAsBytesIfKeysNonExistent(
+ Map keysAndValues) {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+
+ public void setMultipleIfKeysNonExistent(Map keysAndValues) {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+
+ public int append(String key, String value) {
+ throw new RuntimeException("unimplemented");
+ }
+
+ public void convertAndSet(String key, Object value) {
+ setAsBytes(key, this.redisConverter.serialize(value));
+ }
+
+ public void convertAndSet(String key, Object value, long expiryInMillis) {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+
+ public void convertAndSetIfKeyNonExistent(String key, Object value) {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+
+ public void convertAndSetMultiple(Map keysAndValues) {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+
+ public void convertAndSetMultipleIfKeysNonExistent(
+ Map keysAndValues) {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+
+ public int decrement(String key) {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+
+ public int decrementBy(String key, int value) {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+
+ public List getValues(List keys) {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+
+ public int increment(String key) {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+
+ public int incrementBy(String key, int value) {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+
+ public String subString(String key, int fromIndex, int toIndex) {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("unimplemented");
+ }
+ public List getAndConvertValues(List keys,
+ Class requiredType) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+ public String getSubString(String key, int fromIndex, int toIndex) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+ public boolean containsKey(String key) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+ public boolean deleteKeys(final String... keys) {
+ return execute(new RedisCallback() {
+ public Boolean doInRedis(RedisClient redisClient) throws Exception {
+ Integer intVal = redisClient.del(keys);
+ return (intVal == 0) ? false : true;
+ }
+ });
+ }
+
+ public SetOperations getSetOperations() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+
+
+
+
+}
\ No newline at end of file
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/ServerOperations.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/ServerOperations.java
new file mode 100644
index 000000000..10e270757
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/ServerOperations.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2002-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.datastore.redis.core;
+
+import java.util.Map;
+
+/**
+ * Server operations for Redis
+ *
+ * @author Mark Pollack
+ *
+ */
+public interface ServerOperations {
+
+
+ // Connection handling
+
+
+
+
+ /**
+ * Calls the Redis 'info' command that returns different information and statistics about the server.
+ * The reply is parsed into a Map for easy programmatic access.
+ * Corresponds to the Redis InfoCommand INFO
+ * @see InfoCommand
+ * @return
+ */
+ Map getServerInfo();
+
+ // TODO Commands Monitor, SlaveOf, Config
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/SetOperations.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/SetOperations.java
new file mode 100644
index 000000000..d3026e649
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/SetOperations.java
@@ -0,0 +1,35 @@
+package org.springframework.datastore.redis.core;
+
+import java.util.Set;
+
+public interface SetOperations {
+
+ boolean add(String key, String member);
+
+ Set getAll(String key);
+
+ boolean remove(String key, String member);
+
+ boolean removeRandom(String key);
+
+ boolean moveBetweenSets(String srckey, String dstkey, String member);
+
+ int size(String key);
+
+ boolean contains(String key, String member);
+
+ Set getIntersectionOfSets(String... keys);
+
+ void storeIntersectionOfSets(String dstkey, String... keys);
+
+ Set getUnionOfSets(String... keys);
+
+ void storeUnionOfSets(String dstkey, String... keys);
+
+ Set getDifferenceBetweenSets(String... keys);
+
+ void storeDifferenceBetweenSets(String dstkey, String... keys);
+
+ String getRandom(String key);
+
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jedis/CachingJedisClientFactory.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jedis/CachingJedisClientFactory.java
new file mode 100644
index 000000000..1a6d87456
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jedis/CachingJedisClientFactory.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2002-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.datastore.redis.core.jedis;
+
+import java.util.concurrent.TimeoutException;
+
+import org.springframework.datastore.redis.CannotGetRedisConnectionException;
+import org.springframework.datastore.redis.core.AbstractRedisClientFactory;
+import org.springframework.datastore.redis.core.RedisClient;
+import org.springframework.datastore.redis.support.RedisPersistenceExceptionTranslator;
+
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+
+/**
+ * A RedisClientFactory implementation that uses Jedis's native connection/client
+ * caching features.
+ *
+ * @author Mark Pollack
+ *
+ */
+public class CachingJedisClientFactory extends AbstractRedisClientFactory {
+
+ private JedisPool pool;
+
+ private int timeout;
+
+ private int clientCacheSize;
+
+ private long maxWaitTime;
+
+ /**
+ *
+ * @param clientCacheSize
+ */
+ public CachingJedisClientFactory(int clientCacheSize) {
+ this.clientCacheSize = clientCacheSize;
+ }
+
+ public CachingJedisClientFactory(JedisPool pool) {
+ this.pool = pool;
+ }
+
+ public int getClientCacheSize() {
+ return this.clientCacheSize;
+ }
+
+ public JedisPool getJedisPool() {
+ return this.pool;
+ }
+
+ public int getTimeout() {
+ return timeout;
+ }
+
+ protected void setTimeout(int timeout) {
+ this.timeout = timeout;
+ }
+
+ public long getMaxWaitTime() {
+ return this.maxWaitTime;
+ }
+
+ /**
+ * Sets the maximum amount of time (in milliseconds) the getResource() method
+ * should block before throwing an TimeoutException.
+ * @param maxWaitTime The maximum time you would like to wait for the resource.
+ */
+ public void setMaxWaitTime(long maxWaitTime) {
+ this.maxWaitTime = maxWaitTime;
+ }
+
+ @Override
+ public RedisClient doGetClient() {
+ Jedis jedis;
+ if (getClientCacheSize() != 0) {
+ pool = new JedisPool(getHostName(), getPort(), getTimeout());
+ pool.setResourcesNumber(getClientCacheSize());
+ }
+ try {
+ if (getMaxWaitTime() != 0)
+ jedis = pool.getResource(getMaxWaitTime());
+ else {
+ jedis = pool.getResource();
+ }
+ } catch (TimeoutException e) {
+ throw new CannotGetRedisConnectionException(
+ "Timed out. Could not get Redis Connection", e);
+ }
+ return new JedisClient(jedis, getExceptionTranslator() );
+ }
+
+ @Override
+ public RedisPersistenceExceptionTranslator getExceptionTranslator() {
+ return new JedisPersistenceExceptionTranslator();
+ }
+
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jedis/JedisClient.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jedis/JedisClient.java
new file mode 100644
index 000000000..ec57649da
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jedis/JedisClient.java
@@ -0,0 +1,535 @@
+/*
+ * Copyright 2002-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.datastore.redis.core.jedis;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.datastore.redis.core.AbstractRedisClient;
+import org.springframework.datastore.redis.support.RedisPersistenceExceptionTranslator;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+import redis.clients.jedis.Jedis;
+
+/**
+ * Jedis based implementation of Spring's RedisClient interface. Presents a low
+ * level API where method names map onto Redis commands.
+ *
+ * @author Mark Pollack
+ *
+ */
+public class JedisClient extends AbstractRedisClient {
+
+ private Jedis _jedis;
+ private RedisPersistenceExceptionTranslator exceptionTranslator;
+
+ public JedisClient(Jedis jedis,
+ RedisPersistenceExceptionTranslator exceptionTranslator) {
+ this._jedis = jedis;
+ this.exceptionTranslator = exceptionTranslator;
+ }
+
+ public T execute(JedisClientCallback action) {
+ Assert.notNull(action, "Callback object must not be null");
+
+ // TODO jredisClient resource mgmt.
+ try {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Executing callback on Jedis : " + _jedis);
+ }
+ return action.doInJedis(_jedis);
+ } catch (Exception e) {
+ throw convertJedisAccessException(e);
+ }
+
+ }
+
+ protected DataAccessException convertJedisAccessException(Exception ex) {
+ return exceptionTranslator.translateException(ex);
+ }
+
+ public void disconnect() throws IOException {
+ execute(new JedisClientCallback() {
+ public Object doInJedis(Jedis jedis) throws Exception {
+ jedis.disconnect();
+ return null;
+ }
+ });
+ }
+
+ // Database control commands
+
+ public String save() {
+ return execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.save();
+ }
+ });
+ }
+
+ public String bgsave() {
+ return execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.bgsave();
+ }
+ });
+ }
+
+ public String bgrewriteaof() {
+ return execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.bgrewriteaof();
+ }
+ });
+ }
+
+ public Integer lastsave() {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.lastsave();
+ }
+ });
+ }
+
+ public String shutdown() {
+ return execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.shutdown();
+ }
+ });
+ }
+
+ public Map info() {
+ return execute(new JedisClientCallback>() {
+ public Map doInJedis(Jedis jedis) throws Exception {
+ String[] response = StringUtils.delimitedListToStringArray(
+ jedis.info(), "\r\n");
+ Map responseMap = new HashMap();
+ for (String responseLine : response) {
+ if (!responseLine.isEmpty()) {
+ String[] keyValue = StringUtils
+ .split(responseLine, ":");
+ if (keyValue == null) {
+ logger.warn("Could not parse info reponse line ["
+ + responseLine + "]");
+ continue;
+ }
+ responseMap.put(keyValue[0], keyValue[1]);
+ }
+ }
+ return responseMap;
+ }
+ });
+ }
+
+ public String slaveof(final String host, final int port) {
+ return execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.slaveof(host, port);
+ }
+ });
+ }
+
+ public String slaveofNoOne() {
+ return execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.slaveofNoOne();
+ }
+ });
+ }
+
+ public String select(final int index) {
+ return execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.select(index);
+ }
+ });
+ }
+
+ public String flushDb() {
+ return execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.flushDB();
+ }
+ });
+ }
+
+ public String flushAll() {
+ return execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.flushAll();
+ }
+ });
+ }
+
+ public Integer move(final String key, final int dbIndex) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.move(key, dbIndex);
+ }
+ });
+ }
+
+ public String auth(final String password) {
+ return execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.auth(password);
+ }
+ });
+ }
+
+ public Integer dbSize() {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.dbSize();
+ }
+ });
+ }
+
+ // Commands operating on string value types "StringOperations" or
+ // "Operations"
+
+ public void set(final String key, final String value) {
+ execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.set(key, value);
+ }
+ });
+ }
+
+ public void set(String key, byte[] value) {
+ set(key, byteToString(value));
+ }
+
+ public String get(final String key) {
+ return execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.get(key);
+ }
+ });
+ }
+
+ public byte[] getAsBytes(String key) {
+ return stringToByte(get(key));
+ }
+
+ public String getSet(final String key, final String value) {
+ return execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.getSet(key, value);
+ }
+ });
+ }
+
+ public List mget(final String... keys) {
+ return execute(new JedisClientCallback>() {
+ public List doInJedis(Jedis jedis) throws Exception {
+ return jedis.mget(keys);
+ }
+ });
+ }
+
+ public Integer setnx(final String key, final String value) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.setnx(key, value);
+ }
+ });
+ }
+
+ public String setex(final String key, final int seconds, final String value) {
+ return execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.setex(key, seconds, value);
+ }
+ });
+ }
+
+ public String mset(final String... keysvalues) {
+ return execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.mset(keysvalues);
+ }
+ });
+ }
+
+ public Integer msetnx(final String... keysvalues) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.msetnx(keysvalues);
+ }
+ });
+ }
+
+ public Integer incrBy(final String key, final int increment) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.incrBy(key, increment);
+ }
+ });
+ }
+
+ public Integer incr(final String key) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.incr(key);
+ }
+ });
+ }
+
+ public Integer decr(final String key) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.decr(key);
+ }
+ });
+ }
+
+ public Integer decrBy(final String key, final int increment) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.decrBy(key, increment);
+ }
+ });
+ }
+
+ public Integer append(final String key, final String value) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.append(key, value);
+ }
+ });
+ }
+
+ public String substr(final String key, final int start, final int end) {
+ return execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.substr(key, start, end);
+ }
+ });
+ }
+
+ // Commands operating on all value types "KeySpaceOperations"
+
+ public Integer exists(final String key) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.exists(key);
+ }
+ });
+ }
+
+ public Integer del(final String... keys) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.del(keys);
+ }
+ });
+ }
+
+ public String type(final String key) {
+ return execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.type(key);
+ }
+ });
+ }
+
+ public List keys(final String pattern) {
+ return execute(new JedisClientCallback>() {
+ public List doInJedis(Jedis jedis) throws Exception {
+ return jedis.keys(pattern);
+ }
+ });
+ }
+
+ public String randomKey() {
+ return execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.randomKey();
+ }
+ });
+ }
+
+ public String rename(final String oldkey, final String newkey) {
+ return execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.rename(oldkey, newkey);
+ }
+ });
+ }
+
+ public Integer renamenx(final String oldkey, final String newkey) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.renamenx(oldkey, newkey);
+ }
+ });
+ }
+
+ public Integer expire(final String key, final int seconds) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.expire(key, seconds);
+ }
+ });
+ }
+
+ public Integer expireAt(final String key, final long unixTime) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.expireAt(key, unixTime);
+ }
+ });
+ }
+
+ public Integer ttl(final String key) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.ttl(key);
+ }
+ });
+ }
+
+ public Integer persist(final String key) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.persist(key);
+ }
+ });
+ }
+
+ // Commands operating on Sets
+
+ public Integer sadd(final String key, final String member) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.sadd(key, member);
+ }
+ });
+ }
+
+ public Set smembers(final String key) {
+ return execute(new JedisClientCallback>() {
+ public Set doInJedis(Jedis jedis) throws Exception {
+ return jedis.smembers(key);
+ }
+ });
+ }
+
+ public Integer srem(final String key, final String member) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.srem(key, member);
+ }
+ });
+ }
+
+ public String spop(final String key) {
+ return execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.spop(key);
+ }
+ });
+ }
+
+ public Integer smove(final String srckey, final String dstkey,
+ final String member) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.smove(srckey, dstkey, member);
+ }
+ });
+ }
+
+ public Integer scard(final String key) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.scard(key);
+ }
+ });
+ }
+
+ public Integer sismember(final String key, final String member) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.sismember(key, member);
+ }
+ });
+ }
+
+ public Set sinter(final String... keys) {
+ return execute(new JedisClientCallback>() {
+ public Set doInJedis(Jedis jedis) throws Exception {
+ return jedis.sinter(keys);
+ }
+ });
+ }
+
+ public Integer sinterstore(final String dstkey, final String... keys) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.sinterstore(dstkey, keys);
+ }
+ });
+ }
+
+ public Set sunion(final String... keys) {
+ return execute(new JedisClientCallback>() {
+ public Set doInJedis(Jedis jedis) throws Exception {
+ return jedis.sunion(keys);
+ }
+ });
+ }
+
+ public Integer sunionstore(final String dstkey, final String... keys) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.sunionstore(dstkey, keys);
+ }
+ });
+ }
+
+ public Set sdiff(final String... keys) {
+ return execute(new JedisClientCallback>() {
+ public Set doInJedis(Jedis jedis) throws Exception {
+ return jedis.sdiff(keys);
+ }
+ });
+ }
+
+ public Integer sdiffstore(final String dstkey, final String... keys) {
+ return execute(new JedisClientCallback() {
+ public Integer doInJedis(Jedis jedis) throws Exception {
+ return jedis.sdiffstore(dstkey, keys);
+ }
+ });
+ }
+
+ public String srandmember(final String key) {
+ return execute(new JedisClientCallback() {
+ public String doInJedis(Jedis jedis) throws Exception {
+ return jedis.srandmember(key);
+ }
+ });
+ }
+
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jedis/JedisClientCallback.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jedis/JedisClientCallback.java
new file mode 100644
index 000000000..d4fd005c6
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jedis/JedisClientCallback.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2002-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.datastore.redis.core.jedis;
+
+import redis.clients.jedis.Jedis;
+
+/**
+ * Basic callback for use in JedisClient
+ * @author Mark Pollack
+ *
+ * @param TODO
+ */
+public interface JedisClientCallback {
+
+ /**
+ * Execute any number of operations against the supplied Jedis
+ * {@link Jedis}, possibly returning a result.
+ */
+ T doInJedis(Jedis jedis) throws Exception;
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jedis/JedisClientFactory.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jedis/JedisClientFactory.java
new file mode 100644
index 000000000..fba7c8304
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jedis/JedisClientFactory.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2002-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.datastore.redis.core.jedis;
+
+import java.io.IOException;
+import java.net.UnknownHostException;
+
+import org.springframework.datastore.redis.CannotGetRedisConnectionException;
+import org.springframework.datastore.redis.core.AbstractRedisClientFactory;
+import org.springframework.datastore.redis.core.RedisClient;
+import org.springframework.datastore.redis.core.RedisClientFactory;
+import org.springframework.datastore.redis.core.jredis.JRedisPersistenceExceptionTranslator;
+import org.springframework.datastore.redis.support.RedisPersistenceExceptionTranslator;
+
+import redis.clients.jedis.Jedis;
+import redis.clients.util.ShardInfo;
+
+/**
+ * A {@link RedisClientFactory} implementation that returns a new instance of a
+ * Jedis backed RedisClient from call {@link #createClient()} calls.
+ *
+ * @author Mark Pollack
+ *
+ */
+public class JedisClientFactory extends AbstractRedisClientFactory {
+
+ private ShardInfo shardInfo;
+
+ private int timeout;
+
+ private RedisPersistenceExceptionTranslator exceptionTranslator = new JRedisPersistenceExceptionTranslator();
+
+ public JedisClientFactory() {
+ setHostName(getDefaultHostName());
+ }
+
+ public JedisClientFactory(String hostname) {
+ setHostName(hostname);
+ }
+
+ public JedisClientFactory(String hostname, int port)
+ {
+ setHostName(hostname);
+ setPort(port);
+ }
+
+ public JedisClientFactory(String hostname, int port, int timeout)
+ {
+ setHostName(hostname);
+ setPort(port);
+ setTimeout(timeout);
+ }
+
+ public JedisClientFactory(ShardInfo shardInfo) {
+ this.shardInfo = shardInfo;
+ }
+
+ protected ShardInfo getShardInfo() {
+ return this.shardInfo;
+ }
+
+ public int getTimeout() {
+ return timeout;
+ }
+
+ protected void setTimeout(int timeout) {
+ this.timeout = timeout;
+ }
+
+ @Override
+ public RedisClient doGetClient() {
+ Jedis jedis;
+ if (getShardInfo() != null) {
+ jedis = new Jedis(getShardInfo());
+ }
+ if (getPort() != 0 && getTimeout() != 0) {
+ jedis = new Jedis(getHostName(), getPort(), getTimeout());
+ } else if (getPort() != 0) {
+ jedis = new Jedis(getHostName(), getPort());
+ } else {
+ jedis = new Jedis(getHostName());
+ }
+ try {
+ jedis.connect();
+ if (getPassword() != null) {
+ jedis.auth(getPassword());
+ }
+ } catch (UnknownHostException e) {
+ throw new CannotGetRedisConnectionException(
+ "Could not get Redis Connection", e);
+ } catch (IOException e) {
+ throw new CannotGetRedisConnectionException(
+ "Could not get Redis Connection", e);
+ }
+ return new JedisClient(jedis, getExceptionTranslator());
+ }
+
+ @Override
+ public RedisPersistenceExceptionTranslator getExceptionTranslator() {
+ return exceptionTranslator;
+ }
+
+ public void setExceptionTranslator(
+ RedisPersistenceExceptionTranslator exceptionTranslator) {
+ this.exceptionTranslator = exceptionTranslator;
+ }
+
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jedis/JedisPersistenceExceptionTranslator.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jedis/JedisPersistenceExceptionTranslator.java
new file mode 100644
index 000000000..edc4312f8
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jedis/JedisPersistenceExceptionTranslator.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2002-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.datastore.redis.core.jedis;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.datastore.redis.support.RedisPersistenceExceptionTranslator;
+
+/**
+ * Translates error messages from Jedis to Spring's Data Access exception class hierarchy
+ *
+ * @author Mark Pollack
+ *
+ */
+public class JedisPersistenceExceptionTranslator implements
+ RedisPersistenceExceptionTranslator {
+
+ public DataAccessException translateException(Exception ex) {
+ return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
+ }
+
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jredis/JRedisClientCallback.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jredis/JRedisClientCallback.java
new file mode 100644
index 000000000..11dbeca50
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jredis/JRedisClientCallback.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2002-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.datastore.redis.core.jredis;
+
+import org.jredis.ri.alphazero.JRedisClient;
+
+/**
+ * Basic callback for use in JRedisClient
+ * @author Mark Pollack
+ *
+ * @param TODO
+ */
+public interface JRedisClientCallback {
+
+ /**
+ * Execute any number of operations against the supplied RedisClient
+ * {@link RedicClient}, possibly returning a result.
+ */
+ T doInJRedis(JRedisClient jredisClient) throws Exception;
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jredis/JRedisClientFactory.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jredis/JRedisClientFactory.java
new file mode 100644
index 000000000..f951e13a9
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jredis/JRedisClientFactory.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2002-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.datastore.redis.core.jredis;
+
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import org.jredis.ClientRuntimeException;
+import org.jredis.connector.ConnectionSpec;
+import org.jredis.ri.alphazero.JRedisClient;
+import org.jredis.ri.alphazero.connection.DefaultConnectionSpec;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.datastore.redis.core.AbstractRedisClientFactory;
+import org.springframework.datastore.redis.core.RedisClient;
+import org.springframework.datastore.redis.support.RedisPersistenceExceptionTranslator;
+
+public class JRedisClientFactory extends AbstractRedisClientFactory {
+
+ public static final String DEFAULT_CHARSET = "UTF-8";
+
+ private volatile String defaultCharset = DEFAULT_CHARSET;
+
+
+ private RedisPersistenceExceptionTranslator exceptionTranslator;
+
+ private ConnectionSpec connectionSpec;
+
+ public JRedisClientFactory() {
+ setHostName(getDefaultHostName());
+ exceptionTranslator = new JRedisPersistenceExceptionTranslator();
+ }
+
+ public JRedisClientFactory(ConnectionSpec connectionSpec) {
+ this.connectionSpec = connectionSpec;
+ }
+
+ @Override
+ public RedisClient doGetClient() {
+ JRedisClient jredis;
+ if (connectionSpec == null) {
+
+ connectionSpec = DefaultConnectionSpec.newSpec();
+
+ InetAddress address;
+ try {
+ address = InetAddress.getByName(getHostName());
+ } catch (UnknownHostException e) {
+ throw new ClientRuntimeException("unknown host: "
+ + getHostName(), e);
+ }
+ connectionSpec.setAddress(address);
+
+ if (getPort() != 0) {
+ connectionSpec.setPort(getPort());
+ }
+
+ if (getPassword() != null) {
+ connectionSpec.setCredentials(stringToByte(getPassword()));
+ }
+ }
+
+ jredis = new JRedisClient(connectionSpec);
+ return new JRedisSpringClient(jredis, getExceptionTranslator());
+ }
+
+ protected byte[] stringToByte(String string) throws InvalidDataAccessApiUsageException {
+ try {
+ return string.getBytes(this.defaultCharset);
+ } catch (UnsupportedEncodingException e) {
+ throw new InvalidDataAccessApiUsageException(defaultCharset
+ + " encoding not supported.", e);
+ }
+ }
+
+ /**
+ * Specify the default charset to use when converting to or from text-based
+ * Message body content. If not specified, the charset will be "UTF-8".
+ */
+ public void setDefaultCharset(String defaultCharset) {
+ this.defaultCharset = (defaultCharset != null) ? defaultCharset : DEFAULT_CHARSET;
+ }
+
+ public String getDefaultCharset() {
+ return defaultCharset;
+ }
+
+ @Override
+ public RedisPersistenceExceptionTranslator getExceptionTranslator() {
+ return exceptionTranslator;
+ }
+
+ public void setExceptionTranslator(
+ RedisPersistenceExceptionTranslator exceptionTranslator) {
+ this.exceptionTranslator = exceptionTranslator;
+ }
+
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jredis/JRedisPersistenceExceptionTranslator.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jredis/JRedisPersistenceExceptionTranslator.java
new file mode 100644
index 000000000..19cb45d9f
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jredis/JRedisPersistenceExceptionTranslator.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2002-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.datastore.redis.core.jredis;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.datastore.redis.support.RedisPersistenceExceptionTranslator;
+
+public class JRedisPersistenceExceptionTranslator implements
+ RedisPersistenceExceptionTranslator {
+
+ public DataAccessException translateException(Exception ex) {
+ return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
+ }
+
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jredis/JRedisSpringClient.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jredis/JRedisSpringClient.java
new file mode 100644
index 000000000..712aa1381
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/core/jredis/JRedisSpringClient.java
@@ -0,0 +1,494 @@
+/*
+ * Copyright 2002-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.datastore.redis.core.jredis;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.jredis.ri.alphazero.JRedisClient;
+import org.jredis.ri.alphazero.support.DefaultCodec;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.DataRetrievalFailureException;
+import org.springframework.datastore.redis.core.AbstractRedisClient;
+import org.springframework.datastore.redis.support.RedisPersistenceExceptionTranslator;
+import org.springframework.util.Assert;
+
+/**
+ * JRedis implementation of RedisClient. Name has 'Spring' in it to avoid naming
+ * conflict with classes in JRedis itself.
+ *
+ * @author Mark Pollack
+ *
+ */
+public class JRedisSpringClient extends AbstractRedisClient {
+
+ /** Logger available to subclasses */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ private JRedisClient _jredisClient;
+ private RedisPersistenceExceptionTranslator exceptionTranslator;
+
+ public JRedisSpringClient(JRedisClient jredisClient,
+ RedisPersistenceExceptionTranslator exceptionTransator) {
+ this._jredisClient = jredisClient;
+ this.exceptionTranslator = exceptionTransator;
+ this.setDefaultCharset(DefaultCodec.SUPPORTED_CHARSET_NAME);
+ }
+
+
+ protected Integer convertToInteger(long longTime) {
+ if (longTime < Integer.MIN_VALUE
+ || longTime > Integer.MAX_VALUE) {
+ throw new DataRetrievalFailureException(
+ longTime
+ + " cannot be cast to int without changing its value.");
+ }
+ return (int) longTime;
+ }
+
+ public T execute(JRedisClientCallback action) {
+ Assert.notNull(action, "Callback object must not be null");
+
+ // TODO jredisClient resource mgmt.
+ try {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Executing callback on JRedisClient : "
+ + _jredisClient);
+ }
+ return action.doInJRedis(_jredisClient);
+ } catch (Exception e) {
+ throw convertJRedisAccessException(e);
+ }
+
+ }
+
+ protected DataAccessException convertJRedisAccessException(Exception ex) {
+ return exceptionTranslator.translateException(ex);
+ }
+
+ public void disconnect() {
+ // TODO look at disconnect exception translation
+ execute(new JRedisClientCallback() {
+ public Object doInJRedis(JRedisClient jredisClient)
+ throws Exception {
+ jredisClient.quit();
+ return null;
+ }
+ });
+ }
+
+ public String get(final String key) {
+ return execute(new JRedisClientCallback() {
+ public String doInJRedis(JRedisClient jredisClient)
+ throws Exception {
+ return byteToString(jredisClient.get(key));
+ }
+ });
+ }
+
+ public byte[] getAsBytes(final String key) {
+ return execute(new JRedisClientCallback() {
+ public byte[] doInJRedis(JRedisClient jredisClient)
+ throws Exception {
+ return jredisClient.get(key);
+ }
+ });
+ }
+
+ public void set(final String key, final String value) {
+ execute(new JRedisClientCallback() {
+ public String doInJRedis(JRedisClient jredisClient)
+ throws Exception {
+ jredisClient.set(key, value);
+ return null;
+ }
+ });
+ }
+
+ public void set(final String key, final byte[] value) {
+ execute(new JRedisClientCallback() {
+ public String doInJRedis(JRedisClient jredisClient)
+ throws Exception {
+ jredisClient.set(key, value);
+ return null;
+ }
+ });
+ }
+
+ public String save() {
+ return execute(new JRedisClientCallback() {
+ public String doInJRedis(JRedisClient jredisClient)
+ throws Exception {
+ jredisClient.save();
+ return "OK";
+ }
+ });
+ }
+
+ public String bgsave() {
+ return execute(new JRedisClientCallback() {
+ public String doInJRedis(JRedisClient jredisClient)
+ throws Exception {
+ jredisClient.bgsave();
+ return "Background saving started";
+ }
+ });
+ }
+
+ public String bgrewriteaof() {
+ return execute(new JRedisClientCallback() {
+ public String doInJRedis(JRedisClient jredisClient)
+ throws Exception {
+ jredisClient.bgrewriteaof();
+ return "Background append only file rewriting started";
+ }
+ });
+ }
+
+ public Integer lastsave() {
+ return execute(new JRedisClientCallback() {
+ public Integer doInJRedis(JRedisClient jredisClient)
+ throws Exception {
+ long longTime = jredisClient.lastsave();
+ // odd that JRedis return long when the Redis command spec says
+ // int.
+ return convertToInteger(longTime);
+ }
+ });
+ }
+
+ public String shutdown() {
+ return execute(new JRedisClientCallback() {
+ public String doInJRedis(JRedisClient jredisClient)
+ throws Exception {
+ throw new UnsupportedOperationException("JRedis does not implement SHUTDOWN command");
+ }
+ });
+ }
+
+ public Map info() {
+ return execute(new JRedisClientCallback>() {
+ public Map doInJRedis(JRedisClient jredisClient)
+ throws Exception {
+ return jredisClient.info();
+ }
+ });
+ }
+
+ public String slaveof(final String host, final int port) {
+ return execute(new JRedisClientCallback() {
+ public String doInJRedis(JRedisClient jredisClient)
+ throws Exception {
+ jredisClient.slaveof(host,port);
+ return "TODO - EXTRACT CORRECT STRING RESPONSE FROM REDIS FOR COMMAND SLAVEOF";
+ }
+ });
+ }
+
+ public String slaveofNoOne() {
+ return execute(new JRedisClientCallback() {
+ public String doInJRedis(JRedisClient jredisClient)
+ throws Exception {
+ jredisClient.slaveofnone();
+ return "TODO - EXTRACT CORRECT STRING RESPONSE FROM REDIS FOR COMMAND SLAVEOF NO ONE";
+ }
+ });
+ }
+
+ public String select(int index) {
+ return execute(new JRedisClientCallback() {
+ public String doInJRedis(JRedisClient jredisClient)
+ throws Exception {
+ throw new UnsupportedOperationException("JRedis does not implement SELECT command");
+ }
+ });
+ }
+
+ public String flushDb() {
+ return execute(new JRedisClientCallback() {
+ public String doInJRedis(JRedisClient jredisClient)
+ throws Exception {
+ jredisClient.flushdb();
+ //TODO why does flushdb() return JRedis interface?
+ return "OK";
+ }
+ });
+ }
+
+ public String flushAll() {
+ return execute(new JRedisClientCallback() {
+ public String doInJRedis(JRedisClient jredisClient)
+ throws Exception {
+ jredisClient.flushall();
+ //TODO why does flushdb() return JRedis interface?
+ return "OK";
+ }
+ });
+ }
+
+ public Integer move(final String key, final int dbIndex) {
+ return execute(new JRedisClientCallback() {
+ public Integer doInJRedis(JRedisClient jredisClient)
+ throws Exception {
+ return jredisClient.move(key, dbIndex) ? 1 : 0;
+ }
+ });
+ }
+
+ public String auth(String password) {
+ return execute(new JRedisClientCallback() {
+ public String doInJRedis(JRedisClient jredisClient)
+ throws Exception {
+ throw new UnsupportedOperationException("JRedis does not implement AUTH command");
+ }
+ });
+ }
+
+ public Integer dbSize() {
+ return execute(new JRedisClientCallback() {
+ public Integer doInJRedis(JRedisClient jredisClient)
+ throws Exception {
+ return convertToInteger(jredisClient.dbsize());
+ }
+ });
+ }
+
+
+ public String getSet(String key, String value) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public List mget(String... keys) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer setnx(String key, String value) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public String setex(String key, int seconds, String value) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public String mset(String... keysvalues) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer msetnx(String... keysvalues) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer incrBy(String key, int increment) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer incr(String key) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer decr(String key) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer decrBy(String key, int decrement) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer append(String key, String value) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public String substr(String key, int start, int end) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer exists(String key) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer del(String... keys) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public String type(String key) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public List keys(String pattern) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public String randomKey() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public String rename(String oldkey, String newkey) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer renamenx(String oldkey, String newkey) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer expire(String key, int seconds) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer expireAt(String key, long unixTime) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer ttl(String key) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer persist(String key) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer sadd(String key, String member) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Set smembers(String key) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer srem(String key, String member) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public String spop(String key) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer smove(String srckey, String dstkey, String member) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer scard(String key) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer sismember(String key, String member) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Set sinter(String... keys) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer sinterstore(String dstkey, String... keys) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Set sunion(String... keys) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer sunionstore(String dstkey, String... keys) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Set sdiff(String... keys) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Integer sdiffstore(String dstkey, String... keys) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public String srandmember(String key) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/support/RedisPersistenceExceptionTranslator.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/support/RedisPersistenceExceptionTranslator.java
new file mode 100644
index 000000000..58809dbc4
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/support/RedisPersistenceExceptionTranslator.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2002-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.datastore.redis.support;
+
+import org.springframework.dao.DataAccessException;
+
+/**
+ * Interface implemented by Spring integrations with Redis for drivers
+ * that throw runtime and checked exceptions.
+ *
+ * @author Mark Pollack
+ *
+ */
+public interface RedisPersistenceExceptionTranslator {
+
+
+ //NOTE some client libraries throw checked exceptions.
+ DataAccessException translateException(Exception ex);
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/support/RedisUtils.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/support/RedisUtils.java
new file mode 100644
index 000000000..b4cc17721
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/support/RedisUtils.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-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.datastore.redis.support;
+
+import java.io.IOException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.datastore.redis.core.RedisClient;
+
+/**
+ * Generic utility methods for working with Redis. Mainly for internal use
+ * within the framework.
+ * @author Mark Pollack
+ *
+ */
+public class RedisUtils {
+
+
+ private static final Log logger = LogFactory.getLog(RedisUtils.class);
+
+
+ /**
+ * Close the given Redis Client and ignore any thrown exception.
+ * This is useful for typical finally blocks in manual Redis code.
+ * @param channel the RabbitMQ Channel to close (may be null)
+ */
+ public static void closeClient(RedisClient redisClient) {
+ if (redisClient != null) {
+ try {
+ redisClient.disconnect();
+ }
+ catch (IOException ex) {
+ logger.debug("Could not close Redis Channel", ex);
+ }
+ catch (Throwable ex) {
+ logger.debug("Unexpected exception on closing Redis Client", ex);
+ }
+ }
+ }
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/support/converter/DefaultRedisConverter.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/support/converter/DefaultRedisConverter.java
new file mode 100644
index 000000000..784edab56
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/support/converter/DefaultRedisConverter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-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.datastore.redis.support.converter;
+
+import org.springframework.commons.serializer.DefaultDeserializer;
+import org.springframework.commons.serializer.DefaultSerializer;
+import org.springframework.commons.serializer.DeserializingConverter;
+import org.springframework.commons.serializer.SerializingConverter;
+
+/**
+ * Implementation using Java Serialization
+ *
+ * @author Mark Pollack
+ *
+ */
+public class DefaultRedisConverter implements RedisConverter {
+
+ private DeserializingConverter fromBytes = new DeserializingConverter(new DefaultDeserializer());
+ private SerializingConverter toBytes = new SerializingConverter(new DefaultSerializer());
+
+ public Object deserialize(byte[] bytes) {
+ return fromBytes.convert(bytes);
+ }
+
+ public byte[] serialize(Object object) {
+ return toBytes.convert(object);
+ }
+
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/support/converter/RedisConverter.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/support/converter/RedisConverter.java
new file mode 100644
index 000000000..7cb1fc230
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/support/converter/RedisConverter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2002-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.datastore.redis.support.converter;
+
+/**
+ * Basic interface serialization and deserialization from object to byte[]. Nothing is specifc to Redis
+ * @author Mark Pollack
+ *
+ */
+public interface RedisConverter {
+
+ byte[] serialize(Object object);
+
+ Object deserialize(byte[] bytes);
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/util/AbstractRedisCollection.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/util/AbstractRedisCollection.java
new file mode 100644
index 000000000..6077d7f9b
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/util/AbstractRedisCollection.java
@@ -0,0 +1,83 @@
+package org.springframework.datastore.redis.util;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.springframework.datastore.redis.core.RedisTemplate;
+
+public abstract class AbstractRedisCollection implements RedisCollection {
+
+ protected RedisTemplate redisTemplate;
+ protected String redisKey;
+
+ public AbstractRedisCollection(RedisTemplate redisTemplate, String redisKey) {
+ this.redisTemplate = redisTemplate;
+ this.redisKey = redisKey;
+ }
+
+
+ /**
+ * They key used by the collection
+ *
+ * @return The redis key
+ */
+ public String getRedisKey() {
+ return redisKey;
+ }
+
+ public void clear() {
+ redisTemplate.deleteKeys(redisKey);
+ }
+
+ public boolean isEmpty() {
+ return size() == 0;
+ }
+
+ public Object[] toArray() {
+ return new Object[0];
+ }
+
+ public boolean containsAll(Collection c) {
+ for (Object o : c) {
+ if(!contains(o)) return false;
+ }
+ return true;
+ }
+
+ public boolean addAll(Collection c) {
+ boolean changed = false;
+ for (Object e : c) {
+ boolean elChange = add(e);
+ if(elChange && !changed) changed = true;
+ }
+ return changed;
+ }
+
+ public boolean retainAll(Collection c) {
+ Iterator i = iterator();
+ boolean changed = false;
+ while (i.hasNext()) {
+ Object o = i.next();
+ if(!c.contains(o)) {
+ i.remove();
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ public boolean removeAll(Collection c) {
+ boolean changed = false;
+ for (Object e : c) {
+ boolean elChange = remove(e);
+ if(elChange && !changed) changed = true;
+ }
+ return changed;
+
+ }
+
+ public Object[] toArray(Object[] array) {
+ return new Object[0];
+ }
+
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/util/RedisCollection.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/util/RedisCollection.java
new file mode 100644
index 000000000..b7d29da8b
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/util/RedisCollection.java
@@ -0,0 +1,21 @@
+package org.springframework.datastore.redis.util;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ *
+ * @author Graeme Rocher
+ *
+ */
+public interface RedisCollection extends Collection {
+
+ /**
+ * They key used by the collection
+ *
+ * @return The redis key
+ */
+ String getRedisKey();
+
+ Set members();
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/util/RedisSet.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/util/RedisSet.java
new file mode 100644
index 000000000..dbf30f2ca
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/util/RedisSet.java
@@ -0,0 +1,79 @@
+package org.springframework.datastore.redis.util;
+
+import java.util.Iterator;
+import java.util.Set;
+
+import org.springframework.datastore.redis.core.RedisTemplate;
+
+/**
+ *
+ * @author Graeme Rocher
+ *
+ */
+public class RedisSet extends AbstractRedisCollection implements Set {
+
+ public RedisSet(RedisTemplate redisTemplate, String redisKey) {
+ super(redisTemplate, redisKey);
+ }
+
+ public int size() {
+ return redisTemplate.getSetOperations().size(redisKey);
+ }
+
+ public boolean contains(Object o) {
+ //TODO investigate cast
+ return redisTemplate.getSetOperations().contains(redisKey, (String)o);
+ }
+
+ public Iterator iterator() {
+ return redisTemplate.getSetOperations().getAll(redisKey).iterator();
+ }
+
+ public boolean add(Object o) {
+ //TODO investigate cast
+ return redisTemplate.getSetOperations().add(redisKey, (String)o);
+ }
+
+ public boolean remove(Object o) {
+ //TODO investigate cast
+ return redisTemplate.getSetOperations().remove(redisKey, (String)o);
+ }
+
+
+ public Set members() {
+ return redisTemplate.getSetOperations().getAll(redisKey);
+ }
+
+ /*
+ public List members(final int offset, final int max) {
+ return redisTemplate.sort(redisKey, redisTemplate.sortParams().limit(offset, max));
+
+ }*/
+
+ public String getRandom() {
+ return redisTemplate.getSetOperations().getRandom(redisKey);
+ }
+
+ public boolean removeRandom() {
+ return redisTemplate.getSetOperations().removeRandom(redisKey);
+ }
+
+
+ void intersection(RedisSet... redisSets) {
+ //storeIntersectionOfSets..
+ }
+
+ void union(RedisSet... redisSets) {
+ //storeUnionOfSets
+ }
+
+ void difference(RedisSet... redisSets) {
+
+ }
+
+ //consider methods in google collections such as
+ // cartesianProduct, filter, powerSet, symmetricDifference, newRedisSet
+ //TODO move to another set
+
+ //
+}
diff --git a/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/util/Sets.java b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/util/Sets.java
new file mode 100644
index 000000000..8c095ea14
--- /dev/null
+++ b/spring-datastore-redis/src/main/java/org/springframework/datastore/redis/util/Sets.java
@@ -0,0 +1,18 @@
+package org.springframework.datastore.redis.util;
+
+import org.springframework.datastore.redis.core.RedisTemplate;
+
+public class Sets {
+
+ protected RedisTemplate redisTemplate;
+
+ public Sets(RedisTemplate redisTemplate) {
+ this.redisTemplate = redisTemplate;
+ }
+
+ //TODO what key to assing?
+
+
+
+
+}
diff --git a/spring-datastore-redis/src/main/resources/META-INF/spring/app-context.xml b/spring-datastore-redis/src/main/resources/META-INF/spring/app-context.xml
new file mode 100644
index 000000000..ca51b1a69
--- /dev/null
+++ b/spring-datastore-redis/src/main/resources/META-INF/spring/app-context.xml
@@ -0,0 +1,10 @@
+
+
+
+ Example configuration to get you started.
+
+
+
+
diff --git a/spring-datastore-redis/src/test/java/org/springframework/datastore/redis/core/AbstractClientIntegrationTests.java b/spring-datastore-redis/src/test/java/org/springframework/datastore/redis/core/AbstractClientIntegrationTests.java
new file mode 100644
index 000000000..9b3035364
--- /dev/null
+++ b/spring-datastore-redis/src/test/java/org/springframework/datastore/redis/core/AbstractClientIntegrationTests.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2002-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.datastore.redis.core;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.junit.After;
+import org.junit.Test;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+
+import redis.clients.jedis.JedisException;
+
+public abstract class AbstractClientIntegrationTests {
+
+ protected RedisClient client;
+
+ @After
+ public void tearDown() throws IOException {
+ client.disconnect();
+ }
+ @Test
+ public void save() {
+ String status = client.save();
+ assertEquals("OK", status);
+ }
+
+ @Test
+ public void bgsave() {
+ try {
+ String status = client.bgsave();
+ assertEquals("Background saving started", status);
+ } catch (InvalidDataAccessApiUsageException e) {
+ assertEquals("ERR Background save already in progress",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void bgrewriteaof() {
+ String status = client.bgrewriteaof();
+ assertEquals("Background append only file rewriting started", status);
+ }
+
+ @Test
+ public void lastsave() throws InterruptedException {
+ int before = client.lastsave();
+ String st = "";
+ while (!st.equals("OK")) {
+ try {
+ Thread.sleep(1000);
+ st = client.save();
+ } catch (JedisException e) {
+
+ }
+ }
+ int after = client.lastsave();
+ assertTrue((after - before) > 0);
+ }
+
+
+
+ @Test
+ public void info() {
+ Map infoResponse = client.info();
+ Assert.assertNotNull(infoResponse);
+ Assert.assertTrue(infoResponse.containsKey("redis_version"));
+ //Map infoResponse = client.info();
+ //Assert.assertTrue("Expected non empty map of info about the server.", infoResponse.size() > 0);
+ //Assert.assertTrue("Expected key 'redis_version' in map of info about the server.",
+ // infoResponse.containsKey("redis_version"));
+ }
+
+ @Test
+ public void setAndGet() {
+ client.set("foo", "blah blah");
+ String value = client.get("foo");
+ Assert.assertEquals("blah blah", value);
+ }
+
+ @Test
+ public void conversions() {
+ Person p = new Person("Joe", "Trader", 33);
+
+ }
+}
diff --git a/spring-datastore-redis/src/test/java/org/springframework/datastore/redis/core/Person.java b/spring-datastore-redis/src/test/java/org/springframework/datastore/redis/core/Person.java
new file mode 100644
index 000000000..5d919b892
--- /dev/null
+++ b/spring-datastore-redis/src/test/java/org/springframework/datastore/redis/core/Person.java
@@ -0,0 +1,96 @@
+package org.springframework.datastore.redis.core;
+
+/*
+ * Copyright 2002-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.
+ */
+
+import java.io.Serializable;
+
+public class Person implements Serializable {
+
+ private String firstName;
+
+ private String lastName;
+
+ private int age;
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ public Person(String firstName, String lastName, int age) {
+ super();
+ this.firstName = firstName;
+ this.lastName = lastName;
+ this.age = age;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + age;
+ result = prime * result
+ + ((firstName == null) ? 0 : firstName.hashCode());
+ result = prime * result
+ + ((lastName == null) ? 0 : lastName.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Person other = (Person) obj;
+ if (age != other.age)
+ return false;
+ if (firstName == null) {
+ if (other.firstName != null)
+ return false;
+ } else if (!firstName.equals(other.firstName))
+ return false;
+ if (lastName == null) {
+ if (other.lastName != null)
+ return false;
+ } else if (!lastName.equals(other.lastName))
+ return false;
+ return true;
+ }
+
+}
diff --git a/spring-datastore-redis/src/test/java/org/springframework/datastore/redis/core/RedisTemplateIntegrationTests.java b/spring-datastore-redis/src/test/java/org/springframework/datastore/redis/core/RedisTemplateIntegrationTests.java
new file mode 100644
index 000000000..3844bed3d
--- /dev/null
+++ b/spring-datastore-redis/src/test/java/org/springframework/datastore/redis/core/RedisTemplateIntegrationTests.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-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.datastore.redis.core;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.datastore.redis.core.jredis.JRedisClientFactory;
+
+public class RedisTemplateIntegrationTests {
+
+ RedisTemplate template;
+ @Before
+ public void setUp() {
+ template = new RedisTemplate(new JRedisClientFactory());
+ }
+
+ @Test
+ public void conversions() {
+ Person p = new Person("Joe", "Trader", 33);
+ template.convertAndSet("trader:1", p);
+ Person samePerson = template.getAndConvert("trader:1", Person.class);
+ Assert.assertEquals(p, samePerson);
+ }
+}
diff --git a/spring-datastore-redis/src/test/java/org/springframework/datastore/redis/core/jedis/JedisRedisClientIntegrationTests.java b/spring-datastore-redis/src/test/java/org/springframework/datastore/redis/core/jedis/JedisRedisClientIntegrationTests.java
new file mode 100644
index 000000000..fb52ab8fb
--- /dev/null
+++ b/spring-datastore-redis/src/test/java/org/springframework/datastore/redis/core/jedis/JedisRedisClientIntegrationTests.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2002-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.datastore.redis.core.jedis;
+
+import org.junit.Before;
+import org.springframework.datastore.redis.core.AbstractClientIntegrationTests;
+import org.springframework.datastore.redis.core.RedisClientFactory;
+
+public class JedisRedisClientIntegrationTests extends
+ AbstractClientIntegrationTests {
+
+ @Before
+ public void setUp() {
+ RedisClientFactory clientFactory = new JedisClientFactory();
+ clientFactory.setPassword("foobared");
+ client = clientFactory.createClient();
+ client.flushAll();
+ }
+
+
+
+}
diff --git a/spring-datastore-redis/src/test/java/org/springframework/datastore/redis/core/jredis/JRedisClientIntegrationTests.java b/spring-datastore-redis/src/test/java/org/springframework/datastore/redis/core/jredis/JRedisClientIntegrationTests.java
new file mode 100644
index 000000000..ad6f67338
--- /dev/null
+++ b/spring-datastore-redis/src/test/java/org/springframework/datastore/redis/core/jredis/JRedisClientIntegrationTests.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2002-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.datastore.redis.core.jredis;
+
+import org.junit.Before;
+import org.springframework.datastore.redis.core.AbstractClientIntegrationTests;
+import org.springframework.datastore.redis.core.RedisClientFactory;
+
+public class JRedisClientIntegrationTests extends AbstractClientIntegrationTests {
+
+
+ @Before
+ public void setUp() {
+ RedisClientFactory clientFactory = new JRedisClientFactory();
+ clientFactory.setPassword("foobared");
+ client = clientFactory.createClient();
+ }
+
+}
diff --git a/spring-datastore-redis/src/test/resources/log4j.properties b/spring-datastore-redis/src/test/resources/log4j.properties
new file mode 100644
index 000000000..6d5422d74
--- /dev/null
+++ b/spring-datastore-redis/src/test/resources/log4j.properties
@@ -0,0 +1,13 @@
+log4j.rootCategory=INFO, stdout
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
+
+log4j.category.org.apache.activemq=ERROR
+log4j.category.org.springframework.batch=DEBUG
+log4j.category.org.springframework.transaction=INFO
+
+log4j.category.org.hibernate.SQL=DEBUG
+# for debugging datasource initialization
+# log4j.category.test.jdbc=DEBUG
diff --git a/spring-datastore-redis/src/test/resources/org/springframework/datastore/ExampleConfigurationTests-context.xml b/spring-datastore-redis/src/test/resources/org/springframework/datastore/ExampleConfigurationTests-context.xml
new file mode 100644
index 000000000..4717a9b6b
--- /dev/null
+++ b/spring-datastore-redis/src/test/resources/org/springframework/datastore/ExampleConfigurationTests-context.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/spring-datastore-redis/template.mf b/spring-datastore-redis/template.mf
index 1b83aedfd..571e40fea 100644
--- a/spring-datastore-redis/template.mf
+++ b/spring-datastore-redis/template.mf
@@ -11,10 +11,15 @@ Import-Template:
org.springframework.util.*;version="[3.0.0, 4.0.0)",
org.springframework.data.core.*;version="[1.0.0, 2.0.0)",
org.springframework.datastore.core.*;version="[1.0.0, 2.0.0)",
+ org.springframework.datastore.*;version="[1.0.0, 2.0.0)",
org.springframework.datastore.persistence.*;version="[1.0.0, 2.0.0)",
org.springframework.datastore.document.*;version="[1.0.0, 2.0.0)",
org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional,
org.apache.commons.logging.*;version="[1.1.1, 2.0.0)",
- org.w3c.dom.*;version="0"
-
+ org.w3c.dom.*;version="0",
+ org.jredis.*;version="[1.0.0, 2.0.0)",
+ org.jredis.ri.alphazero.*;version="[1.0.0, 2.0.0)",
+ org.springframework.commons.serializer.*;version="[1.0.0, 2.0.0)",
+ redis.clients.jedis.*;version="[1.0.0, 2.0.0)",
+ redis.clients.util.*;version="[1.0.0, 2.0.0)",