diff --git a/README.md b/README.md index d3d635d64..06067d43f 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ Spring Data - Key Value The primary goal of the [Spring Data](http://www.springsource.org/spring-data) project is to make it easier to build Spring-powered applications that use new data access technologies such as non-relational databases, map-reduce frameworks, and cloud based data services. As the name implies, the **Key Value** modules provides integration with key value stores such as [Redis](http://code.google.com/p/redis/) and [Riak](http://www.basho.com/Riak.html). +Examples +-------- +For examples on using the Spring Data Key Value, see the dedicated project, also available on [GitHub](https://github.com/SpringSource/spring-data-keyvalue-examples) + Getting Help ------------ diff --git a/docs/src/info/changelog.txt b/docs/src/info/changelog.txt index 94d51f782..d2d5c4626 100644 --- a/docs/src/info/changelog.txt +++ b/docs/src/info/changelog.txt @@ -2,6 +2,32 @@ SPRING DATA KEY/VALUE INTEGRATION CHANGELOG =========================================== http://www.springsource.org/spring-data +Changes in version 1.0.0.M3 (2011-04-06) +---------------------------------------- + +Redis +----- + +General +* Added support for RJC (new Redis client) +* Added dedicated SORT and SORT/GET support +* Introduced HashMapper feature for mapping objects to and from maps +* Improved exception hierarchy to be more consistent with Spring DAO +* Made several Redis dependencies optional to eliminate unnecessary jars from the classpath + +Package o.s.d.k.redis.connection +* Added support for indexes to RedisConnectionFactories +* Added new key operations to KeyOperations (formerly KeyBound) +* Improved handling of Jedis exceptions + +Package o.s.d.k.redis.core +* Serializers are exposed to RedisCallback +* Added missing operations (move, select) to RedisTemplate +* Fixed the signature of various method + +Package o.s.d.k.redis.support.atomic +* Fixed incorrect serialization leading to error for RedisAtomicInteger & RedisAtomicLong + Changes in version 1.0.0.M2 (2011-02-10) ---------------------------------------- diff --git a/docs/src/reference/docbook/reference/redis.xml b/docs/src/reference/docbook/reference/redis.xml index ba5fd6494..98d4a8eff 100644 --- a/docs/src/reference/docbook/reference/redis.xml +++ b/docs/src/reference/docbook/reference/redis.xml @@ -17,9 +17,9 @@
Redis Requirements SDKV requires Redis 2.0 or above (Redis 2.2 is recommended) and Java SE 6.0 or above. - In terms of language bindings (or connectors), SDKV integrates with Jedis and - JRedis, two popular open source Java libraries for Redis. If you are aware of - any other connector that we should be integrating is, please send us feedback. + In terms of language bindings (or connectors), SDKV integrates with Jedis, + JRedis and RJC, three popular open source Java libraries for Redis. + If you are aware of any other connector that we should be integrating is, please send us feedback.
diff --git a/docs/src/reference/resources/xsl/fopdf.xsl b/docs/src/reference/resources/xsl/fopdf.xsl index 62539d30d..4b3692f19 100644 --- a/docs/src/reference/resources/xsl/fopdf.xsl +++ b/docs/src/reference/resources/xsl/fopdf.xsl @@ -62,7 +62,7 @@ - Copyright © 2006-2009 + Copyright © 2010-2011 @@ -106,7 +106,7 @@ - Spring Data Redis () + Spring Data Key Value () diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/UncategorizedRedisException.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/RedisSystemException.java similarity index 85% rename from spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/UncategorizedRedisException.java rename to spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/RedisSystemException.java index 664a23403..b72123868 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/UncategorizedRedisException.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/RedisSystemException.java @@ -23,9 +23,9 @@ import org.springframework.data.keyvalue.UncategorizedKeyvalueStoreException; * * @author Costin Leau */ -public class UncategorizedRedisException extends UncategorizedKeyvalueStoreException { +public class RedisSystemException extends UncategorizedKeyvalueStoreException { - public UncategorizedRedisException(String msg, Throwable cause) { + public RedisSystemException(String msg, Throwable cause) { super(msg, cause); } } diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisCollectionParser.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisCollectionParser.java new file mode 100644 index 000000000..c806f8dcb --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisCollectionParser.java @@ -0,0 +1,48 @@ +/* + * 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.keyvalue.redis.config; + +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; +import org.springframework.data.keyvalue.redis.support.collections.RedisCollectionFactoryBean; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +/** + * Parser for the Redis <collection> element. + * + * @author Costin Leau + */ +public class RedisCollectionParser extends AbstractSimpleBeanDefinitionParser { + + @Override + protected Class getBeanClass(Element element) { + return RedisCollectionFactoryBean.class; + } + + @Override + protected void postProcess(BeanDefinitionBuilder beanDefinition, Element element) { + String template = element.getAttribute("template"); + if (StringUtils.hasText(template)) { + beanDefinition.addPropertyReference("template", template); + } + } + + @Override + protected boolean isEligibleAttribute(String attributeName) { + return super.isEligibleAttribute(attributeName) && (!"template".equals(attributeName)); + } +} diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisListenerContainerParser.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisListenerContainerParser.java index 1c300a8f4..12fd192fd 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisListenerContainerParser.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisListenerContainerParser.java @@ -37,7 +37,7 @@ import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; /** - * Parser for the JMS <listener-container> element. + * Parser for the Redis <listener-container> element. * * @author Costin Leau */ diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisNamespaceHandler.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisNamespaceHandler.java index c2cc323e7..2a136f377 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisNamespaceHandler.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisNamespaceHandler.java @@ -28,5 +28,6 @@ class RedisNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("listener-container", new RedisListenerContainerParser()); + registerBeanDefinitionParser("collection", new RedisCollectionParser()); } } diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/DefaultSortParameters.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/DefaultSortParameters.java index 194957054..62a34bba1 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/DefaultSortParameters.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/DefaultSortParameters.java @@ -19,7 +19,6 @@ import java.util.ArrayList; import java.util.List; - /** * Default implementation for {@link SortParameters}. * @@ -126,32 +125,42 @@ public class DefaultSortParameters implements SortParameters { // builder like methods // - public SortParameters order(Order order) { + public DefaultSortParameters order(Order order) { setOrder(order); return this; } - public SortParameters alpha() { + public DefaultSortParameters alpha() { setAlphabetic(true); return this; } - public SortParameters numeric() { + public DefaultSortParameters asc() { + setOrder(Order.ASC); + return this; + } + + public DefaultSortParameters desc() { + setOrder(Order.DESC); + return this; + } + + public DefaultSortParameters numeric() { setAlphabetic(false); return this; } - public SortParameters get(byte[] pattern) { + public DefaultSortParameters get(byte[] pattern) { addGetPattern(pattern); return this; } - public SortParameters by(byte[] pattern) { + public DefaultSortParameters by(byte[] pattern) { setByPattern(pattern); return this; } - public SortParameters limit(long start, long count) { + public DefaultSortParameters limit(long start, long count) { setLimit(new Range(start, count)); return this; } diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/DefaultStringRedisConnection.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/DefaultStringRedisConnection.java index 57e9fb804..04484b29f 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/DefaultStringRedisConnection.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/DefaultStringRedisConnection.java @@ -15,7 +15,6 @@ */ package org.springframework.data.keyvalue.redis.connection; -import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -24,8 +23,9 @@ import java.util.Map; import java.util.Properties; import java.util.Set; -import org.springframework.data.keyvalue.redis.UncategorizedRedisException; +import org.springframework.data.keyvalue.redis.RedisSystemException; import org.springframework.data.keyvalue.redis.serializer.RedisSerializer; +import org.springframework.data.keyvalue.redis.serializer.SerializationUtils; import org.springframework.data.keyvalue.redis.serializer.StringRedisSerializer; import org.springframework.util.Assert; @@ -88,7 +88,7 @@ public class DefaultStringRedisConnection implements StringRedisConnection { return delegate.bRPopLPush(timeout, srcKey, dstKey); } - public void close() throws UncategorizedRedisException { + public void close() throws RedisSystemException { delegate.close(); } @@ -156,7 +156,7 @@ public class DefaultStringRedisConnection implements StringRedisConnection { return delegate.getNativeConnection(); } - public byte[] getRange(byte[] key, int start, int end) { + public byte[] getRange(byte[] key, long start, long end) { return delegate.getRange(key, start, end); } @@ -240,7 +240,7 @@ public class DefaultStringRedisConnection implements StringRedisConnection { return delegate.isSubscribed(); } - public Collection keys(byte[] pattern) { + public Set keys(byte[] pattern) { return delegate.keys(pattern); } @@ -308,6 +308,10 @@ public class DefaultStringRedisConnection implements StringRedisConnection { return delegate.persist(key); } + public Boolean move(byte[] key, int dbIndex) { + return delegate.move(key, dbIndex); + } + public String ping() { return delegate.ping(); } @@ -396,8 +400,8 @@ public class DefaultStringRedisConnection implements StringRedisConnection { return delegate.setNX(key, value); } - public void setRange(byte[] key, int start, int end) { - delegate.setRange(key, start, end); + public void setRange(byte[] key, byte[] value, long start) { + delegate.setRange(key, value, start); } public void shutdown() { @@ -576,7 +580,7 @@ public class DefaultStringRedisConnection implements StringRedisConnection { byte[][] ret = new byte[keys.length][]; for (int i = 0; i < ret.length; i++) { - byte[] bs = serializer.serialize(keys[i]); + ret[i] = serializer.serialize(keys[i]); } return ret; @@ -593,20 +597,12 @@ public class DefaultStringRedisConnection implements StringRedisConnection { } - private List deserialize(Collection data) { - List result = new ArrayList(data.size()); - for (byte[] raw : data) { - result.add(serializer.deserialize(raw)); - } - return result; + private List deserialize(List data) { + return SerializationUtils.deserialize(data, serializer); } private Set deserialize(Set data) { - Set result = new LinkedHashSet(data.size()); - for (byte[] raw : data) { - result.add(serializer.deserialize(raw)); - } - return result; + return SerializationUtils.deserialize(data, serializer); } private String deserialize(byte[] data) { @@ -614,6 +610,9 @@ public class DefaultStringRedisConnection implements StringRedisConnection { } private Set deserializeTuple(Set data) { + if (data == null) { + return null; + } Set result = new LinkedHashSet(data.size()); for (Tuple raw : data) { result.add(new DefaultStringTuple(raw, serializer.deserialize(raw.getValue()))); @@ -688,7 +687,7 @@ public class DefaultStringRedisConnection implements StringRedisConnection { } @Override - public String getRange(String key, int start, int end) { + public String getRange(String key, long start, long end) { return deserialize(delegate.getRange(serialize(key), start, end)); } @@ -843,6 +842,11 @@ public class DefaultStringRedisConnection implements StringRedisConnection { return delegate.persist(serialize(key)); } + @Override + public Boolean move(String key, int dbIndex) { + return delegate.move(serialize(key), dbIndex); + } + @Override public void pSubscribe(MessageListener listener, String... patterns) { delegate.pSubscribe(listener, serializeMulti(patterns)); @@ -924,8 +928,8 @@ public class DefaultStringRedisConnection implements StringRedisConnection { } @Override - public void setRange(String key, int start, int end) { - delegate.setRange(serialize(key), start, end); + public void setRange(String key, String value, long start) { + delegate.setRange(serialize(key), serialize(value), start); } @Override diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/RedisCommands.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/RedisCommands.java index 31a989954..072439633 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/RedisCommands.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/RedisCommands.java @@ -16,47 +16,13 @@ package org.springframework.data.keyvalue.redis.connection; -import java.util.Collection; -import java.util.List; /** * Interface for the commands supported by Redis. * * @author Costin Leau */ -public interface RedisCommands extends RedisTxCommands, RedisStringCommands, RedisListCommands, RedisSetCommands, - RedisZSetCommands, RedisHashCommands, RedisServerCommands, RedisPubSubCommands { - - Boolean exists(byte[] key); - - Long del(byte[]... keys); - - DataType type(byte[] key); - - Collection keys(byte[] pattern); - - byte[] randomKey(); - - void rename(byte[] oldName, byte[] newName); - - Boolean renameNX(byte[] oldName, byte[] newName); - - Boolean expire(byte[] key, long seconds); - - Boolean expireAt(byte[] key, long unixTime); - - Boolean persist(byte[] key); - - Long ttl(byte[] key); - - void select(int dbIndex); - - byte[] echo(byte[] message); - - String ping(); - - // sort commands - List sort(byte[] key, SortParameters params); - - Long sort(byte[] key, SortParameters params, byte[] storeKey); +public interface RedisCommands extends RedisKeyCommands, RedisStringCommands, RedisListCommands, RedisSetCommands, + RedisZSetCommands, RedisHashCommands, RedisTxCommands, RedisPubSubCommands, RedisConnectionCommands, + RedisServerCommands { } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/SerializerUtils.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/RedisConnectionCommands.java similarity index 68% rename from spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/SerializerUtils.java rename to spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/RedisConnectionCommands.java index aee3832b6..f3a3ac104 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/SerializerUtils.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/RedisConnectionCommands.java @@ -13,17 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.keyvalue.redis.serializer; +package org.springframework.data.keyvalue.redis.connection; + /** - * Minimal class used for sharing pieces of code between the serializers + * Connection-specific commands supported by Redis. * * @author Costin Leau */ -abstract class SerializerUtils { - static final byte[] EMPTY_ARRAY = new byte[0]; +public interface RedisConnectionCommands { - static boolean isEmpty(byte[] data) { - return (data == null || data.length == 0); - } -} + public abstract void select(int dbIndex); + + public abstract byte[] echo(byte[] message); + + public abstract String ping(); + +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/RedisInvalidSubscriptionException.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/RedisInvalidSubscriptionException.java new file mode 100644 index 000000000..485a87bae --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/RedisInvalidSubscriptionException.java @@ -0,0 +1,45 @@ +/* + * 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.keyvalue.redis.connection; + +import org.springframework.dao.InvalidDataAccessResourceUsageException; + +/** + * Exception thrown when subscribing to an expired/dead {@link Subscription}. + * + * @author Costin Leau + */ +public class RedisInvalidSubscriptionException extends InvalidDataAccessResourceUsageException { + + /** + * Constructs a new RedisInvalidSubscriptionException instance. + * + * @param msg + * @param cause + */ + public RedisInvalidSubscriptionException(String msg, Throwable cause) { + super(msg, cause); + } + + /** + * Constructs a new RedisInvalidSubscriptionException instance. + * + * @param msg + */ + public RedisInvalidSubscriptionException(String msg) { + super(msg); + } +} diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/RedisKeyCommands.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/RedisKeyCommands.java new file mode 100644 index 000000000..41d047c35 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/RedisKeyCommands.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.keyvalue.redis.connection; + +import java.util.List; +import java.util.Set; + + +/** + * Key-specific commands supported by Redis. + * + * @author Costin Leau + */ +public interface RedisKeyCommands { + + public abstract Boolean exists(byte[] key); + + public abstract Long del(byte[]... keys); + + public abstract DataType type(byte[] key); + + public abstract Set keys(byte[] pattern); + + public abstract byte[] randomKey(); + + public abstract void rename(byte[] oldName, byte[] newName); + + public abstract Boolean renameNX(byte[] oldName, byte[] newName); + + public abstract Boolean expire(byte[] key, long seconds); + + public abstract Boolean expireAt(byte[] key, long unixTime); + + public abstract Boolean persist(byte[] key); + + public abstract Boolean move(byte[] key, int dbIndex); + + public abstract Long ttl(byte[] key); + + // sort commands + public abstract List sort(byte[] key, SortParameters params); + + public abstract Long sort(byte[] key, SortParameters params, byte[] storeKey); + +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/RedisStringCommands.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/RedisStringCommands.java index ea96dfde6..d763774ae 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/RedisStringCommands.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/RedisStringCommands.java @@ -52,9 +52,9 @@ public interface RedisStringCommands { Long append(byte[] key, byte[] value); - byte[] getRange(byte[] key, int begin, int end); + byte[] getRange(byte[] key, long begin, long end); - void setRange(byte[] key, int begin, int end); + void setRange(byte[] key, byte[] value, long offset); Boolean getBit(byte[] key, long offset); diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/SubscribedRedisConnectionException.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/RedisSubscribedConnectionException.java similarity index 74% rename from spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/SubscribedRedisConnectionException.java rename to spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/RedisSubscribedConnectionException.java index 2a1945e57..bcc93bab7 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/SubscribedRedisConnectionException.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/RedisSubscribedConnectionException.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.keyvalue.redis; +package org.springframework.data.keyvalue.redis.connection; import org.springframework.dao.InvalidDataAccessApiUsageException; @@ -24,24 +24,24 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; * @author Costin Leau * @see org.springframework.data.keyvalue.redis.connection.RedisPubSubCommands */ -public class SubscribedRedisConnectionException extends InvalidDataAccessApiUsageException { +public class RedisSubscribedConnectionException extends InvalidDataAccessApiUsageException { /** - * Constructs a new SubscribedRedisConnectionException instance. + * Constructs a new RedisSubscribedConnectionException instance. * * @param msg * @param cause */ - public SubscribedRedisConnectionException(String msg, Throwable cause) { + public RedisSubscribedConnectionException(String msg, Throwable cause) { super(msg, cause); } /** - * Constructs a new SubscribedRedisConnectionException instance. + * Constructs a new RedisSubscribedConnectionException instance. * * @param msg */ - public SubscribedRedisConnectionException(String msg) { + public RedisSubscribedConnectionException(String msg) { super(msg); } } diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/StringRedisConnection.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/StringRedisConnection.java index 7622c3b56..28886d595 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/StringRedisConnection.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/StringRedisConnection.java @@ -60,6 +60,8 @@ public interface StringRedisConnection extends RedisConnection { Boolean persist(String key); + Boolean move(String key, int dbIndex); + Long ttl(String key); String echo(String message); @@ -95,9 +97,9 @@ public interface StringRedisConnection extends RedisConnection { Long append(String key, String value); - String getRange(String key, int start, int end); + String getRange(String key, long start, long end); - void setRange(String key, int start, int end); + void setRange(String key, String value, long offset); Boolean getBit(String key, long offset); diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/Subscription.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/Subscription.java index 3000820f1..bdad9ae35 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/Subscription.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/Subscription.java @@ -18,7 +18,10 @@ package org.springframework.data.keyvalue.redis.connection; import java.util.Collection; /** - * Subscription for Redis channels. + * Subscription for Redis channels. Just like the underlying {@link RedisConnection}, + * it should not be used by multiple threads. + * + * Note that once a subscription died, it cannot accept any more subscriptions. * * @author Costin Leau */ @@ -29,14 +32,14 @@ public interface Subscription { * * @param channels channel names */ - void subscribe(byte[]... channels); + void subscribe(byte[]... channels) throws RedisInvalidSubscriptionException; /** * Adds the given channel patterns to the current subscription. * * @param patterns channel patterns */ - void pSubscribe(byte[]... patterns); + void pSubscribe(byte[]... patterns) throws RedisInvalidSubscriptionException; /** * Cancels the current subscription for all channels given by name. diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jedis/JedisConnection.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jedis/JedisConnection.java index 3187f0370..0d751765a 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jedis/JedisConnection.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jedis/JedisConnection.java @@ -18,7 +18,6 @@ package org.springframework.data.keyvalue.redis.connection.jedis; import java.io.IOException; import java.lang.reflect.Field; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -27,10 +26,10 @@ import java.util.Set; import org.springframework.dao.DataAccessException; import org.springframework.data.keyvalue.UncategorizedKeyvalueStoreException; -import org.springframework.data.keyvalue.redis.SubscribedRedisConnectionException; import org.springframework.data.keyvalue.redis.connection.DataType; import org.springframework.data.keyvalue.redis.connection.MessageListener; import org.springframework.data.keyvalue.redis.connection.RedisConnection; +import org.springframework.data.keyvalue.redis.connection.RedisSubscribedConnectionException; import org.springframework.data.keyvalue.redis.connection.SortParameters; import org.springframework.data.keyvalue.redis.connection.Subscription; import org.springframework.util.ReflectionUtils; @@ -72,6 +71,7 @@ public class JedisConnection implements RedisConnection { private volatile JedisSubscription subscription; private volatile Pipeline pipeline; + private final int dbIndex; /** * Constructs a new JedisConnection instance. @@ -79,7 +79,7 @@ public class JedisConnection implements RedisConnection { * @param jedis Jedis entity */ public JedisConnection(Jedis jedis) { - this(jedis, null); + this(jedis, null, 0); } /** @@ -89,13 +89,20 @@ public class JedisConnection implements RedisConnection { * @param jedis * @param pool can be null, if no pool is used */ - public JedisConnection(Jedis jedis, Pool pool) { + public JedisConnection(Jedis jedis, Pool pool, int dbIndex) { this.jedis = jedis; // extract underlying connection for batch operations client = (Client) ReflectionUtils.getField(CLIENT_FIELD, jedis); transaction = new Transaction(client); this.pool = pool; + + this.dbIndex = dbIndex; + + // select the db + if (dbIndex > 0) { + select(dbIndex); + } } protected DataAccessException convertJedisAccessException(Exception ex) { @@ -122,6 +129,11 @@ public class JedisConnection implements RedisConnection { pool.returnBrokenResource(jedis); } else { + // reset the connection + if (dbIndex > 0) { + select(0); + } + pool.returnResource(jedis); } } @@ -178,10 +190,14 @@ public class JedisConnection implements RedisConnection { } } + @SuppressWarnings("unchecked") @Override public List closePipeline() { if (pipeline != null) { - return pipeline.execute(); + List execute = pipeline.execute(); + if (execute != null && !execute.isEmpty()) { + return execute; + } } return Collections.emptyList(); } @@ -209,7 +225,7 @@ public class JedisConnection implements RedisConnection { else { pipeline.sort(key); } - + return null; } return (sortParams != null ? jedis.sort(key, sortParams) : jedis.sort(key)); @@ -565,7 +581,7 @@ public class JedisConnection implements RedisConnection { } @Override - public Collection keys(byte[] pattern) { + public Set keys(byte[] pattern) { try { if (isQueueing()) { transaction.keys(pattern); @@ -614,6 +630,23 @@ public class JedisConnection implements RedisConnection { } } + @Override + public Boolean move(byte[] key, int dbIndex) { + try { + if (isQueueing()) { + client.move(key, dbIndex); + return null; + } + if (isPipelined()) { + client.move(key, dbIndex); + return null; + } + return (jedis.move(key, dbIndex) == 1); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + @Override public byte[] randomKey() { try { @@ -733,7 +766,8 @@ public class JedisConnection implements RedisConnection { for (byte[] key : keys) { if (isPipelined()) { pipeline.watch(key); - } else { + } + else { jedis.watch(key); } } @@ -902,7 +936,7 @@ public class JedisConnection implements RedisConnection { } @Override - public byte[] getRange(byte[] key, int start, int end) { + public byte[] getRange(byte[] key, long start, long end) { try { if (isQueueing()) { transaction.substr(key, (int) start, (int) end); @@ -1021,7 +1055,7 @@ public class JedisConnection implements RedisConnection { } @Override - public void setRange(byte[] key, int start, int end) { + public void setRange(byte[] key, byte[] value, long start) { throw new UnsupportedOperationException(); } @@ -1088,11 +1122,11 @@ public class JedisConnection implements RedisConnection { if (isPipelined()) { final List args = new ArrayList(); for (final byte[] arg : keys) { - args.add(arg); - } - args.add(Protocol.toByteArray(timeout)); - pipeline.blpop(args.toArray(new byte[args.size()][])); - return null; + args.add(arg); + } + args.add(Protocol.toByteArray(timeout)); + pipeline.blpop(args.toArray(new byte[args.size()][])); + return null; } return jedis.blpop(timeout, keys); } catch (Exception ex) { @@ -1109,11 +1143,11 @@ public class JedisConnection implements RedisConnection { if (isPipelined()) { final List args = new ArrayList(); for (final byte[] arg : keys) { - args.add(arg); - } - args.add(Protocol.toByteArray(timeout)); - pipeline.brpop(args.toArray(new byte[args.size()][])); - return null; + args.add(arg); + } + args.add(Protocol.toByteArray(timeout)); + pipeline.brpop(args.toArray(new byte[args.size()][])); + return null; } return jedis.brpop(timeout, keys); } catch (Exception ex) { @@ -1758,11 +1792,10 @@ public class JedisConnection implements RedisConnection { public Set zRevRangeWithScore(byte[] key, long start, long end) { try { if (isQueueing()) { - transaction.zrangeWithScores(key, (int) start, (int) end); - return null; + throw new UnsupportedOperationException(); } if (isPipelined()) { - pipeline.zrangeWithScores(key, (int) start, (int) end); + pipeline.zrangeByScoreWithScores(key, (int) start, (int) end); return null; } return JedisUtils.convertJedisTuple(jedis.zrangeByScoreWithScores(key, (int) start, (int) end)); @@ -2194,7 +2227,7 @@ public class JedisConnection implements RedisConnection { @Override public void pSubscribe(MessageListener listener, byte[]... patterns) { if (isSubscribed()) { - throw new SubscribedRedisConnectionException( + throw new RedisSubscribedConnectionException( "Connection already subscribed; use the connection Subscription to cancel or add new channels"); } @@ -2210,6 +2243,7 @@ public class JedisConnection implements RedisConnection { subscription = new JedisSubscription(listener, jedisPubSub, null, patterns); jedis.psubscribe(jedisPubSub, patterns); + } catch (Exception ex) { throw convertJedisAccessException(ex); } @@ -2218,7 +2252,7 @@ public class JedisConnection implements RedisConnection { @Override public void subscribe(MessageListener listener, byte[]... channels) { if (isSubscribed()) { - throw new SubscribedRedisConnectionException( + throw new RedisSubscribedConnectionException( "Connection already subscribed; use the connection Subscription to cancel or add new channels"); } @@ -2234,6 +2268,7 @@ public class JedisConnection implements RedisConnection { subscription = new JedisSubscription(listener, jedisPubSub, channels, null); jedis.subscribe(jedisPubSub, channels); + } catch (Exception ex) { throw convertJedisAccessException(ex); } @@ -2241,7 +2276,7 @@ public class JedisConnection implements RedisConnection { private void checkSubscription() { if (isSubscribed()) { - throw new SubscribedRedisConnectionException("Cannot execute command - connection is subscribed"); + throw new RedisSubscribedConnectionException("Cannot execute command - connection is subscribed"); } } } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jedis/JedisConnectionFactory.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jedis/JedisConnectionFactory.java index 20cdfba51..1f41fbb6b 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jedis/JedisConnectionFactory.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jedis/JedisConnectionFactory.java @@ -24,6 +24,7 @@ import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.data.keyvalue.redis.connection.RedisConnection; import org.springframework.data.keyvalue.redis.connection.RedisConnectionFactory; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; import redis.clients.jedis.Jedis; @@ -33,7 +34,7 @@ import redis.clients.jedis.JedisShardInfo; import redis.clients.jedis.Protocol; /** - * Connection factory using creating Jedis based connections. + * Connection factory creating Jedis based connections. * * @author Costin Leau */ @@ -51,6 +52,8 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, private JedisPool pool = null; private JedisPoolConfig poolConfig = new JedisPoolConfig(); + private int dbIndex = 0; + /** * Constructs a new JedisConnectionFactory instance * with default settings (default connection pooling, no shard information). @@ -99,6 +102,18 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, } } + /** + * Post process a newly retrieved connection. Useful for decorating or executing + * initialization commands on a new connection. + * This implementation simply returns the connection. + * + * @param connection + * @return processed connection + */ + protected JedisConnection postProcessConnection(JedisConnection connection) { + return connection; + } + public void afterPropertiesSet() { if (shardInfo == null) { shardInfo = new JedisShardInfo(hostName, port); @@ -113,8 +128,8 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, } if (usePool) { - pool = new JedisPool(poolConfig, shardInfo.getHost(), shardInfo.getPort(), - shardInfo.getTimeout(), shardInfo.getPassword()); + pool = new JedisPool(poolConfig, shardInfo.getHost(), shardInfo.getPort(), shardInfo.getTimeout(), + shardInfo.getPassword()); } } @@ -131,7 +146,8 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, public JedisConnection getConnection() { Jedis jedis = fetchJedisConnector(); - return (usePool ? new JedisConnection(jedis, pool) : new JedisConnection(jedis)); + return postProcessConnection((usePool ? new JedisConnection(jedis, pool, dbIndex) : new JedisConnection(jedis, + null, dbIndex))); } @Override @@ -263,4 +279,25 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, public void setPoolConfig(JedisPoolConfig poolConfig) { this.poolConfig = poolConfig; } + + + /** + * Returns the index of the database. + * + * @return Returns the database index + */ + public int getDatabase() { + return dbIndex; + } + + /** + * Sets the index of the database used by this connection factory. + * Default is 0. + * + * @param index database index + */ + public void setDatabase(int index) { + Assert.isTrue(index >= 0, "invalid DB index (a positive index required)"); + this.dbIndex = index; + } } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jedis/JedisSubscription.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jedis/JedisSubscription.java index 93dfbeeef..a2be1371a 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jedis/JedisSubscription.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jedis/JedisSubscription.java @@ -15,13 +15,8 @@ */ package org.springframework.data.keyvalue.redis.connection.jedis; -import java.util.ArrayList; -import java.util.Collection; - import org.springframework.data.keyvalue.redis.connection.MessageListener; -import org.springframework.data.keyvalue.redis.connection.Subscription; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; +import org.springframework.data.keyvalue.redis.connection.util.AbstractSubscription; import redis.clients.jedis.BinaryJedisPubSub; @@ -30,132 +25,48 @@ import redis.clients.jedis.BinaryJedisPubSub; * * @author Costin Leau */ -class JedisSubscription implements Subscription { +class JedisSubscription extends AbstractSubscription { - private final MessageListener listener; private final BinaryJedisPubSub jedisPubSub; - private final Collection channels = new ArrayList(2); - private final Collection patterns = new ArrayList(2); - JedisSubscription(MessageListener listener, BinaryJedisPubSub jedisPubSub, byte[][] channels, byte[][] patterns) { - Assert.notNull(listener); - this.listener = listener; + super(listener, channels, patterns); this.jedisPubSub = jedisPubSub; - - if (!ObjectUtils.isEmpty(channels)) { - synchronized (this.channels) { - for (byte[] bs : channels) { - this.channels.add(bs); - } - } - } - - if (!ObjectUtils.isEmpty(patterns)) { - synchronized (this.patterns) { - for (byte[] bs : patterns) { - this.patterns.add(bs); - } - } - } } @Override - public Collection getChannels() { - synchronized (channels) { - return new ArrayList(channels); - } - } - - @Override - public MessageListener getListener() { - return listener; - } - - @Override - public Collection getPatterns() { - synchronized (patterns) { - return new ArrayList(patterns); - } - } - - @Override - public void pSubscribe(byte[]... patterns) { - Assert.notEmpty(patterns, "at least one pattern required"); - - synchronized (this.patterns) { - for (byte[] bs : patterns) { - this.patterns.add(bs); - } - } - - jedisPubSub.psubscribe(patterns); - } - - @Override - public void pUnsubscribe() { - synchronized (patterns) { - patterns.clear(); - } + protected void doClose() { + jedisPubSub.unsubscribe(); jedisPubSub.punsubscribe(); } @Override - public void pUnsubscribe(byte[]... patterns) { - if (ObjectUtils.isEmpty(patterns)) { - unsubscribe(); + protected void doPsubscribe(byte[]... patterns) { + jedisPubSub.psubscribe(patterns); + } + + @Override + protected void doPUnsubscribe(boolean all, byte[]... patterns) { + if (all) { + jedisPubSub.punsubscribe(); } - else { - synchronized (this.patterns) { - for (byte[] bs : patterns) { - this.patterns.remove(bs); - } - } - jedisPubSub.punsubscribe(patterns); } } @Override - public void subscribe(byte[]... channels) { - Assert.notEmpty(channels, "at least one channel required"); - - synchronized (this.channels) { - for (byte[] bs : channels) { - this.channels.add(bs); - } - } - + protected void doSubscribe(byte[]... channels) { jedisPubSub.subscribe(channels); } @Override - public void unsubscribe() { - synchronized (channels) { - channels.clear(); - } - jedisPubSub.unsubscribe(); - } - - @Override - public void unsubscribe(byte[]... channels) { - if (ObjectUtils.isEmpty(channels)) { - unsubscribe(); + protected void doUnsubscribe(boolean all, byte[]... channels) { + if (all) { + jedisPubSub.unsubscribe(); } else { - synchronized (this.channels) { - for (byte[] bs : channels) { - this.channels.remove(bs); - } - } - jedisPubSub.unsubscribe(channels); } } - - @Override - public boolean isAlive() { - return jedisPubSub.isSubscribed(); - } } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jedis/JedisUtils.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jedis/JedisUtils.java index bdfe315cd..06b0011da 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jedis/JedisUtils.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jedis/JedisUtils.java @@ -29,7 +29,7 @@ import java.util.concurrent.TimeoutException; import org.springframework.dao.DataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.keyvalue.redis.RedisConnectionFailureException; -import org.springframework.data.keyvalue.redis.UncategorizedRedisException; +import org.springframework.data.keyvalue.redis.RedisSystemException; import org.springframework.data.keyvalue.redis.connection.DefaultTuple; import org.springframework.data.keyvalue.redis.connection.MessageListener; import org.springframework.data.keyvalue.redis.connection.SortParameters; @@ -55,8 +55,8 @@ public abstract class JedisUtils { private static final String OK_CODE = "OK"; private static final String OK_MULTI_CODE = "+OK"; - private static final byte[] ONE = new byte[] { 0 }; - private static final byte[] ZERO = new byte[] { 1 }; + private static final byte[] ONE = new byte[] { 1 }; + private static final byte[] ZERO = new byte[] { 0 }; /** * Converts the given, native Jedis exception to Spring's DAO hierarchy. @@ -87,7 +87,7 @@ public abstract class JedisUtils { return convertJedisAccessException((JedisException) ex); } - return new UncategorizedRedisException("Unknown exception", ex); + return new RedisSystemException("Unknown exception", ex); } static DataAccessException convertJedisAccessException(IOException ex) { @@ -194,10 +194,13 @@ public abstract class JedisUtils { static Properties info(String string) { Properties info = new Properties(); + StringReader stringReader = new StringReader(string); try { - info.load(new StringReader(string)); + info.load(stringReader); } catch (Exception ex) { - throw new UncategorizedRedisException("Cannot read Redis info", ex); + throw new RedisSystemException("Cannot read Redis info", ex); + } finally { + stringReader.close(); } return info; } diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jredis/JredisConnection.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jredis/JredisConnection.java index e8360b1b4..ada3441e1 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jredis/JredisConnection.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jredis/JredisConnection.java @@ -16,7 +16,6 @@ package org.springframework.data.keyvalue.redis.connection.jredis; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; @@ -32,7 +31,7 @@ import org.jredis.Query.Support; import org.jredis.ri.alphazero.JRedisService; import org.springframework.dao.DataAccessException; import org.springframework.data.keyvalue.UncategorizedKeyvalueStoreException; -import org.springframework.data.keyvalue.redis.UncategorizedRedisException; +import org.springframework.data.keyvalue.redis.RedisSystemException; import org.springframework.data.keyvalue.redis.connection.DataType; import org.springframework.data.keyvalue.redis.connection.MessageListener; import org.springframework.data.keyvalue.redis.connection.RedisConnection; @@ -76,13 +75,17 @@ public class JredisConnection implements RedisConnection { } @Override - public void close() throws UncategorizedRedisException { + public void close() throws RedisSystemException { isClosed = true; // don't actually close the connection // if a pool is used if (!isPool) { - jredis.quit(); + try { + jredis.quit(); + } catch (Exception ex) { + throw convertJredisAccessException(ex); + } } } @@ -300,9 +303,9 @@ public class JredisConnection implements RedisConnection { } @Override - public Collection keys(byte[] pattern) { + public Set keys(byte[] pattern) { try { - return JredisUtils.convertCollection(jredis.keys(JredisUtils.decode(pattern))); + return JredisUtils.convertToSet(jredis.keys(JredisUtils.decode(pattern))); } catch (Exception ex) { throw convertJredisAccessException(ex); } @@ -318,6 +321,16 @@ public class JredisConnection implements RedisConnection { throw new UnsupportedOperationException(); } + + @Override + public Boolean move(byte[] key, int dbIndex) { + try { + return jredis.move(JredisUtils.decode(key), dbIndex); + } catch (Exception ex) { + throw convertJredisAccessException(ex); + } + } + @Override public byte[] randomKey() { try { @@ -460,7 +473,7 @@ public class JredisConnection implements RedisConnection { } @Override - public byte[] getRange(byte[] key, int start, int end) { + public byte[] getRange(byte[] key, long start, long end) { try { return jredis.substr(JredisUtils.decode(key), start, end); } catch (Exception ex) { @@ -515,7 +528,7 @@ public class JredisConnection implements RedisConnection { } @Override - public void setRange(byte[] key, int start, int end) { + public void setRange(byte[] key, byte[] value, long start) { throw new UnsupportedOperationException(); } @@ -1029,7 +1042,7 @@ public class JredisConnection implements RedisConnection { @Override public Set hKeys(byte[] key) { try { - return new LinkedHashSet(JredisUtils.convertCollection(jredis.hkeys(JredisUtils.decode(key)))); + return new LinkedHashSet(JredisUtils.convertToSet(jredis.hkeys(JredisUtils.decode(key)))); } catch (Exception ex) { throw convertJredisAccessException(ex); } diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jredis/JredisConnectionFactory.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jredis/JredisConnectionFactory.java index 832ca8f87..a52d377a1 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jredis/JredisConnectionFactory.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jredis/JredisConnectionFactory.java @@ -45,6 +45,7 @@ public class JredisConnectionFactory implements InitializingBean, DisposableBean private int timeout; private boolean usePool = true; + private int dbIndex = DEFAULT_REDIS_DB; private JRedisService pool = null; // taken from JRedis code @@ -75,7 +76,7 @@ public class JredisConnectionFactory implements InitializingBean, DisposableBean public void afterPropertiesSet() { if (connectionSpec == null) { Assert.hasText(hostName); - connectionSpec = DefaultConnectionSpec.newSpec(hostName, port, DEFAULT_REDIS_DB, DEFAULT_REDIS_PASSWORD); + connectionSpec = DefaultConnectionSpec.newSpec(hostName, port, dbIndex, DEFAULT_REDIS_PASSWORD); connectionSpec.setConnectionFlag(Connection.Flag.RELIABLE, false); if (StringUtils.hasLength(password)) { @@ -105,10 +106,22 @@ public class JredisConnectionFactory implements InitializingBean, DisposableBean @Override public RedisConnection getConnection() { - return new JredisConnection((usePool ? pool : new JRedisClient(connectionSpec))); + return postProcessConnection(new JredisConnection((usePool ? pool : new JRedisClient(connectionSpec)))); } + /** + * Post process a newly retrieved connection. Useful for decorating or executing + * initialization commands on a new connection. + * This implementation simply returns the connection. + * + * @param connection + * @return processed connection + */ + protected RedisConnection postProcessConnection(JredisConnection connection) { + return connection; + } + @Override public DataAccessException translateExceptionIfPossible(RuntimeException ex) { if (ex instanceof ClientRuntimeException) { @@ -210,4 +223,24 @@ public class JredisConnectionFactory implements InitializingBean, DisposableBean this.poolSize = poolSize; usePool = true; } + + /** + * Returns the index of the database. + * + * @return Returns the database index + */ + public int getDatabase() { + return dbIndex; + } + + /** + * Sets the index of the database used by this connection factory. + * Can be between 0 (default) and 15. + * + * @param index database index + */ + public void setDatabase(int index) { + Assert.isTrue(index >= 0, "invalid DB index (a positive index required)"); + this.dbIndex = index; + } } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jredis/JredisUtils.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jredis/JredisUtils.java index a41bd982a..9cb3dc146 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jredis/JredisUtils.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jredis/JredisUtils.java @@ -16,11 +16,10 @@ package org.springframework.data.keyvalue.redis.connection.jredis; -import java.util.ArrayList; import java.util.Collection; -import java.util.LinkedHashMap; import java.util.Map; import java.util.Properties; +import java.util.Set; import org.jredis.ClientRuntimeException; import org.jredis.RedisException; @@ -33,6 +32,7 @@ import org.springframework.data.keyvalue.redis.connection.DataType; import org.springframework.data.keyvalue.redis.connection.SortParameters; import org.springframework.data.keyvalue.redis.connection.SortParameters.Order; import org.springframework.data.keyvalue.redis.connection.SortParameters.Range; +import org.springframework.data.keyvalue.redis.connection.util.DecodeUtils; /** * Helper class featuring methods for JRedis connection handling, providing support for exception translation. @@ -81,47 +81,28 @@ public abstract class JredisUtils { } static String decode(byte[] bytes) { - return Base64.encodeToString(bytes, false); - } - - static String[] decodeMultiple(byte[]... bytes) { - String[] result = new String[bytes.length]; - for (int i = 0; i < bytes.length; i++) { - result[i] = decode(bytes[i]); - } - return result; + return DecodeUtils.decode(bytes); } static byte[] encode(String string) { - return Base64.decode(string); + return DecodeUtils.encode(string); + } + + static String[] decodeMultiple(byte[]... bytes) { + return DecodeUtils.decodeMultiple(bytes); } static Map encodeMap(Map map) { - Map result = new LinkedHashMap(map.size()); - for (Map.Entry entry : map.entrySet()) { - result.put(encode(entry.getKey()), entry.getValue()); - } - return result; + return DecodeUtils.encodeMap(map); } - static Collection convertCollection(Collection keys) { - Collection list = new ArrayList(keys.size()); - - for (String string : keys) { - list.add(Base64.decode(string)); - } - return list; - } - - static Map decodeMap(Map tuple) { - Map result = new LinkedHashMap(tuple.size()); - for (Map.Entry entry : tuple.entrySet()) { - result.put(decode(entry.getKey()), entry.getValue()); - } - return result; + return DecodeUtils.decodeMap(tuple); } + static Set convertToSet(Collection keys) { + return DecodeUtils.convertToSet(keys); + } static Sort applySortingParams(Sort jredisSort, SortParameters params, byte[] storeKey) { if (params != null) { diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/CloseSuppressingRjcConnection.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/CloseSuppressingRjcConnection.java new file mode 100644 index 000000000..f5adba611 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/CloseSuppressingRjcConnection.java @@ -0,0 +1,117 @@ +/* + * 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.keyvalue.redis.connection.rjc; + +import java.io.IOException; +import java.net.UnknownHostException; +import java.util.List; + +import org.idevlab.rjc.ds.RedisConnection; +import org.idevlab.rjc.message.RedisNodeSubscriber; +import org.idevlab.rjc.protocol.Protocol.Command; + +/** + * Basic decorator suppressing close() calls to the underlying connection. + * Used for reusing arbitrary connections with {@link RedisNodeSubscriber} without + * resorting to connection pooling. + * + * @author Costin Leau + */ +class CloseSuppressingRjcConnection implements RedisConnection { + + private final RedisConnection delegate; + + /** + * Constructs a new CloseSuppressingRjcConnection instance. + * + * @param delegate + */ + CloseSuppressingRjcConnection(RedisConnection delegate) { + this.delegate = delegate; + } + + public void close() { + // no-op + } + + public void connect() throws UnknownHostException, IOException { + delegate.connect(); + } + + public List getAll() { + return delegate.getAll(); + } + + public String getBulkReply() { + return delegate.getBulkReply(); + } + + public String getHost() { + return delegate.getHost(); + } + + public Long getIntegerReply() { + return delegate.getIntegerReply(); + } + + public List getMultiBulkReply() { + return delegate.getMultiBulkReply(); + } + + public List getObjectMultiBulkReply() { + return delegate.getObjectMultiBulkReply(); + } + + public Object getOne() { + return delegate.getOne(); + } + + public int getPort() { + return delegate.getPort(); + } + + public String getStatusCodeReply() { + return delegate.getStatusCodeReply(); + } + + public int getTimeout() { + return delegate.getTimeout(); + } + + public boolean isConnected() { + return delegate.isConnected(); + } + + public void rollbackTimeout() { + delegate.rollbackTimeout(); + } + + public void sendCommand(Command arg0, byte[]... arg1) { + delegate.sendCommand(arg0, arg1); + } + + public void sendCommand(Command arg0, String... arg1) { + delegate.sendCommand(arg0, arg1); + } + + public void sendCommand(Command arg0) { + delegate.sendCommand(arg0); + } + + public void setTimeoutInfinite() { + delegate.setTimeoutInfinite(); + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/RjcConnection.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/RjcConnection.java new file mode 100644 index 000000000..5b9c739a1 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/RjcConnection.java @@ -0,0 +1,2070 @@ +/* + * 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.keyvalue.redis.connection.rjc; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.idevlab.rjc.Client; +import org.idevlab.rjc.RedisException; +import org.idevlab.rjc.Session; +import org.idevlab.rjc.SessionFactoryImpl; +import org.idevlab.rjc.SortingParams; +import org.idevlab.rjc.ZParams; +import org.idevlab.rjc.message.RedisNodeSubscriber; +import org.springframework.dao.DataAccessException; +import org.springframework.data.keyvalue.UncategorizedKeyvalueStoreException; +import org.springframework.data.keyvalue.redis.connection.DataType; +import org.springframework.data.keyvalue.redis.connection.MessageListener; +import org.springframework.data.keyvalue.redis.connection.RedisConnection; +import org.springframework.data.keyvalue.redis.connection.RedisSubscribedConnectionException; +import org.springframework.data.keyvalue.redis.connection.SortParameters; +import org.springframework.data.keyvalue.redis.connection.Subscription; + +/** + * {@code RedisConnection} implementation on top of rjc library. + * + * @author Costin Leau + */ +public class RjcConnection implements RedisConnection { + + private final int dbIndex; + private boolean isClosed = false; + + private final Client client; + private final Session session; + private volatile Client pipeline; + + private volatile RjcSubscription subscription; + private volatile RedisNodeSubscriber subscriber; + + public RjcConnection(org.idevlab.rjc.ds.RedisConnection connection, int dbIndex) { + SingleDataSource connectionDataSource = new SingleDataSource(connection); + session = new SessionFactoryImpl(connectionDataSource).create(); + subscriber = new RedisNodeSubscriber(); + subscriber.setDataSource(new SingleDataSource(new CloseSuppressingRjcConnection(connection))); + client = new Client(connection); + + this.dbIndex = dbIndex; + + // select the db + if (dbIndex > 0) { + select(dbIndex); + } + } + + protected DataAccessException convertRjcAccessException(Exception ex) { + if (ex instanceof RedisException) { + return RjcUtils.convertRjcAccessException((RedisException) ex); + } + return new UncategorizedKeyvalueStoreException("Unknown rjc exception", ex); + } + + @Override + public void close() throws DataAccessException { + isClosed = true; + + // reset the connection (in case a pool is being used) + if (dbIndex > 0) { + select(0); + } + + try { + subscriber.close(); + session.close(); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + + @Override + public boolean isClosed() { + return isClosed; + } + + @Override + public Session getNativeConnection() { + return session; + } + + @Override + public boolean isQueueing() { + return client.isInMulti(); + } + + @Override + public boolean isPipelined() { + return (pipeline != null); + } + + @Override + public void openPipeline() { + if (pipeline == null) { + pipeline = client; + } + } + + @SuppressWarnings("unchecked") + @Override + public List closePipeline() { + if (pipeline != null) { + List execute = client.getAll(); + if (execute != null && !execute.isEmpty()) { + return execute; + } + } + return Collections.emptyList(); + } + + @Override + public List sort(byte[] key, SortParameters params) { + + SortingParams sortParams = RjcUtils.convertSortParams(params); + final String stringKey = RjcUtils.decode(key); + + try { + if (isPipelined()) { + if (sortParams != null) { + pipeline.sort(stringKey, sortParams); + } + else { + pipeline.sort(stringKey); + } + + return null; + } + return RjcUtils.convertToList((sortParams != null ? session.sort(stringKey, sortParams) + : session.sort(stringKey))); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long sort(byte[] key, SortParameters params, byte[] sortKey) { + + SortingParams sortParams = RjcUtils.convertSortParams(params); + final String stringKey = RjcUtils.decode(key); + final String stringSortKey = RjcUtils.decode(sortKey); + + try { + if (isPipelined()) { + if (sortParams != null) { + pipeline.sort(stringKey, sortParams, stringSortKey); + } + else { + pipeline.sort(stringKey, stringSortKey); + } + + return null; + } + return (sortParams != null ? session.sort(stringKey, sortParams, stringSortKey) : session.sort(stringKey, + stringSortKey)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long dbSize() { + try { + if (isPipelined()) { + pipeline.dbSize(); + return null; + } + return session.dbSize(); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + + @Override + public void flushDb() { + try { + if (isPipelined()) { + pipeline.flushDB(); + return; + } + session.flushDB(); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void flushAll() { + try { + if (isPipelined()) { + pipeline.flushAll(); + return; + } + session.flushAll(); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void bgSave() { + try { + if (isPipelined()) { + pipeline.bgsave(); + return; + } + session.bgsave(); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void bgWriteAof() { + try { + if (isPipelined()) { + pipeline.bgrewriteaof(); + return; + } + session.bgrewriteaof(); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void save() { + try { + if (isPipelined()) { + pipeline.save(); + return; + } + session.save(); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public List getConfig(String param) { + try { + if (isPipelined()) { + pipeline.configGet(param); + return null; + } + return session.configGet(param); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Properties info() { + try { + if (isPipelined()) { + pipeline.info(); + return null; + } + return RjcUtils.info(session.info()); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long lastSave() { + try { + if (isPipelined()) { + pipeline.lastsave(); + return null; + } + return session.lastsave(); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void setConfig(String param, String value) { + try { + if (isPipelined()) { + pipeline.configSet(param, value); + return; + } + session.configSet(param, value); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + + @Override + public void resetConfigStats() { + try { + if (isPipelined()) { + pipeline.configResetStat(); + return; + } + client.configResetStat(); + client.getStatusCodeReply(); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void shutdown() { + try { + if (isPipelined()) { + pipeline.shutdown(); + return; + } + session.shutdown(); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public byte[] echo(byte[] message) { + String stringMsg = RjcUtils.decode(message); + try { + if (isPipelined()) { + pipeline.echo(stringMsg); + return null; + } + return RjcUtils.encode(session.echo(stringMsg)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public String ping() { + try { + if (isPipelined()) { + pipeline.ping(); + } + return session.ping(); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long del(byte[]... keys) { + String[] stringKeys = RjcUtils.decodeMultiple(keys); + + try { + if (isPipelined()) { + pipeline.del(stringKeys); + return null; + } + return session.del(stringKeys); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void discard() { + try { + if (isPipelined()) { + pipeline.discard(); + return; + } + + session.discard(); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public List exec() { + try { + if (isPipelined()) { + pipeline.exec(); + return null; + } + return session.exec(); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Boolean exists(byte[] key) { + String stringKey = RjcUtils.decode(key); + + try { + if (isPipelined()) { + pipeline.exists(stringKey); + return null; + } + return session.exists(stringKey); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Boolean expire(byte[] key, long seconds) { + String stringKey = RjcUtils.decode(key); + + try { + if (isPipelined()) { + pipeline.expire(stringKey, (int) seconds); + return null; + } + return session.expire(stringKey, (int) seconds); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Boolean expireAt(byte[] key, long unixTime) { + String stringKey = RjcUtils.decode(key); + + try { + if (isPipelined()) { + pipeline.expireAt(stringKey, unixTime); + return null; + } + return session.expireAt(stringKey, unixTime); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Set keys(byte[] pattern) { + String stringKey = RjcUtils.decode(pattern); + + try { + if (isPipelined()) { + pipeline.keys(stringKey); + return null; + } + return RjcUtils.convertToSet(session.keys(stringKey)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void multi() { + if (isQueueing()) { + return; + } + try { + if (isPipelined()) { + pipeline.multi(); + return; + } + session.multi(); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Boolean persist(byte[] key) { + String stringKey = RjcUtils.decode(key); + + try { + if (isPipelined()) { + pipeline.persist(stringKey); + return null; + } + return session.persist(stringKey); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Boolean move(byte[] key, int dbIndex) { + String stringKey = RjcUtils.decode(key); + + try { + if (isPipelined()) { + pipeline.move(stringKey, dbIndex); + return null; + } + return session.move(stringKey, dbIndex); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public byte[] randomKey() { + try { + if (isPipelined()) { + pipeline.randomKey(); + return null; + } + return RjcUtils.encode(session.randomKey()); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void rename(byte[] oldName, byte[] newName) { + String stringOldKey = RjcUtils.decode(oldName); + String stringNewKey = RjcUtils.decode(newName); + + try { + if (isPipelined()) { + pipeline.rename(stringOldKey, stringNewKey); + return; + } + session.rename(stringOldKey, stringNewKey); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Boolean renameNX(byte[] oldName, byte[] newName) { + String stringOldKey = RjcUtils.decode(oldName); + String stringNewKey = RjcUtils.decode(newName); + + try { + if (isPipelined()) { + pipeline.renamenx(stringOldKey, stringNewKey); + return null; + } + return session.renamenx(stringOldKey, stringNewKey); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void select(int dbIndex) { + try { + if (isPipelined()) { + pipeline.select(dbIndex); + return; + } + session.select(dbIndex); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long ttl(byte[] key) { + String stringKey = RjcUtils.decode(key); + + try { + if (isPipelined()) { + pipeline.ttl(stringKey); + return null; + } + return session.ttl(stringKey); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public DataType type(byte[] key) { + String stringKey = RjcUtils.decode(key); + + try { + if (isPipelined()) { + pipeline.type(stringKey); + return null; + } + return DataType.fromCode(session.type(stringKey)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void unwatch() { + try { + if (isPipelined()) { + pipeline.unwatch(); + return; + } + + session.unwatch(); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void watch(byte[]... keys) { + String[] stringKeys = RjcUtils.decodeMultiple(keys); + + if (isQueueing()) { + return; + } + try { + if (isPipelined()) { + pipeline.watch(stringKeys); + return; + } + else { + session.watch(stringKeys); + } + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + // + // String commands + // + + @Override + public byte[] get(byte[] key) { + String stringKey = RjcUtils.decode(key); + + try { + if (isPipelined()) { + pipeline.get(stringKey); + return null; + } + + return RjcUtils.encode(session.get(stringKey)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void set(byte[] key, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + + try { + if (isPipelined()) { + pipeline.set(stringKey, stringValue); + return; + } + session.set(stringKey, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + + @Override + public byte[] getSet(byte[] key, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + + try { + if (isPipelined()) { + pipeline.getSet(stringKey, stringValue); + return null; + } + return RjcUtils.encode(session.getSet(stringKey, stringValue)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long append(byte[] key, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + + try { + if (isPipelined()) { + pipeline.append(stringKey, stringValue); + return null; + } + return session.append(stringKey, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public List mGet(byte[]... keys) { + String[] stringKeys = RjcUtils.decodeMultiple(keys); + + try { + if (isPipelined()) { + pipeline.mget(stringKeys); + return null; + } + return RjcUtils.convertToList(session.mget(stringKeys)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void mSet(Map tuples) { + String[] decodeMap = RjcUtils.flatten(tuples); + + try { + if (isPipelined()) { + pipeline.mset(decodeMap); + return; + } + session.mset(decodeMap); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void mSetNX(Map tuples) { + String[] decodeMap = RjcUtils.flatten(tuples); + + try { + + if (isPipelined()) { + pipeline.msetnx(decodeMap); + return; + } + session.msetnx(decodeMap); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void setEx(byte[] key, long time, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + + try { + if (isPipelined()) { + pipeline.setex(stringKey, (int) time, stringValue); + return; + } + session.setex(stringKey, (int) time, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Boolean setNX(byte[] key, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + + try { + if (isPipelined()) { + pipeline.setnx(stringKey, stringValue); + return null; + } + return session.setnx(stringKey, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public byte[] getRange(byte[] key, long start, long end) { + String stringKey = RjcUtils.decode(key); + + try { + if (isPipelined()) { + pipeline.getRange(stringKey, (int) start, (int) end); + return null; + } + return RjcUtils.encode(session.getRange(stringKey, (int) start, (int) end)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long decr(byte[] key) { + String stringKey = RjcUtils.decode(key); + try { + + if (isPipelined()) { + pipeline.decr(stringKey); + return null; + } + return session.decr(stringKey); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long decrBy(byte[] key, long value) { + String stringKey = RjcUtils.decode(key); + try { + + if (isPipelined()) { + pipeline.decrBy(stringKey, (int) value); + return null; + } + return session.decrBy(stringKey, (int) value); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long incr(byte[] key) { + String stringKey = RjcUtils.decode(key); + + try { + + if (isPipelined()) { + pipeline.incr(stringKey); + return null; + } + return session.incr(stringKey); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long incrBy(byte[] key, long value) { + String stringKey = RjcUtils.decode(key); + + + try { + if (isPipelined()) { + pipeline.incrBy(stringKey, (int) value); + return null; + } + return session.incrBy(stringKey, (int) value); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Boolean getBit(byte[] key, long offset) { + String stringKey = RjcUtils.decode(key); + + try { + if (isPipelined()) { + pipeline.getbit(stringKey, (int) offset); + return null; + } + return (session.getBit(stringKey, (int) offset) == 0 ? Boolean.FALSE : Boolean.TRUE); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void setBit(byte[] key, long offset, boolean value) { + String stringKey = RjcUtils.decode(key); + + try { + if (isPipelined()) { + pipeline.setbit(stringKey, (int) offset, RjcUtils.asBit(value)); + return; + } + session.setBit(stringKey, (int) offset, RjcUtils.asBit(value)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void setRange(byte[] key, byte[] value, long offset) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + + try { + if (isPipelined()) { + pipeline.setRange(stringKey, (int) offset, stringValue); + return; + } + session.setRange(stringKey, (int) offset, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long strLen(byte[] key) { + String stringKey = RjcUtils.decode(key); + + try { + if (isPipelined()) { + pipeline.strlen(stringKey); + return null; + } + return session.strlen(stringKey); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + // + // List commands + // + + @Override + public Long lPush(byte[] key, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + + try { + if (isPipelined()) { + pipeline.lpush(stringKey, stringValue); + return null; + } + return session.lpush(stringKey, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long rPush(byte[] key, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + + try { + + if (isPipelined()) { + pipeline.rpush(stringKey, stringValue); + return null; + } + return session.rpush(stringKey, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public List bLPop(int timeout, byte[]... keys) { + String[] stringKeys = RjcUtils.decodeMultiple(keys); + + try { + if (isPipelined()) { + pipeline.blpop(stringKeys); + return null; + } + return RjcUtils.convertToList(session.blpop(timeout, stringKeys)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public List bRPop(int timeout, byte[]... keys) { + String[] stringKeys = RjcUtils.decodeMultiple(keys); + + try { + if (isPipelined()) { + pipeline.brpop(stringKeys); + return null; + } + return RjcUtils.convertToList(session.brpop(timeout, stringKeys)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public byte[] lIndex(byte[] key, long index) { + String stringKey = RjcUtils.decode(key); + + + try { + if (isPipelined()) { + pipeline.lindex(stringKey, (int) index); + return null; + } + return RjcUtils.encode(session.lindex(stringKey, (int) index)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long lInsert(byte[] key, Position where, byte[] pivot, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + String stringPivot = RjcUtils.decode(pivot); + Client.LIST_POSITION position = RjcUtils.convertPosition(where); + + try { + if (isPipelined()) { + pipeline.linsert(stringKey, position, stringPivot, stringValue); + return null; + } + return session.linsert(stringKey, position, stringPivot, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long lLen(byte[] key) { + String stringKey = RjcUtils.decode(key); + + try { + + if (isPipelined()) { + pipeline.llen(stringKey); + return null; + } + return session.llen(stringKey); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public byte[] lPop(byte[] key) { + String stringKey = RjcUtils.decode(key); + + try { + + if (isPipelined()) { + pipeline.lpop(stringKey); + return null; + } + return RjcUtils.encode(session.lpop(stringKey)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public List lRange(byte[] key, long start, long end) { + String stringKey = RjcUtils.decode(key); + + try { + + if (isPipelined()) { + pipeline.lrange(stringKey, (int) start, (int) end); + return null; + } + return RjcUtils.convertToList(session.lrange(stringKey, (int) start, (int) end)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long lRem(byte[] key, long count, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + + try { + + if (isPipelined()) { + pipeline.lrem(stringKey, (int) count, stringValue); + return null; + } + return session.lrem(stringKey, (int) count, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void lSet(byte[] key, long index, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + try { + + if (isPipelined()) { + pipeline.lset(stringKey, (int) index, stringValue); + return; + } + session.lset(stringKey, (int) index, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void lTrim(byte[] key, long start, long end) { + String stringKey = RjcUtils.decode(key); + + try { + + if (isPipelined()) { + pipeline.ltrim(stringKey, (int) start, (int) end); + return; + } + session.ltrim(stringKey, (int) start, (int) end); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public byte[] rPop(byte[] key) { + String stringKey = RjcUtils.decode(key); + + try { + + if (isPipelined()) { + pipeline.rpop(stringKey); + return null; + } + return RjcUtils.encode(session.rpop(stringKey)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public byte[] rPopLPush(byte[] srcKey, byte[] dstKey) { + String stringKey = RjcUtils.decode(srcKey); + String stringDest = RjcUtils.decode(dstKey); + + try { + + if (isPipelined()) { + pipeline.rpoplpush(stringKey, stringDest); + return null; + } + return RjcUtils.encode(session.rpoplpush(stringKey, stringDest)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public byte[] bRPopLPush(int timeout, byte[] srcKey, byte[] dstKey) { + String stringKey = RjcUtils.decode(srcKey); + String stringDest = RjcUtils.decode(dstKey); + + try { + if (isPipelined()) { + pipeline.brpoplpush(stringKey, stringDest, timeout); + return null; + } + return RjcUtils.encode(session.brpoplpush(stringKey, stringDest, timeout)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long lPushX(byte[] key, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + try { + if (isPipelined()) { + pipeline.lpushx(stringKey, stringValue); + return null; + } + return session.lpushx(stringKey, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long rPushX(byte[] key, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + try { + if (isPipelined()) { + pipeline.rpushx(stringKey, stringValue); + return null; + } + return session.rpushx(stringKey, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + + // + // Set commands + // + + @Override + public Boolean sAdd(byte[] key, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + + try { + + if (isPipelined()) { + pipeline.sadd(stringKey, stringValue); + return null; + } + return session.sadd(stringKey, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long sCard(byte[] key) { + String stringKey = RjcUtils.decode(key); + + try { + + if (isPipelined()) { + pipeline.scard(stringKey); + return null; + } + return session.scard(stringKey); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Set sDiff(byte[]... keys) { + String[] stringKeys = RjcUtils.decodeMultiple(keys); + + try { + + if (isPipelined()) { + pipeline.sdiff(stringKeys); + return null; + } + return RjcUtils.convertToSet(session.sdiff(stringKeys)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void sDiffStore(byte[] destKey, byte[]... keys) { + String stringKey = RjcUtils.decode(destKey); + String[] stringKeys = RjcUtils.decodeMultiple(keys); + + try { + + if (isPipelined()) { + pipeline.sdiffstore(stringKey, stringKeys); + return; + } + session.sdiffstore(stringKey, stringKeys); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Set sInter(byte[]... keys) { + String[] stringKeys = RjcUtils.decodeMultiple(keys); + try { + + if (isPipelined()) { + pipeline.sinter(stringKeys); + return null; + } + return RjcUtils.convertToSet(session.sinter(stringKeys)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void sInterStore(byte[] destKey, byte[]... keys) { + String stringKey = RjcUtils.decode(destKey); + String[] stringKeys = RjcUtils.decodeMultiple(keys); + try { + + if (isPipelined()) { + pipeline.sinterstore(stringKey, stringKeys); + return; + } + session.sinterstore(stringKey, stringKeys); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Boolean sIsMember(byte[] key, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + + try { + + if (isPipelined()) { + pipeline.sismember(stringKey, stringValue); + return null; + } + return session.sismember(stringKey, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Set sMembers(byte[] key) { + String stringKey = RjcUtils.decode(key); + try { + + if (isPipelined()) { + pipeline.smembers(stringKey); + return null; + } + return RjcUtils.convertToSet(session.smembers(stringKey)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Boolean sMove(byte[] srcKey, byte[] destKey, byte[] value) { + String stringSrc = RjcUtils.decode(srcKey); + String stringDest = RjcUtils.decode(destKey); + String stringValue = RjcUtils.decode(value); + + try { + + if (isPipelined()) { + pipeline.smove(stringSrc, stringDest, stringValue); + return null; + } + return session.smove(stringSrc, stringDest, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public byte[] sPop(byte[] key) { + String stringKey = RjcUtils.decode(key); + try { + + if (isPipelined()) { + pipeline.spop(stringKey); + return null; + } + return RjcUtils.encode(session.spop(stringKey)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public byte[] sRandMember(byte[] key) { + String stringKey = RjcUtils.decode(key); + try { + + if (isPipelined()) { + pipeline.srandmember(stringKey); + return null; + } + return RjcUtils.encode(session.srandmember(stringKey)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Boolean sRem(byte[] key, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + + try { + + if (isPipelined()) { + pipeline.srem(stringKey, stringValue); + return null; + } + return session.srem(stringKey, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Set sUnion(byte[]... keys) { + String[] stringKeys = RjcUtils.decodeMultiple(keys); + + try { + + if (isPipelined()) { + pipeline.sunion(stringKeys); + return null; + } + return RjcUtils.convertToSet(session.sunion(stringKeys)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void sUnionStore(byte[] destKey, byte[]... keys) { + String stringKey = RjcUtils.decode(destKey); + String[] stringKeys = RjcUtils.decodeMultiple(keys); + + try { + + if (isPipelined()) { + pipeline.sunionstore(stringKey, stringKeys); + return; + } + session.sunionstore(stringKey, stringKeys); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + // + // ZSet commands + // + + @Override + public Boolean zAdd(byte[] key, double score, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + + try { + if (isPipelined()) { + pipeline.zadd(stringKey, score, stringValue); + return null; + } + return session.zadd(stringKey, score, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long zCard(byte[] key) { + String stringKey = RjcUtils.decode(key); + + try { + if (isPipelined()) { + pipeline.zcard(stringKey); + return null; + } + return session.zcard(stringKey); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long zCount(byte[] key, double min, double max) { + String stringKey = RjcUtils.decode(key); + try { + if (isPipelined()) { + pipeline.zcount(stringKey, min, max); + return null; + } + + return session.zcount(stringKey, min, max); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Double zIncrBy(byte[] key, double increment, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + + try { + if (isPipelined()) { + pipeline.zincrby(stringKey, increment, stringValue); + return null; + } + return Double.valueOf(session.zincrby(stringKey, increment, stringValue)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long zInterStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets) { + String stringKey = RjcUtils.decode(destKey); + String[] stringKeys = RjcUtils.decodeMultiple(sets); + + ZParams zparams = RjcUtils.toZParams(aggregate, weights); + + try { + if (isPipelined()) { + pipeline.zinterstore(stringKey, zparams, stringKeys); + return null; + } + return session.zinterstore(stringKey, zparams, stringKeys); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long zInterStore(byte[] destKey, byte[]... sets) { + String stringKey = RjcUtils.decode(destKey); + String[] stringKeys = RjcUtils.decodeMultiple(sets); + try { + if (isPipelined()) { + pipeline.zinterstore(stringKey, stringKeys); + return null; + } + + return session.zinterstore(stringKey, stringKeys); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Set zRange(byte[] key, long start, long end) { + String stringKey = RjcUtils.decode(key); + try { + + if (isPipelined()) { + pipeline.zrange(stringKey, (int) start, (int) end); + return null; + } + return RjcUtils.convertToSet(session.zrange(stringKey, (int) start, (int) end)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Set zRangeWithScore(byte[] key, long start, long end) { + String stringKey = RjcUtils.decode(key); + try { + + if (isPipelined()) { + pipeline.zrangeWithScores(stringKey, (int) start, (int) end); + return null; + } + return RjcUtils.convertElementScore(session.zrangeWithScores(stringKey, (int) start, (int) end)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Set zRangeByScore(byte[] key, double min, double max) { + String stringKey = RjcUtils.decode(key); + String minString = Double.toString(min); + String maxString = Double.toString(max); + + try { + if (isPipelined()) { + pipeline.zrangeByScore(stringKey, minString, maxString); + return null; + } + return RjcUtils.convertToSet(session.zrangeByScore(stringKey, minString, maxString)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Set zRangeByScoreWithScore(byte[] key, double min, double max) { + String stringKey = RjcUtils.decode(key); + String minString = Double.toString(min); + String maxString = Double.toString(max); + + try { + if (isPipelined()) { + pipeline.zrangeByScoreWithScores(stringKey, minString, maxString); + return null; + } + return RjcUtils.convertElementScore(session.zrangeByScoreWithScores(stringKey, minString, maxString)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Set zRevRangeWithScore(byte[] key, long start, long end) { + String stringKey = RjcUtils.decode(key); + String minString = Long.toString(start); + String maxString = Long.toString(end); + + try { + + if (isPipelined()) { + pipeline.zrangeByScoreWithScores(stringKey, minString, maxString); + return null; + } + return RjcUtils.convertElementScore(session.zrangeByScoreWithScores(stringKey, minString, maxString)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Set zRangeByScore(byte[] key, double min, double max, long offset, long count) { + String stringKey = RjcUtils.decode(key); + String minString = Double.toString(min); + String maxString = Double.toString(max); + + try { + if (isPipelined()) { + pipeline.zrangeByScore(stringKey, minString, maxString, (int) offset, (int) count); + return null; + } + return RjcUtils.convertToSet(session.zrangeByScore(stringKey, minString, maxString, (int) offset, + (int) count)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Set zRangeByScoreWithScore(byte[] key, double min, double max, long offset, long count) { + String stringKey = RjcUtils.decode(key); + String minString = Double.toString(min); + String maxString = Double.toString(max); + + try { + if (isPipelined()) { + pipeline.zrangeByScoreWithScores(stringKey, minString, maxString, (int) offset, (int) count); + return null; + } + return RjcUtils.convertElementScore(session.zrangeByScoreWithScores(stringKey, minString, maxString, + (int) offset, (int) count)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long zRank(byte[] key, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + + try { + if (isPipelined()) { + pipeline.zrank(stringKey, stringValue); + return null; + } + return session.zrank(stringKey, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Boolean zRem(byte[] key, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + + try { + if (isPipelined()) { + pipeline.zrem(stringKey, stringValue); + return null; + } + return session.zrem(stringKey, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long zRemRange(byte[] key, long start, long end) { + String stringKey = RjcUtils.decode(key); + try { + if (isPipelined()) { + pipeline.zremrangeByRank(stringKey, (int) start, (int) end); + return null; + } + return session.zremrangeByRank(stringKey, (int) start, (int) end); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long zRemRangeByScore(byte[] key, double min, double max) { + String stringKey = RjcUtils.decode(key); + String minString = Double.toString(min); + String maxString = Double.toString(max); + + try { + if (isPipelined()) { + pipeline.zremrangeByScore(stringKey, minString, maxString); + return null; + } + return session.zremrangeByScore(stringKey, minString, maxString); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Set zRevRange(byte[] key, long start, long end) { + String stringKey = RjcUtils.decode(key); + try { + + if (isPipelined()) { + pipeline.zrevrange(stringKey, (int) start, (int) end); + return null; + } + return RjcUtils.convertToSet(session.zrevrange(stringKey, (int) start, (int) end)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long zRevRank(byte[] key, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + + try { + if (isPipelined()) { + pipeline.zrevrank(stringKey, stringValue); + return null; + } + return session.zrevrank(stringKey, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Double zScore(byte[] key, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringValue = RjcUtils.decode(value); + + try { + if (isPipelined()) { + pipeline.zscore(stringKey, stringValue); + return null; + } + return RjcUtils.convert(session.zscore(stringKey, stringValue)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long zUnionStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets) { + String stringKey = RjcUtils.decode(destKey); + String[] stringKeys = RjcUtils.decodeMultiple(destKey); + + ZParams zparams = RjcUtils.toZParams(aggregate, weights); + + try { + if (isPipelined()) { + pipeline.zunionstore(stringKey, zparams, stringKeys); + return null; + } + return session.zunionstore(stringKey, zparams, stringKeys); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long zUnionStore(byte[] destKey, byte[]... sets) { + String stringKey = RjcUtils.decode(destKey); + String[] stringKeys = RjcUtils.decodeMultiple(sets); + + try { + if (isPipelined()) { + pipeline.zunionstore(stringKey, stringKeys); + return null; + } + return session.zunionstore(stringKey, stringKeys); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + // + // Hash commands + // + + @Override + public Boolean hSet(byte[] key, byte[] field, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringField = RjcUtils.decode(field); + String stringValue = RjcUtils.decode(value); + + try { + if (isPipelined()) { + pipeline.hset(stringKey, stringField, stringValue); + return null; + } + return session.hset(stringKey, stringField, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Boolean hSetNX(byte[] key, byte[] field, byte[] value) { + String stringKey = RjcUtils.decode(key); + String stringField = RjcUtils.decode(field); + String stringValue = RjcUtils.decode(value); + + try { + if (isPipelined()) { + pipeline.hsetnx(stringKey, stringField, stringValue); + return null; + } + return session.hsetnx(stringKey, stringField, stringValue); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Boolean hDel(byte[] key, byte[] field) { + String stringKey = RjcUtils.decode(key); + String stringField = RjcUtils.decode(field); + + try { + if (isPipelined()) { + pipeline.hdel(stringKey, stringField); + return null; + } + return session.hdel(stringKey, stringField); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Boolean hExists(byte[] key, byte[] field) { + String stringKey = RjcUtils.decode(key); + String stringField = RjcUtils.decode(field); + + try { + if (isPipelined()) { + pipeline.hexists(stringKey, stringField); + return null; + } + return session.hexists(stringKey, stringField); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public byte[] hGet(byte[] key, byte[] field) { + String stringKey = RjcUtils.decode(key); + String stringField = RjcUtils.decode(field); + + try { + if (isPipelined()) { + pipeline.hget(stringKey, stringField); + return null; + } + return RjcUtils.encode(session.hget(stringKey, stringField)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Map hGetAll(byte[] key) { + String stringKey = RjcUtils.decode(key); + + try { + if (isPipelined()) { + pipeline.hgetAll(stringKey); + return null; + } + return RjcUtils.encodeMap(session.hgetAll(stringKey)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long hIncrBy(byte[] key, byte[] field, long delta) { + String stringKey = RjcUtils.decode(key); + String stringField = RjcUtils.decode(field); + + try { + if (isPipelined()) { + pipeline.hincrBy(stringKey, stringField, (int) delta); + return null; + } + return session.hincrBy(stringKey, stringField, (int) delta); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Set hKeys(byte[] key) { + String stringKey = RjcUtils.decode(key); + try { + if (isPipelined()) { + pipeline.hkeys(stringKey); + return null; + } + return RjcUtils.convertToSet(session.hkeys(stringKey)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Long hLen(byte[] key) { + String stringKey = RjcUtils.decode(key); + try { + if (isPipelined()) { + pipeline.hlen(stringKey); + return null; + } + return session.hlen(stringKey); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public List hMGet(byte[] key, byte[]... fields) { + String stringKey = RjcUtils.decode(key); + String[] stringKeys = RjcUtils.decodeMultiple(fields); + + try { + if (isPipelined()) { + pipeline.hmget(stringKey, stringKeys); + return null; + } + return RjcUtils.convertToList(session.hmget(stringKey, stringKeys)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void hMSet(byte[] key, Map tuple) { + String stringKey = RjcUtils.decode(key); + Map stringTuple = RjcUtils.decodeMap(tuple); + + try { + if (isPipelined()) { + pipeline.hmset(stringKey, stringTuple); + return; + } + session.hmset(stringKey, stringTuple); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public List hVals(byte[] key) { + String stringKey = RjcUtils.decode(key); + try { + + if (isPipelined()) { + pipeline.hvals(stringKey); + return null; + } + return RjcUtils.convertToList(session.hvals(stringKey)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + + // + // Pub/Sub functionality + // + @Override + public Long publish(byte[] channel, byte[] message) { + try { + if (isQueueing()) { + throw new UnsupportedOperationException(); + } + if (isPipelined()) { + throw new UnsupportedOperationException(); + } + return session.publish(RjcUtils.decode(channel), RjcUtils.decode(message)); + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public Subscription getSubscription() { + return subscription; + } + + @Override + public boolean isSubscribed() { + return (subscription != null && subscription.isAlive()); + } + + @Override + public void pSubscribe(MessageListener listener, byte[]... patterns) { + if (isSubscribed()) { + throw new RedisSubscribedConnectionException( + "Connection already subscribed; use the connection Subscription to cancel or add new channels"); + } + + try { + if (isQueueing()) { + throw new UnsupportedOperationException(); + } + if (isPipelined()) { + throw new UnsupportedOperationException(); + } + + subscription = new RjcSubscription(listener, subscriber); + subscription.pSubscribe(patterns); + subscriber.runSubscription(); + + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + @Override + public void subscribe(MessageListener listener, byte[]... channels) { + if (isSubscribed()) { + throw new RedisSubscribedConnectionException( + "Connection already subscribed; use the connection Subscription to cancel or add new channels"); + } + + try { + if (isQueueing()) { + throw new UnsupportedOperationException(); + } + if (isPipelined()) { + throw new UnsupportedOperationException(); + } + + subscription = new RjcSubscription(listener, subscriber); + subscription.subscribe(channels); + subscriber.runSubscription(); + + } catch (Exception ex) { + throw convertRjcAccessException(ex); + } + } + + private void checkSubscription() { + if (isSubscribed()) { + throw new RedisSubscribedConnectionException("Cannot execute command - connection is subscribed"); + } + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/RjcConnectionFactory.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/RjcConnectionFactory.java new file mode 100644 index 000000000..641c9c61f --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/RjcConnectionFactory.java @@ -0,0 +1,218 @@ +/* + * 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.keyvalue.redis.connection.rjc; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.idevlab.rjc.ds.DataSource; +import org.idevlab.rjc.ds.PoolableDataSource; +import org.idevlab.rjc.ds.SimpleDataSource; +import org.idevlab.rjc.protocol.Protocol; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.dao.DataAccessException; +import org.springframework.data.keyvalue.redis.connection.RedisConnection; +import org.springframework.data.keyvalue.redis.connection.RedisConnectionFactory; +import org.springframework.data.keyvalue.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.util.Assert; + +/** + * Connection factory creating rjc based connections. + * + * @author Costin Leau + */ +public class RjcConnectionFactory implements InitializingBean, DisposableBean, RedisConnectionFactory { + + private final static Log log = LogFactory.getLog(JedisConnectionFactory.class); + + private String hostName = "localhost"; + private int port = Protocol.DEFAULT_PORT; + private int timeout = Protocol.DEFAULT_TIMEOUT; + private String password; + + private boolean usePool = true; + private int dbIndex = 0; + private DataSource dataSource; + + + /** + * Constructs a new RjcConnectionFactory instance + * with default settings (default connection pooling, no shard information). + */ + public RjcConnectionFactory() { + } + + + public void afterPropertiesSet() { + if (usePool) { + PoolableDataSource pool = new PoolableDataSource(); + pool.setHost(hostName); + pool.setPort(port); + pool.setPassword(password); + pool.setTimeout(timeout); + + pool.init(); + + dataSource = pool; + + } + else { + dataSource = new SimpleDataSource(hostName, port, timeout, password); + } + } + + public void destroy() { + if (usePool && dataSource != null) { + try { + ((PoolableDataSource) dataSource).close(); + } catch (Exception ex) { + log.warn("Cannot properly close Rjc pool", ex); + } + dataSource = null; + } + } + + @Override + public RedisConnection getConnection() { + return postProcessConnection(new RjcConnection(dataSource.getConnection(), dbIndex)); + } + + /** + * Post process a newly retrieved connection. Useful for decorating or executing + * initialization commands on a new connection. + * This implementation simply returns the connection. + * + * @param connection + * @return processed connection + */ + protected RjcConnection postProcessConnection(RjcConnection connection) { + return connection; + } + + @Override + public DataAccessException translateExceptionIfPossible(RuntimeException ex) { + return RjcUtils.convertRjcAccessException(ex); + } + + + /** + * Returns the Redis hostName. + * + * @return Returns the hostName + */ + public String getHostName() { + return hostName; + } + + /** + * Sets the Redis hostName. + * + * @param hostName The hostName to set. + */ + public void setHostName(String hostName) { + this.hostName = hostName; + } + + /** + * Returns the password used for authenticating with the Redis server. + * + * @return password for authentication + */ + public String getPassword() { + return password; + } + + /** + * Sets the password used for authenticating with the Redis server. + * + * @param password the password to set + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * Returns the port used to connect to the Redis instance. + * + * @return Redis port. + */ + public int getPort() { + return port; + + } + + /** + * Sets the port used to connect to the Redis instance. + * + * @param port Redis port + */ + public void setPort(int port) { + this.port = port; + } + /** + * Returns the timeout. + * + * @return Returns the timeout + */ + public int getTimeout() { + return timeout; + } + + /** + * @param timeout The timeout to set. + */ + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + /** + * Indicates the use of a connection pool. + * + * @return Returns the use of connection pooling. + */ + public boolean getUsePool() { + return usePool; + } + + /** + * Turns on or off the use of connection pooling. + * + * @param usePool The usePool to set. + */ + public void setUsePool(boolean usePool) { + this.usePool = usePool; + } + + /** + * Returns the index of the database. + * + * @return Returns the database index + */ + public int getDatabase() { + return dbIndex; + } + + /** + * Sets the index of the database used by this connection factory. + * Can be between 0 (default) and 15. + * + * @param index database index + */ + public void setDatabase(int index) { + Assert.isTrue(index >= 0, "invalid DB index (a positive index required)"); + this.dbIndex = index; + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/RjcMessageListener.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/RjcMessageListener.java new file mode 100644 index 000000000..c16a2040f --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/RjcMessageListener.java @@ -0,0 +1,45 @@ +/* + * 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.keyvalue.redis.connection.rjc; + +import org.idevlab.rjc.message.MessageListener; +import org.idevlab.rjc.message.PMessageListener; +import org.springframework.data.keyvalue.redis.connection.DefaultMessage; + +/** + * Message listener adapter for RJC library. + * + * @author Costin Leau + */ +class RjcMessageListener implements MessageListener, PMessageListener { + + private final org.springframework.data.keyvalue.redis.connection.MessageListener listener; + + RjcMessageListener(org.springframework.data.keyvalue.redis.connection.MessageListener messageListener) { + this.listener = messageListener; + } + + @Override + public void onMessage(String channel, String message) { + listener.onMessage(new DefaultMessage(RjcUtils.encode(channel), RjcUtils.encode(message)), null); + } + + @Override + public void onMessage(String pattern, String channel, String message) { + listener.onMessage(new DefaultMessage(RjcUtils.encode(channel), RjcUtils.encode(message)), + RjcUtils.encode(pattern)); + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/RjcSubscription.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/RjcSubscription.java new file mode 100644 index 000000000..78c5f2277 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/RjcSubscription.java @@ -0,0 +1,62 @@ +/* + * 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.keyvalue.redis.connection.rjc; + +import org.idevlab.rjc.message.RedisNodeSubscriber; +import org.springframework.data.keyvalue.redis.connection.MessageListener; +import org.springframework.data.keyvalue.redis.connection.util.AbstractSubscription; + +/** + * Message subscription on top of RJC. + * + * @author Costin Leau + */ +class RjcSubscription extends AbstractSubscription { + + private final RedisNodeSubscriber subscriber; + + RjcSubscription(MessageListener listener, RedisNodeSubscriber subscriber) { + super(listener); + this.subscriber = subscriber; + subscriber.setMessageListener(new RjcMessageListener(listener)); + subscriber.setPMessageListener(new RjcMessageListener(listener)); + } + + @Override + protected void doClose() { + subscriber.close(); + } + + @Override + protected void doPsubscribe(byte[]... patterns) { + subscriber.psubscribe(RjcUtils.decodeMultiple(patterns)); + } + + @Override + protected void doPUnsubscribe(boolean all, byte[]... patterns) { + subscriber.punsubscribe(RjcUtils.decodeMultiple(patterns)); + } + + @Override + protected void doSubscribe(byte[]... channels) { + subscriber.subscribe(RjcUtils.decodeMultiple(channels)); + } + + @Override + protected void doUnsubscribe(boolean all, byte[]... channels) { + subscriber.punsubscribe(RjcUtils.decodeMultiple(channels)); + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/RjcUtils.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/RjcUtils.java new file mode 100644 index 000000000..e373fe469 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/RjcUtils.java @@ -0,0 +1,239 @@ +/* + * 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.keyvalue.redis.connection.rjc; + +import java.io.StringReader; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.idevlab.rjc.ElementScore; +import org.idevlab.rjc.RedisException; +import org.idevlab.rjc.SortingParams; +import org.idevlab.rjc.ZParams; +import org.idevlab.rjc.Client.LIST_POSITION; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.keyvalue.redis.RedisSystemException; +import org.springframework.data.keyvalue.redis.connection.DataType; +import org.springframework.data.keyvalue.redis.connection.DefaultTuple; +import org.springframework.data.keyvalue.redis.connection.SortParameters; +import org.springframework.data.keyvalue.redis.connection.RedisListCommands.Position; +import org.springframework.data.keyvalue.redis.connection.RedisZSetCommands.Aggregate; +import org.springframework.data.keyvalue.redis.connection.RedisZSetCommands.Tuple; +import org.springframework.data.keyvalue.redis.connection.SortParameters.Order; +import org.springframework.data.keyvalue.redis.connection.SortParameters.Range; +import org.springframework.data.keyvalue.redis.connection.util.DecodeUtils; +import org.springframework.util.ObjectUtils; + + +/** + * Helper class featuring methods for RJC connection handling, providing support for exception translation. + * + * @author Costin Leau + */ +public abstract class RjcUtils { + + private static final String ONE = "1"; + private static final String ZERO = "0"; + + + public static DataAccessException convertRjcAccessException(RuntimeException ex) { + if (ex instanceof RedisException) { + return convertRjcAccessException((RedisException) ex); + } + + return new RedisSystemException("Unknown exception", ex); + } + + public static DataAccessException convertRjcAccessException(RedisException ex) { + return new InvalidDataAccessApiUsageException(ex.getMessage(), ex); + } + + static DataType convertDataType(String type) { + if ("string".equals(type)) { + return DataType.STRING; + } + else if ("list".equals(type)) { + return DataType.LIST; + } + else if ("set".equals(type)) { + return DataType.SET; + } + else if ("zset".equals(type)) { + return DataType.ZSET; + } + else if ("hash".equals(type)) { + return DataType.HASH; + } + else if ("none".equals(type)) { + return DataType.NONE; + } + + return null; + } + + static String decode(byte[] bytes) { + return DecodeUtils.decode(bytes); + } + + static byte[] encode(String string) { + return DecodeUtils.encode(string); + } + + static String[] decodeMultiple(byte[]... bytes) { + return DecodeUtils.decodeMultiple(bytes); + } + + static String[] flatten(Map tuple) { + String[] result = new String[tuple.size() * 2]; + int index = 0; + for (Map.Entry entry : tuple.entrySet()) { + result[index++] = decode(entry.getKey()); + result[index++] = decode(entry.getValue()); + } + return result; + + } + + static Set convertToSet(Collection keys) { + if (keys == null) { + return null; + } + + return DecodeUtils.convertToSet(keys); + } + + static List convertToList(Collection keys) { + if (keys == null) { + return null; + } + return DecodeUtils.convertToList(keys); + } + + static SortingParams convertSortParams(SortParameters params) { + SortingParams rjcSort = null; + + if (params != null) { + rjcSort = new SortingParams(); + + byte[] byPattern = params.getByPattern(); + if (byPattern != null) { + rjcSort.by(DecodeUtils.decode(byPattern)); + } + byte[][] getPattern = params.getGetPattern(); + + if (getPattern != null && getPattern.length > 0) { + for (byte[] bs : getPattern) { + rjcSort.get(DecodeUtils.decode(bs)); + } + } + Range limit = params.getLimit(); + if (limit != null) { + rjcSort.limit((int) limit.getStart(), (int) limit.getCount()); + } + Order order = params.getOrder(); + if (order != null && order.equals(Order.DESC)) { + rjcSort.desc(); + } + Boolean isAlpha = params.isAlphabetic(); + if (isAlpha != null && isAlpha) { + rjcSort.alpha(); + } + } + return rjcSort; + } + + static Properties info(String string) { + Properties info = new Properties(); + StringReader stringReader = new StringReader(string); + try { + info.load(stringReader); + } catch (Exception ex) { + throw new RedisSystemException("Cannot read Redis info", ex); + } finally { + stringReader.close(); + } + return info; + } + + static String asBit(boolean value) { + return (value ? ONE : ZERO); + } + + static LIST_POSITION convertPosition(Position where) { + switch (where) { + case BEFORE: + return LIST_POSITION.BEFORE; + + case AFTER: + return LIST_POSITION.AFTER; + } + return null; + } + + static ZParams toZParams(Aggregate aggregate, int[] weights) { + return new ZParams().weights(weights).aggregate(ZParams.Aggregate.valueOf(aggregate.name())); + } + + static Set convertElementScore(List tuples) { + Set value = new LinkedHashSet(tuples.size()); + for (ElementScore tuple : tuples) { + value.add(new DefaultTuple(encode(tuple.getElement()), Double.valueOf(tuple.getScore()))); + } + + return value; + } + + static Map encodeMap(Map map) { + Map result = new LinkedHashMap(map.size()); + for (Map.Entry entry : map.entrySet()) { + result.put(encode(entry.getKey()), encode(entry.getValue())); + } + return result; + } + + static Map decodeMap(Map map) { + Map result = new LinkedHashMap(map.size()); + for (Map.Entry entry : map.entrySet()) { + result.put(decode(entry.getKey()), decode(entry.getValue())); + } + return result; + } + + static Double convert(String zscore) { + return (zscore == null ? null : Double.valueOf(zscore)); + } + + + static String[] addArray(String[] one, String[] two) { + if (ObjectUtils.isEmpty(one)) { + return two; + } + if (ObjectUtils.isEmpty(two)) { + return one; + } + + String[] result = Arrays.copyOf(one, one.length + two.length); + System.arraycopy(two, 0, result, one.length, two.length); + return result; + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultKeyBound.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/SingleDataSource.java similarity index 56% rename from spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultKeyBound.java rename to spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/SingleDataSource.java index 478c2eeb3..db152b72c 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultKeyBound.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/SingleDataSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2011 the original author or authors. + * 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. @@ -13,29 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.keyvalue.redis.core; +package org.springframework.data.keyvalue.redis.connection.rjc; +import org.idevlab.rjc.ds.DataSource; +import org.idevlab.rjc.ds.RedisConnection; /** - * Default {@link KeyBound} implementation. - * Meant for internal usage. + * Basic data source that always returns the same connection. * * @author Costin Leau */ -class DefaultKeyBound implements KeyBound { +class SingleDataSource implements DataSource { - private K key; + private final RedisConnection connection; - public DefaultKeyBound(K key) { - setKey(key); + SingleDataSource(RedisConnection connection) { + this.connection = connection; } @Override - public K getKey() { - return key; - } - - protected void setKey(K key) { - this.key = key; + public RedisConnection getConnection() { + return connection; } } diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/package-info.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/package-info.java new file mode 100644 index 000000000..66a90b8ae --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/rjc/package-info.java @@ -0,0 +1,5 @@ +/** + * Connection package for RJC library. + */ +package org.springframework.data.keyvalue.redis.connection.rjc; + diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/util/AbstractSubscription.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/util/AbstractSubscription.java new file mode 100644 index 000000000..6d20a8bf5 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/util/AbstractSubscription.java @@ -0,0 +1,261 @@ +/* + * 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.keyvalue.redis.connection.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.springframework.data.keyvalue.redis.connection.MessageListener; +import org.springframework.data.keyvalue.redis.connection.RedisInvalidSubscriptionException; +import org.springframework.data.keyvalue.redis.connection.Subscription; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Base implementation for a subscription handling the channel/pattern registration so subclasses only have to deal + * with the actual registration/unregistration. + * + * @author Costin Leau + */ +public abstract class AbstractSubscription implements Subscription { + + private final Collection channels = new ArrayList(2); + private final Collection patterns = new ArrayList(2); + private final AtomicBoolean alive = new AtomicBoolean(true); + private final MessageListener listener; + + protected AbstractSubscription(MessageListener listener) { + this(listener, null, null); + } + + /** + * Constructs a new AbstractSubscription instance. Allows channels and patterns to be added + * to the subscription w/o triggering a subscription action (as some clients (Jedis) require an initial call + * before entering into listening mode). + * + * @param listener + * @param channels + * @param patterns + */ + protected AbstractSubscription(MessageListener listener, byte[][] channels, byte[][] patterns) { + Assert.notNull(listener); + this.listener = listener; + + synchronized (this.channels) { + add(this.channels, channels); + } + synchronized (this.patterns) { + add(this.patterns, patterns); + } + } + + /** + * Subscribe to the given channels. + * + * @param channels channels to subscribe to + */ + protected abstract void doSubscribe(byte[]... channels); + + /** + * Channel unsubscribe. + * + * @param all true if all the channels are unsubscribed (used as a hint for the underlying implementation). + * @param channels channels to be unsubscribed + */ + protected abstract void doUnsubscribe(boolean all, byte[]... channels); + + /** + * Subscribe to the given patterns + * + * @param patterns patterns to subscribe to + */ + protected abstract void doPsubscribe(byte[]... patterns); + + /** + * Pattern unsubscribe. + * + * @param all true if all the patterns are unsubscribed (used as a hint for the underlying implementation). + * @param patterns patterns to be unsubscribed + */ + protected abstract void doPUnsubscribe(boolean all, byte[]... patterns); + + /** + * Shutdown the subscription and free any resources held. + */ + protected abstract void doClose(); + + @Override + public MessageListener getListener() { + return listener; + } + + @Override + public Collection getChannels() { + synchronized (channels) { + return clone(channels); + } + } + + @Override + public Collection getPatterns() { + synchronized (patterns) { + return clone(patterns); + } + } + + @Override + public void pSubscribe(byte[]... patterns) { + checkPulse(); + + Assert.notEmpty(patterns, "at least one pattern required"); + + synchronized (this.patterns) { + add(this.patterns, patterns); + } + + doPsubscribe(patterns); + } + + @Override + public void pUnsubscribe() { + pUnsubscribe((byte[][]) null); + } + + + @Override + public void subscribe(byte[]... channels) { + checkPulse(); + + Assert.notEmpty(channels, "at least one channel required"); + + synchronized (this.channels) { + add(this.channels, channels); + } + + doSubscribe(channels); + } + + @Override + public void unsubscribe() { + unsubscribe((byte[][]) null); + } + + @Override + public void pUnsubscribe(byte[]... patts) { + if (!isAlive()) { + return; + } + + // shortcut for unsubscribing all patterns + if (ObjectUtils.isEmpty(patts)) { + if (!this.patterns.isEmpty()) { + patts = getPatterns().toArray(new byte[this.patterns.size()][]); + synchronized (this.patterns) { + this.patterns.clear(); + } + } + else { + // nothing to unsubscribe from + return; + } + } + else { + synchronized (this.patterns) { + remove(this.patterns, patts); + } + } + + if (isWorking()) { + doPUnsubscribe(this.patterns.isEmpty(), patts); + } + } + + @Override + public void unsubscribe(byte[]... chans) { + if (!isAlive()) { + return; + } + + // shortcut for unsubscribing all channels + if (ObjectUtils.isEmpty(chans)) { + if (!this.channels.isEmpty()) { + chans = getPatterns().toArray(new byte[this.channels.size()][]); + synchronized (this.channels) { + this.channels.clear(); + } + } + else { + // nothing to unsubscribe from + return; + } + } + else { + synchronized (this.channels) { + remove(this.channels, chans); + } + } + + if (isWorking()) { + doUnsubscribe(this.channels.isEmpty(), chans); + } + } + + @Override + public boolean isAlive() { + return alive.get(); + } + + private void checkPulse() { + if (!isAlive()) { + throw new RedisInvalidSubscriptionException("Subscription has been unsubscribed and cannot be used anymore"); + } + } + + private boolean isWorking() { + if (channels.isEmpty() && patterns.isEmpty()) { + alive.set(false); + doClose(); + } + return isAlive(); + } + + + private static Collection clone(Collection col) { + Collection list = new ArrayList(col.size()); + for (ByteArrayWrapper wrapper : col) { + list.add(wrapper.getArray().clone()); + } + return list; + } + + + private static void add(Collection col, byte[]... bytes) { + if (!ObjectUtils.isEmpty(bytes)) { + for (byte[] bs : bytes) { + col.add(new ByteArrayWrapper(bs)); + } + } + } + + private static void remove(Collection col, byte[]... bytes) { + if (!ObjectUtils.isEmpty(bytes)) { + for (byte[] bs : bytes) { + col.remove(new ByteArrayWrapper(bs)); + } + } + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jredis/Base64.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/util/Base64.java similarity index 99% rename from spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jredis/Base64.java rename to spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/util/Base64.java index 6feb3a4d6..3e99472d6 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/jredis/Base64.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/util/Base64.java @@ -1,4 +1,4 @@ -package org.springframework.data.keyvalue.redis.connection.jredis; +package org.springframework.data.keyvalue.redis.connection.util; import java.util.Arrays; diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/util/ByteArrayWrapper.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/util/ByteArrayWrapper.java new file mode 100644 index 000000000..7c708192b --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/util/ByteArrayWrapper.java @@ -0,0 +1,57 @@ +/* + * 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.keyvalue.redis.connection.util; + +import java.util.Arrays; + +/** + * Simple wrapper class used for wrapping arrays so they can be used as keys inside maps. + * + * @author Costin Leau + */ +public class ByteArrayWrapper { + + private final byte[] array; + private final int hashCode; + + public ByteArrayWrapper(byte[] array) { + this.array = array; + this.hashCode = Arrays.hashCode(array); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ByteArrayWrapper) { + return Arrays.equals(array, ((ByteArrayWrapper) obj).array); + } + + return false; + } + + @Override + public int hashCode() { + return hashCode; + } + + /** + * Returns the array. + * + * @return Returns the array + */ + public byte[] getArray() { + return array; + } +} diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/util/DecodeUtils.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/util/DecodeUtils.java new file mode 100644 index 000000000..b40867607 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/util/DecodeUtils.java @@ -0,0 +1,82 @@ +/* + * 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.keyvalue.redis.connection.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Simple class containing various decoding utilities. + * + * @author Costin Leau + */ +public abstract class DecodeUtils { + + public static String decode(byte[] bytes) { + return Base64.encodeToString(bytes, false); + } + + public static String[] decodeMultiple(byte[]... bytes) { + String[] result = new String[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + result[i] = decode(bytes[i]); + } + return result; + } + + public static byte[] encode(String string) { + return (string == null ? null : Base64.decode(string)); + } + + public static Map encodeMap(Map map) { + Map result = new LinkedHashMap(map.size()); + for (Map.Entry entry : map.entrySet()) { + result.put(encode(entry.getKey()), entry.getValue()); + } + return result; + } + + public static Map decodeMap(Map tuple) { + Map result = new LinkedHashMap(tuple.size()); + for (Map.Entry entry : tuple.entrySet()) { + result.put(decode(entry.getKey()), entry.getValue()); + } + return result; + } + + public static Set convertToSet(Collection keys) { + Set set = new LinkedHashSet(keys.size()); + + for (String string : keys) { + set.add(encode(string)); + } + return set; + } + + public static List convertToList(Collection keys) { + List set = new ArrayList(keys.size()); + + for (String string : keys) { + set.add(encode(string)); + } + return set; + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/util/package-info.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/util/package-info.java new file mode 100644 index 000000000..c8f07d8ce --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/connection/util/package-info.java @@ -0,0 +1,5 @@ +/** + * Internal utility package for encoding/decoding Strings to byte[] (using Base64) library. + */ +package org.springframework.data.keyvalue.redis.connection.util; + diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/AbstractOperations.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/AbstractOperations.java new file mode 100644 index 000000000..ccaeedfe4 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/AbstractOperations.java @@ -0,0 +1,193 @@ +/* + * 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.keyvalue.redis.core; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.data.keyvalue.redis.connection.RedisConnection; +import org.springframework.data.keyvalue.redis.serializer.RedisSerializer; +import org.springframework.data.keyvalue.redis.serializer.SerializationUtils; +import org.springframework.util.Assert; + +/** + * Internal base class used by various RedisTemplate XXXOperations implementations. + * + * @author Costin Leau + */ +abstract class AbstractOperations { + + // utility methods for the template internal methods + abstract class ValueDeserializingRedisCallback implements RedisCallback { + private Object key; + + public ValueDeserializingRedisCallback(Object key) { + this.key = key; + } + + @Override + public final V doInRedis(RedisConnection connection) { + byte[] result = inRedis(rawKey(key), connection); + return deserializeValue(result); + } + + protected abstract byte[] inRedis(byte[] rawKey, RedisConnection connection); + } + + RedisSerializer keySerializer = null; + RedisSerializer valueSerializer = null; + RedisSerializer hashKeySerializer = null; + RedisSerializer hashValueSerializer = null; + RedisSerializer stringSerializer = null; + RedisTemplate template; + + AbstractOperations(RedisTemplate template) { + keySerializer = template.getKeySerializer(); + valueSerializer = template.getValueSerializer(); + hashKeySerializer = template.getHashKeySerializer(); + hashValueSerializer = template.getHashValueSerializer(); + stringSerializer = template.getStringSerializer(); + + this.template = template; + } + + + T execute(RedisCallback callback, boolean b) { + return template.execute(callback, b); + } + + public RedisOperations getOperations() { + return template; + } + + @SuppressWarnings("unchecked") + byte[] rawKey(Object key) { + Assert.notNull(key, "non null key required"); + return keySerializer.serialize(key); + } + + byte[] rawString(String key) { + return stringSerializer.serialize(key); + } + + @SuppressWarnings("unchecked") + byte[] rawValue(Object value) { + return valueSerializer.serialize(value); + } + + @SuppressWarnings("unchecked") + byte[] rawHashKey(HK hashKey) { + Assert.notNull(hashKey, "non null hash key required"); + return hashKeySerializer.serialize(hashKey); + } + + @SuppressWarnings("unchecked") + byte[] rawHashValue(HV value) { + return hashValueSerializer.serialize(value); + } + + byte[][] rawKeys(K key, K otherKey) { + final byte[][] rawKeys = new byte[2][]; + + + rawKeys[0] = rawKey(key); + rawKeys[1] = rawKey(key); + return rawKeys; + } + + byte[][] rawKeys(Collection keys) { + return rawKeys(null, keys); + } + + byte[][] rawKeys(K key, Collection keys) { + final byte[][] rawKeys = new byte[keys.size() + (key != null ? 1 : 0)][]; + + int i = 0; + + if (key != null) { + rawKeys[i++] = rawKey(key); + } + + for (K k : keys) { + rawKeys[i++] = rawKey(k); + } + + return rawKeys; + } + + @SuppressWarnings("unchecked") + Set deserializeValues(Set rawValues) { + return SerializationUtils.deserialize(rawValues, valueSerializer); + } + + @SuppressWarnings("unchecked") + List deserializeValues(List rawValues) { + return SerializationUtils.deserialize(rawValues, valueSerializer); + } + + @SuppressWarnings("unchecked") + Set deserializeHashKeys(Set rawKeys) { + return SerializationUtils.deserialize(rawKeys, hashKeySerializer); + } + + @SuppressWarnings("unchecked") + List deserializeHashValues(List rawValues) { + return SerializationUtils.deserialize(rawValues, hashValueSerializer); + } + + @SuppressWarnings("unchecked") + Map deserializeHashMap(Map entries) { + // connection in pipeline/multi mode + if (entries == null) { + return null; + } + + Map map = new LinkedHashMap(entries.size()); + + for (Map.Entry entry : entries.entrySet()) { + map.put((HK) deserializeHashKey(entry.getKey()), (HV) deserializeHashValue(entry.getValue())); + } + + return map; + } + + @SuppressWarnings("unchecked") + K deserializeKey(byte[] value) { + return (K) keySerializer.deserialize(value); + } + + @SuppressWarnings("unchecked") + V deserializeValue(byte[] value) { + return (V) valueSerializer.deserialize(value); + } + + String deserializeString(byte[] value) { + return (String) stringSerializer.deserialize(value); + } + + @SuppressWarnings( { "unchecked" }) + HK deserializeHashKey(byte[] value) { + return (HK) hashKeySerializer.deserialize(value); + } + + @SuppressWarnings("unchecked") + HV deserializeHashValue(byte[] value) { + return (HV) hashValueSerializer.deserialize(value); + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundHashOperations.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundHashOperations.java index dd8f53525..d559ed00d 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundHashOperations.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundHashOperations.java @@ -24,7 +24,7 @@ import java.util.Set; * * @author Costin Leau */ -public interface BoundHashOperations extends KeyBound { +public interface BoundHashOperations extends BoundKeyOperations { RedisOperations getOperations(); diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundKeyOperations.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundKeyOperations.java new file mode 100644 index 000000000..d6791ea0f --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundKeyOperations.java @@ -0,0 +1,85 @@ +/* + * Copyright 2010-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.keyvalue.redis.core; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import org.springframework.data.keyvalue.redis.connection.DataType; + +/** + * Operations over a Redis key. + * + * Useful for executing common key-'bound' operations to all implementations. + * + *

As the rest of the APIs, if the underlying connection is pipelined or queued/in multi mode, + * all methods will return null. + *

+ * @author Costin Leau + */ +public interface BoundKeyOperations { + + /** + * Returns the key associated with this entity. + * + * @return key associated with the implementing entity + */ + K getKey(); + + /** + * Returns the associated Redis type. + * + * @return key type + */ + DataType getType(); + + /** + * Returns the expiration of this key. + * + * @return expiration value (in seconds) + */ + Long getExpire(); + + /** + * Sets the key time-to-live/expiration. + * + * @param timeout expiration value + * @param unit expiration unit + * @return true if expiration was set, false otherwise + */ + Boolean expire(long timeout, TimeUnit unit); + + /** + * Sets the key time-to-live/expiration. + * + * @param date expiration date + * @return true if expiration was set, false otherwise + */ + Boolean expireAt(Date date); + + /** + * Removes the expiration (if any) of the key. + * @return true if expiration was removed, false otherwise + */ + Boolean persist(); + + /** + * Renames the key. + * + * @param newKey new key + */ + void rename(K newKey); +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundListOperations.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundListOperations.java index a51df518a..5701587ba 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundListOperations.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundListOperations.java @@ -23,7 +23,7 @@ import java.util.concurrent.TimeUnit; * * @author Costin Leau */ -public interface BoundListOperations extends KeyBound { +public interface BoundListOperations extends BoundKeyOperations { RedisOperations getOperations(); diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundSetOperations.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundSetOperations.java index 2da61f806..e13520885 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundSetOperations.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundSetOperations.java @@ -24,7 +24,7 @@ import java.util.Set; * * @author Costin Leau */ -public interface BoundSetOperations extends KeyBound { +public interface BoundSetOperations extends BoundKeyOperations { RedisOperations getOperations(); diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundValueOperations.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundValueOperations.java index ea7988ed2..ae6267bf9 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundValueOperations.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundValueOperations.java @@ -22,27 +22,27 @@ import java.util.concurrent.TimeUnit; * * @author Costin Leau */ -public interface BoundValueOperations extends KeyBound { +public interface BoundValueOperations extends BoundKeyOperations { RedisOperations getOperations(); void set(V value); + void set(V value, long offset); + void set(V value, long timeout, TimeUnit unit); Boolean setIfAbsent(V value); V get(); + String get(long start, long end); + V getAndSet(V value); Long increment(long delta); Integer append(String value); - String get(int start, int end); - - void set(int start, int end); - Long size(); } diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundZSetOperations.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundZSetOperations.java index 37222cc93..2ba5783d4 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundZSetOperations.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BoundZSetOperations.java @@ -25,7 +25,7 @@ import java.util.Set; * * @author Costin Leau */ -public interface BoundZSetOperations extends KeyBound { +public interface BoundZSetOperations extends BoundKeyOperations { RedisOperations getOperations(); diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/CloseSuppressingInvocationHandler.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/CloseSuppressingInvocationHandler.java new file mode 100644 index 000000000..a44527cb4 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/CloseSuppressingInvocationHandler.java @@ -0,0 +1,64 @@ +/* + * 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.keyvalue.redis.core; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.springframework.data.keyvalue.redis.connection.RedisConnection; + +/** +* Invocation handler that suppresses close calls on {@link RedisConnection}. +* @see RedisConnection#close() +* @author Costin Leau +*/ +class CloseSuppressingInvocationHandler implements InvocationHandler { + + private static final String CLOSE = "close"; + private static final String HASH_CODE = "hashCode"; + private static final String EQUALS = "equals"; + + private final RedisConnection target; + + public CloseSuppressingInvocationHandler(RedisConnection target) { + this.target = target; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + + if (method.getName().equals(EQUALS)) { + // Only consider equal when proxies are identical. + return (proxy == args[0]); + } + else if (method.getName().equals(HASH_CODE)) { + // Use hashCode of PersistenceManager proxy. + return System.identityHashCode(proxy); + } + else if (method.getName().equals(CLOSE)) { + // Handle close method: suppress, not valid. + return null; + } + + // Invoke method on target RedisConnection. + try { + Object retVal = method.invoke(this.target, args); + return retVal; + } catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundHashOperations.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundHashOperations.java index f55ed85be..c8e6a531e 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundHashOperations.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundHashOperations.java @@ -19,12 +19,14 @@ import java.util.Collection; import java.util.Map; import java.util.Set; +import org.springframework.data.keyvalue.redis.connection.DataType; + /** * Default implementation for {@link HashOperations}. * * @author Costin Leau */ -class DefaultBoundHashOperations extends DefaultKeyBound implements BoundHashOperations { +class DefaultBoundHashOperations extends DefaultBoundKeyOperations implements BoundHashOperations { private final HashOperations ops; @@ -35,7 +37,7 @@ class DefaultBoundHashOperations extends DefaultKeyBound implement * @param template */ public DefaultBoundHashOperations(H key, RedisOperations operations) { - super(key); + super(key, operations); this.ops = operations.opsForHash(); } @@ -103,4 +105,9 @@ class DefaultBoundHashOperations extends DefaultKeyBound implement public Map entries() { return ops.entries(getKey()); } + + @Override + public DataType getType() { + return DataType.HASH; + } } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundKeyOperations.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundKeyOperations.java new file mode 100644 index 000000000..105c6e48b --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundKeyOperations.java @@ -0,0 +1,72 @@ +/* + * Copyright 2010-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.keyvalue.redis.core; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + + +/** + * Default {@link BoundKeyOperations} implementation. + * Meant for internal usage. + * + * @author Costin Leau + */ +abstract class DefaultBoundKeyOperations implements BoundKeyOperations { + + private K key; + private final RedisOperations ops; + + public DefaultBoundKeyOperations(K key, RedisOperations operations) { + setKey(key); + this.ops = operations; + } + + @Override + public K getKey() { + return key; + } + + protected void setKey(K key) { + this.key = key; + } + + @Override + public Boolean expire(long timeout, TimeUnit unit) { + return ops.expire(key, timeout, unit); + } + + @Override + public Boolean expireAt(Date date) { + return ops.expireAt(key, date); + } + + @Override + public Long getExpire() { + return ops.getExpire(key); + } + + @Override + public Boolean persist() { + return ops.persist(key); + } + + @Override + public void rename(K newKey) { + ops.rename(key, newKey); + key = newKey; + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundListOperations.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundListOperations.java index df302ad11..45a34511c 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundListOperations.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundListOperations.java @@ -18,13 +18,15 @@ package org.springframework.data.keyvalue.redis.core; import java.util.List; import java.util.concurrent.TimeUnit; +import org.springframework.data.keyvalue.redis.connection.DataType; + /** * Default implementation for {@link BoundListOperations}. * * @author Costin Leau */ -class DefaultBoundListOperations extends DefaultKeyBound implements BoundListOperations { +class DefaultBoundListOperations extends DefaultBoundKeyOperations implements BoundListOperations { private final ListOperations ops; @@ -35,7 +37,7 @@ class DefaultBoundListOperations extends DefaultKeyBound implements Bou * @param operations */ public DefaultBoundListOperations(K key, RedisOperations operations) { - super(key); + super(key, operations); this.ops = operations.opsForList(); } @@ -124,4 +126,9 @@ class DefaultBoundListOperations extends DefaultKeyBound implements Bou public void set(long index, V value) { ops.set(getKey(), index, value); } + + @Override + public DataType getType() { + return DataType.LIST; + } } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundSetOperations.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundSetOperations.java index e92e21bd2..d0010b63a 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundSetOperations.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundSetOperations.java @@ -19,12 +19,14 @@ package org.springframework.data.keyvalue.redis.core; import java.util.Collection; import java.util.Set; +import org.springframework.data.keyvalue.redis.connection.DataType; + /** * Default implementation for {@link BoundSetOperations}. * * @author Costin Leau */ -class DefaultBoundSetOperations extends DefaultKeyBound implements BoundSetOperations { +class DefaultBoundSetOperations extends DefaultBoundKeyOperations implements BoundSetOperations { private final SetOperations ops; @@ -36,7 +38,7 @@ class DefaultBoundSetOperations extends DefaultKeyBound implements Boun * @param operations */ DefaultBoundSetOperations(K key, RedisOperations operations) { - super(key); + super(key, operations); this.ops = operations.opsForSet(); } @@ -146,4 +148,9 @@ class DefaultBoundSetOperations extends DefaultKeyBound implements Boun public void unionAndStore(Collection keys, K destKey) { ops.unionAndStore(getKey(), keys, destKey); } + + @Override + public DataType getType() { + return DataType.SET; + } } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundValueOperations.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundValueOperations.java index 7d0b2322f..b9ec6b168 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundValueOperations.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundValueOperations.java @@ -17,10 +17,12 @@ package org.springframework.data.keyvalue.redis.core; import java.util.concurrent.TimeUnit; +import org.springframework.data.keyvalue.redis.connection.DataType; + /** * @author Costin Leau */ -class DefaultBoundValueOperations extends DefaultKeyBound implements BoundValueOperations { +class DefaultBoundValueOperations extends DefaultBoundKeyOperations implements BoundValueOperations { private final ValueOperations ops; @@ -31,7 +33,7 @@ class DefaultBoundValueOperations extends DefaultKeyBound implements Bo * @param operations */ public DefaultBoundValueOperations(K key, RedisOperations operations) { - super(key); + super(key, operations); this.ops = operations.opsForValue(); } @@ -56,7 +58,7 @@ class DefaultBoundValueOperations extends DefaultKeyBound implements Bo } @Override - public String get(int start, int end) { + public String get(long start, long end) { return ops.get(getKey(), start, end); } @@ -76,8 +78,8 @@ class DefaultBoundValueOperations extends DefaultKeyBound implements Bo } @Override - public void set(int start, int end) { - ops.set(getKey(), start, end); + public void set(V value, long offset) { + ops.set(getKey(), value, offset); } @Override @@ -89,4 +91,9 @@ class DefaultBoundValueOperations extends DefaultKeyBound implements Bo public RedisOperations getOperations() { return ops.getOperations(); } + + @Override + public DataType getType() { + return DataType.STRING; + } } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundZSetOperations.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundZSetOperations.java index 343c1d72b..71590d863 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundZSetOperations.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultBoundZSetOperations.java @@ -19,12 +19,14 @@ package org.springframework.data.keyvalue.redis.core; import java.util.Collection; import java.util.Set; +import org.springframework.data.keyvalue.redis.connection.DataType; + /** * Default implementation for {@link BoundZSetOperations}. * * @author Costin Leau */ -class DefaultBoundZSetOperations extends DefaultKeyBound implements BoundZSetOperations { +class DefaultBoundZSetOperations extends DefaultBoundKeyOperations implements BoundZSetOperations { private final ZSetOperations ops; @@ -34,9 +36,9 @@ class DefaultBoundZSetOperations extends DefaultKeyBound implements Bou * @param key * @param oeprations */ - public DefaultBoundZSetOperations(K key, RedisOperations oeprations) { - super(key); - this.ops = oeprations.opsForZSet(); + public DefaultBoundZSetOperations(K key, RedisOperations operations) { + super(key, operations); + this.ops = operations.opsForZSet(); } @Override @@ -128,4 +130,9 @@ class DefaultBoundZSetOperations extends DefaultKeyBound implements Bou public void unionAndStore(Collection otherKeys, K destKey) { ops.unionAndStore(getKey(), otherKeys, destKey); } + + @Override + public DataType getType() { + return DataType.ZSET; + } } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultHashOperations.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultHashOperations.java new file mode 100644 index 000000000..afe1def4f --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultHashOperations.java @@ -0,0 +1,228 @@ +/* + * 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.keyvalue.redis.core; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.data.keyvalue.redis.connection.RedisConnection; + +/** + * Default implementation of {@link HashOperations}. + * + * @author Costin Leau + */ +class DefaultHashOperations extends AbstractOperations implements HashOperations { + + @SuppressWarnings("unchecked") + DefaultHashOperations(RedisTemplate template) { + super((RedisTemplate) template); + } + + @SuppressWarnings("unchecked") + @Override + public HV get(K key, Object hashKey) { + final byte[] rawKey = rawKey(key); + final byte[] rawHashKey = rawHashKey(hashKey); + + byte[] rawHashValue = execute(new RedisCallback() { + @Override + public byte[] doInRedis(RedisConnection connection) { + return connection.hGet(rawKey, rawHashKey); + } + }, true); + + return (HV) deserializeHashValue(rawHashValue); + } + + @Override + public Boolean hasKey(K key, Object hashKey) { + final byte[] rawKey = rawKey(key); + final byte[] rawHashKey = rawHashKey(hashKey); + + return execute(new RedisCallback() { + @Override + public Boolean doInRedis(RedisConnection connection) { + return connection.hExists(rawKey, rawHashKey); + } + }, true); + } + + @Override + public Long increment(K key, HK hashKey, final long delta) { + final byte[] rawKey = rawKey(key); + final byte[] rawHashKey = rawHashKey(hashKey); + + return execute(new RedisCallback() { + @Override + public Long doInRedis(RedisConnection connection) { + return connection.hIncrBy(rawKey, rawHashKey, delta); + } + }, true); + + } + + @Override + public Set keys(K key) { + final byte[] rawKey = rawKey(key); + + Set rawValues = execute(new RedisCallback>() { + @Override + public Set doInRedis(RedisConnection connection) { + return connection.hKeys(rawKey); + } + }, true); + + return deserializeHashKeys(rawValues); + } + + @Override + public Long size(K key) { + final byte[] rawKey = rawKey(key); + + return execute(new RedisCallback() { + @Override + public Long doInRedis(RedisConnection connection) { + return connection.hLen(rawKey); + } + }, true); + } + + @Override + public void putAll(K key, Map m) { + if (m.isEmpty()) { + return; + } + + final byte[] rawKey = rawKey(key); + + final Map hashes = new LinkedHashMap(m.size()); + + for (Map.Entry entry : m.entrySet()) { + hashes.put(rawHashKey(entry.getKey()), rawHashValue(entry.getValue())); + } + + execute(new RedisCallback() { + @Override + public Object doInRedis(RedisConnection connection) { + connection.hMSet(rawKey, hashes); + return null; + } + }, true); + } + + + @Override + public Collection multiGet(K key, Collection fields) { + if (fields.isEmpty()) { + return Collections.emptyList(); + } + + final byte[] rawKey = rawKey(key); + + final byte[][] rawHashKeys = new byte[fields.size()][]; + + int counter = 0; + for (HK hashKey : fields) { + rawHashKeys[counter++] = rawHashKey(hashKey); + } + + List rawValues = execute(new RedisCallback>() { + @Override + public List doInRedis(RedisConnection connection) { + return connection.hMGet(rawKey, rawHashKeys); + } + }, true); + + return deserializeHashValues(rawValues); + } + + @Override + public void put(K key, HK hashKey, HV value) { + final byte[] rawKey = rawKey(key); + final byte[] rawHashKey = rawHashKey(hashKey); + final byte[] rawHashValue = rawHashValue(value); + + execute(new RedisCallback() { + @Override + public Object doInRedis(RedisConnection connection) { + connection.hSet(rawKey, rawHashKey, rawHashValue); + return null; + } + }, true); + } + + @Override + public Boolean putIfAbsent(K key, HK hashKey, HV value) { + final byte[] rawKey = rawKey(key); + final byte[] rawHashKey = rawHashKey(hashKey); + final byte[] rawHashValue = rawHashValue(value); + + return execute(new RedisCallback() { + @Override + public Boolean doInRedis(RedisConnection connection) { + return connection.hSetNX(rawKey, rawHashKey, rawHashValue); + } + }, true); + } + + + @Override + public List values(K key) { + final byte[] rawKey = rawKey(key); + + List rawValues = execute(new RedisCallback>() { + @Override + public List doInRedis(RedisConnection connection) { + return connection.hVals(rawKey); + } + }, true); + + return deserializeHashValues(rawValues); + } + + @Override + public void delete(K key, Object hashKey) { + final byte[] rawKey = rawKey(key); + final byte[] rawHashKey = rawHashKey(hashKey); + + execute(new RedisCallback() { + @Override + public Object doInRedis(RedisConnection connection) { + connection.hDel(rawKey, rawHashKey); + return null; + } + }, true); + } + + @Override + public Map entries(K key) { + final byte[] rawKey = rawKey(key); + + Map entries = execute(new RedisCallback>() { + @Override + public Map doInRedis(RedisConnection connection) { + return connection.hGetAll(rawKey); + } + }, true); + + return deserializeHashMap(entries); + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultListOperations.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultListOperations.java new file mode 100644 index 000000000..b6c67936f --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultListOperations.java @@ -0,0 +1,246 @@ +/* + * 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.keyvalue.redis.core; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.springframework.data.keyvalue.redis.connection.RedisConnection; +import org.springframework.data.keyvalue.redis.connection.RedisListCommands.Position; + +/** + * Default implementation of {@link ListOperations}. + * + * @author Costin Leau + */ +class DefaultListOperations extends AbstractOperations implements ListOperations { + + DefaultListOperations(RedisTemplate template) { + super(template); + } + + @Override + public V index(K key, final long index) { + return execute(new ValueDeserializingRedisCallback(key) { + @Override + protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { + return connection.lIndex(rawKey, index); + } + }, true); + } + + @Override + public V leftPop(K key) { + return execute(new ValueDeserializingRedisCallback(key) { + @Override + protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { + return connection.lPop(rawKey); + } + }, true); + } + + @Override + public V leftPop(K key, long timeout, TimeUnit unit) { + final int tm = (int) unit.toSeconds(timeout); + + return execute(new ValueDeserializingRedisCallback(key) { + @Override + protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { + return connection.bLPop(tm, rawKey).get(0); + } + }, true); + } + + @Override + public Long leftPush(K key, V value) { + final byte[] rawKey = rawKey(key); + final byte[] rawValue = rawValue(value); + return execute(new RedisCallback() { + @Override + public Long doInRedis(RedisConnection connection) { + return connection.lPush(rawKey, rawValue); + } + }, true); + } + + @Override + public Long leftPushIfPresent(K key, V value) { + final byte[] rawKey = rawKey(key); + final byte[] rawValue = rawValue(value); + return execute(new RedisCallback() { + @Override + public Long doInRedis(RedisConnection connection) { + return connection.lPushX(rawKey, rawValue); + } + }, true); + } + + @Override + public Long leftPush(K key, V pivot, V value) { + final byte[] rawKey = rawKey(key); + final byte[] rawPivot = rawValue(pivot); + final byte[] rawValue = rawValue(value); + return execute(new RedisCallback() { + @Override + public Long doInRedis(RedisConnection connection) { + return connection.lInsert(rawKey, Position.BEFORE, rawPivot, rawValue); + } + }, true); + } + + @Override + public Long size(K key) { + final byte[] rawKey = rawKey(key); + return execute(new RedisCallback() { + @Override + public Long doInRedis(RedisConnection connection) { + return connection.lLen(rawKey); + } + }, true); + } + + @Override + public List range(K key, final long start, final long end) { + final byte[] rawKey = rawKey(key); + return execute(new RedisCallback>() { + @SuppressWarnings("unchecked") + @Override + public List doInRedis(RedisConnection connection) { + return deserializeValues(connection.lRange(rawKey, start, end)); + } + }, true); + } + + @Override + public Long remove(K key, final long count, Object value) { + final byte[] rawKey = rawKey(key); + final byte[] rawValue = rawValue(value); + return execute(new RedisCallback() { + @Override + public Long doInRedis(RedisConnection connection) { + return connection.lRem(rawKey, count, rawValue); + } + }, true); + } + + @Override + public V rightPop(K key) { + return execute(new ValueDeserializingRedisCallback(key) { + @Override + protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { + return connection.rPop(rawKey); + } + }, true); + } + + @Override + public V rightPop(K key, long timeout, TimeUnit unit) { + final int tm = (int) unit.toSeconds(timeout); + + return execute(new ValueDeserializingRedisCallback(key) { + @Override + protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { + return connection.bRPop(tm, rawKey).get(0); + } + }, true); + } + + @Override + public Long rightPush(K key, V value) { + final byte[] rawKey = rawKey(key); + final byte[] rawValue = rawValue(value); + return execute(new RedisCallback() { + @Override + public Long doInRedis(RedisConnection connection) { + return connection.rPush(rawKey, rawValue); + } + }, true); + } + + @Override + public Long rightPushIfPresent(K key, V value) { + final byte[] rawKey = rawKey(key); + final byte[] rawValue = rawValue(value); + return execute(new RedisCallback() { + @Override + public Long doInRedis(RedisConnection connection) { + return connection.rPushX(rawKey, rawValue); + } + }, true); + } + + @Override + public Long rightPush(K key, V pivot, V value) { + final byte[] rawKey = rawKey(key); + final byte[] rawPivot = rawValue(pivot); + final byte[] rawValue = rawValue(value); + + return execute(new RedisCallback() { + @Override + public Long doInRedis(RedisConnection connection) { + return connection.lInsert(rawKey, Position.AFTER, rawPivot, rawValue); + } + }, true); + } + + @Override + public V rightPopAndLeftPush(K sourceKey, K destinationKey) { + final byte[] rawDestKey = rawKey(destinationKey); + + return execute(new ValueDeserializingRedisCallback(sourceKey) { + @Override + protected byte[] inRedis(byte[] rawSourceKey, RedisConnection connection) { + return connection.rPopLPush(rawSourceKey, rawDestKey); + } + }, true); + } + + @Override + public V rightPopAndLeftPush(K sourceKey, K destinationKey, long timeout, TimeUnit unit) { + final int tm = (int) unit.toSeconds(timeout); + final byte[] rawDestKey = rawKey(destinationKey); + + return execute(new ValueDeserializingRedisCallback(sourceKey) { + @Override + protected byte[] inRedis(byte[] rawSourceKey, RedisConnection connection) { + return connection.bRPopLPush(tm, rawSourceKey, rawDestKey); + } + }, true); + } + + @Override + public void set(K key, final long index, V value) { + final byte[] rawValue = rawValue(value); + execute(new ValueDeserializingRedisCallback(key) { + @Override + protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { + connection.lSet(rawKey, index, rawValue); + return null; + } + }, true); + } + + @Override + public void trim(K key, final long start, final long end) { + execute(new ValueDeserializingRedisCallback(key) { + @Override + protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { + connection.lTrim(rawKey, start, end); + return null; + } + }, true); + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultSetOperations.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultSetOperations.java new file mode 100644 index 000000000..a4893104f --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultSetOperations.java @@ -0,0 +1,241 @@ +/* + * 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.keyvalue.redis.core; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +import org.springframework.data.keyvalue.redis.connection.RedisConnection; + +/** + * Default implementation of {@link SetOperations}. + * + * @author Costin Leau + */ +class DefaultSetOperations extends AbstractOperations implements SetOperations { + + public DefaultSetOperations(RedisTemplate template) { + super(template); + } + + @Override + public Boolean add(K key, V value) { + final byte[] rawKey = rawKey(key); + final byte[] rawValue = rawValue(value); + return execute(new RedisCallback() { + @Override + public Boolean doInRedis(RedisConnection connection) { + return connection.sAdd(rawKey, rawValue); + } + }, true); + } + + @Override + public Set difference(K key, K otherKey) { + return difference(key, Collections.singleton(otherKey)); + } + + @SuppressWarnings("unchecked") + @Override + public Set difference(final K key, final Collection otherKeys) { + final byte[][] rawKeys = rawKeys(key, otherKeys); + Set rawValues = execute(new RedisCallback>() { + @Override + public Set doInRedis(RedisConnection connection) { + return connection.sDiff(rawKeys); + } + }, true); + + return deserializeValues(rawValues); + } + + @Override + public void differenceAndStore(K key, K otherKey, K destKey) { + differenceAndStore(key, Collections.singleton(otherKey), destKey); + } + + @Override + public void differenceAndStore(final K key, final Collection otherKeys, K destKey) { + final byte[][] rawKeys = rawKeys(key, otherKeys); + final byte[] rawDestKey = rawKey(destKey); + execute(new RedisCallback() { + @Override + public Object doInRedis(RedisConnection connection) { + connection.sDiffStore(rawDestKey, rawKeys); + return null; + } + }, true); + } + + @Override + public Set intersect(K key, K otherKey) { + return intersect(key, Collections.singleton(otherKey)); + } + + @SuppressWarnings("unchecked") + @Override + public Set intersect(K key, Collection otherKeys) { + final byte[][] rawKeys = rawKeys(key, otherKeys); + Set rawValues = execute(new RedisCallback>() { + @Override + public Set doInRedis(RedisConnection connection) { + return connection.sInter(rawKeys); + } + }, true); + + return deserializeValues(rawValues); + } + + @Override + public void intersectAndStore(K key, K otherKey, K destKey) { + intersectAndStore(key, Collections.singleton(otherKey), destKey); + } + + @Override + public void intersectAndStore(K key, Collection otherKeys, K destKey) { + final byte[][] rawKeys = rawKeys(key, otherKeys); + final byte[] rawDestKey = rawKey(destKey); + execute(new RedisCallback() { + @Override + public Object doInRedis(RedisConnection connection) { + connection.sInterStore(rawDestKey, rawKeys); + return null; + } + }, true); + } + + @Override + public Boolean isMember(K key, Object o) { + final byte[] rawKey = rawKey(key); + final byte[] rawValue = rawValue(o); + return execute(new RedisCallback() { + @Override + public Boolean doInRedis(RedisConnection connection) { + return connection.sIsMember(rawKey, rawValue); + } + }, true); + } + + @SuppressWarnings("unchecked") + @Override + public Set members(K key) { + final byte[] rawKey = rawKey(key); + Set rawValues = execute(new RedisCallback>() { + @Override + public Set doInRedis(RedisConnection connection) { + return connection.sMembers(rawKey); + } + }, true); + + return deserializeValues(rawValues); + } + + @Override + public Boolean move(K key, V value, K destKey) { + final byte[] rawKey = rawKey(key); + final byte[] rawDestKey = rawKey(destKey); + final byte[] rawValue = rawValue(value); + + return execute(new RedisCallback() { + @Override + public Boolean doInRedis(RedisConnection connection) { + return connection.sMove(rawKey, rawDestKey, rawValue); + } + }, true); + } + + @Override + public V randomMember(K key) { + + return execute(new ValueDeserializingRedisCallback(key) { + @Override + protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { + return connection.randomKey(); + } + }, true); + } + + @Override + public Boolean remove(K key, Object o) { + final byte[] rawKey = rawKey(key); + final byte[] rawValue = rawValue(o); + return execute(new RedisCallback() { + @Override + public Boolean doInRedis(RedisConnection connection) { + return connection.sRem(rawKey, rawValue); + } + }, true); + } + + @Override + public V pop(K key) { + return execute(new ValueDeserializingRedisCallback(key) { + @Override + protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { + return connection.sPop(rawKey); + } + }, true); + } + + @Override + public Long size(K key) { + final byte[] rawKey = rawKey(key); + return execute(new RedisCallback() { + @Override + public Long doInRedis(RedisConnection connection) { + return connection.sCard(rawKey); + } + }, true); + } + + @Override + public Set union(K key, K otherKey) { + return union(key, Collections.singleton(otherKey)); + } + + @SuppressWarnings("unchecked") + @Override + public Set union(K key, Collection otherKeys) { + final byte[][] rawKeys = rawKeys(key, otherKeys); + Set rawValues = execute(new RedisCallback>() { + @Override + public Set doInRedis(RedisConnection connection) { + return connection.sUnion(rawKeys); + } + }, true); + + return deserializeValues(rawValues); + } + + @Override + public void unionAndStore(K key, K otherKey, K destKey) { + unionAndStore(key, Collections.singleton(otherKey), destKey); + } + + @Override + public void unionAndStore(K key, Collection otherKeys, K destKey) { + final byte[][] rawKeys = rawKeys(key, otherKeys); + final byte[] rawDestKey = rawKey(destKey); + execute(new RedisCallback() { + @Override + public Object doInRedis(RedisConnection connection) { + connection.sUnionStore(rawDestKey, rawKeys); + return null; + } + }, true); + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultValueOperations.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultValueOperations.java new file mode 100644 index 000000000..bc2c13d0d --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultValueOperations.java @@ -0,0 +1,244 @@ +/* + * 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.keyvalue.redis.core; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.springframework.dao.DataAccessException; +import org.springframework.data.keyvalue.redis.connection.RedisConnection; + +/** + * Default implementation of {@link ValueOperations}. + * + * @author Costin Leau + */ +class DefaultValueOperations extends AbstractOperations implements ValueOperations { + + DefaultValueOperations(RedisTemplate template) { + super(template); + } + + @Override + public V get(final Object key) { + + return execute(new ValueDeserializingRedisCallback(key) { + @Override + protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { + return connection.get(rawKey); + } + }, true); + } + + @Override + public V getAndSet(K key, V newValue) { + final byte[] rawValue = rawValue(newValue); + return execute(new ValueDeserializingRedisCallback(key) { + @Override + protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { + return connection.getSet(rawKey, rawValue); + } + }, true); + } + + @Override + public Long increment(K key, final long delta) { + final byte[] rawKey = rawKey(key); + // TODO add conversion service in here ? + return execute(new RedisCallback() { + @Override + public Long doInRedis(RedisConnection connection) { + if (delta == 1) { + return connection.incr(rawKey); + } + + if (delta == -1) { + return connection.decr(rawKey); + } + + if (delta < 0) { + return connection.decrBy(rawKey, delta); + } + + return connection.incrBy(rawKey, delta); + } + }, true); + } + + @Override + public Integer append(K key, String value) { + final byte[] rawKey = rawKey(key); + final byte[] rawString = rawString(value); + + return execute(new RedisCallback() { + @Override + public Integer doInRedis(RedisConnection connection) { + return connection.append(rawKey, rawString).intValue(); + } + }, true); + } + + @Override + public String get(K key, final long start, final long end) { + final byte[] rawKey = rawKey(key); + + byte[] rawReturn = execute(new RedisCallback() { + @Override + public byte[] doInRedis(RedisConnection connection) { + return connection.getRange(rawKey, start, end); + } + }, true); + + return deserializeString(rawReturn); + } + + @SuppressWarnings("unchecked") + @Override + public List multiGet(Collection keys) { + if (keys.isEmpty()) { + return Collections.emptyList(); + } + + final byte[][] rawKeys = new byte[keys.size()][]; + + int counter = 0; + for (K hashKey : keys) { + rawKeys[counter++] = rawKey(hashKey); + } + + List rawValues = execute(new RedisCallback>() { + @Override + public List doInRedis(RedisConnection connection) { + return connection.mGet(rawKeys); + } + }, true); + + return deserializeValues(rawValues); + } + + @Override + public void multiSet(Map m) { + if (m.isEmpty()) { + return; + } + + final Map rawKeys = new LinkedHashMap(m.size()); + + for (Map.Entry entry : m.entrySet()) { + rawKeys.put(rawKey(entry.getKey()), rawValue(entry.getValue())); + } + + execute(new RedisCallback() { + @Override + public Object doInRedis(RedisConnection connection) { + connection.mSet(rawKeys); + return null; + } + }, true); + } + + @Override + public void multiSetIfAbsent(Map m) { + if (m.isEmpty()) { + return; + } + + final Map rawKeys = new LinkedHashMap(m.size()); + + for (Map.Entry entry : m.entrySet()) { + rawKeys.put(rawKey(entry.getKey()), rawValue(entry.getValue())); + } + + execute(new RedisCallback() { + @Override + public Object doInRedis(RedisConnection connection) { + connection.mSetNX(rawKeys); + return null; + } + }, true); + } + + @Override + public void set(K key, V value) { + final byte[] rawValue = rawValue(value); + execute(new ValueDeserializingRedisCallback(key) { + @Override + protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { + connection.set(rawKey, rawValue); + return null; + } + }, true); + } + + @Override + public void set(K key, V value, long timeout, TimeUnit unit) { + final byte[] rawKey = rawKey(key); + final byte[] rawValue = rawValue(value); + final long rawTimeout = unit.toSeconds(timeout); + + execute(new RedisCallback() { + @Override + public Object doInRedis(RedisConnection connection) throws DataAccessException { + connection.setEx(rawKey, (int) rawTimeout, rawValue); + return null; + } + }, true); + } + + @Override + public Boolean setIfAbsent(K key, V value) { + final byte[] rawKey = rawKey(key); + final byte[] rawValue = rawValue(value); + + return execute(new RedisCallback() { + @Override + public Boolean doInRedis(RedisConnection connection) throws DataAccessException { + return connection.setNX(rawKey, rawValue); + } + }, true); + } + + + @Override + public void set(K key, final V value, final long offset) { + final byte[] rawKey = rawKey(key); + final byte[] rawValue = rawValue(value); + + execute(new RedisCallback() { + @Override + public Object doInRedis(RedisConnection connection) { + connection.setRange(rawKey, rawValue, offset); + return null; + } + }, true); + } + + @Override + public Long size(K key) { + final byte[] rawKey = rawKey(key); + + return execute(new RedisCallback() { + @Override + public Long doInRedis(RedisConnection connection) { + return connection.strLen(rawKey); + } + }, true); + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultZSetOperations.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultZSetOperations.java new file mode 100644 index 000000000..154163fe7 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/DefaultZSetOperations.java @@ -0,0 +1,243 @@ +/* + * 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.keyvalue.redis.core; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +import org.springframework.data.keyvalue.redis.connection.RedisConnection; + +/** + * Default implementation of {@link ZSetOperations}. + * + * @author Costin Leau + */ +class DefaultZSetOperations extends AbstractOperations implements ZSetOperations { + + DefaultZSetOperations(RedisTemplate template) { + super(template); + } + + @Override + public Boolean add(final K key, final V value, final double score) { + final byte[] rawKey = rawKey(key); + final byte[] rawValue = rawValue(value); + + return execute(new RedisCallback() { + @Override + public Boolean doInRedis(RedisConnection connection) { + return connection.zAdd(rawKey, score, rawValue); + } + }, true); + } + + @Override + public Double incrementScore(K key, V value, final double delta) { + final byte[] rawKey = rawKey(key); + final byte[] rawValue = rawValue(value); + + return execute(new RedisCallback() { + @Override + public Double doInRedis(RedisConnection connection) { + return connection.zIncrBy(rawKey, delta, rawValue); + } + }, true); + } + + @Override + public void intersectAndStore(K key, K otherKey, K destKey) { + intersectAndStore(key, Collections.singleton(otherKey), destKey); + } + + @Override + public void intersectAndStore(K key, Collection otherKeys, K destKey) { + final byte[][] rawKeys = rawKeys(key, otherKeys); + final byte[] rawDestKey = rawKey(destKey); + execute(new RedisCallback() { + @Override + public Object doInRedis(RedisConnection connection) { + connection.zInterStore(rawDestKey, rawKeys); + return null; + } + }, true); + } + + @SuppressWarnings("unchecked") + @Override + public Set range(K key, final long start, final long end) { + final byte[] rawKey = rawKey(key); + + Set rawValues = execute(new RedisCallback>() { + @Override + public Set doInRedis(RedisConnection connection) { + return connection.zRange(rawKey, start, end); + } + }, true); + + return deserializeValues(rawValues); + } + + @SuppressWarnings("unchecked") + @Override + public Set rangeByScore(K key, final double min, final double max) { + final byte[] rawKey = rawKey(key); + + Set rawValues = execute(new RedisCallback>() { + @Override + public Set doInRedis(RedisConnection connection) { + return connection.zRangeByScore(rawKey, min, max); + } + }, true); + + return deserializeValues(rawValues); + } + + @Override + public Long rank(K key, Object o) { + final byte[] rawKey = rawKey(key); + final byte[] rawValue = rawValue(o); + + return execute(new RedisCallback() { + @Override + public Long doInRedis(RedisConnection connection) { + Long zRank = connection.zRank(rawKey, rawValue); + return (zRank != null && zRank.longValue() >= 0 ? zRank : null); + } + }, true); + } + + @Override + public Long reverseRank(K key, Object o) { + final byte[] rawKey = rawKey(key); + final byte[] rawValue = rawValue(o); + + return execute(new RedisCallback() { + @Override + public Long doInRedis(RedisConnection connection) { + Long zRank = connection.zRevRank(rawKey, rawValue); + return (zRank != null && zRank.longValue() >= 0 ? zRank : null); + } + }, true); + } + + @Override + public Boolean remove(K key, Object o) { + final byte[] rawKey = rawKey(key); + final byte[] rawValue = rawValue(o); + + return execute(new RedisCallback() { + @Override + public Boolean doInRedis(RedisConnection connection) { + return connection.zRem(rawKey, rawValue); + } + }, true); + } + + @Override + public void removeRange(K key, final long start, final long end) { + final byte[] rawKey = rawKey(key); + execute(new RedisCallback() { + @Override + public Object doInRedis(RedisConnection connection) { + connection.zRemRange(rawKey, start, end); + return null; + } + }, true); + } + + @Override + public void removeRangeByScore(K key, final double min, final double max) { + final byte[] rawKey = rawKey(key); + execute(new RedisCallback() { + @Override + public Object doInRedis(RedisConnection connection) { + connection.zRemRangeByScore(rawKey, min, max); + return null; + } + }, true); + } + + @SuppressWarnings("unchecked") + @Override + public Set reverseRange(K key, final long start, final long end) { + final byte[] rawKey = rawKey(key); + + Set rawValues = execute(new RedisCallback>() { + @Override + public Set doInRedis(RedisConnection connection) { + return connection.zRevRange(rawKey, start, end); + } + }, true); + + return deserializeValues(rawValues); + } + + @Override + public Double score(K key, Object o) { + final byte[] rawKey = rawKey(key); + final byte[] rawValue = rawValue(o); + + return execute(new RedisCallback() { + @Override + public Double doInRedis(RedisConnection connection) { + return connection.zScore(rawKey, rawValue); + } + }, true); + } + + @Override + public Long count(K key, final double min, final double max) { + final byte[] rawKey = rawKey(key); + + return execute(new RedisCallback() { + @Override + public Long doInRedis(RedisConnection connection) { + return connection.zCount(rawKey, min, max); + } + }, true); + } + + @Override + public Long size(K key) { + final byte[] rawKey = rawKey(key); + + return execute(new RedisCallback() { + @Override + public Long doInRedis(RedisConnection connection) { + return connection.zCard(rawKey); + } + }, true); + } + + @Override + public void unionAndStore(K key, K otherKey, K destKey) { + unionAndStore(key, Collections.singleton(otherKey), destKey); + } + + @Override + public void unionAndStore(K key, Collection otherKeys, K destKey) { + final byte[][] rawKeys = rawKeys(key, otherKeys); + final byte[] rawDestKey = rawKey(destKey); + execute(new RedisCallback() { + @Override + public Object doInRedis(RedisConnection connection) { + connection.zUnionStore(rawDestKey, rawKeys); + return null; + } + }, true); + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/KeyBound.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/KeyBound.java deleted file mode 100644 index 98aaa9b63..000000000 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/KeyBound.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2010-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.keyvalue.redis.core; - -/** - * Contract defining the bind of the implementing entity to a Redis 'key'. - * Useful for executing 'bound' operations or operating over Redis 'collection' or 'views'. - * - * @author Costin Leau - */ -public interface KeyBound { - - /** - * Returns the key associated with this entity. - * - * @return key associated with the implementing entity - */ - K getKey(); -} diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/RedisOperations.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/RedisOperations.java index f9c4411c3..57e51fd1e 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/RedisOperations.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/RedisOperations.java @@ -64,6 +64,17 @@ public interface RedisOperations { */ T execute(SessionCallback session); + // /** + // * Executes the given action object on a pipelined connection, returning the results. Note that the callback cannot + // * return a non-null value as it gets overwritten by the pipeline. + // * + // * @param list element return type + // * @param action callback object to execute + // * @return list of objects returned by the pipeline + // */ + // List executePipelined(RedisCallback action); + + Boolean hasKey(K key); void delete(K key); @@ -84,7 +95,9 @@ public interface RedisOperations { Boolean expireAt(K key, Date date); - void persist(K key); + Boolean persist(K key); + + Boolean move(K key, int dbIndex); Long getExpire(K key); @@ -101,7 +114,7 @@ public interface RedisOperations { void discard(); - Object exec(); + List exec(); // pubsub functionality on the template void convertAndSend(String destination, Object message); @@ -192,13 +205,15 @@ public interface RedisOperations { List sort(SortQuery query); - List sort(SortQuery query, RedisSerializer resultSerializer); - List sort(SortQuery query, BulkMapper bulkMapper); List sort(SortQuery query, BulkMapper bulkMapper, RedisSerializer resultSerializer); Long sort(SortQuery query, K storeKey); + + RedisSerializer getValueSerializer(); + + RedisSerializer getKeySerializer(); } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/RedisTemplate.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/RedisTemplate.java index 52bded364..eb9c1d398 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/RedisTemplate.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/RedisTemplate.java @@ -15,31 +15,25 @@ */ package org.springframework.data.keyvalue.redis.core; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import org.springframework.dao.DataAccessException; import org.springframework.data.keyvalue.redis.connection.DataType; -import org.springframework.data.keyvalue.redis.connection.DefaultSortParameters; import org.springframework.data.keyvalue.redis.connection.RedisConnection; import org.springframework.data.keyvalue.redis.connection.RedisConnectionFactory; import org.springframework.data.keyvalue.redis.connection.SortParameters; -import org.springframework.data.keyvalue.redis.connection.RedisListCommands.Position; +import org.springframework.data.keyvalue.redis.core.query.QueryUtils; import org.springframework.data.keyvalue.redis.core.query.SortQuery; import org.springframework.data.keyvalue.redis.serializer.JdkSerializationRedisSerializer; import org.springframework.data.keyvalue.redis.serializer.RedisSerializer; +import org.springframework.data.keyvalue.redis.serializer.SerializationUtils; import org.springframework.data.keyvalue.redis.serializer.StringRedisSerializer; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; @@ -81,10 +75,10 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation private RedisSerializer stringSerializer = new StringRedisSerializer(); // cache singleton objects (where possible) - private final ValueOperations valueOps = new DefaultValueOperations(); - private final ListOperations listOps = new DefaultListOperations(); - private final SetOperations setOps = new DefaultSetOperations(); - private final ZSetOperations zSetOps = new DefaultZSetOperations(); + private ValueOperations valueOps; + private ListOperations listOps; + private SetOperations setOps; + private ZSetOperations zSetOps; /** * Constructs a new RedisTemplate instance. @@ -94,7 +88,8 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation } /** - * Constructs a new RedisTemplate instance. + * Constructs a new RedisTemplate instance and automatically initializes the template. + * If other parameters need to be set, it is recommended to use {@link #setConnectionFactory(RedisConnectionFactory)} instead. * * @param connectionFactory connection factory for creating new connections */ @@ -131,6 +126,11 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation if (defaultUsed) { Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized"); } + + valueOps = new DefaultValueOperations(this); + listOps = new DefaultListOperations(this); + setOps = new DefaultSetOperations(this); + zSetOps = new DefaultZSetOperations(this); } @Override @@ -151,12 +151,14 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation } /** - * Executes the given action object within a connection, that can be pipelined or not and which can be exposed or not. + * Executes the given action object within a connection that can be exposed or not. Additionally, the connection + * can be pipelined. Note the results of the pipeline are discarded (making it suitable for write-only scenarios). + * Use {@link #executePipelined(RedisCallback)} as an alternative. * * @param return type * @param action callback object to execute * @param exposeConnection whether to enforce exposure of the native Redis Connection to callback code - * @param pipeline whether to pipeline or not the connection for the execution duration + * @param pipeline whether to pipeline or not the connection for the execution * @return object returned by the action */ public T execute(RedisCallback action, boolean exposeConnection, boolean pipeline) { @@ -189,6 +191,56 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation } } + + @Override + public T execute(SessionCallback session) { + RedisConnectionFactory factory = getConnectionFactory(); + // bind connection + RedisConnectionUtils.bindConnection(factory); + try { + return session.execute(this); + } finally { + RedisConnectionUtils.unbindConnection(factory); + } + } + + // @SuppressWarnings("unchecked") + // public List executePipelined(final RedisCallback action) { + // return executePipelined(action, valueSerializer); + // } + // + // /** + // * Executes the given action object on a pipelined connection, returning the results using a dedicated serializer. + // * Note that the callback cannot return a non-null value as it gets overwritten by the pipeline. + // * + // * @param action callback object to execute + // * @param resultSerializer + // * @return list of objects returned by the pipeline + // */ + // public List executePipelined(final RedisCallback action, final RedisSerializer resultSerializer) { + // return execute(new RedisCallback>() { + // public List doInRedis(RedisConnection connection) throws DataAccessException { + // connection.openPipeline(); + // boolean pipelinedClosed = false; + // try { + // Object result = action.doInRedis(connection); + // if (result != null) { + // throw new InvalidDataAccessApiUsageException( + // "Callback cannot returned a non-null value as it gets overwritten by the pipeline"); + // } + // List closePipeline = connection.closePipeline(); + // pipelinedClosed = true; + // //return SerializationUtils.deserialize(pipeline, resultSerializer); + // + // } finally { + // if (!pipelinedClosed) { + // connection.closePipeline(); + // } + // } + // } + // }); + // } + protected RedisConnection createRedisConnectionProxy(RedisConnection pm) { Class[] ifcs = ClassUtils.getAllInterfacesForClass(pm.getClass(), getClass().getClassLoader()); return (RedisConnection) Proxy.newProxyInstance(pm.getClass().getClassLoader(), ifcs, @@ -208,18 +260,6 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation return result; } - @Override - public T execute(SessionCallback session) { - RedisConnectionFactory factory = getConnectionFactory(); - // bind connection - RedisConnectionUtils.bindConnection(factory); - try { - return session.execute(this); - } finally { - RedisConnectionUtils.unbindConnection(factory); - } - } - /** * Returns whether to expose the native Redis connection to RedisCallback code, or rather a connection proxy (the default). * @@ -295,6 +335,15 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation return valueSerializer; } + /** + * Returns the hashKeySerializer. + * + * @return Returns the hashKeySerializer + */ + public RedisSerializer getHashKeySerializer() { + return hashKeySerializer; + } + /** * Sets the hash key (or field) serializer to be used by this template. Defaults to {@link #getDefaultSerializer()}. * @@ -304,6 +353,15 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation this.hashKeySerializer = hashKeySerializer; } + /** + * Returns the hashValueSerializer. + * + * @return Returns the hashValueSerializer + */ + public RedisSerializer getHashValueSerializer() { + return hashValueSerializer; + } + /** * Sets the hash value serializer to be used by this template. Defaults to {@link #getDefaultSerializer()}. * @@ -313,55 +371,26 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation this.hashValueSerializer = hashValueSerializer; } + /** + * Returns the stringSerializer. + * + * @return Returns the stringSerializer + */ + public RedisSerializer getStringSerializer() { + return stringSerializer; + } + /** * Sets the string value serializer to be used by this template (when the arguments or return types * are always strings). Defaults to {@link StringRedisSerializer}. * - * @see ValueOperations#get(Object, int, int) + * @see ValueOperations#get(Object, long, long) * @param stringSerializer The stringValueSerializer to set. */ public void setStringSerializer(RedisSerializer stringSerializer) { this.stringSerializer = stringSerializer; } - /** - * Invocation handler that suppresses close calls on {@link RedisConnection}. - * @see RedisConnection#close() - */ - private class CloseSuppressingInvocationHandler implements InvocationHandler { - - private final RedisConnection target; - - public CloseSuppressingInvocationHandler(RedisConnection target) { - this.target = target; - } - - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - // Invocation on PersistenceManager interface (or provider-specific extension) coming in... - - if (method.getName().equals("equals")) { - // Only consider equal when proxies are identical. - return (proxy == args[0]); - } - else if (method.getName().equals("hashCode")) { - // Use hashCode of PersistenceManager proxy. - return System.identityHashCode(proxy); - } - else if (method.getName().equals("close")) { - // Handle close method: suppress, not valid. - return null; - } - - // Invoke method on target RedisConnection. - try { - Object retVal = method.invoke(this.target, args); - return retVal; - } catch (InvocationTargetException ex) { - throw ex.getTargetException(); - } - } - } - @SuppressWarnings("unchecked") private byte[] rawKey(Object key) { Assert.notNull(key, "non null key required"); @@ -377,17 +406,6 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation return valueSerializer.serialize(value); } - @SuppressWarnings("unchecked") - private byte[] rawHashKey(HK hashKey) { - Assert.notNull(hashKey, "non null hash key required"); - return hashKeySerializer.serialize(hashKey); - } - - @SuppressWarnings("unchecked") - private byte[] rawHashValue(HV value) { - return hashValueSerializer.serialize(value); - } - private byte[][] rawKeys(Collection keys) { final byte[][] rawKeys = new byte[keys.size()][]; @@ -399,164 +417,20 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation return rawKeys; } - private byte[][] rawKeys(K key, K otherKey) { - final byte[][] rawKeys = new byte[2][]; - - - rawKeys[0] = rawKey(key); - rawKeys[1] = rawKey(key); - return rawKeys; - } - - private byte[][] rawKeys(K key, Collection keys) { - final byte[][] rawKeys = new byte[keys.size() + 1][]; - - - rawKeys[0] = rawKey(key); - int i = 1; - for (K k : keys) { - rawKeys[i++] = rawKey(k); - } - - return rawKeys; - } - - @SuppressWarnings("unchecked") - private > T deserializeValues(Collection rawValues, Class type) { - return (T) deserializeValues(rawValues, type, valueSerializer); - } - - @SuppressWarnings("unchecked") - private > T deserializeValues(Collection rawValues, Class type, RedisSerializer redisSerializer) { - Collection values = (List.class.isAssignableFrom(type) ? new ArrayList(rawValues.size()) - : new LinkedHashSet(rawValues.size())); - for (byte[] bs : rawValues) { - if (bs != null) { - values.add(redisSerializer.deserialize(bs)); - } - } - - return (T) values; - } - - @SuppressWarnings("unchecked") - private Collection deserializeHashKeys(Collection rawKeys, Class type) { - Collection values = (List.class.isAssignableFrom(type) ? new ArrayList(rawKeys.size()) - : new LinkedHashSet(rawKeys.size())); - for (byte[] bs : rawKeys) { - if (bs != null) { - values.add((H) hashKeySerializer.deserialize(bs)); - } - } - - return values; - } - - @SuppressWarnings("unchecked") - private Collection deserializeHashValues(Collection rawValues, Class type) { - Collection values = (List.class.isAssignableFrom(type) ? new ArrayList(rawValues.size()) - : new LinkedHashSet(rawValues.size())); - for (byte[] bs : rawValues) { - if (bs != null) { - values.add((H) hashValueSerializer.deserialize(bs)); - } - } - - return values; - } - - - @SuppressWarnings("unchecked") - private Map deserializeHashMap(Map entries) { - Map map = new LinkedHashMap(entries.size()); - - for (Map.Entry entry : entries.entrySet()) { - map.put((HK) deserializeHashKey(entry.getKey()), (HV) deserializeHashValue(entry.getValue())); - } - - return map; - } - - - @SuppressWarnings("unchecked") - private Collection deserializeKeys(Collection rawKeys, Class type) { - Collection values = (List.class.isAssignableFrom(type) ? new ArrayList(rawKeys.size()) - : new LinkedHashSet(rawKeys.size())); - for (byte[] bs : rawKeys) { - if (bs != null) { - values.add((K) hashValueSerializer.deserialize(bs)); - } - } - - return values; - } - @SuppressWarnings("unchecked") private K deserializeKey(byte[] value) { - return (K) deserialize(value, keySerializer); + return (K) keySerializer.deserialize(value); } - @SuppressWarnings("unchecked") - private V deserializeValue(byte[] value) { - return (V) deserialize(value, valueSerializer); - } - - @SuppressWarnings("unchecked") - private String deserializeString(byte[] value) { - return deserialize(value, stringSerializer); - } - - @SuppressWarnings( { "unchecked" }) - private HK deserializeHashKey(byte[] value) { - return (HK) deserialize(value, hashKeySerializer); - } - - @SuppressWarnings("unchecked") - private HV deserializeHashValue(byte[] value) { - return (HV) deserialize(value, hashValueSerializer); - } - - private T deserialize(byte[] value, RedisSerializer serializer) { - if (isEmpty(value)) { - return null; - } - return serializer.deserialize(value); - } - - - private static boolean isEmpty(byte[] data) { - return (data == null || data.length == 0); - } - - // utility methods for the template internal methods - private abstract class ValueDeserializingRedisCallback implements RedisCallback { - private Object key; - - public ValueDeserializingRedisCallback(Object key) { - this.key = key; - } - - @Override - public final V doInRedis(RedisConnection connection) { - byte[] result = inRedis(rawKey(key), connection); - return deserializeValue(result); - } - - protected abstract byte[] inRedis(byte[] rawKey, RedisConnection connection); - } - - // // RedisOperations // - - @Override - public Object exec() { - return execute(new RedisCallback() { + public List exec() { + return execute(new RedisCallback>() { @Override - public Object doInRedis(RedisConnection connection) throws DataAccessException { + public List doInRedis(RedisConnection connection) throws DataAccessException { return connection.exec(); } }); @@ -659,29 +533,41 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation }, true); } + @SuppressWarnings("unchecked") @Override public Set keys(K pattern) { final byte[] rawKey = rawKey(pattern); - Collection rawKeys = execute(new RedisCallback>() { + Set rawKeys = execute(new RedisCallback>() { @Override - public Collection doInRedis(RedisConnection connection) { + public Set doInRedis(RedisConnection connection) { return connection.keys(rawKey); } }, true); - return (Set) deserializeKeys(rawKeys, Set.class); + return SerializationUtils.deserialize(rawKeys, keySerializer); } @Override - public void persist(K key) { + public Boolean persist(K key) { final byte[] rawKey = rawKey(key); - execute(new RedisCallback() { + return execute(new RedisCallback() { @Override - public Object doInRedis(RedisConnection connection) { - connection.persist(rawKey); - return null; + public Boolean doInRedis(RedisConnection connection) { + return connection.persist(rawKey); + } + }, true); + } + + @Override + public Boolean move(K key, final int dbIndex) { + final byte[] rawKey = rawKey(key); + + return execute(new RedisCallback() { + @Override + public Boolean doInRedis(RedisConnection connection) { + return connection.move(rawKey, dbIndex); } }, true); } @@ -797,1148 +683,18 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation }, true); } - // - // Value Ops - // - - @Override - public BoundValueOperations boundValueOps(K key) { - return new DefaultBoundValueOperations(key, this); - } - - @Override - public ValueOperations opsForValue() { - return valueOps; - } - - private class DefaultValueOperations implements ValueOperations { - - @Override - public V get(final Object key) { - - return execute(new ValueDeserializingRedisCallback(key) { - @Override - protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { - return connection.get(rawKey); - } - }, true); - } - - @Override - public V getAndSet(K key, V newValue) { - final byte[] rawValue = rawValue(newValue); - return execute(new ValueDeserializingRedisCallback(key) { - @Override - protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { - return connection.getSet(rawKey, rawValue); - } - }, true); - } - - @Override - public Long increment(K key, final long delta) { - final byte[] rawKey = rawKey(key); - // TODO add conversion service in here ? - return execute(new RedisCallback() { - @Override - public Long doInRedis(RedisConnection connection) { - if (delta == 1) { - return connection.incr(rawKey); - } - - if (delta == -1) { - return connection.decr(rawKey); - } - - if (delta < 0) { - return connection.decrBy(rawKey, delta); - } - - return connection.incrBy(rawKey, delta); - } - }, true); - } - - @Override - public Integer append(K key, String value) { - final byte[] rawKey = rawKey(key); - final byte[] rawString = rawString(value); - - return execute(new RedisCallback() { - @Override - public Integer doInRedis(RedisConnection connection) { - return connection.append(rawKey, rawString).intValue(); - } - }, true); - } - - @Override - public String get(K key, final int start, final int end) { - final byte[] rawKey = rawKey(key); - - byte[] rawReturn = execute(new RedisCallback() { - @Override - public byte[] doInRedis(RedisConnection connection) { - return connection.getRange(rawKey, start, end); - } - }, true); - - return deserializeString(rawReturn); - } - - @Override - public Collection multiGet(Collection keys) { - if (keys.isEmpty()) { - return Collections.emptyList(); - } - - final byte[][] rawKeys = new byte[keys.size()][]; - - int counter = 0; - for (K hashKey : keys) { - rawKeys[counter++] = rawKey(hashKey); - } - - List rawValues = execute(new RedisCallback>() { - @Override - public List doInRedis(RedisConnection connection) { - return connection.mGet(rawKeys); - } - }, true); - - return (List) deserializeValues(rawValues, List.class); - } - - @Override - public void multiSet(Map m) { - if (m.isEmpty()) { - return; - } - - final Map rawKeys = new LinkedHashMap(m.size()); - - for (Map.Entry entry : m.entrySet()) { - rawKeys.put(rawKey(entry.getKey()), rawValue(entry.getValue())); - } - - execute(new RedisCallback() { - @Override - public Object doInRedis(RedisConnection connection) { - connection.mSet(rawKeys); - return null; - } - }, true); - } - - @Override - public void multiSetIfAbsent(Map m) { - if (m.isEmpty()) { - return; - } - - final Map rawKeys = new LinkedHashMap(m.size()); - - for (Map.Entry entry : m.entrySet()) { - rawKeys.put(rawKey(entry.getKey()), rawValue(entry.getValue())); - } - - execute(new RedisCallback() { - @Override - public Object doInRedis(RedisConnection connection) { - connection.mSetNX(rawKeys); - return null; - } - }, true); - } - - @Override - public void set(K key, V value) { - final byte[] rawValue = rawValue(value); - execute(new ValueDeserializingRedisCallback(key) { - @Override - protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { - connection.set(rawKey, rawValue); - return null; - } - }, true); - } - - @Override - public void set(K key, V value, long timeout, TimeUnit unit) { - final byte[] rawKey = rawKey(key); - final byte[] rawValue = rawValue(value); - final long rawTimeout = unit.toSeconds(timeout); - - execute(new RedisCallback() { - @Override - public Object doInRedis(RedisConnection connection) throws DataAccessException { - connection.setEx(rawKey, (int) rawTimeout, rawValue); - return null; - } - }, true); - } - - @Override - public Boolean setIfAbsent(K key, V value) { - final byte[] rawKey = rawKey(key); - final byte[] rawValue = rawValue(value); - - return execute(new RedisCallback() { - @Override - public Boolean doInRedis(RedisConnection connection) throws DataAccessException { - return connection.setNX(rawKey, rawValue); - } - }, true); - } - - - @Override - public void set(K key, final int start, final int end) { - final byte[] rawKey = rawKey(key); - - execute(new RedisCallback() { - @Override - public Object doInRedis(RedisConnection connection) { - connection.setRange(rawKey, start, end); - return null; - } - }, true); - } - - @Override - public Long size(K key) { - final byte[] rawKey = rawKey(key); - - return execute(new RedisCallback() { - @Override - public Long doInRedis(RedisConnection connection) { - return connection.strLen(rawKey); - } - }, true); - } - - @Override - public RedisOperations getOperations() { - return RedisTemplate.this; - } - } - - @Override - public ListOperations opsForList() { - return listOps; - } - - @Override - public BoundListOperations boundListOps(K key) { - return new DefaultBoundListOperations(key, this); - } - - - - // - // List operations - // - - private class DefaultListOperations implements ListOperations { - - @Override - public V index(K key, final long index) { - return execute(new ValueDeserializingRedisCallback(key) { - @Override - protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { - return connection.lIndex(rawKey, index); - } - }, true); - } - - @Override - public V leftPop(K key) { - return execute(new ValueDeserializingRedisCallback(key) { - @Override - protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { - return connection.lPop(rawKey); - } - }, true); - } - - @Override - public V leftPop(K key, long timeout, TimeUnit unit) { - final int tm = (int) unit.toSeconds(timeout); - - return execute(new ValueDeserializingRedisCallback(key) { - @Override - protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { - return connection.bLPop(tm, rawKey).get(0); - } - }, true); - } - - @Override - public Long leftPush(K key, V value) { - final byte[] rawKey = rawKey(key); - final byte[] rawValue = rawValue(value); - return execute(new RedisCallback() { - @Override - public Long doInRedis(RedisConnection connection) { - return connection.lPush(rawKey, rawValue); - } - }, true); - } - - @Override - public Long leftPushIfPresent(K key, V value) { - final byte[] rawKey = rawKey(key); - final byte[] rawValue = rawValue(value); - return execute(new RedisCallback() { - @Override - public Long doInRedis(RedisConnection connection) { - return connection.lPushX(rawKey, rawValue); - } - }, true); - } - - @Override - public Long leftPush(K key, V pivot, V value) { - final byte[] rawKey = rawKey(key); - final byte[] rawPivot = rawValue(pivot); - final byte[] rawValue = rawValue(value); - return execute(new RedisCallback() { - @Override - public Long doInRedis(RedisConnection connection) { - return connection.lInsert(rawKey, Position.BEFORE, rawPivot, rawValue); - } - }, true); - } - - @Override - public Long size(K key) { - final byte[] rawKey = rawKey(key); - return execute(new RedisCallback() { - @Override - public Long doInRedis(RedisConnection connection) { - return connection.lLen(rawKey); - } - }, true); - } - - @Override - public List range(K key, final long start, final long end) { - final byte[] rawKey = rawKey(key); - return execute(new RedisCallback>() { - @Override - public List doInRedis(RedisConnection connection) { - return deserializeValues(connection.lRange(rawKey, start, end), List.class); - } - }, true); - } - - @Override - public Long remove(K key, final long count, Object value) { - final byte[] rawKey = rawKey(key); - final byte[] rawValue = rawValue(value); - return execute(new RedisCallback() { - @Override - public Long doInRedis(RedisConnection connection) { - return connection.lRem(rawKey, count, rawValue); - } - }, true); - } - - @Override - public V rightPop(K key) { - return execute(new ValueDeserializingRedisCallback(key) { - @Override - protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { - return connection.rPop(rawKey); - } - }, true); - } - - @Override - public V rightPop(K key, long timeout, TimeUnit unit) { - final int tm = (int) unit.toSeconds(timeout); - - return execute(new ValueDeserializingRedisCallback(key) { - @Override - protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { - return connection.bRPop(tm, rawKey).get(0); - } - }, true); - } - - @Override - public Long rightPush(K key, V value) { - final byte[] rawKey = rawKey(key); - final byte[] rawValue = rawValue(value); - return execute(new RedisCallback() { - @Override - public Long doInRedis(RedisConnection connection) { - return connection.rPush(rawKey, rawValue); - } - }, true); - } - - @Override - public Long rightPushIfPresent(K key, V value) { - final byte[] rawKey = rawKey(key); - final byte[] rawValue = rawValue(value); - return execute(new RedisCallback() { - @Override - public Long doInRedis(RedisConnection connection) { - return connection.rPushX(rawKey, rawValue); - } - }, true); - } - - @Override - public Long rightPush(K key, V pivot, V value) { - final byte[] rawKey = rawKey(key); - final byte[] rawPivot = rawValue(pivot); - final byte[] rawValue = rawValue(value); - - return execute(new RedisCallback() { - @Override - public Long doInRedis(RedisConnection connection) { - return connection.lInsert(rawKey, Position.AFTER, rawPivot, rawValue); - } - }, true); - } - - @Override - public V rightPopAndLeftPush(K sourceKey, K destinationKey) { - final byte[] rawDestKey = rawKey(destinationKey); - - return execute(new ValueDeserializingRedisCallback(sourceKey) { - @Override - protected byte[] inRedis(byte[] rawSourceKey, RedisConnection connection) { - return connection.rPopLPush(rawSourceKey, rawDestKey); - } - }, true); - } - - @Override - public V rightPopAndLeftPush(K sourceKey, K destinationKey, long timeout, TimeUnit unit) { - final int tm = (int) unit.toSeconds(timeout); - final byte[] rawDestKey = rawKey(destinationKey); - - return execute(new ValueDeserializingRedisCallback(sourceKey) { - @Override - protected byte[] inRedis(byte[] rawSourceKey, RedisConnection connection) { - return connection.bRPopLPush(tm, rawSourceKey, rawDestKey); - } - }, true); - } - - @Override - public void set(K key, final long index, V value) { - final byte[] rawValue = rawValue(value); - execute(new ValueDeserializingRedisCallback(key) { - @Override - protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { - connection.lSet(rawKey, index, rawValue); - return null; - } - }, true); - } - - @Override - public void trim(K key, final long start, final long end) { - execute(new ValueDeserializingRedisCallback(key) { - @Override - protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { - connection.lTrim(rawKey, start, end); - return null; - } - }, true); - } - - @Override - public RedisOperations getOperations() { - return RedisTemplate.this; - } - } - - // - // Set operations - // - - @Override - public BoundSetOperations boundSetOps(K key) { - return new DefaultBoundSetOperations(key, this); - } - - @Override - public SetOperations opsForSet() { - return setOps; - } - - private class DefaultSetOperations implements SetOperations { - - @Override - public Boolean add(K key, V value) { - final byte[] rawKey = rawKey(key); - final byte[] rawValue = rawValue(value); - return execute(new RedisCallback() { - @Override - public Boolean doInRedis(RedisConnection connection) { - return connection.sAdd(rawKey, rawValue); - } - }, true); - } - - @Override - public Set difference(K key, K otherKey) { - return difference(key, Collections.singleton(otherKey)); - } - - @Override - public Set difference(final K key, final Collection otherKeys) { - final byte[][] rawKeys = rawKeys(key, otherKeys); - Set rawValues = execute(new RedisCallback>() { - @Override - public Set doInRedis(RedisConnection connection) { - return connection.sDiff(rawKeys); - } - }, true); - - return deserializeValues(rawValues, Set.class); - } - - @Override - public void differenceAndStore(K key, K otherKey, K destKey) { - differenceAndStore(key, Collections.singleton(otherKey), destKey); - } - - @Override - public void differenceAndStore(final K key, final Collection otherKeys, K destKey) { - final byte[][] rawKeys = rawKeys(key, otherKeys); - final byte[] rawDestKey = rawKey(destKey); - execute(new RedisCallback() { - @Override - public Object doInRedis(RedisConnection connection) { - connection.sDiffStore(rawDestKey, rawKeys); - return null; - } - }, true); - } - - @Override - public RedisOperations getOperations() { - return RedisTemplate.this; - } - - @Override - public Set intersect(K key, K otherKey) { - return intersect(key, Collections.singleton(otherKey)); - } - - @Override - public Set intersect(K key, Collection otherKeys) { - final byte[][] rawKeys = rawKeys(key, otherKeys); - Set rawValues = execute(new RedisCallback>() { - @Override - public Set doInRedis(RedisConnection connection) { - return connection.sInter(rawKeys); - } - }, true); - - return deserializeValues(rawValues, Set.class); - } - - @Override - public void intersectAndStore(K key, K otherKey, K destKey) { - intersectAndStore(key, Collections.singleton(otherKey), destKey); - } - - @Override - public void intersectAndStore(K key, Collection otherKeys, K destKey) { - final byte[][] rawKeys = rawKeys(key, otherKeys); - final byte[] rawDestKey = rawKey(destKey); - execute(new RedisCallback() { - @Override - public Object doInRedis(RedisConnection connection) { - connection.sInterStore(rawDestKey, rawKeys); - return null; - } - }, true); - } - - @Override - public Boolean isMember(K key, Object o) { - final byte[] rawKey = rawKey(key); - final byte[] rawValue = rawValue(o); - return execute(new RedisCallback() { - @Override - public Boolean doInRedis(RedisConnection connection) { - return connection.sIsMember(rawKey, rawValue); - } - }, true); - } - - @Override - public Set members(K key) { - final byte[] rawKey = rawKey(key); - Set rawValues = execute(new RedisCallback>() { - @Override - public Set doInRedis(RedisConnection connection) { - return connection.sMembers(rawKey); - } - }, true); - - return deserializeValues(rawValues, Set.class); - } - - @Override - public Boolean move(K key, V value, K destKey) { - final byte[] rawKey = rawKey(key); - final byte[] rawDestKey = rawKey(destKey); - final byte[] rawValue = rawValue(value); - - return execute(new RedisCallback() { - @Override - public Boolean doInRedis(RedisConnection connection) { - return connection.sMove(rawKey, rawDestKey, rawValue); - } - }, true); - } - - @Override - public V randomMember(K key) { - - return execute(new ValueDeserializingRedisCallback(key) { - @Override - protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { - return connection.randomKey(); - } - }, true); - } - - @Override - public Boolean remove(K key, Object o) { - final byte[] rawKey = rawKey(key); - final byte[] rawValue = rawValue(o); - return execute(new RedisCallback() { - @Override - public Boolean doInRedis(RedisConnection connection) { - return connection.sRem(rawKey, rawValue); - } - }, true); - } - - @Override - public V pop(K key) { - return execute(new ValueDeserializingRedisCallback(key) { - @Override - protected byte[] inRedis(byte[] rawKey, RedisConnection connection) { - return connection.sPop(rawKey); - } - }, true); - } - - @Override - public Long size(K key) { - final byte[] rawKey = rawKey(key); - return execute(new RedisCallback() { - @Override - public Long doInRedis(RedisConnection connection) { - return connection.sCard(rawKey); - } - }, true); - } - - @Override - public Set union(K key, K otherKey) { - return union(key, Collections.singleton(otherKey)); - } - - @Override - public Set union(K key, Collection otherKeys) { - final byte[][] rawKeys = rawKeys(key, otherKeys); - Set rawValues = execute(new RedisCallback>() { - @Override - public Set doInRedis(RedisConnection connection) { - return connection.sUnion(rawKeys); - } - }, true); - - return deserializeValues(rawValues, Set.class); - } - - @Override - public void unionAndStore(K key, K otherKey, K destKey) { - unionAndStore(key, Collections.singleton(otherKey), destKey); - } - - @Override - public void unionAndStore(K key, Collection otherKeys, K destKey) { - final byte[][] rawKeys = rawKeys(key, otherKeys); - final byte[] rawDestKey = rawKey(destKey); - execute(new RedisCallback() { - @Override - public Object doInRedis(RedisConnection connection) { - connection.sUnionStore(rawDestKey, rawKeys); - return null; - } - }, true); - } - } - - // - // ZSet operations - // - - @Override - public BoundZSetOperations boundZSetOps(K key) { - return new DefaultBoundZSetOperations(key, this); - } - - @Override - public ZSetOperations opsForZSet() { - return zSetOps; - } - - private class DefaultZSetOperations implements ZSetOperations { - - @Override - public Boolean add(final K key, final V value, final double score) { - final byte[] rawKey = rawKey(key); - final byte[] rawValue = rawValue(value); - - return execute(new RedisCallback() { - @Override - public Boolean doInRedis(RedisConnection connection) { - return connection.zAdd(rawKey, score, rawValue); - } - }, true); - } - - @Override - public Double incrementScore(K key, V value, final double delta) { - final byte[] rawKey = rawKey(key); - final byte[] rawValue = rawValue(value); - - return execute(new RedisCallback() { - @Override - public Double doInRedis(RedisConnection connection) { - return connection.zIncrBy(rawKey, delta, rawValue); - } - }, true); - } - - @Override - public RedisOperations getOperations() { - return RedisTemplate.this; - } - - - @Override - public void intersectAndStore(K key, K otherKey, K destKey) { - intersectAndStore(key, Collections.singleton(otherKey), destKey); - } - - @Override - public void intersectAndStore(K key, Collection otherKeys, K destKey) { - final byte[][] rawKeys = rawKeys(key, otherKeys); - final byte[] rawDestKey = rawKey(destKey); - execute(new RedisCallback() { - @Override - public Object doInRedis(RedisConnection connection) { - connection.zInterStore(rawDestKey, rawKeys); - return null; - } - }, true); - } - - @Override - public Set range(K key, final long start, final long end) { - final byte[] rawKey = rawKey(key); - - Set rawValues = execute(new RedisCallback>() { - @Override - public Set doInRedis(RedisConnection connection) { - return connection.zRange(rawKey, start, end); - } - }, true); - - return deserializeValues(rawValues, Set.class); - } - - @Override - public Set rangeByScore(K key, final double min, final double max) { - final byte[] rawKey = rawKey(key); - - Set rawValues = execute(new RedisCallback>() { - @Override - public Set doInRedis(RedisConnection connection) { - return connection.zRangeByScore(rawKey, min, max); - } - }, true); - - return deserializeValues(rawValues, Set.class); - } - - @Override - public Long rank(K key, Object o) { - final byte[] rawKey = rawKey(key); - final byte[] rawValue = rawValue(o); - - return execute(new RedisCallback() { - @Override - public Long doInRedis(RedisConnection connection) { - Long zRank = connection.zRank(rawKey, rawValue); - return (zRank != null && zRank.longValue() >= 0 ? zRank : null); - } - }, true); - } - - @Override - public Long reverseRank(K key, Object o) { - final byte[] rawKey = rawKey(key); - final byte[] rawValue = rawValue(o); - - return execute(new RedisCallback() { - @Override - public Long doInRedis(RedisConnection connection) { - Long zRank = connection.zRevRank(rawKey, rawValue); - return (zRank != null && zRank.longValue() >= 0 ? zRank : null); - } - }, true); - } - - @Override - public Boolean remove(K key, Object o) { - final byte[] rawKey = rawKey(key); - final byte[] rawValue = rawValue(o); - - return execute(new RedisCallback() { - @Override - public Boolean doInRedis(RedisConnection connection) { - return connection.zRem(rawKey, rawValue); - } - }, true); - } - - @Override - public void removeRange(K key, final long start, final long end) { - final byte[] rawKey = rawKey(key); - execute(new RedisCallback() { - @Override - public Object doInRedis(RedisConnection connection) { - connection.zRemRange(rawKey, start, end); - return null; - } - }, true); - } - - @Override - public void removeRangeByScore(K key, final double min, final double max) { - final byte[] rawKey = rawKey(key); - execute(new RedisCallback() { - @Override - public Object doInRedis(RedisConnection connection) { - connection.zRemRangeByScore(rawKey, min, max); - return null; - } - }, true); - } - - @Override - public Set reverseRange(K key, final long start, final long end) { - final byte[] rawKey = rawKey(key); - - Set rawValues = execute(new RedisCallback>() { - @Override - public Set doInRedis(RedisConnection connection) { - return connection.zRevRange(rawKey, start, end); - } - }, true); - - return deserializeValues(rawValues, Set.class); - } - - @Override - public Double score(K key, Object o) { - final byte[] rawKey = rawKey(key); - final byte[] rawValue = rawValue(o); - - return execute(new RedisCallback() { - @Override - public Double doInRedis(RedisConnection connection) { - return connection.zScore(rawKey, rawValue); - } - }, true); - } - - @Override - public Long count(K key, final double min, final double max) { - final byte[] rawKey = rawKey(key); - - return execute(new RedisCallback() { - @Override - public Long doInRedis(RedisConnection connection) { - return connection.zCount(rawKey, min, max); - } - }, true); - } - - @Override - public Long size(K key) { - final byte[] rawKey = rawKey(key); - - return execute(new RedisCallback() { - @Override - public Long doInRedis(RedisConnection connection) { - return connection.zCard(rawKey); - } - }, true); - } - - @Override - public void unionAndStore(K key, K otherKey, K destKey) { - unionAndStore(key, Collections.singleton(otherKey), destKey); - } - - @Override - public void unionAndStore(K key, Collection otherKeys, K destKey) { - final byte[][] rawKeys = rawKeys(key, otherKeys); - final byte[] rawDestKey = rawKey(destKey); - execute(new RedisCallback() { - @Override - public Object doInRedis(RedisConnection connection) { - connection.zUnionStore(rawDestKey, rawKeys); - return null; - } - }, true); - } - } - - - // - // Hash Operations - // - - @Override - public BoundHashOperations boundHashOps(K key) { - return new DefaultBoundHashOperations(key, this); - } - - @Override - public HashOperations opsForHash() { - return new DefaultHashOperations(); - } - - private class DefaultHashOperations implements HashOperations { - - @Override - public RedisOperations getOperations() { - return RedisTemplate.this; - } - - @Override - public HV get(K key, Object hashKey) { - final byte[] rawKey = rawKey(key); - final byte[] rawHashKey = rawHashKey(hashKey); - - byte[] rawHashValue = execute(new RedisCallback() { - @Override - public byte[] doInRedis(RedisConnection connection) { - return connection.hGet(rawKey, rawHashKey); - } - }, true); - - return RedisTemplate.this. deserializeHashValue(rawHashValue); - } - - @Override - public Boolean hasKey(K key, Object hashKey) { - final byte[] rawKey = rawKey(key); - final byte[] rawHashKey = rawHashKey(hashKey); - - return execute(new RedisCallback() { - @Override - public Boolean doInRedis(RedisConnection connection) { - return connection.hExists(rawKey, rawHashKey); - } - }, true); - } - - @Override - public Long increment(K key, HK hashKey, final long delta) { - final byte[] rawKey = rawKey(key); - final byte[] rawHashKey = rawHashKey(hashKey); - - return execute(new RedisCallback() { - @Override - public Long doInRedis(RedisConnection connection) { - return connection.hIncrBy(rawKey, rawHashKey, delta); - } - }, true); - - } - - @SuppressWarnings("unchecked") - @Override - public Set keys(K key) { - final byte[] rawKey = rawKey(key); - - Set rawValues = execute(new RedisCallback>() { - @Override - public Set doInRedis(RedisConnection connection) { - return connection.hKeys(rawKey); - } - }, true); - - return (Set) deserializeHashKeys(rawValues, Set.class); - } - - @Override - public Long size(K key) { - final byte[] rawKey = rawKey(key); - - return execute(new RedisCallback() { - @Override - public Long doInRedis(RedisConnection connection) { - return connection.hLen(rawKey); - } - }, true); - } - - @Override - public void putAll(K key, Map m) { - if (m.isEmpty()) { - return; - } - - final byte[] rawKey = rawKey(key); - - final Map hashes = new LinkedHashMap(m.size()); - - for (Map.Entry entry : m.entrySet()) { - hashes.put(rawHashKey(entry.getKey()), rawHashValue(entry.getValue())); - } - - execute(new RedisCallback() { - @Override - public Object doInRedis(RedisConnection connection) { - connection.hMSet(rawKey, hashes); - return null; - } - }, true); - } - - - @SuppressWarnings("unchecked") - @Override - public Collection multiGet(K key, Collection fields) { - if (fields.isEmpty()) { - return Collections.emptyList(); - } - - final byte[] rawKey = rawKey(key); - - final byte[][] rawHashKeys = new byte[fields.size()][]; - - int counter = 0; - for (HK hashKey : fields) { - rawHashKeys[counter++] = rawHashKey(hashKey); - } - - List rawValues = execute(new RedisCallback>() { - @Override - public List doInRedis(RedisConnection connection) { - return connection.hMGet(rawKey, rawHashKeys); - } - }, true); - - return (List) deserializeHashValues(rawValues, List.class); - } - - @Override - public void put(K key, HK hashKey, HV value) { - final byte[] rawKey = rawKey(key); - final byte[] rawHashKey = rawHashKey(hashKey); - final byte[] rawHashValue = rawHashValue(value); - - execute(new RedisCallback() { - @Override - public Object doInRedis(RedisConnection connection) { - connection.hSet(rawKey, rawHashKey, rawHashValue); - return null; - } - }, true); - } - - @Override - public Boolean putIfAbsent(K key, HK hashKey, HV value) { - final byte[] rawKey = rawKey(key); - final byte[] rawHashKey = rawHashKey(hashKey); - final byte[] rawHashValue = rawHashValue(value); - - return execute(new RedisCallback() { - @Override - public Boolean doInRedis(RedisConnection connection) { - return connection.hSetNX(rawKey, rawHashKey, rawHashValue); - } - }, true); - } - - - @SuppressWarnings("unchecked") - @Override - public List values(K key) { - final byte[] rawKey = rawKey(key); - - List rawValues = execute(new RedisCallback>() { - @Override - public List doInRedis(RedisConnection connection) { - return connection.hVals(rawKey); - } - }, true); - - return (List) deserializeHashValues(rawValues, List.class); - } - - @Override - public void delete(K key, Object hashKey) { - final byte[] rawKey = rawKey(key); - final byte[] rawHashKey = rawHashKey(hashKey); - - execute(new RedisCallback() { - @Override - public Object doInRedis(RedisConnection connection) { - connection.hDel(rawKey, rawHashKey); - return null; - } - }, true); - } - - @Override - public Map entries(K key) { - final byte[] rawKey = rawKey(key); - - Map entries = execute(new RedisCallback>() { - @Override - public Map doInRedis(RedisConnection connection) { - return connection.hGetAll(rawKey); - } - }, true); - - return deserializeHashMap(entries); - } - } - // Sort operations + @SuppressWarnings("unchecked") @Override public List sort(SortQuery query) { return sort(query, valueSerializer); } - @SuppressWarnings("unchecked") @Override public List sort(SortQuery query, RedisSerializer resultSerializer) { final byte[] rawKey = rawKey(query.getKey()); - final SortParameters params = convertQuery(query, stringSerializer); + final SortParameters params = QueryUtils.convertQuery(query, stringSerializer); List vals = execute(new RedisCallback>() { @Override @@ -1947,7 +703,7 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation } }, true); - return (List) deserializeValues(vals, List.class, resultSerializer); + return SerializationUtils.deserialize(vals, resultSerializer); } @SuppressWarnings("unchecked") @@ -1981,7 +737,7 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation public Long sort(SortQuery query, K storeKey) { final byte[] rawStoreKey = rawKey(storeKey); final byte[] rawKey = rawKey(query.getKey()); - final SortParameters params = convertQuery(query, stringSerializer); + final SortParameters params = QueryUtils.convertQuery(query, stringSerializer); return execute(new RedisCallback() { @Override @@ -1991,24 +747,53 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation }, true); } - private static SortParameters convertQuery(SortQuery query, RedisSerializer stringSerializer) { - - return new DefaultSortParameters(stringSerializer.serialize(query.getBy()), query.getLimit(), serialize( - query.getGetPattern(), stringSerializer), query.getOrder(), query.isAlphabetic()); + @Override + public BoundValueOperations boundValueOps(K key) { + return new DefaultBoundValueOperations(key, this); } - private static byte[][] serialize(List strings, RedisSerializer stringSerializer) { - List raw = null; + @Override + public ValueOperations opsForValue() { + return valueOps; + } - if (strings == null) { - raw = Collections.emptyList(); - } - else { - raw = new ArrayList(strings.size()); - for (String key : strings) { - raw.add(stringSerializer.serialize(key)); - } - } - return raw.toArray(new byte[raw.size()][]); + @Override + public ListOperations opsForList() { + return listOps; + } + + @Override + public BoundListOperations boundListOps(K key) { + return new DefaultBoundListOperations(key, this); + } + + @Override + public BoundSetOperations boundSetOps(K key) { + return new DefaultBoundSetOperations(key, this); + } + + @Override + public SetOperations opsForSet() { + return setOps; + } + + @Override + public BoundZSetOperations boundZSetOps(K key) { + return new DefaultBoundZSetOperations(key, this); + } + + @Override + public ZSetOperations opsForZSet() { + return zSetOps; + } + + @Override + public BoundHashOperations boundHashOps(K key) { + return new DefaultBoundHashOperations(key, this); + } + + @Override + public HashOperations opsForHash() { + return new DefaultHashOperations(this); } } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/SessionCallback.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/SessionCallback.java index d80e2ca3a..8af247965 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/SessionCallback.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/SessionCallback.java @@ -15,6 +15,8 @@ */ package org.springframework.data.keyvalue.redis.core; +import org.springframework.dao.DataAccessException; + /** * Callback executing all operations against a surrogate 'session' (basically against the same underlying Redis connection). * Allows 'transactions' to take place through the use of multi/discard/exec/watch/unwatch commands. @@ -29,5 +31,5 @@ public interface SessionCallback { * @param operations Redis operations * @return return value */ - T execute(RedisOperations operations); + T execute(RedisOperations operations) throws DataAccessException; } diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/StringRedisTemplate.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/StringRedisTemplate.java index 6a364b51f..90b22c2d0 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/StringRedisTemplate.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/StringRedisTemplate.java @@ -51,12 +51,9 @@ public class StringRedisTemplate extends RedisTemplate { * @param connectionFactory connection factory for creating new connections */ public StringRedisTemplate(RedisConnectionFactory connectionFactory) { - super(connectionFactory); - RedisSerializer stringSerializer = new StringRedisSerializer(); - setKeySerializer(stringSerializer); - setValueSerializer(stringSerializer); - setHashKeySerializer(stringSerializer); - setHashValueSerializer(stringSerializer); + this(); + setConnectionFactory(connectionFactory); + afterPropertiesSet(); } @Override diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/ValueOperations.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/ValueOperations.java index 32b2a9622..133922952 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/ValueOperations.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/ValueOperations.java @@ -16,6 +16,7 @@ package org.springframework.data.keyvalue.redis.core; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -40,15 +41,15 @@ public interface ValueOperations { V getAndSet(K key, V value); - Collection multiGet(Collection keys); + List multiGet(Collection keys); Long increment(K key, long delta); Integer append(K key, String value); - String get(K key, int start, int end); + String get(K key, long start, long end); - void set(K key, int start, int end); + void set(K key, V value, long offset); Long size(K key); diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/QueryUtils.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/QueryUtils.java new file mode 100644 index 000000000..a8b08ee42 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/QueryUtils.java @@ -0,0 +1,53 @@ +/* + * 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.keyvalue.redis.core.query; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.keyvalue.redis.connection.DefaultSortParameters; +import org.springframework.data.keyvalue.redis.connection.SortParameters; +import org.springframework.data.keyvalue.redis.serializer.RedisSerializer; + +/** + * Utilities for {@link SortQuery} implementations. + * + * @author Costin Leau + */ +public abstract class QueryUtils { + + public static SortParameters convertQuery(SortQuery query, RedisSerializer stringSerializer) { + + return new DefaultSortParameters(stringSerializer.serialize(query.getBy()), query.getLimit(), serialize( + query.getGetPattern(), stringSerializer), query.getOrder(), query.isAlphabetic()); + } + + private static byte[][] serialize(List strings, RedisSerializer stringSerializer) { + List raw = null; + + if (strings == null) { + raw = Collections.emptyList(); + } + else { + raw = new ArrayList(strings.size()); + for (String key : strings) { + raw.add(stringSerializer.serialize(key)); + } + } + return raw.toArray(new byte[raw.size()][]); + } +} diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/package-info.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/package-info.java new file mode 100644 index 000000000..3a0c87b28 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/package-info.java @@ -0,0 +1,5 @@ +/** + * Query package for Redis template. + */ +package org.springframework.data.keyvalue.redis.core.query; + diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/hash/JacksonHashMapper.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/hash/JacksonHashMapper.java index 895e0edfb..1f4d0d105 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/hash/JacksonHashMapper.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/hash/JacksonHashMapper.java @@ -30,7 +30,7 @@ public class JacksonHashMapper implements HashMapper { private final ObjectMapper mapper; private final JavaType userType; - private final JavaType mapType = TypeFactory.type(Map.class); + private final JavaType mapType = TypeFactory.mapType(Map.class, String.class, Object.class); public JacksonHashMapper(Class type) { this(type, new ObjectMapper()); @@ -47,7 +47,6 @@ public class JacksonHashMapper implements HashMapper { return (T) mapper.convertValue(hash, userType); } - @SuppressWarnings("unchecked") @Override public Map toHash(T object) { return mapper.convertValue(object, mapType); diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/hash/package-info.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/hash/package-info.java new file mode 100644 index 000000000..3209d57ca --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/hash/package-info.java @@ -0,0 +1,7 @@ +/** + * Dedicated support package for Redis hashes. + * + * Provides mapping of objects to hashes/maps (and vice versa). + */ +package org.springframework.data.keyvalue.redis.hash; + diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/listener/RedisMessageListenerContainer.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/listener/RedisMessageListenerContainer.java index 1c9676e4c..0691b8363 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/listener/RedisMessageListenerContainer.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/listener/RedisMessageListenerContainer.java @@ -16,7 +16,6 @@ package org.springframework.data.keyvalue.redis.listener; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -39,6 +38,7 @@ import org.springframework.data.keyvalue.redis.connection.MessageListener; import org.springframework.data.keyvalue.redis.connection.RedisConnection; import org.springframework.data.keyvalue.redis.connection.RedisConnectionFactory; import org.springframework.data.keyvalue.redis.connection.Subscription; +import org.springframework.data.keyvalue.redis.connection.util.ByteArrayWrapper; import org.springframework.data.keyvalue.redis.serializer.RedisSerializer; import org.springframework.data.keyvalue.redis.serializer.StringRedisSerializer; import org.springframework.scheduling.SchedulingAwareRunnable; @@ -101,9 +101,9 @@ public class RedisMessageListenerContainer implements InitializingBean, Disposab // to avoid creation of hashes for each message, the maps use raw byte arrays (wrapped to respect the equals/hashcode contract) // lookup map between patterns and listeners - private final Map> patternMapping = new ConcurrentHashMap>(); + private final Map> patternMapping = new ConcurrentHashMap>(); // lookup map between channels and listeners - private final Map> channelMapping = new ConcurrentHashMap>(); + private final Map> channelMapping = new ConcurrentHashMap>(); private final SubscriptionTask subscriptionTask = new SubscriptionTask(); @@ -448,7 +448,7 @@ public class RedisMessageListenerContainer implements InitializingBean, Disposab for (Topic topic : topics) { - ArrayHolder holder = new ArrayHolder(serializer.serialize(topic.getTopic())); + ByteArrayWrapper holder = new ByteArrayWrapper(serializer.serialize(topic.getTopic())); if (topic instanceof ChannelTopic) { Collection collection = channelMapping.get(holder); @@ -457,7 +457,7 @@ public class RedisMessageListenerContainer implements InitializingBean, Disposab channelMapping.put(holder, collection); } collection.add(listener); - channels.add(holder.array); + channels.add(holder.getArray()); if (trace) logger.trace("Adding listener '" + listener + "' on channel '" + topic.getTopic() + "'"); @@ -470,7 +470,7 @@ public class RedisMessageListenerContainer implements InitializingBean, Disposab patternMapping.put(holder, collection); } collection.add(listener); - patterns.add(holder.array); + patterns.add(holder.getArray()); if (trace) logger.trace("Adding listener '" + listener + "' for pattern '" + topic.getTopic() + "'"); @@ -598,7 +598,7 @@ public class RedisMessageListenerContainer implements InitializingBean, Disposab } } - private byte[][] unwrap(Collection holders) { + private byte[][] unwrap(Collection holders) { if (CollectionUtils.isEmpty(holders)) { return new byte[0][]; } @@ -606,8 +606,8 @@ public class RedisMessageListenerContainer implements InitializingBean, Disposab byte[][] unwrapped = new byte[holders.size()][]; int index = 0; - for (ArrayHolder arrayHolder : holders) { - unwrapped[index++] = arrayHolder.array; + for (ByteArrayWrapper arrayHolder : holders) { + unwrapped[index++] = arrayHolder.getArray(); } return unwrapped; @@ -700,12 +700,12 @@ public class RedisMessageListenerContainer implements InitializingBean, Disposab // do channel matching first byte[] channel = message.getChannel(); - Collection ch = channelMapping.get(new ArrayHolder(channel)); + Collection ch = channelMapping.get(new ByteArrayWrapper(channel)); Collection pt = null; // followed by pattern matching if (pattern != null && pattern.length > 0) { - pt = patternMapping.get(new ArrayHolder(pattern)); + pt = patternMapping.get(new ByteArrayWrapper(pattern)); } if (!CollectionUtils.isEmpty(ch)) { @@ -739,34 +739,4 @@ public class RedisMessageListenerContainer implements InitializingBean, Disposab } } } - - /** - * Simple wrapper class used for wrapping arrays so they can be used as keys inside maps. - * - * @author Costin Leau - */ - private class ArrayHolder { - - private final byte[] array; - private final int hashCode; - - ArrayHolder(byte[] array) { - this.array = array; - this.hashCode = Arrays.hashCode(array); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof ArrayHolder) { - return Arrays.equals(array, ((ArrayHolder) obj).array); - } - - return false; - } - - @Override - public int hashCode() { - return hashCode; - } - } } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/listener/adapter/MessageListenerAdapter.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/listener/adapter/MessageListenerAdapter.java index 035bb0473..8def8aa52 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/listener/adapter/MessageListenerAdapter.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/listener/adapter/MessageListenerAdapter.java @@ -15,7 +15,6 @@ */ package org.springframework.data.keyvalue.redis.listener.adapter; -import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import org.apache.commons.logging.Log; @@ -152,8 +151,7 @@ public class MessageListenerAdapter implements MessageListener { /** * Set the serializer that will convert incoming raw Redis messages to * listener method arguments. - *

The default converter is a {@link JdkSerializationRedisSerializer}, which is able - * to handle {@link Serializable} objects. + *

The default converter is a {@link StringRedisSerializer}. */ public void setSerializer(RedisSerializer serializer) { this.serializer = serializer; @@ -286,11 +284,11 @@ public class MessageListenerAdapter implements MessageListener { throw (DataAccessException) targetEx; } else { - throw new ListenerExecutionFailedException("Listener method '" + methodName + "' threw exception", + throw new RedisListenerExecutionFailedException("Listener method '" + methodName + "' threw exception", targetEx); } } catch (Throwable ex) { - throw new ListenerExecutionFailedException("Failed to invoke target method '" + methodName + throw new RedisListenerExecutionFailedException("Failed to invoke target method '" + methodName + "' with arguments " + ObjectUtils.nullSafeToString(arguments), ex); } } diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/listener/adapter/ListenerExecutionFailedException.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/listener/adapter/RedisListenerExecutionFailedException.java similarity index 71% rename from spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/listener/adapter/ListenerExecutionFailedException.java rename to spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/listener/adapter/RedisListenerExecutionFailedException.java index cb47028bf..8f94a7a95 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/listener/adapter/ListenerExecutionFailedException.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/listener/adapter/RedisListenerExecutionFailedException.java @@ -23,24 +23,24 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; * @author Costin Leau * @see MessageListenerAdapter */ -public class ListenerExecutionFailedException extends InvalidDataAccessApiUsageException { +public class RedisListenerExecutionFailedException extends InvalidDataAccessApiUsageException { /** - * Constructs a new ListenerExecutionFailedException instance. + * Constructs a new RedisListenerExecutionFailedException instance. * * @param msg * @param cause */ - public ListenerExecutionFailedException(String msg, Throwable cause) { + public RedisListenerExecutionFailedException(String msg, Throwable cause) { super(msg, cause); } /** - * Constructs a new ListenerExecutionFailedException instance. + * Constructs a new RedisListenerExecutionFailedException instance. * * @param msg */ - public ListenerExecutionFailedException(String msg) { + public RedisListenerExecutionFailedException(String msg) { super(msg); } } diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/BasicNumberToStringSerializer.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/BasicNumberToStringSerializer.java deleted file mode 100644 index e7a493879..000000000 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/BasicNumberToStringSerializer.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.keyvalue.redis.serializer; - -import java.lang.reflect.Constructor; -import java.nio.charset.Charset; - -import org.springframework.beans.BeanUtils; -import org.springframework.util.Assert; - -/** - * Simple toString() serializer for the core (lang) numberic JDK types. - * - * @see String#valueOf(Object) - * @see Long#valueOf(String) - * @author Costin Leau - */ -public class BasicNumberToStringSerializer implements RedisSerializer { - - private final Charset charset; - private final Constructor ctor; - - public BasicNumberToStringSerializer(Class type) { - this(type, Charset.forName("UTF8")); - } - - public BasicNumberToStringSerializer(Class type, Charset charset) { - Assert.notNull(type); - this.charset = charset; - - if (!(Byte.class.isAssignableFrom(type) || Short.class.isAssignableFrom(type) - || Long.class.isAssignableFrom(type) || Integer.class.isAssignableFrom(type) - || Float.class.isAssignableFrom(type) || Double.class.isAssignableFrom(type))) { - throw new IllegalArgumentException("Type " + type + " not supported"); - } - - try { - ctor = type.getConstructor(String.class); - } catch (Exception ex) { - throw new IllegalArgumentException("Cannot find suitable constructor for " + type); - } - } - - @Override - public T deserialize(byte[] bytes) { - String string = new String(bytes, charset); - return BeanUtils.instantiateClass(ctor, string); - } - - @Override - public byte[] serialize(T object) { - String string = String.valueOf(object); - return string.getBytes(charset); - } -} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/GenericToStringSerializer.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/GenericToStringSerializer.java index 8daafa1b9..b53387366 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/GenericToStringSerializer.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/GenericToStringSerializer.java @@ -23,6 +23,7 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.support.ConversionServiceFactory; import org.springframework.util.Assert; /** @@ -40,7 +41,7 @@ import org.springframework.util.Assert; public class GenericToStringSerializer implements RedisSerializer, BeanFactoryAware { private final Charset charset; - private Converter converter; + private Converter converter = new Converter(ConversionServiceFactory.createDefaultConversionService()); private Class type; public GenericToStringSerializer(Class type) { @@ -65,12 +66,19 @@ public class GenericToStringSerializer implements RedisSerializer, BeanFac @Override public T deserialize(byte[] bytes) { + if (bytes == null) { + return null; + } + String string = new String(bytes, charset); return converter.convert(string, type); } @Override public byte[] serialize(T object) { + if (object == null) { + return null; + } String string = converter.convert(object, String.class); return string.getBytes(charset); } diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/JacksonJsonRedisSerializer.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/JacksonJsonRedisSerializer.java index bf9adaf64..c858cfcb2 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/JacksonJsonRedisSerializer.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/JacksonJsonRedisSerializer.java @@ -26,6 +26,8 @@ import org.springframework.util.Assert; * {@link RedisSerializer} that can read and write JSON using Jackson's {@link ObjectMapper}. * *

This converter can be used to bind to typed beans, or untyped {@link java.util.HashMap HashMap} instances. + * + * Note:Null objects are serialized as empty arrays and vice versa. * * @author Costin Leau */ @@ -44,7 +46,7 @@ public class JacksonJsonRedisSerializer implements RedisSerializer { @SuppressWarnings("unchecked") @Override public T deserialize(byte[] bytes) throws SerializationException { - if (SerializerUtils.isEmpty(bytes)) { + if (SerializationUtils.isEmpty(bytes)) { return null; } try { @@ -57,7 +59,7 @@ public class JacksonJsonRedisSerializer implements RedisSerializer { @Override public byte[] serialize(Object t) throws SerializationException { if (t == null) { - return SerializerUtils.EMPTY_ARRAY; + return SerializationUtils.EMPTY_ARRAY; } try { return this.objectMapper.writeValueAsBytes(t); diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/JdkSerializationRedisSerializer.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/JdkSerializationRedisSerializer.java index 202c9203d..fe6de7886 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/JdkSerializationRedisSerializer.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/JdkSerializationRedisSerializer.java @@ -34,6 +34,10 @@ public class JdkSerializationRedisSerializer implements RedisSerializer @SuppressWarnings("unchecked") @Override public Object deserialize(byte[] bytes) { + if (SerializationUtils.isEmpty(bytes)) { + return null; + } + try { return deserializer.convert(bytes); } catch (Exception ex) { @@ -43,6 +47,9 @@ public class JdkSerializationRedisSerializer implements RedisSerializer @Override public byte[] serialize(Object object) { + if (object == null) { + return SerializationUtils.EMPTY_ARRAY; + } try { return serializer.convert(object); } catch (Exception ex) { diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/OxmSerializer.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/OxmSerializer.java index 596e22f87..b1a2354f8 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/OxmSerializer.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/OxmSerializer.java @@ -31,7 +31,7 @@ import org.springframework.util.Assert; * Delegates serialization/deserialization to OXM {@link Marshaller} and * {@link Unmarshaller}. * - * Note:Null objects are serialized as empty arrays. + * Note:Null objects are serialized as empty arrays and vice versa. * * @author Costin Leau */ @@ -72,7 +72,7 @@ public class OxmSerializer implements InitializingBean, RedisSerializer @Override public Object deserialize(byte[] bytes) throws SerializationException { - if (SerializerUtils.isEmpty(bytes)) { + if (SerializationUtils.isEmpty(bytes)) { return null; } @@ -86,7 +86,7 @@ public class OxmSerializer implements InitializingBean, RedisSerializer @Override public byte[] serialize(Object t) throws SerializationException { if (t == null) { - return SerializerUtils.EMPTY_ARRAY; + return SerializationUtils.EMPTY_ARRAY; } ByteArrayOutputStream stream = new ByteArrayOutputStream(); diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/RedisSerializer.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/RedisSerializer.java index 910a4333c..18543c579 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/RedisSerializer.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/RedisSerializer.java @@ -19,6 +19,7 @@ package org.springframework.data.keyvalue.redis.serializer; * Basic interface serialization and deserialization of Objects to byte arrays (binary data). * * It is recommended that implementations are designed to handle null objects/empty arrays on serialization and deserialization side. + * Note that Redis does not accept null keys or values but can return null replies (for non existing keys). * * @author Mark Pollack * @author Costin Leau diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/SerializationUtils.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/SerializationUtils.java new file mode 100644 index 000000000..fab0120d8 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/SerializationUtils.java @@ -0,0 +1,68 @@ +/* + * 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.keyvalue.redis.serializer; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * Utility class with various serialization-related methods. + * + * @author Costin Leau + */ +public abstract class SerializationUtils { + + static final byte[] EMPTY_ARRAY = new byte[0]; + + static boolean isEmpty(byte[] data) { + return (data == null || data.length == 0); + } + + + @SuppressWarnings("unchecked") + static > T deserializeValues(Collection rawValues, Class type, RedisSerializer redisSerializer) { + // connection in pipeline/multi mode + if (rawValues == null) { + return null; + } + + Collection values = (List.class.isAssignableFrom(type) ? new ArrayList(rawValues.size()) + : new LinkedHashSet(rawValues.size())); + for (byte[] bs : rawValues) { + values.add(redisSerializer.deserialize(bs)); + } + + return (T) values; + } + + @SuppressWarnings("unchecked") + public static Set deserialize(Set rawValues, RedisSerializer redisSerializer) { + return deserializeValues(rawValues, Set.class, redisSerializer); + } + + @SuppressWarnings("unchecked") + public static List deserialize(List rawValues, RedisSerializer redisSerializer) { + return deserializeValues(rawValues, List.class, redisSerializer); + } + + @SuppressWarnings("unchecked") + public static Collection deserialize(Collection rawValues, RedisSerializer redisSerializer) { + return deserializeValues(rawValues, List.class, redisSerializer); + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/StringRedisSerializer.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/StringRedisSerializer.java index dbb0f8b3e..d0b361ba1 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/StringRedisSerializer.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/serializer/StringRedisSerializer.java @@ -25,14 +25,12 @@ import org.springframework.util.Assert; *

* Useful when the interaction with the Redis happens mainly through Strings. * - *

Converts null into empty arrays (which get translated into empty strings on deserialization). + *

Does not perform any null conversion since empty strings are valid keys/values. * * @author Costin Leau */ public class StringRedisSerializer implements RedisSerializer { - private final static byte[] EMPTY_ARRAY = new byte[0]; - private final String EMPTY_STRING = ""; private final Charset charset; public StringRedisSerializer() { @@ -46,11 +44,11 @@ public class StringRedisSerializer implements RedisSerializer { @Override public String deserialize(byte[] bytes) { - return (SerializerUtils.isEmpty(bytes) ? EMPTY_STRING : new String(bytes, charset)); + return (bytes == null ? null : new String(bytes, charset)); } @Override public byte[] serialize(String string) { - return (string == null ? EMPTY_ARRAY : string.getBytes(charset)); + return (string == null ? null : string.getBytes(charset)); } } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/atomic/CASUtils.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/atomic/CASUtils.java deleted file mode 100644 index 4ef4175fc..000000000 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/atomic/CASUtils.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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.keyvalue.redis.support.atomic; - -import java.util.Collections; -import java.util.concurrent.Callable; - -import org.springframework.data.keyvalue.redis.core.RedisOperations; -import org.springframework.data.keyvalue.redis.core.SessionCallback; - -/** - * Check-And-Set (CAS) utility. Performs the CAS loop until successful pattern using - * Redis watch/exec operations. - * - * The given callback can contain one or multiple reads followed by a multi call - * and one or multiple writes: - * - *

- * return CASUtils.execute(ops, key, new Callable() {
- *  @Override
- *  public Integer call() throws Exception {
- *    // check
- *    int value = get();
- *    // start MULTI
- *    ops.multi();
- *    // set
- *    ops.increment(key, 1);
- *    return value;
- *  }
- * });
- * 
- * - * @author Costin Leau - */ -abstract class CASUtils { - - public static T execute(final RedisOperations ops, final K key, final Callable callback) { - return ops.execute(new SessionCallback() { - @SuppressWarnings("unchecked") - @Override - public T execute(RedisOperations operations) { - try { - for (;;) { - operations.watch(Collections.singleton(key)); - T result = callback.call(); - if (operations.exec() != null) { - return result; - } - } - } catch (Exception ex) { - // includes DataAccessException - if (ex instanceof RuntimeException) { - throw (RuntimeException) ex; - } - throw new RuntimeException("Callback threw exception", ex); - } - } - }); - } -} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/atomic/RedisAtomicInteger.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/atomic/RedisAtomicInteger.java index cae600c76..79e5b523e 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/atomic/RedisAtomicInteger.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/atomic/RedisAtomicInteger.java @@ -17,14 +17,17 @@ package org.springframework.data.keyvalue.redis.support.atomic; import java.io.Serializable; import java.util.Collections; +import java.util.Date; +import java.util.concurrent.TimeUnit; +import org.springframework.data.keyvalue.redis.connection.DataType; import org.springframework.data.keyvalue.redis.connection.RedisConnectionFactory; -import org.springframework.data.keyvalue.redis.core.KeyBound; +import org.springframework.data.keyvalue.redis.core.BoundKeyOperations; import org.springframework.data.keyvalue.redis.core.RedisOperations; import org.springframework.data.keyvalue.redis.core.RedisTemplate; import org.springframework.data.keyvalue.redis.core.SessionCallback; import org.springframework.data.keyvalue.redis.core.ValueOperations; -import org.springframework.data.keyvalue.redis.serializer.BasicNumberToStringSerializer; +import org.springframework.data.keyvalue.redis.serializer.GenericToStringSerializer; import org.springframework.data.keyvalue.redis.serializer.StringRedisSerializer; /** @@ -34,9 +37,9 @@ import org.springframework.data.keyvalue.redis.serializer.StringRedisSerializer; * @see java.util.concurrent.atomic.AtomicInteger * @author Costin Leau */ -public class RedisAtomicInteger extends Number implements Serializable, KeyBound { +public class RedisAtomicInteger extends Number implements Serializable, BoundKeyOperations { - private final String key; + private volatile String key; private ValueOperations operations; private RedisOperations generalOps; @@ -48,16 +51,7 @@ public class RedisAtomicInteger extends Number implements Serializable, KeyBound * @param factory connection factory */ public RedisAtomicInteger(String redisCounter, RedisConnectionFactory factory) { - RedisTemplate redisTemplate = new RedisTemplate(factory); - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(new BasicNumberToStringSerializer(Integer.class)); - redisTemplate.setExposeConnection(true); - this.key = redisCounter; - this.generalOps = redisTemplate; - this.operations = generalOps.opsForValue(); - if (this.operations.get(redisCounter) == null) { - set(0); - } + this(redisCounter, factory, null); } /** @@ -68,12 +62,29 @@ public class RedisAtomicInteger extends Number implements Serializable, KeyBound * @param initialValue */ public RedisAtomicInteger(String redisCounter, RedisConnectionFactory factory, int initialValue) { - RedisTemplate redisTemplate = new RedisTemplate(factory); + this(redisCounter, factory, Integer.valueOf(initialValue)); + } + + private RedisAtomicInteger(String redisCounter, RedisConnectionFactory factory, Integer initialValue) { + RedisTemplate redisTemplate = new RedisTemplate(); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new GenericToStringSerializer(Integer.class)); redisTemplate.setExposeConnection(true); + redisTemplate.setConnectionFactory(factory); + redisTemplate.afterPropertiesSet(); + this.key = redisCounter; this.generalOps = redisTemplate; this.operations = generalOps.opsForValue(); - this.operations.set(redisCounter, initialValue); + + if (initialValue == null) { + if (this.operations.get(redisCounter) == null) { + set(0); + } + } + else { + set(initialValue); + } } /** @@ -82,6 +93,8 @@ public class RedisAtomicInteger extends Number implements Serializable, KeyBound * * Use {@link #RedisAtomicInteger(String, RedisOperations, int)} to set the counter to a certain value * as an alternative constructor or {@link #set(int)}. + * + * Note that integers need to be properly serialized so that Redis can recognized the values as numeric and thus modify their value. * * @param redisCounter * @param operations @@ -98,6 +111,8 @@ public class RedisAtomicInteger extends Number implements Serializable, KeyBound /** * Constructs a new RedisAtomicInteger instance with the given initial value. * + * Note that integers need to be properly serialized so that Redis can recognized the values as numeric and thus modify their value. + * * @param redisCounter * @param operations * @param initialValue @@ -109,11 +124,6 @@ public class RedisAtomicInteger extends Number implements Serializable, KeyBound this.operations.set(redisCounter, initialValue); } - @Override - public String getKey() { - return key; - } - /** * Get the current value. * @@ -251,4 +261,40 @@ public class RedisAtomicInteger extends Number implements Serializable, KeyBound public double doubleValue() { return (double) get(); } + + @Override + public String getKey() { + return key; + } + + @Override + public Boolean expire(long timeout, TimeUnit unit) { + return generalOps.expire(key, timeout, unit); + } + + @Override + public Boolean expireAt(Date date) { + return generalOps.expireAt(key, date); + } + + @Override + public Long getExpire() { + return generalOps.getExpire(key); + } + + @Override + public Boolean persist() { + return generalOps.persist(key); + } + + @Override + public void rename(String newKey) { + generalOps.rename(key, newKey); + key = newKey; + } + + @Override + public DataType getType() { + return DataType.STRING; + } } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/atomic/RedisAtomicLong.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/atomic/RedisAtomicLong.java index 001ee19ba..9da634b3e 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/atomic/RedisAtomicLong.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/atomic/RedisAtomicLong.java @@ -17,14 +17,17 @@ package org.springframework.data.keyvalue.redis.support.atomic; import java.io.Serializable; import java.util.Collections; +import java.util.Date; +import java.util.concurrent.TimeUnit; +import org.springframework.data.keyvalue.redis.connection.DataType; import org.springframework.data.keyvalue.redis.connection.RedisConnectionFactory; -import org.springframework.data.keyvalue.redis.core.KeyBound; +import org.springframework.data.keyvalue.redis.core.BoundKeyOperations; import org.springframework.data.keyvalue.redis.core.RedisOperations; import org.springframework.data.keyvalue.redis.core.RedisTemplate; import org.springframework.data.keyvalue.redis.core.SessionCallback; import org.springframework.data.keyvalue.redis.core.ValueOperations; -import org.springframework.data.keyvalue.redis.serializer.BasicNumberToStringSerializer; +import org.springframework.data.keyvalue.redis.serializer.GenericToStringSerializer; import org.springframework.data.keyvalue.redis.serializer.StringRedisSerializer; /** @@ -34,9 +37,9 @@ import org.springframework.data.keyvalue.redis.serializer.StringRedisSerializer; * @see java.util.concurrent.atomic.AtomicLong * @author Costin Leau */ -public class RedisAtomicLong extends Number implements Serializable, KeyBound { +public class RedisAtomicLong extends Number implements Serializable, BoundKeyOperations { - private final String key; + private volatile String key; private ValueOperations operations; private RedisOperations generalOps; @@ -48,16 +51,7 @@ public class RedisAtomicLong extends Number implements Serializable, KeyBound redisTemplate = new RedisTemplate(factory); - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(new BasicNumberToStringSerializer(Long.class)); - redisTemplate.setExposeConnection(true); - this.key = redisCounter; - this.generalOps = redisTemplate; - this.operations = generalOps.opsForValue(); - if (this.operations.get(redisCounter) == null) { - set(0); - } + this(redisCounter, factory, null); } /** @@ -68,21 +62,39 @@ public class RedisAtomicLong extends Number implements Serializable, KeyBound redisTemplate = new RedisTemplate(factory); + this(redisCounter, factory, Long.valueOf(initialValue)); + } + + private RedisAtomicLong(String redisCounter, RedisConnectionFactory factory, Long initialValue) { + RedisTemplate redisTemplate = new RedisTemplate(); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new GenericToStringSerializer(Long.class)); redisTemplate.setExposeConnection(true); + redisTemplate.setConnectionFactory(factory); + redisTemplate.afterPropertiesSet(); + this.key = redisCounter; this.generalOps = redisTemplate; this.operations = generalOps.opsForValue(); - this.operations.set(redisCounter, initialValue); - } + if (initialValue == null) { + if (this.operations.get(redisCounter) == null) { + set(0); + } + } + else { + set(initialValue); + } + } /** * Constructs a new RedisAtomicLong instance. Uses as initial value * the data from the backing store (sets the counter to 0 if no value is found). * * Use {@link #RedisAtomicLong(String, RedisOperations, long)} to set the counter to a certain value - * as an alternative constructor or {@link #set(long)}. + * as an alternative constructor or {@link #set(long)}. + * + * Note that longs need to be properly serialized so that Redis can recognized the values as numeric and thus modify their value. * * @param redisCounter * @param operations @@ -99,6 +111,8 @@ public class RedisAtomicLong extends Number implements Serializable, KeyBoundRedisAtomicLong instance with the given initial value. * + * Note that longs need to be properly serialized so that Redis can recognized the values as numeric and thus modify their value. + * * @param redisCounter * @param operations * @param initialValue @@ -109,11 +123,6 @@ public class RedisAtomicLong extends Number implements Serializable, KeyBound extends AbstractCollection i public static final String ENCODING = "UTF-8"; - private final String key; + private volatile String key; private final RedisOperations operations; public AbstractRedisCollection(String key, RedisOperations operations) { @@ -116,4 +118,30 @@ public abstract class AbstractRedisCollection extends AbstractCollection i sb.append(getKey()); return sb.toString(); } + + @Override + public Boolean expire(long timeout, TimeUnit unit) { + return operations.expire(key, timeout, unit); + } + + @Override + public Boolean expireAt(Date date) { + return operations.expireAt(key, date); + } + + @Override + public Long getExpire() { + return operations.getExpire(key); + } + + @Override + public Boolean persist() { + return operations.persist(key); + } + + @Override + public void rename(final String newKey) { + CollectionUtils.rename(key, newKey, operations); + key = newKey; + } } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/CollectionUtils.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/CollectionUtils.java index 20cfd6450..e98c8287a 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/CollectionUtils.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/CollectionUtils.java @@ -20,6 +20,10 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import org.springframework.dao.DataAccessException; +import org.springframework.data.keyvalue.redis.core.RedisOperations; +import org.springframework.data.keyvalue.redis.core.SessionCallback; + /** * Utility class used mainly for type conversion by the default collection implementations. * Meant for internal use. @@ -48,4 +52,55 @@ abstract class CollectionUtils { return keys; } -} + + static void rename(final K key, final K newKey, RedisOperations operations) { + operations.execute(new SessionCallback() { + @SuppressWarnings("unchecked") + @Override + public Object execute(RedisOperations operations) throws DataAccessException { + do { + operations.watch(key); + + if (operations.hasKey(key)) { + operations.multi(); + operations.rename(key, newKey); + } + else { + operations.multi(); + } + } while (operations.exec() == null); + return null; + } + }); + } + + static Boolean renameIfAbsent(final K key, final K newKey, RedisOperations operations) { + return operations.execute(new SessionCallback() { + @Override + public Boolean execute(RedisOperations operations) throws DataAccessException { + List exec = null; + do { + operations.watch(key); + + if (operations.hasKey(key)) { + operations.multi(); + operations.renameIfAbsent(key, newKey); + } + else { + operations.watch(newKey); + operations.multi(); + operations.hasKey(newKey); + operations.hasKey(newKey); + } + exec = operations.exec(); + } while (exec == null); + + boolean result = ((Long) exec.get(0) == 1); + if (exec.size() > 1) { + result = !result; + } + return result; + } + }); + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/DefaultRedisList.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/DefaultRedisList.java index 8992ddbfc..6149838c4 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/DefaultRedisList.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/DefaultRedisList.java @@ -23,6 +23,7 @@ import java.util.ListIterator; import java.util.NoSuchElementException; import java.util.concurrent.TimeUnit; +import org.springframework.data.keyvalue.redis.connection.DataType; import org.springframework.data.keyvalue.redis.core.BoundListOperations; import org.springframework.data.keyvalue.redis.core.RedisOperations; @@ -498,4 +499,9 @@ public class DefaultRedisList extends AbstractRedisCollection implements R public E takeLast() throws InterruptedException { return pollLast(0, TimeUnit.SECONDS); } + + @Override + public DataType getType() { + return DataType.LIST; + } } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/DefaultRedisMap.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/DefaultRedisMap.java index e5d0e21db..291b6b00c 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/DefaultRedisMap.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/DefaultRedisMap.java @@ -17,11 +17,14 @@ package org.springframework.data.keyvalue.redis.support.collections; import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; +import org.springframework.data.keyvalue.redis.connection.DataType; import org.springframework.data.keyvalue.redis.core.BoundHashOperations; import org.springframework.data.keyvalue.redis.core.RedisOperations; @@ -84,11 +87,6 @@ public class DefaultRedisMap implements RedisMap { return hashOps.increment(key, delta); } - @Override - public String getKey() { - return hashOps.getKey(); - } - @Override public RedisOperations getOperations() { return hashOps.getOperations(); @@ -295,4 +293,40 @@ public class DefaultRedisMap implements RedisMap { // } // } } + + @Override + public Boolean expire(long timeout, TimeUnit unit) { + return hashOps.expire(timeout, unit); + } + + @Override + public Boolean expireAt(Date date) { + return hashOps.expireAt(date); + } + + @Override + public Long getExpire() { + return hashOps.getExpire(); + } + + @Override + public Boolean persist() { + return hashOps.persist(); + } + + + @Override + public String getKey() { + return hashOps.getKey(); + } + + @Override + public void rename(String newKey) { + hashOps.rename(newKey); + } + + @Override + public DataType getType() { + return hashOps.getType(); + } } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/DefaultRedisSet.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/DefaultRedisSet.java index 50a69ea11..368d7c204 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/DefaultRedisSet.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/DefaultRedisSet.java @@ -21,6 +21,7 @@ import java.util.Iterator; import java.util.Set; import java.util.UUID; +import org.springframework.data.keyvalue.redis.connection.DataType; import org.springframework.data.keyvalue.redis.core.BoundSetOperations; import org.springframework.data.keyvalue.redis.core.RedisOperations; @@ -166,4 +167,9 @@ public class DefaultRedisSet extends AbstractRedisCollection implements Re public int size() { return boundSetOps.size().intValue(); } + + @Override + public DataType getType() { + return DataType.SET; + } } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/DefaultRedisZSet.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/DefaultRedisZSet.java index d3ee04765..4794aae99 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/DefaultRedisZSet.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/DefaultRedisZSet.java @@ -20,6 +20,7 @@ import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; +import org.springframework.data.keyvalue.redis.connection.DataType; import org.springframework.data.keyvalue.redis.core.BoundZSetOperations; import org.springframework.data.keyvalue.redis.core.RedisOperations; @@ -211,4 +212,9 @@ public class DefaultRedisZSet extends AbstractRedisCollection implements R public Double score(Object o) { return boundZSetOps.score(o); } + + @Override + public DataType getType() { + return DataType.ZSET; + } } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/RedisCollectionFactoryBean.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/RedisCollectionFactoryBean.java new file mode 100644 index 000000000..0f0fa8249 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/RedisCollectionFactoryBean.java @@ -0,0 +1,167 @@ +/* + * 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.keyvalue.redis.support.collections; + +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.data.keyvalue.redis.connection.DataType; +import org.springframework.data.keyvalue.redis.core.RedisTemplate; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Factory bean that facilitates creation of Redis-based collections. Supports list, set, zset (or sortedSet), map (or hash) and properties. + * Will use the key type if it exists or to create a dedicated collection (Properties vs Map). + * Otherwise uses the provided type (default is list). + * + * @author Costin Leau + */ +public class RedisCollectionFactoryBean implements InitializingBean, BeanNameAware, FactoryBean { + + public enum CollectionType { + LIST { + @Override + public DataType dataType() { + return DataType.LIST; + } + }, + SET { + @Override + public DataType dataType() { + return DataType.SET; + } + }, + ZSET { + @Override + public DataType dataType() { + return DataType.ZSET; + } + }, + MAP { + @Override + public DataType dataType() { + return DataType.HASH; + } + }, + PROPERTIES { + @Override + public DataType dataType() { + return DataType.HASH; + } + }; + + abstract DataType dataType(); + } + + + private RedisStore store; + private CollectionType type = null; + private RedisTemplate template; + private String key; + private String beanName; + + @Override + public void afterPropertiesSet() { + if (!StringUtils.hasText(key)) { + key = beanName; + } + + Assert.hasText(key, "Collection key is required - no key or bean name specified"); + Assert.notNull(template, "Redis template is required"); + + DataType dt = template.type(key); + + // can't create store + Assert.isTrue(!DataType.STRING.equals(dt), "Cannot create store on keys of type 'string'"); + + store = createStore(dt); + + if (store == null) { + if (type == null) { + type = CollectionType.LIST; + } + store = createStore(type.dataType()); + } + } + + private RedisStore createStore(DataType dt) { + switch (dt) { + case LIST: + return new DefaultRedisList(key, template); + + case SET: + return new DefaultRedisSet(key, template); + + case ZSET: + return new DefaultRedisZSet(key, template); + + case HASH: + if (CollectionType.PROPERTIES.equals(type)) { + return new RedisProperties(key, template); + } + return new DefaultRedisMap(key, template); + } + return null; + } + + @Override + public RedisStore getObject() { + return store; + } + + @Override + public Class getObjectType() { + return (store != null ? store.getClass() : RedisStore.class); + } + + @Override + public boolean isSingleton() { + return true; + } + + @Override + public void setBeanName(String name) { + this.beanName = name; + } + + /** + * Sets the store type. Used if the key does not exist. + * + * @param type The type to set. + */ + public void setType(CollectionType type) { + this.type = type; + } + + /** + * Sets the template used by the resulting store. + * + * @param template The template to set. + */ + public void setTemplate(RedisTemplate template) { + this.template = template; + } + + /** + * Sets the key of the store. + * + * @param key The key to set. + */ + public void setKey(String key) { + this.key = key; + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/RedisProperties.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/RedisProperties.java new file mode 100644 index 000000000..0fec1d1b9 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/RedisProperties.java @@ -0,0 +1,277 @@ +/* + * 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.keyvalue.redis.support.collections; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; + +import org.springframework.data.keyvalue.redis.connection.DataType; +import org.springframework.data.keyvalue.redis.core.BoundHashOperations; +import org.springframework.data.keyvalue.redis.core.RedisOperations; + +/** + * {@link Properties} extension for a Redis back-store. Useful for reading (and storing) properties + * inside a Redis hash. Particularly useful inside a Spring container for hooking into Spring's property + * placeholder or {@link org.springframework.beans.factory.config.PropertiesFactoryBean}. + *

+ * Note that this implementation only accepts Strings - objects of other type are not supported. + * + * @see Properties + * @see org.springframework.core.io.support.PropertiesLoaderSupport + * @author Costin Leau + */ +public class RedisProperties extends Properties implements RedisMap { + + private final BoundHashOperations hashOps; + private final RedisMap delegate; + + /** + * Constructs a new RedisProperties instance. + * + */ + public RedisProperties(BoundHashOperations boundOps) { + this(null, boundOps); + } + + /** + * Constructs a new RedisProperties instance. + * + * @param boundOps + */ + public RedisProperties(String key, RedisOperations operations) { + this(null, operations. boundHashOps(key)); + } + + /** + * Constructs a new RedisProperties instance. + * + * @param defaults + */ + public RedisProperties(Properties defaults, BoundHashOperations boundOps) { + super(defaults); + this.hashOps = boundOps; + this.delegate = new DefaultRedisMap(boundOps); + } + + /** + * Constructs a new RedisProperties instance. + * + * @param defaults + * @param boundOps + */ + public RedisProperties(Properties defaults, String key, RedisOperations operations) { + this(defaults, operations. boundHashOps(key)); + } + + @Override + public synchronized Object get(Object key) { + return delegate.get(key); + } + + @Override + public synchronized Object put(Object key, Object value) { + return delegate.put((String) key, (String) value); + } + + @SuppressWarnings("unchecked") + @Override + public synchronized void putAll(Map t) { + delegate.putAll((Map) t); + } + + @Override + public Enumeration propertyNames() { + Set keys = new LinkedHashSet(delegate.keySet()); + keys.addAll(defaults.stringPropertyNames()); + return Collections.enumeration(keys); + } + + @Override + public synchronized void clear() { + delegate.clear(); + } + + @Override + public synchronized Object clone() { + return new RedisProperties(defaults, hashOps); + } + + @Override + public synchronized boolean contains(Object value) { + return containsValue(value); + } + + @Override + public synchronized boolean containsKey(Object key) { + return delegate.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return delegate.containsValue(value); + } + + @SuppressWarnings("unchecked") + @Override + public synchronized Enumeration elements() { + Collection values = delegate.values(); + return Collections.enumeration(values); + } + + @Override + @SuppressWarnings("unchecked") + public Set> entrySet() { + Set entries = delegate.entrySet(); + return entries; + } + + @Override + public synchronized boolean equals(Object o) { + if (o == this) + return true; + + if (o instanceof RedisProperties) { + return o.hashCode() == hashCode(); + } + return false; + } + + @Override + public synchronized int hashCode() { + int hash = RedisProperties.class.hashCode(); + return hash * 17 + delegate.hashCode(); + } + + @Override + public synchronized boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public synchronized Enumeration keys() { + Set keys = keySet(); + return Collections.enumeration(keys); + } + + @SuppressWarnings("unchecked") + @Override + public Set keySet() { + Set keys = delegate.keySet(); + return keys; + } + + @Override + public synchronized Object remove(Object key) { + return delegate.remove(key); + } + + @Override + public synchronized int size() { + return delegate.size(); + } + + @SuppressWarnings("unchecked") + @Override + public Collection values() { + Collection vals = delegate.values(); + return vals; + } + + @Override + public Long increment(Object key, long delta) { + return hashOps.increment((String) key, delta); + } + + @Override + public RedisOperations getOperations() { + return hashOps.getOperations(); + } + + @Override + public Boolean expire(long timeout, TimeUnit unit) { + return hashOps.expire(timeout, unit); + } + + @Override + public Boolean expireAt(Date date) { + return hashOps.expireAt(date); + } + + @Override + public Long getExpire() { + return hashOps.getExpire(); + } + + @Override + public String getKey() { + return hashOps.getKey(); + } + + @Override + public DataType getType() { + return hashOps.getType(); + } + + @Override + public Boolean persist() { + return hashOps.persist(); + } + + @Override + public void rename(String newKey) { + hashOps.rename(newKey); + } + + @Override + public Object putIfAbsent(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean replace(Object key, Object oldValue, Object newValue) { + throw new UnsupportedOperationException(); + } + + @Override + public Object replace(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public synchronized void storeToXML(OutputStream os, String comment, String encoding) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public synchronized void storeToXML(OutputStream os, String comment) throws IOException { + throw new UnsupportedOperationException(); + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/RedisStore.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/RedisStore.java index 5a8c1fbfc..d3c9205d8 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/RedisStore.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/RedisStore.java @@ -15,7 +15,7 @@ */ package org.springframework.data.keyvalue.redis.support.collections; -import org.springframework.data.keyvalue.redis.core.KeyBound; +import org.springframework.data.keyvalue.redis.core.BoundKeyOperations; import org.springframework.data.keyvalue.redis.core.RedisOperations; /** @@ -26,7 +26,7 @@ import org.springframework.data.keyvalue.redis.core.RedisOperations; * * @author Costin Leau */ -public interface RedisStore extends KeyBound { +public interface RedisStore extends BoundKeyOperations { /** * Returns the underlying Redis operations used by the backing implementation. diff --git a/spring-data-redis/src/main/resources/org/springframework/data/keyvalue/redis/config/spring-redis-1.0.xsd b/spring-data-redis/src/main/resources/org/springframework/data/keyvalue/redis/config/spring-redis-1.0.xsd index 868215ee1..dea12ea92 100644 --- a/spring-data-redis/src/main/resources/org/springframework/data/keyvalue/redis/config/spring-redis-1.0.xsd +++ b/spring-data-redis/src/main/resources/org/springframework/data/keyvalue/redis/config/spring-redis-1.0.xsd @@ -149,4 +149,61 @@ listener method arguments. Default is a StringRedisSerializer. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/ConnectionFactoryTracker.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/ConnectionFactoryTracker.java index 9ef6c5e59..634d3448a 100644 --- a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/ConnectionFactoryTracker.java +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/ConnectionFactoryTracker.java @@ -40,7 +40,7 @@ public abstract class ConnectionFactoryTracker { for (RedisConnectionFactory connectionFactory : connFactories) { try { ((DisposableBean) connectionFactory).destroy(); - System.out.println("Succesfully cleaned up factory " + connectionFactory); + //System.out.println("Succesfully cleaned up factory " + connectionFactory); } catch (Exception ex) { System.err.println("Cannot clean factory " + connectionFactory + ex); } diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/connection/AbstractConnectionIntegrationTests.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/connection/AbstractConnectionIntegrationTests.java index 95ee8ec75..875d65b79 100644 --- a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/connection/AbstractConnectionIntegrationTests.java +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/connection/AbstractConnectionIntegrationTests.java @@ -18,15 +18,23 @@ package org.springframework.data.keyvalue.redis.connection; import static org.junit.Assert.*; +import java.util.Arrays; +import java.util.List; import java.util.Properties; import java.util.UUID; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.After; +import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import org.springframework.dao.DataAccessException; import org.springframework.data.keyvalue.redis.Address; +import org.springframework.data.keyvalue.redis.ConnectionFactoryTracker; import org.springframework.data.keyvalue.redis.Person; +import org.springframework.data.keyvalue.redis.core.StringRedisTemplate; import org.springframework.data.keyvalue.redis.serializer.JdkSerializationRedisSerializer; import org.springframework.data.keyvalue.redis.serializer.RedisSerializer; import org.springframework.data.keyvalue.redis.serializer.StringRedisSerializer; @@ -40,12 +48,21 @@ public abstract class AbstractConnectionIntegrationTests { private static final String listName = "test-list"; private static final byte[] EMPTY_ARRAY = new byte[0]; + protected abstract RedisConnectionFactory getConnectionFactory(); + + @Before public void setUp() { connection = new DefaultStringRedisConnection(getConnectionFactory().getConnection()); + ConnectionFactoryTracker.add(getConnectionFactory()); + + } + + @AfterClass + public static void cleanUp() { + ConnectionFactoryTracker.cleanUp(); } - protected abstract RedisConnectionFactory getConnectionFactory(); @After public void tearDown() { @@ -55,16 +72,19 @@ public abstract class AbstractConnectionIntegrationTests { @Test public void testLPush() throws Exception { - Long index = connection.lPush(listName.getBytes(), "bar".getBytes()); + byte[] val = "bar".getBytes(); + Long index = connection.lPush(listName.getBytes(), val); if (index != null) { - assertEquals((Long) (index + 1), connection.lPush(listName.getBytes(), "bar".getBytes())); + assertEquals((Long) (index + 1), connection.lPush(listName.getBytes(), val)); } } @Test public void testSetAndGet() { - connection.set("foo".getBytes(), "blahblah".getBytes()); - assertEquals("blahblah", new String(connection.get("foo".getBytes()))); + String key = "foo"; + String value = "blabla"; + connection.set(key.getBytes(), value.getBytes()); + assertEquals(value, new String(connection.get(key.getBytes()))); } private boolean isJredis() { @@ -102,8 +122,12 @@ public abstract class AbstractConnectionIntegrationTests { @Test public void testNullKey() throws Exception { - connection.decr((String) null); connection.decr(EMPTY_ARRAY); + try { + connection.decr((String) null); + } catch (Exception ex) { + // excepted + } } @Test @@ -140,4 +164,159 @@ public abstract class AbstractConnectionIntegrationTests { // expected } } + + @Test + public void testNullSerialization() throws Exception { + String[] keys = new String[] { "~", "[" }; + List mGet = connection.mGet(keys); + assertEquals(2, mGet.size()); + assertNull(mGet.get(0)); + assertNull(mGet.get(1)); + + StringRedisTemplate stringTemplate = new StringRedisTemplate(getConnectionFactory()); + List multiGet = stringTemplate.opsForValue().multiGet(Arrays.asList(keys)); + assertEquals(2, multiGet.size()); + assertNull(multiGet.get(0)); + assertNull(multiGet.get(1)); + } + + @Test + public void testNullCollections() throws Exception { + connection.openPipeline(); + assertNull(connection.keys("~*")); + assertNull(connection.hKeys("~")); + connection.closePipeline(); + } + + // pub sub test + + @Test + public void testPubSub() throws Exception { + + final BlockingDeque queue = new LinkedBlockingDeque(); + + final MessageListener ml = new MessageListener() { + @Override + public void onMessage(Message message, byte[] pattern) { + queue.add(message); + System.out.println("received message"); + } + }; + + final byte[] channel = "foo.tv".getBytes(); + final RedisConnection subConn = getConnectionFactory().getConnection(); + + assertNotSame(connection, subConn); + + + final AtomicBoolean flag = new AtomicBoolean(true); + + Runnable listener = new Runnable() { + @Override + public void run() { + subConn.subscribe(ml, channel); + System.out.println("Subscribed"); + while (flag.get()) { + try { + Thread.currentThread().sleep(2000); + } catch (Exception ex) { + return; + } + } + } + }; + + Thread th = new Thread(listener, "listener"); + th.start(); + + try { + Thread.sleep(1500); + connection.publish(channel, "one".getBytes()); + connection.publish(channel, "two".getBytes()); + connection.publish(channel, "I see you".getBytes()); + System.out.println("Done publishing..."); + Thread.sleep(5000); + System.out.println("Done waiting ..."); + } finally { + flag.set(false); + } + System.out.println(queue); + assertEquals(3, queue.size()); + } + + @Test + public void testPubSubWithNamedChannels() { + final byte[] expectedChannel = "channel1".getBytes(); + final byte[] expectedMessage = "msg".getBytes(); + + MessageListener listener = new MessageListener() { + + @Override + public void onMessage(Message message, byte[] pattern) { + assertArrayEquals(expectedChannel, message.getChannel()); + assertArrayEquals(expectedMessage, message.getBody()); + } + }; + + Thread th = new Thread(new Runnable() { + @Override + public void run() { + // sleep 1 second to let the registration happen + try { + Thread.currentThread().sleep(1000); + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + + // open a new connection + RedisConnection connection2 = getConnectionFactory().getConnection(); + connection2.publish(expectedMessage, expectedChannel); + connection2.close(); + // unsubscribe connection + connection.getSubscription().unsubscribe(); + } + }); + + th.start(); + connection.subscribe(listener, expectedChannel); + } + + @Test + public void testPubSubWithPatterns() { + final byte[] expectedPattern = "channel*".getBytes(); + final byte[] expectedMessage = "msg".getBytes(); + + MessageListener listener = new MessageListener() { + + @Override + public void onMessage(Message message, byte[] pattern) { + assertArrayEquals(expectedPattern, pattern); + assertArrayEquals(expectedMessage, message.getBody()); + System.out.println("Received message '" + new String(message.getBody()) + "'"); + } + }; + + Thread th = new Thread(new Runnable() { + @Override + public void run() { + // sleep 1 second to let the registration happen + try { + Thread.currentThread().sleep(1000); + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + + // open a new connection + RedisConnection connection2 = getConnectionFactory().getConnection(); + connection2.publish(expectedMessage, "channel1".getBytes()); + connection2.publish(expectedMessage, "channel2".getBytes()); + connection2.close(); + // unsubscribe connection + connection.getSubscription().pUnsubscribe(expectedPattern); + } + }); + + th.start(); + connection.pSubscribe(listener, expectedPattern); + } } \ No newline at end of file diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/connection/jedis/JedisConnectionIntegrationTests.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/connection/jedis/JedisConnectionIntegrationTests.java index 8ff75a8a0..302a94e49 100644 --- a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/connection/jedis/JedisConnectionIntegrationTests.java +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/connection/jedis/JedisConnectionIntegrationTests.java @@ -16,13 +16,9 @@ package org.springframework.data.keyvalue.redis.connection.jedis; -import static org.junit.Assert.*; - import org.junit.Test; import org.springframework.data.keyvalue.redis.SettingsUtils; import org.springframework.data.keyvalue.redis.connection.AbstractConnectionIntegrationTests; -import org.springframework.data.keyvalue.redis.connection.Message; -import org.springframework.data.keyvalue.redis.connection.MessageListener; import org.springframework.data.keyvalue.redis.connection.RedisConnectionFactory; import redis.clients.jedis.BinaryJedis; @@ -47,83 +43,6 @@ public class JedisConnectionIntegrationTests extends AbstractConnectionIntegrati return factory; } - @Test - public void testPubSubWithNamedChannels() { - final byte[] expectedChannel = "channel1".getBytes(); - final byte[] expectedMessage = "msg".getBytes(); - - MessageListener listener = new MessageListener() { - - @Override - public void onMessage(Message message, byte[] pattern) { - assertArrayEquals(expectedChannel, message.getChannel()); - assertArrayEquals(expectedMessage, message.getBody()); - System.out.println("Received message '" + new String(message.getBody()) + "'"); - } - }; - - Thread th = new Thread(new Runnable() { - @Override - public void run() { - // sleep 1 second to let the registration happen - try { - Thread.currentThread().sleep(1000); - } catch (InterruptedException ex) { - throw new RuntimeException(ex); - } - - // open a new connection - JedisConnection connection2 = factory.getConnection(); - connection2.publish(expectedMessage, expectedChannel); - connection2.close(); - // unsubscribe connection - connection.getSubscription().unsubscribe(); - } - }); - - th.start(); - connection.subscribe(listener, expectedChannel); - } - - @Test - public void testPubSubWithPatterns() { - final byte[] expectedPattern = "channel*".getBytes(); - final byte[] expectedMessage = "msg".getBytes(); - - MessageListener listener = new MessageListener() { - - @Override - public void onMessage(Message message, byte[] pattern) { - assertArrayEquals(expectedPattern, pattern); - assertArrayEquals(expectedMessage, message.getBody()); - System.out.println("Received message '" + new String(message.getBody()) + "'"); - } - }; - - Thread th = new Thread(new Runnable() { - @Override - public void run() { - // sleep 1 second to let the registration happen - try { - Thread.currentThread().sleep(1000); - } catch (InterruptedException ex) { - throw new RuntimeException(ex); - } - - // open a new connection - JedisConnection connection2 = factory.getConnection(); - connection2.publish(expectedMessage, "channel1".getBytes()); - connection2.publish(expectedMessage, "channel2".getBytes()); - connection2.close(); - // unsubscribe connection - connection.getSubscription().pUnsubscribe(expectedPattern); - } - }); - - th.start(); - connection.pSubscribe(listener, expectedPattern); - } - @Test public void testMulti() throws Exception { byte[] key = "key".getBytes(); diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/connection/jredis/JRedisConnectionIntegrationTests.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/connection/jredis/JRedisConnectionIntegrationTests.java index 91263aabd..ed92f9f63 100644 --- a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/connection/jredis/JRedisConnectionIntegrationTests.java +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/connection/jredis/JRedisConnectionIntegrationTests.java @@ -17,6 +17,7 @@ package org.springframework.data.keyvalue.redis.connection.jredis; import org.jredis.JRedis; +import org.junit.Ignore; import org.junit.Test; import org.springframework.data.keyvalue.redis.SettingsUtils; import org.springframework.data.keyvalue.redis.connection.AbstractConnectionIntegrationTests; @@ -43,10 +44,47 @@ public class JRedisConnectionIntegrationTests extends AbstractConnectionIntegrat @Test public void testRaw() throws Exception { JRedis jr = (JRedis) factory.getConnection().getNativeConnection(); - + System.out.println(jr.dbsize()); System.out.println(jr.exists("foobar")); jr.set("foobar", "barfoo"); System.out.println(jr.get("foobar")); } -} + + @Ignore("JRedis does not support pipelining") + public void testNullCollections() { + } + + @Ignore + public void testNullKey() throws Exception { + } + + @Ignore + public void testNullValue() throws Exception { + } + + @Ignore + public void testHashNullKey() throws Exception { + } + + @Ignore + public void testHashNullValue() throws Exception { + } + + @Ignore + public void testNullSerialization() throws Exception { + } + + @Ignore + public void testPubSub() throws Exception { + } + + @Ignore + public void testPubSubWithPatterns() { + } + + @Ignore + public void testPubSubWithNamedChannels() { + + } +} \ No newline at end of file diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/connection/rjc/RjcConnectionIntegrationTests.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/connection/rjc/RjcConnectionIntegrationTests.java new file mode 100644 index 000000000..4fa5b3ed8 --- /dev/null +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/connection/rjc/RjcConnectionIntegrationTests.java @@ -0,0 +1,54 @@ +/* + * 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.keyvalue.redis.connection.rjc; + +import org.idevlab.rjc.Session; +import org.junit.Test; +import org.springframework.data.keyvalue.redis.SettingsUtils; +import org.springframework.data.keyvalue.redis.connection.AbstractConnectionIntegrationTests; +import org.springframework.data.keyvalue.redis.connection.RedisConnectionFactory; + +/** + * @author Costin Leau + */ +public class RjcConnectionIntegrationTests extends AbstractConnectionIntegrationTests { + + RjcConnectionFactory factory; + + public RjcConnectionIntegrationTests() { + factory = new RjcConnectionFactory(); + factory.setPort(SettingsUtils.getPort()); + factory.setHostName(SettingsUtils.getHost()); + + factory.setUsePool(false); + factory.afterPropertiesSet(); + } + + @Override + protected RedisConnectionFactory getConnectionFactory() { + return factory; + } + + @Test + public void testRaw() throws Exception { + Session jr = (Session) factory.getConnection().getNativeConnection(); + + System.out.println(jr.dbSize()); + System.out.println(jr.exists("foobar")); + jr.set("foobar", "barfoo"); + System.out.println(jr.get("foobar")); + } +} diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/core/SessionTest.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/core/SessionTest.java index eb9e6c559..f550facfb 100644 --- a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/core/SessionTest.java +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/core/SessionTest.java @@ -36,7 +36,7 @@ public class SessionTest { when(factory.getConnection()).thenReturn(conn); final StringRedisTemplate template = new StringRedisTemplate(factory); - template.execute(new SessionCallback() { + template.execute(new SessionCallback() { @Override public Object execute(RedisOperations operations) { checkConnection(template, conn); @@ -48,7 +48,7 @@ public class SessionTest { }); } - private void checkConnection(RedisTemplate template, final RedisConnection expectedConnection) { + private void checkConnection(RedisTemplate template, final RedisConnection expectedConnection) { template.execute(new RedisCallback() { @Override diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/core/TemplateTest.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/core/TemplateTest.java new file mode 100644 index 000000000..96cf1d05c --- /dev/null +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/core/TemplateTest.java @@ -0,0 +1,59 @@ +/* + * 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.keyvalue.redis.core; + +import static org.junit.Assert.*; + +import java.util.Collection; + +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.springframework.data.keyvalue.redis.ConnectionFactoryTracker; +import org.springframework.data.keyvalue.redis.support.collections.CollectionTestParams; +import org.springframework.data.keyvalue.redis.support.collections.ObjectFactory; + +/** + * @author Costin Leau + */ +@RunWith(Parameterized.class) +public class TemplateTest { + private ObjectFactory objFactory; + private RedisTemplate template; + + public TemplateTest(ObjectFactory objFactory, RedisTemplate template) { + this.objFactory = objFactory; + this.template = template; + ConnectionFactoryTracker.add(template.getConnectionFactory()); + } + + @AfterClass + public static void cleanUp() { + ConnectionFactoryTracker.cleanUp(); + } + + @Parameters + public static Collection testParams() { + return CollectionTestParams.testParams(); + } + + @Test + public void testKeys() throws Exception { + assertTrue(template.keys("*") != null); + } +} diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/listener/PubSubTestParams.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/listener/PubSubTestParams.java index ee7dd80bb..cba742c7f 100644 --- a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/listener/PubSubTestParams.java +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/listener/PubSubTestParams.java @@ -21,6 +21,7 @@ import java.util.Collection; import org.springframework.data.keyvalue.redis.Person; import org.springframework.data.keyvalue.redis.SettingsUtils; import org.springframework.data.keyvalue.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.keyvalue.redis.connection.rjc.RjcConnectionFactory; import org.springframework.data.keyvalue.redis.core.RedisTemplate; import org.springframework.data.keyvalue.redis.core.StringRedisTemplate; import org.springframework.data.keyvalue.redis.support.collections.ObjectFactory; @@ -38,16 +39,30 @@ public class PubSubTestParams { ObjectFactory personFactory = new PersonObjectFactory(); JedisConnectionFactory jedisConnFactory = new JedisConnectionFactory(); - jedisConnFactory.setUsePool(false); + jedisConnFactory.setUsePool(true); jedisConnFactory.setPort(SettingsUtils.getPort()); jedisConnFactory.setHostName(SettingsUtils.getHost()); + jedisConnFactory.setDatabase(2); jedisConnFactory.afterPropertiesSet(); RedisTemplate stringTemplate = new StringRedisTemplate(jedisConnFactory); RedisTemplate personTemplate = new RedisTemplate(jedisConnFactory); + // create RJC - return Arrays.asList(new Object[][] { { stringFactory, stringTemplate }, { personFactory, personTemplate } }); + RjcConnectionFactory rjcConnFactory = new RjcConnectionFactory(); + rjcConnFactory.setUsePool(false); + rjcConnFactory.setPort(SettingsUtils.getPort()); + rjcConnFactory.setHostName(SettingsUtils.getHost()); + rjcConnFactory.afterPropertiesSet(); + + RedisTemplate stringTemplateRJC = new StringRedisTemplate(rjcConnFactory); + RedisTemplate personTemplateRJC = new RedisTemplate(rjcConnFactory); + + + return Arrays.asList(new Object[][] { { stringFactory, stringTemplate }, { personFactory, personTemplate }, + { stringFactory, stringTemplateRJC }, { personFactory, personTemplateRJC } + }); } } diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/listener/PubSubTests.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/listener/PubSubTests.java index daad32b5d..a61f923f0 100644 --- a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/listener/PubSubTests.java +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/listener/PubSubTests.java @@ -32,8 +32,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.data.keyvalue.redis.connection.RedisConnectionFactory; +import org.springframework.data.keyvalue.redis.ConnectionFactoryTracker; import org.springframework.data.keyvalue.redis.core.RedisTemplate; import org.springframework.data.keyvalue.redis.listener.adapter.MessageListenerAdapter; import org.springframework.data.keyvalue.redis.support.collections.ObjectFactory; @@ -51,13 +50,11 @@ public class PubSubTests { protected RedisMessageListenerContainer container; protected ObjectFactory factory; protected RedisTemplate template; - private static Set connFactories = new LinkedHashSet(); private final BlockingDeque bag = new LinkedBlockingDeque(99); private final Object handler = new Object() { void handleMessage(String message) { - System.out.println("Received message " + message); bag.add(message); } }; @@ -74,7 +71,7 @@ public class PubSubTests { container.addMessageListener(adapter, Arrays.asList(new ChannelTopic(CHANNEL))); container.afterPropertiesSet(); - Thread.sleep(500); + Thread.sleep(1000); } @After @@ -85,21 +82,12 @@ public class PubSubTests { public PubSubTests(ObjectFactory factory, RedisTemplate template) { this.factory = factory; this.template = template; - connFactories.add(template.getConnectionFactory()); + ConnectionFactoryTracker.add(template.getConnectionFactory()); } @AfterClass public static void cleanUp() { - if (connFactories != null) { - for (RedisConnectionFactory connectionFactory : connFactories) { - try { - ((DisposableBean) connectionFactory).destroy(); - System.out.println("Succesfully cleaned up factory " + connectionFactory); - } catch (Exception ex) { - System.err.println("Cannot clean factory " + connectionFactory + ex); - } - } - } + ConnectionFactoryTracker.cleanUp(); } @Parameters @@ -127,6 +115,8 @@ public class PubSubTests { set.add(bag.poll(1, TimeUnit.SECONDS)); set.add(bag.poll(1, TimeUnit.SECONDS)); + System.out.println(set); + assertTrue(set.contains(payload1)); assertTrue(set.contains(payload2)); } diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/listener/adapter/MessageListenerTest.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/listener/adapter/MessageListenerTest.java index b7e8a5906..4fec13737 100644 --- a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/listener/adapter/MessageListenerTest.java +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/listener/adapter/MessageListenerTest.java @@ -25,8 +25,8 @@ import org.mockito.MockitoAnnotations; import org.springframework.data.keyvalue.redis.connection.DefaultMessage; import org.springframework.data.keyvalue.redis.connection.Message; import org.springframework.data.keyvalue.redis.connection.MessageListener; -import org.springframework.data.keyvalue.redis.serializer.JdkSerializationRedisSerializer; import org.springframework.data.keyvalue.redis.serializer.RedisSerializer; +import org.springframework.data.keyvalue.redis.serializer.StringRedisSerializer; /** * Unit test for MessageListenerAdapter. @@ -35,16 +35,16 @@ import org.springframework.data.keyvalue.redis.serializer.RedisSerializer; */ public class MessageListenerTest { - private static final RedisSerializer serializer = new JdkSerializationRedisSerializer(); + private static final RedisSerializer serializer = new StringRedisSerializer(); private static final String CHANNEL = "some::test:"; private static final byte[] RAW_CHANNEL = serializer.serialize(CHANNEL); private static final String PAYLOAD = "do re mi"; private static final byte[] RAW_PAYLOAD = serializer.serialize(PAYLOAD); - private static final Message STRING_MSG = new DefaultMessage(RAW_PAYLOAD, RAW_CHANNEL); + private static final Message STRING_MSG = new DefaultMessage(RAW_CHANNEL, RAW_PAYLOAD); private MessageListenerAdapter adapter; - interface Delegate { + public static interface Delegate { void handleMessage(String argument); void customMethod(String arg); @@ -76,7 +76,6 @@ public class MessageListenerTest { MessageListenerAdapter adapter = new MessageListenerAdapter(mock); adapter.onMessage(STRING_MSG, null); - verify(mock).onMessage(STRING_MSG, null); } diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/BoundKeyOperationsTest.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/BoundKeyOperationsTest.java new file mode 100644 index 000000000..a7626125f --- /dev/null +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/BoundKeyOperationsTest.java @@ -0,0 +1,94 @@ +/* + * 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.keyvalue.redis.support; + +import static org.junit.Assert.*; + +import java.util.Collection; +import java.util.concurrent.TimeUnit; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.springframework.data.keyvalue.redis.ConnectionFactoryTracker; +import org.springframework.data.keyvalue.redis.core.BoundKeyOperations; +import org.springframework.data.keyvalue.redis.core.RedisTemplate; +import org.springframework.data.keyvalue.redis.support.collections.ObjectFactory; + +/** + * @author Costin Leau + */ +@RunWith(Parameterized.class) +public class BoundKeyOperationsTest { + private BoundKeyOperations keyOps; + private ObjectFactory objFactory; + private RedisTemplate template; + + public BoundKeyOperationsTest(BoundKeyOperations keyOps, ObjectFactory objFactory, + RedisTemplate template) { + this.objFactory = objFactory; + this.keyOps = keyOps; + this.template = template; + ConnectionFactoryTracker.add(template.getConnectionFactory()); + } + + @After + public void stop() { + } + + @AfterClass + public static void cleanUp() { + ConnectionFactoryTracker.cleanUp(); + } + + @Parameters + public static Collection testParams() { + return BoundKeyParams.testParams(); + } + + @Test + public void testRename() throws Exception { + Object key = keyOps.getKey(); + assertNotNull(key); + Object newName = objFactory.instance(); + keyOps.rename(newName); + assertEquals(newName, keyOps.getKey()); + keyOps.rename(key); + assertEquals(key, keyOps.getKey()); + } + @Test + public void testExpire() throws Exception { + assertEquals(Long.valueOf(-1), keyOps.getExpire()); + if (keyOps.expire(10, TimeUnit.SECONDS)) { + long expire = keyOps.getExpire().longValue(); + assertTrue(expire <= 10 && expire > 5); + } + } + + @Test + public void testPersist() throws Exception { + keyOps.persist(); + assertEquals(Long.valueOf(-1), keyOps.getExpire()); + if (keyOps.expire(10, TimeUnit.SECONDS)) { + assertTrue(keyOps.getExpire().longValue() > 0); + } + keyOps.persist(); + assertEquals(-1, keyOps.getExpire().longValue()); + } +} \ No newline at end of file diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/BoundKeyParams.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/BoundKeyParams.java new file mode 100644 index 000000000..df2156509 --- /dev/null +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/BoundKeyParams.java @@ -0,0 +1,68 @@ +/* + * 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.keyvalue.redis.support; + +import java.util.Arrays; +import java.util.Collection; + +import org.springframework.data.keyvalue.redis.SettingsUtils; +import org.springframework.data.keyvalue.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.keyvalue.redis.connection.jredis.JredisConnectionFactory; +import org.springframework.data.keyvalue.redis.core.StringRedisTemplate; +import org.springframework.data.keyvalue.redis.support.atomic.RedisAtomicInteger; +import org.springframework.data.keyvalue.redis.support.atomic.RedisAtomicLong; +import org.springframework.data.keyvalue.redis.support.collections.DefaultRedisList; +import org.springframework.data.keyvalue.redis.support.collections.DefaultRedisMap; +import org.springframework.data.keyvalue.redis.support.collections.DefaultRedisSet; +import org.springframework.data.keyvalue.redis.support.collections.RedisList; +import org.springframework.data.keyvalue.redis.support.collections.StringObjectFactory; + +/** + * @author Costin Leau + */ +public class BoundKeyParams { + + public static Collection testParams() { + // create Jedis Factory + JedisConnectionFactory jedisConnFactory = new JedisConnectionFactory(); + jedisConnFactory.setPort(SettingsUtils.getPort()); + jedisConnFactory.setHostName(SettingsUtils.getHost()); + jedisConnFactory.afterPropertiesSet(); + + // jredis factory + JredisConnectionFactory jredisConnFactory = new JredisConnectionFactory(); + jredisConnFactory.setUsePool(true); + jredisConnFactory.setPort(SettingsUtils.getPort()); + jredisConnFactory.setHostName(SettingsUtils.getHost()); + jredisConnFactory.afterPropertiesSet(); + + StringRedisTemplate templateJS = new StringRedisTemplate(jedisConnFactory); + StringRedisTemplate templateJR = new StringRedisTemplate(jredisConnFactory); + + StringObjectFactory sof = new StringObjectFactory(); + + DefaultRedisMap mapJS = new DefaultRedisMap("bound:key:map", templateJS); + + DefaultRedisSet setJS = new DefaultRedisSet("bound:key:set", templateJS); + + RedisList list = new DefaultRedisList("bound:key:list", templateJS); + + return Arrays.asList(new Object[][] { + { new RedisAtomicInteger("bound:key:int", jedisConnFactory), sof, templateJS }, + { new RedisAtomicLong("bound:key:long", jedisConnFactory), sof, templateJS }, + { list, sof, templateJS }, { setJS, sof, templateJS }, { mapJS, sof, templateJS } }); + } +} diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/atomic/RedisAtomicTests.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/atomic/RedisAtomicTests.java index 306f7dff9..d69d19add 100644 --- a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/atomic/RedisAtomicTests.java +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/atomic/RedisAtomicTests.java @@ -44,6 +44,7 @@ public class RedisAtomicTests { intCounter = new RedisAtomicInteger(getClass().getSimpleName() + ":int", factory); longCounter = new RedisAtomicLong(getClass().getSimpleName() + ":long", factory); this.factory = factory; + ConnectionFactoryTracker.add(factory); } @After @@ -104,4 +105,11 @@ public class RedisAtomicTests { int delta = 5; assertEquals(delta, intCounter.addAndGet(delta)); } + + @Test + public void testReadExistingValue() throws Exception { + longCounter.set(5); + RedisAtomicLong keyCopy = new RedisAtomicLong(longCounter.getKey(), factory); + assertEquals(longCounter.get(), keyCopy.get()); + } } \ No newline at end of file diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/AbstractRedisMapTests.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/AbstractRedisMapTests.java index 1218c2e68..f9d266355 100644 --- a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/AbstractRedisMapTests.java +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/AbstractRedisMapTests.java @@ -162,7 +162,7 @@ public abstract class AbstractRedisMapTests { K k1 = getKey(); V v1 = getValue(); - assertNull(map.get(UUID.randomUUID())); + assertNull(map.get(UUID.randomUUID().toString())); assertNull(map.get(k1)); map.put(k1, v1); assertEquals(v1, map.get(k1)); diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/AbstractRedisZSetTest.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/AbstractRedisZSetTest.java index 9aae0fde4..6a96f001c 100644 --- a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/AbstractRedisZSetTest.java +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/AbstractRedisZSetTest.java @@ -141,7 +141,7 @@ public abstract class AbstractRedisZSetTest extends AbstractRedisCollectionTe assertEquals(Long.valueOf(0), zSet.rank(t1)); assertEquals(Long.valueOf(1), zSet.rank(t2)); assertEquals(Long.valueOf(2), zSet.rank(t3)); - System.out.println(zSet.rank(getT())); + assertNull(zSet.rank(getT())); //assertNull(); } diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/CollectionTestParams.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/CollectionTestParams.java index e3e5c0d73..e320a974e 100644 --- a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/CollectionTestParams.java +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/CollectionTestParams.java @@ -22,6 +22,7 @@ import org.springframework.data.keyvalue.redis.Person; import org.springframework.data.keyvalue.redis.SettingsUtils; import org.springframework.data.keyvalue.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.keyvalue.redis.connection.jredis.JredisConnectionFactory; +import org.springframework.data.keyvalue.redis.connection.rjc.RjcConnectionFactory; import org.springframework.data.keyvalue.redis.core.RedisTemplate; import org.springframework.data.keyvalue.redis.serializer.JacksonJsonRedisSerializer; import org.springframework.data.keyvalue.redis.serializer.OxmSerializer; @@ -70,6 +71,7 @@ public abstract class CollectionTestParams { RedisTemplate jsonPersonTemplate = new RedisTemplate(jedisConnFactory); jsonPersonTemplate.setValueSerializer(jsonSerializer); + // jredis JredisConnectionFactory jredisConnFactory = new JredisConnectionFactory(); jredisConnFactory.setUsePool(true); @@ -88,15 +90,42 @@ public abstract class CollectionTestParams { RedisTemplate xstreamPersonTemplateJR = new RedisTemplate(jredisConnFactory); xstreamPersonTemplateJR.setValueSerializer(serializer); - - // json JR RedisTemplate jsonPersonTemplateJR = new RedisTemplate(jredisConnFactory); jsonPersonTemplate.setValueSerializer(jsonSerializer); - return Arrays.asList(new Object[][] { { stringFactory, stringTemplateJR }, { personFactory, personTemplateJR }, - { stringFactory, stringTemplate }, { personFactory, personTemplate }, - { stringFactory, xstreamStringTemplate }, { personFactory, xstreamPersonTemplate }, - { stringFactory, xstreamStringTemplateJR }, { personFactory, xstreamPersonTemplateJR }, - { personFactory, jsonPersonTemplate }, { personFactory, jsonPersonTemplateJR } }); + + // rjc + RjcConnectionFactory rjcConnFactory = new RjcConnectionFactory(); + rjcConnFactory.setUsePool(true); + rjcConnFactory.setPort(SettingsUtils.getPort()); + rjcConnFactory.setHostName(SettingsUtils.getHost()); + rjcConnFactory.afterPropertiesSet(); + + RedisTemplate stringTemplateRJC = new RedisTemplate(rjcConnFactory); + RedisTemplate personTemplateRJC = new RedisTemplate(rjcConnFactory); + + RedisTemplate xstreamStringTemplateRJC = new RedisTemplate(); + xstreamStringTemplateRJC.setConnectionFactory(rjcConnFactory); + xstreamStringTemplateRJC.setDefaultSerializer(serializer); + xstreamStringTemplateRJC.afterPropertiesSet(); + + RedisTemplate xstreamPersonTemplateRJC = new RedisTemplate(); + xstreamPersonTemplateRJC.setValueSerializer(serializer); + xstreamPersonTemplateRJC.setConnectionFactory(rjcConnFactory); + xstreamPersonTemplateRJC.afterPropertiesSet(); + + RedisTemplate jsonPersonTemplateRJC = new RedisTemplate(); + jsonPersonTemplateRJC.setValueSerializer(jsonSerializer); + jsonPersonTemplateRJC.setConnectionFactory(rjcConnFactory); + jsonPersonTemplateRJC.afterPropertiesSet(); + + return Arrays.asList(new Object[][] { { stringFactory, stringTemplateRJC }, + { personFactory, personTemplateRJC }, { stringFactory, stringTemplateJR }, + { personFactory, personTemplateJR }, { stringFactory, stringTemplate }, + { personFactory, personTemplate }, { stringFactory, xstreamStringTemplate }, + { personFactory, xstreamPersonTemplate }, { stringFactory, xstreamStringTemplateJR }, + { personFactory, xstreamPersonTemplateJR }, { personFactory, jsonPersonTemplate }, + { personFactory, jsonPersonTemplateJR }, { stringFactory, xstreamStringTemplateRJC }, + { personFactory, xstreamPersonTemplateRJC }, { personFactory, jsonPersonTemplateRJC } }); } } diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisCollectionFactoryBeanTests.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisCollectionFactoryBeanTests.java new file mode 100644 index 000000000..3f6742d40 --- /dev/null +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisCollectionFactoryBeanTests.java @@ -0,0 +1,123 @@ +/* + * 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.keyvalue.redis.support.collections; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Test; +import org.springframework.data.keyvalue.redis.ConnectionFactoryTracker; +import org.springframework.data.keyvalue.redis.SettingsUtils; +import org.springframework.data.keyvalue.redis.connection.RedisConnection; +import org.springframework.data.keyvalue.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.keyvalue.redis.core.RedisCallback; +import org.springframework.data.keyvalue.redis.core.StringRedisTemplate; +import org.springframework.data.keyvalue.redis.support.collections.RedisCollectionFactoryBean.CollectionType; + +/** + * @author Costin Leau + */ +public class RedisCollectionFactoryBeanTests { + + protected ObjectFactory factory = new StringObjectFactory(); + protected StringRedisTemplate template; + protected RedisStore col; + + public RedisCollectionFactoryBeanTests() { + JedisConnectionFactory jedisConnFactory = new JedisConnectionFactory(); + jedisConnFactory.setUsePool(true); + + jedisConnFactory.setPort(SettingsUtils.getPort()); + jedisConnFactory.setHostName(SettingsUtils.getHost()); + + jedisConnFactory.afterPropertiesSet(); + + this.template = new StringRedisTemplate(jedisConnFactory); + ConnectionFactoryTracker.add(jedisConnFactory); + } + + @AfterClass + public static void cleanUp() { + ConnectionFactoryTracker.cleanUp(); + } + + @After + public void tearDown() throws Exception { + // clean up the whole db + template.execute(new RedisCallback() { + + @Override + public Object doInRedis(RedisConnection connection) { + connection.flushDb(); + return null; + } + }); + } + + private RedisStore createCollection(String key) { + return createCollection(key, null); + } + + private RedisStore createCollection(String key, CollectionType type) { + RedisCollectionFactoryBean fb = new RedisCollectionFactoryBean(); + fb.setKey(key); + fb.setTemplate(template); + fb.setType(type); + fb.afterPropertiesSet(); + + return fb.getObject(); + } + + @Test + public void testNone() throws Exception { + RedisStore store = createCollection("nosrt", CollectionType.PROPERTIES); + assertThat(store, instanceOf(RedisProperties.class)); + + store = createCollection("nosrt", CollectionType.MAP); + assertThat(store, instanceOf(DefaultRedisMap.class)); + + store = createCollection("nosrt", CollectionType.SET); + assertThat(store, instanceOf(DefaultRedisSet.class)); + + store = createCollection("nosrt", CollectionType.LIST); + assertThat(store, instanceOf(DefaultRedisList.class)); + + store = createCollection("nosrt"); + assertThat(store, instanceOf(DefaultRedisList.class)); + } + + + @Test + public void testExistingCol() throws Exception { + String key = "set"; + String val = "value"; + + template.boundSetOps(key).add(val); + RedisStore col = createCollection(key); + assertThat(col, is(DefaultRedisSet.class)); + + key = "map"; + template.boundHashOps(key).put(val, val); + col = createCollection(key); + assertThat(col, is(DefaultRedisMap.class)); + + col = createCollection(key, CollectionType.PROPERTIES); + assertThat(col, is(RedisProperties.class)); + + } +} \ No newline at end of file diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisMapTests.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisMapTests.java index 462b0669f..12efc9fc9 100644 --- a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisMapTests.java +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisMapTests.java @@ -23,6 +23,7 @@ import org.springframework.data.keyvalue.redis.Person; import org.springframework.data.keyvalue.redis.SettingsUtils; import org.springframework.data.keyvalue.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.keyvalue.redis.connection.jredis.JredisConnectionFactory; +import org.springframework.data.keyvalue.redis.connection.rjc.RjcConnectionFactory; import org.springframework.data.keyvalue.redis.core.RedisTemplate; import org.springframework.data.keyvalue.redis.serializer.JacksonJsonRedisSerializer; import org.springframework.data.keyvalue.redis.serializer.OxmSerializer; @@ -77,7 +78,6 @@ public class RedisMapTests extends AbstractRedisMapTests { xstreamGenericTemplate.setDefaultSerializer(serializer); xstreamGenericTemplate.afterPropertiesSet(); - // json RedisTemplate jsonPersonTemplate = new RedisTemplate(); jsonPersonTemplate.setConnectionFactory(jedisConnFactory); jsonPersonTemplate.setDefaultSerializer(jsonSerializer); @@ -85,34 +85,49 @@ public class RedisMapTests extends AbstractRedisMapTests { jsonPersonTemplate.setHashValueSerializer(jsonStringSerializer); jsonPersonTemplate.afterPropertiesSet(); - + // JRedis JredisConnectionFactory jredisConnFactory = new JredisConnectionFactory(); jredisConnFactory.setUsePool(true); - jredisConnFactory.setPort(SettingsUtils.getPort()); jredisConnFactory.setHostName(SettingsUtils.getHost()); - - jredisConnFactory.afterPropertiesSet(); RedisTemplate genericTemplateJR = new RedisTemplate(jredisConnFactory); - RedisTemplate xGenericTemplateJR = new RedisTemplate(); xGenericTemplateJR.setConnectionFactory(jredisConnFactory); xGenericTemplateJR.setDefaultSerializer(serializer); xGenericTemplateJR.afterPropertiesSet(); - RedisTemplate xstreamPersonTemplateJR = new RedisTemplate(jredisConnFactory); - xstreamPersonTemplateJR.setValueSerializer(serializer); - - // json JR RedisTemplate jsonPersonTemplateJR = new RedisTemplate(); jsonPersonTemplateJR.setConnectionFactory(jredisConnFactory); jsonPersonTemplateJR.setDefaultSerializer(jsonSerializer); jsonPersonTemplateJR.setHashKeySerializer(jsonSerializer); jsonPersonTemplateJR.setHashValueSerializer(jsonStringSerializer); jsonPersonTemplateJR.afterPropertiesSet(); - + + // RJC + + // rjc + RjcConnectionFactory rjcConnFactory = new RjcConnectionFactory(); + rjcConnFactory.setUsePool(true); + rjcConnFactory.setPort(SettingsUtils.getPort()); + rjcConnFactory.setHostName(SettingsUtils.getHost()); + rjcConnFactory.afterPropertiesSet(); + + RedisTemplate genericTemplateRJC = new RedisTemplate(jredisConnFactory); + RedisTemplate xGenericTemplateRJC = new RedisTemplate(); + xGenericTemplateRJC.setConnectionFactory(rjcConnFactory); + xGenericTemplateRJC.setDefaultSerializer(serializer); + xGenericTemplateRJC.afterPropertiesSet(); + + RedisTemplate jsonPersonTemplateRJC = new RedisTemplate(); + jsonPersonTemplateRJC.setConnectionFactory(rjcConnFactory); + jsonPersonTemplateRJC.setDefaultSerializer(jsonSerializer); + jsonPersonTemplateRJC.setHashKeySerializer(jsonSerializer); + jsonPersonTemplateRJC.setHashValueSerializer(jsonStringSerializer); + jsonPersonTemplateRJC.afterPropertiesSet(); + + return Arrays.asList(new Object[][] { { stringFactory, stringFactory, genericTemplate }, { personFactory, personFactory, genericTemplate }, { stringFactory, personFactory, genericTemplate }, { personFactory, stringFactory, genericTemplate }, @@ -123,6 +138,12 @@ public class RedisMapTests extends AbstractRedisMapTests { { personFactory, stringFactory, genericTemplateJR }, { personFactory, stringFactory, xGenericTemplateJR }, { personFactory, stringFactory, jsonPersonTemplate }, - { personFactory, stringFactory, jsonPersonTemplateJR } }); + { personFactory, stringFactory, jsonPersonTemplateJR }, + { stringFactory, stringFactory, genericTemplateRJC }, + { personFactory, personFactory, genericTemplateRJC }, + { stringFactory, personFactory, genericTemplateRJC }, + { personFactory, stringFactory, genericTemplateRJC }, + { personFactory, stringFactory, xGenericTemplateRJC }, + { personFactory, stringFactory, jsonPersonTemplateRJC } }); } } \ No newline at end of file diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisPropertiesTests.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisPropertiesTests.java new file mode 100644 index 000000000..8053fde02 --- /dev/null +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisPropertiesTests.java @@ -0,0 +1,311 @@ +/* + * 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.keyvalue.redis.support.collections; + +import static org.junit.Assert.*; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.LinkedHashSet; +import java.util.Properties; +import java.util.Set; + +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runners.Parameterized.Parameters; +import org.springframework.data.keyvalue.redis.Person; +import org.springframework.data.keyvalue.redis.SettingsUtils; +import org.springframework.data.keyvalue.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.keyvalue.redis.connection.jredis.JredisConnectionFactory; +import org.springframework.data.keyvalue.redis.connection.rjc.RjcConnectionFactory; +import org.springframework.data.keyvalue.redis.core.RedisTemplate; +import org.springframework.data.keyvalue.redis.core.StringRedisTemplate; +import org.springframework.data.keyvalue.redis.serializer.JacksonJsonRedisSerializer; +import org.springframework.data.keyvalue.redis.serializer.OxmSerializer; +import org.springframework.oxm.xstream.XStreamMarshaller; + +/** + * @author Costin Leau + */ +public class RedisPropertiesTests extends RedisMapTests { + + protected Properties defaults = new Properties(); + protected RedisProperties props; + + /** + * Constructs a new RedisPropertiesTests instance. + * + * @param keyFactory + * @param valueFactory + * @param template + */ + public RedisPropertiesTests(ObjectFactory keyFactory, ObjectFactory valueFactory, + RedisTemplate template) { + super(keyFactory, valueFactory, template); + } + + @Override + RedisMap createMap() { + String redisName = getClass().getSimpleName(); + props = new RedisProperties(defaults, redisName, new StringRedisTemplate(template.getConnectionFactory())); + return props; + } + + @Override + protected RedisStore copyStore(RedisStore store) { + return new RedisProperties(store.getKey(), store.getOperations()); + } + + @Test + public void testGetOperations() { + assertTrue(map.getOperations() instanceof StringRedisTemplate); + } + + @Test + public void testPropertiesLoad() throws Exception { + InputStream stream = getClass().getResourceAsStream( + "/org/springframework/data/keyvalue/redis/support/collections/props.properties"); + + assertNotNull(stream); + + int size = props.size(); + + try { + props.load(stream); + } finally { + stream.close(); + } + + assertEquals("bar", props.get("foo")); + assertEquals("head", props.get("bucket")); + assertEquals("island", props.get("lotus")); + assertEquals(size + 3, props.size()); + } + + @Test + @Ignore + public void testPropertiesLoadXml() throws Exception { + InputStream stream = getClass().getResourceAsStream( + "/org/springframework/data/keyvalue/redis/support/collections/props.properties"); + + assertNotNull(stream); + + int size = props.size(); + + try { + props.loadFromXML(stream); + } finally { + stream.close(); + } + + assertEquals("bar", props.get("foo")); + assertEquals("head", props.get("bucket")); + assertEquals("island", props.get("lotus")); + assertEquals(size + 3, props.size()); + } + + @Test + public void testPropertiesSave() throws Exception { + props.setProperty("x", "y"); + props.setProperty("a", "b"); + + StringWriter writer = new StringWriter(); + props.store(writer, "no-comment"); + //System.out.println(writer.toString()); + } + + @Test + @Ignore + public void testPropertiesSaveXml() throws Exception { + props.setProperty("x", "y"); + props.setProperty("a", "b"); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + props.storeToXML(bos, "comment"); + System.out.println(bos.toString()); + } + + @Test + public void testGetProperty() throws Exception { + String property = props.getProperty("a"); + assertNull(property); + defaults.put("a", "x"); + assertEquals("x", props.getProperty("a")); + } + + @Test + public void testGetPropertyDefault() throws Exception { + assertEquals("x", props.getProperty("a", "x")); + } + + @Test + public void testSetProperty() throws Exception { + assertNull(props.getProperty("a")); + defaults.setProperty("a", "x"); + assertEquals("x", props.getProperty("a")); + } + + @Test + public void testPropertiesList() throws Exception { + defaults.setProperty("a", "b"); + props.setProperty("x", "y"); + StringWriter wr = new StringWriter(); + props.list(new PrintWriter(wr)); + } + + @Test + public void testPropertyNames() throws Exception { + String key1="foo"; + String key2="x"; + String key3 = "d"; + + String val ="o"; + + defaults.setProperty(key3, val); + props.setProperty(key1, val); + props.setProperty(key2, val); + + Enumeration names = props.propertyNames(); + Set keys = new LinkedHashSet(); + keys.add(names.nextElement()); + keys.add(names.nextElement()); + keys.add(names.nextElement()); + + assertFalse(names.hasMoreElements()); + } + + @Test + public void testStringPropertyNames() throws Exception { + String key1 = "foo"; + String key2 = "x"; + String key3 = "d"; + + String val = "o"; + + defaults.setProperty(key3, val); + props.setProperty(key1, val); + props.setProperty(key2, val); + + Set keys = props.stringPropertyNames(); + assertTrue(keys.contains(key1)); + assertTrue(keys.contains(key2)); + assertTrue(keys.contains(key3)); + } + + @Parameters + public static Collection testParams() { + // XStream serializer + XStreamMarshaller xstream = new XStreamMarshaller(); + try { + xstream.afterPropertiesSet(); + } catch (Exception ex) { + throw new RuntimeException("Cannot init XStream", ex); + } + OxmSerializer serializer = new OxmSerializer(xstream, xstream); + JacksonJsonRedisSerializer jsonSerializer = new JacksonJsonRedisSerializer(Person.class); + JacksonJsonRedisSerializer jsonStringSerializer = new JacksonJsonRedisSerializer(String.class); + + // create Jedis Factory + ObjectFactory stringFactory = new StringObjectFactory(); + + JedisConnectionFactory jedisConnFactory = new JedisConnectionFactory(); + jedisConnFactory.setUsePool(false); + + jedisConnFactory.setPort(SettingsUtils.getPort()); + jedisConnFactory.setHostName(SettingsUtils.getHost()); + + jedisConnFactory.afterPropertiesSet(); + + RedisTemplate genericTemplate = new RedisTemplate(jedisConnFactory); + + RedisTemplate xstreamGenericTemplate = new RedisTemplate(); + xstreamGenericTemplate.setConnectionFactory(jedisConnFactory); + xstreamGenericTemplate.setDefaultSerializer(serializer); + xstreamGenericTemplate.afterPropertiesSet(); + + RedisTemplate jsonPersonTemplate = new RedisTemplate(); + jsonPersonTemplate.setConnectionFactory(jedisConnFactory); + jsonPersonTemplate.setDefaultSerializer(jsonSerializer); + jsonPersonTemplate.setHashKeySerializer(jsonSerializer); + jsonPersonTemplate.setHashValueSerializer(jsonStringSerializer); + jsonPersonTemplate.afterPropertiesSet(); + + // JRedis + JredisConnectionFactory jredisConnFactory = new JredisConnectionFactory(); + jredisConnFactory.setUsePool(true); + jredisConnFactory.setPort(SettingsUtils.getPort()); + jredisConnFactory.setHostName(SettingsUtils.getHost()); + jredisConnFactory.afterPropertiesSet(); + + RedisTemplate genericTemplateJR = new RedisTemplate(jredisConnFactory); + RedisTemplate xGenericTemplateJR = new RedisTemplate(); + xGenericTemplateJR.setConnectionFactory(jredisConnFactory); + xGenericTemplateJR.setDefaultSerializer(serializer); + xGenericTemplateJR.afterPropertiesSet(); + + RedisTemplate jsonPersonTemplateJR = new RedisTemplate(); + jsonPersonTemplateJR.setConnectionFactory(jredisConnFactory); + jsonPersonTemplateJR.setDefaultSerializer(jsonSerializer); + jsonPersonTemplateJR.setHashKeySerializer(jsonSerializer); + jsonPersonTemplateJR.setHashValueSerializer(jsonStringSerializer); + jsonPersonTemplateJR.afterPropertiesSet(); + + // RJC + + // rjc + RjcConnectionFactory rjcConnFactory = new RjcConnectionFactory(); + rjcConnFactory.setUsePool(true); + rjcConnFactory.setPort(SettingsUtils.getPort()); + rjcConnFactory.setHostName(SettingsUtils.getHost()); + rjcConnFactory.afterPropertiesSet(); + + RedisTemplate genericTemplateRJC = new RedisTemplate(jredisConnFactory); + RedisTemplate xGenericTemplateRJC = new RedisTemplate(); + xGenericTemplateRJC.setConnectionFactory(rjcConnFactory); + xGenericTemplateRJC.setDefaultSerializer(serializer); + xGenericTemplateRJC.afterPropertiesSet(); + + RedisTemplate jsonPersonTemplateRJC = new RedisTemplate(); + jsonPersonTemplateRJC.setConnectionFactory(rjcConnFactory); + jsonPersonTemplateRJC.setDefaultSerializer(jsonSerializer); + jsonPersonTemplateRJC.setHashKeySerializer(jsonSerializer); + jsonPersonTemplateRJC.setHashValueSerializer(jsonStringSerializer); + jsonPersonTemplateRJC.afterPropertiesSet(); + + + return Arrays.asList(new Object[][] { { stringFactory, stringFactory, genericTemplate }, + { stringFactory, stringFactory, genericTemplate }, { stringFactory, stringFactory, genericTemplate }, + { stringFactory, stringFactory, genericTemplate }, + { stringFactory, stringFactory, xstreamGenericTemplate }, + { stringFactory, stringFactory, genericTemplateJR }, + { stringFactory, stringFactory, genericTemplateJR }, + { stringFactory, stringFactory, genericTemplateJR }, + { stringFactory, stringFactory, genericTemplateJR }, + { stringFactory, stringFactory, xGenericTemplateJR }, + { stringFactory, stringFactory, jsonPersonTemplate }, + { stringFactory, stringFactory, jsonPersonTemplateJR }, + { stringFactory, stringFactory, genericTemplateRJC }, + { stringFactory, stringFactory, genericTemplateRJC }, + { stringFactory, stringFactory, genericTemplateRJC }, + { stringFactory, stringFactory, genericTemplateRJC }, + { stringFactory, stringFactory, xGenericTemplateRJC }, + { stringFactory, stringFactory, jsonPersonTemplateRJC } }); + } +} \ No newline at end of file diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/SupportXmlTests.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/SupportXmlTests.java new file mode 100644 index 000000000..026074fbd --- /dev/null +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/SupportXmlTests.java @@ -0,0 +1,37 @@ +/* + * 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.keyvalue.redis.support.collections; + +import java.util.Map; + +import org.junit.Test; +import org.springframework.context.support.GenericXmlApplicationContext; + +/** + * @author Costin Leau + */ +public class SupportXmlTests { + + @Test + public void testContainerSetup() throws Exception { + GenericXmlApplicationContext ctx = new GenericXmlApplicationContext( + "/org/springframework/data/keyvalue/redis/support/collections/container.xml"); + + RedisList list = ctx.getBean("non-existing", RedisList.class); + RedisProperties props = ctx.getBean("props", RedisProperties.class); + Map map = ctx.getBean("map", Map.class); + } +} diff --git a/spring-data-redis/src/test/resources/org/springframework/data/keyvalue/redis/support/collections/container.xml b/spring-data-redis/src/test/resources/org/springframework/data/keyvalue/redis/support/collections/container.xml new file mode 100644 index 000000000..410c81422 --- /dev/null +++ b/spring-data-redis/src/test/resources/org/springframework/data/keyvalue/redis/support/collections/container.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/spring-data-redis/src/test/resources/org/springframework/data/keyvalue/redis/support/collections/props.properties b/spring-data-redis/src/test/resources/org/springframework/data/keyvalue/redis/support/collections/props.properties new file mode 100644 index 000000000..aad78142d --- /dev/null +++ b/spring-data-redis/src/test/resources/org/springframework/data/keyvalue/redis/support/collections/props.properties @@ -0,0 +1,4 @@ +# redis connection properties +foo=bar +bucket=head +lotus=island \ No newline at end of file diff --git a/spring-data-redis/src/test/resources/org/springframework/data/keyvalue/redis/support/collections/props.xml b/spring-data-redis/src/test/resources/org/springframework/data/keyvalue/redis/support/collections/props.xml new file mode 100644 index 000000000..2e49de5b7 --- /dev/null +++ b/spring-data-redis/src/test/resources/org/springframework/data/keyvalue/redis/support/collections/props.xml @@ -0,0 +1,6 @@ + +Hi +bar +head +island + \ No newline at end of file diff --git a/spring-data-redis/template.mf b/spring-data-redis/template.mf index 8bfce223f..27a5e02c3 100644 --- a/spring-data-redis/template.mf +++ b/spring-data-redis/template.mf @@ -5,14 +5,14 @@ Bundle-ManifestVersion: 2 Import-Package: sun.reflect;version="0";resolution:=optional Import-Template: - org.springframework.beans.*;version="[3.0.0, 4.0.0)", - org.springframework.context.*;version="[3.0.0, 4.0.0)", - org.springframework.core.*;version="[3.0.0, 4.0.0)", - org.springframework.dao.*;version="[3.0.0, 4.0.0)", - org.springframework.scheduling.*;resolution:="optional";version="[3.0.0, 4.0.0)", - org.springframework.util.*;version="[3.0.0, 4.0.0)", - org.springframework.oxm.*;resolution:="optional";version="[3.0.0, 4.0.0)", - org.springframework.transaction.support.*;version="[3.0.0, 4.0.0)", + org.springframework.beans.*;version=${spring.range}, + org.springframework.context.*;version=${spring.range}, + org.springframework.core.*;version=${spring.range}, + org.springframework.dao.*;version=${spring.range}, + org.springframework.scheduling.*;resolution:="optional";version=${spring.range}, + org.springframework.util.*;version=${spring.range}, + org.springframework.oxm.*;resolution:="optional";version=${spring.range}, + org.springframework.transaction.support.*;version=${spring.range}, org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional, org.apache.commons.logging.*;version="[1.1.1, 2.0.0)", org.springframework.data.keyvalue.*;version=${version}, @@ -20,9 +20,9 @@ Import-Template: javax.xml.transform.*;resolution:="optional";version="0", org.jredis.*;version="[1.0.0, 2.0.0)", org.jredis.ri.alphazero.*;version="[1.0.0, 2.0.0)", - redis.clients.jedis.*;version="[1.5.2, 2.0.0)", - redis.clients.util.*;version="[1.5.2, 2.0.0)", + redis.clients.jedis.*;version=${jedis.range}, + redis.clients.util.*;version=${jedis.range}, + org.idevlab.rjc.*;version=${rjc.range}, org.apache.commons.pool.impl.*;version="[1.0.0, 3.0.0)", - org.codehaus.jackson.*;version="[1.6, 2.0.0)", - org.apache.commons.beanutils.*;version="[1.8.0, 2.0.0)" - + org.codehaus.jackson.*;version=${jackson.range}, + org.apache.commons.beanutils.*;version=1.8.5 \ No newline at end of file