Merge branch 'master' into gradle-build-ng

Conflicts:
	pom.xml
	spring-data-redis/pom.xml
	spring-data-redis/template.mf
This commit is contained in:
Costin Leau
2011-04-21 21:05:04 +03:00
115 changed files with 7586 additions and 2155 deletions

View File

@@ -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
------------

View File

@@ -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)
----------------------------------------

View File

@@ -17,9 +17,9 @@
<section id="redis:requirements">
<title>Redis Requirements</title>
<para>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 <ulink url="http://github.com/xetorthio/jedis">Jedis</ulink> and
<ulink url="http://github.com/alphazero/jredis">JRedis</ulink>, 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 <ulink url="http://github.com/xetorthio/jedis">Jedis</ulink>,
<ulink url="http://github.com/alphazero/jredis">JRedis</ulink> and <ulink url="https://github.com/e-mzungu/rjc">RJC</ulink>, 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.
</para>
</section>

View File

@@ -62,7 +62,7 @@
</xsl:for-each>
</fo:block>
<fo:block font-family="Helvetica" font-size="12pt" padding="10mm">
<xsl:text>Copyright &#xA9; 2006-2009</xsl:text>
<xsl:text>Copyright &#xA9; 2010-2011</xsl:text>
</fo:block>
<fo:block font-family="Helvetica" font-size="10pt" padding="1mm">
@@ -106,7 +106,7 @@
<xsl:param name="gentext-key" select="''"/>
<xsl:variable name="Version">
<xsl:if test="//releaseinfo">
<xsl:text>Spring Data Redis (</xsl:text><xsl:value-of select="//releaseinfo" /><xsl:text>)</xsl:text>
<xsl:text>Spring Data Key Value (</xsl:text><xsl:value-of select="//releaseinfo" /><xsl:text>)</xsl:text>
</xsl:if>
</xsl:variable>
<xsl:choose>

View File

@@ -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);
}
}

View File

@@ -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 <code>&lt;collection&gt;</code> 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));
}
}

View File

@@ -37,7 +37,7 @@ import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
/**
* Parser for the JMS <code>&lt;listener-container&gt;</code> element.
* Parser for the Redis <code>&lt;listener-container&gt;</code> element.
*
* @author Costin Leau
*/

View File

@@ -28,5 +28,6 @@ class RedisNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("listener-container", new RedisListenerContainerParser());
registerBeanDefinitionParser("collection", new RedisCollectionParser());
}
}

View File

@@ -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;
}

View File

@@ -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<byte[]> keys(byte[] pattern) {
public Set<byte[]> 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<String> deserialize(Collection<byte[]> data) {
List<String> result = new ArrayList<String>(data.size());
for (byte[] raw : data) {
result.add(serializer.deserialize(raw));
}
return result;
private List<String> deserialize(List<byte[]> data) {
return SerializationUtils.deserialize(data, serializer);
}
private Set<String> deserialize(Set<byte[]> data) {
Set<String> result = new LinkedHashSet<String>(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<StringTuple> deserializeTuple(Set<Tuple> data) {
if (data == null) {
return null;
}
Set<StringTuple> result = new LinkedHashSet<StringTuple>(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

View File

@@ -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<byte[]> 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<byte[]> 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 {
}

View File

@@ -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();
}

View File

@@ -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 <code>RedisInvalidSubscriptionException</code> instance.
*
* @param msg
* @param cause
*/
public RedisInvalidSubscriptionException(String msg, Throwable cause) {
super(msg, cause);
}
/**
* Constructs a new <code>RedisInvalidSubscriptionException</code> instance.
*
* @param msg
*/
public RedisInvalidSubscriptionException(String msg) {
super(msg);
}
}

View File

@@ -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<byte[]> 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<byte[]> sort(byte[] key, SortParameters params);
public abstract Long sort(byte[] key, SortParameters params, byte[] storeKey);
}

View File

@@ -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);

View File

@@ -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 <code>SubscribedRedisConnectionException</code> instance.
* Constructs a new <code>RedisSubscribedConnectionException</code> instance.
*
* @param msg
* @param cause
*/
public SubscribedRedisConnectionException(String msg, Throwable cause) {
public RedisSubscribedConnectionException(String msg, Throwable cause) {
super(msg, cause);
}
/**
* Constructs a new <code>SubscribedRedisConnectionException</code> instance.
* Constructs a new <code>RedisSubscribedConnectionException</code> instance.
*
* @param msg
*/
public SubscribedRedisConnectionException(String msg) {
public RedisSubscribedConnectionException(String msg) {
super(msg);
}
}

View File

@@ -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);

View File

@@ -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.

View File

@@ -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 <code>JedisConnection</code> 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<Jedis> pool) {
public JedisConnection(Jedis jedis, Pool<Jedis> 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<Object> 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<byte[]> keys(byte[] pattern) {
public Set<byte[]> 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<byte[]> args = new ArrayList<byte[]>();
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<byte[]> args = new ArrayList<byte[]>();
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<Tuple> 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");
}
}
}

View File

@@ -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 <a href="http://github.com/xetorthio/jedis">Jedis</a> based connections.
* Connection factory creating <a href="http://github.com/xetorthio/jedis">Jedis</a> 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 <code>JedisConnectionFactory</code> 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;
}
}

View File

@@ -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<byte[]> channels = new ArrayList<byte[]>(2);
private final Collection<byte[]> patterns = new ArrayList<byte[]>(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<byte[]> getChannels() {
synchronized (channels) {
return new ArrayList<byte[]>(channels);
}
}
@Override
public MessageListener getListener() {
return listener;
}
@Override
public Collection<byte[]> getPatterns() {
synchronized (patterns) {
return new ArrayList<byte[]>(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();
}
}

View File

@@ -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;
}

View File

@@ -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<byte[]> keys(byte[] pattern) {
public Set<byte[]> 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<byte[]> hKeys(byte[] key) {
try {
return new LinkedHashSet<byte[]>(JredisUtils.convertCollection(jredis.hkeys(JredisUtils.decode(key))));
return new LinkedHashSet<byte[]>(JredisUtils.convertToSet(jredis.hkeys(JredisUtils.decode(key))));
} catch (Exception ex) {
throw convertJredisAccessException(ex);
}

View File

@@ -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;
}
}

View File

@@ -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<byte[], byte[]> encodeMap(Map<String, byte[]> map) {
Map<byte[], byte[]> result = new LinkedHashMap<byte[], byte[]>(map.size());
for (Map.Entry<String, byte[]> entry : map.entrySet()) {
result.put(encode(entry.getKey()), entry.getValue());
}
return result;
return DecodeUtils.encodeMap(map);
}
static Collection<byte[]> convertCollection(Collection<String> keys) {
Collection<byte[]> list = new ArrayList<byte[]>(keys.size());
for (String string : keys) {
list.add(Base64.decode(string));
}
return list;
}
static Map<String, byte[]> decodeMap(Map<byte[], byte[]> tuple) {
Map<String, byte[]> result = new LinkedHashMap<String, byte[]>(tuple.size());
for (Map.Entry<byte[], byte[]> entry : tuple.entrySet()) {
result.put(decode(entry.getKey()), entry.getValue());
}
return result;
return DecodeUtils.decodeMap(tuple);
}
static Set<byte[]> convertToSet(Collection<String> keys) {
return DecodeUtils.convertToSet(keys);
}
static Sort applySortingParams(Sort jredisSort, SortParameters params, byte[] storeKey) {
if (params != null) {

View File

@@ -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 <code>CloseSuppressingRjcConnection</code> instance.
*
* @param delegate
*/
CloseSuppressingRjcConnection(RedisConnection delegate) {
this.delegate = delegate;
}
public void close() {
// no-op
}
public void connect() throws UnknownHostException, IOException {
delegate.connect();
}
public List<Object> getAll() {
return delegate.getAll();
}
public String getBulkReply() {
return delegate.getBulkReply();
}
public String getHost() {
return delegate.getHost();
}
public Long getIntegerReply() {
return delegate.getIntegerReply();
}
public List<String> getMultiBulkReply() {
return delegate.getMultiBulkReply();
}
public List<Object> 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();
}
}

View File

@@ -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 <a href="http://github.com/e-mzungu/rjc/">rjc</a> 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 <code>RjcConnectionFactory</code> 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;
}
}

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View File

@@ -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<byte[], byte[]> tuple) {
String[] result = new String[tuple.size() * 2];
int index = 0;
for (Map.Entry<byte[], byte[]> entry : tuple.entrySet()) {
result[index++] = decode(entry.getKey());
result[index++] = decode(entry.getValue());
}
return result;
}
static Set<byte[]> convertToSet(Collection<String> keys) {
if (keys == null) {
return null;
}
return DecodeUtils.convertToSet(keys);
}
static List<byte[]> convertToList(Collection<String> 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<Tuple> convertElementScore(List<ElementScore> tuples) {
Set<Tuple> value = new LinkedHashSet<Tuple>(tuples.size());
for (ElementScore tuple : tuples) {
value.add(new DefaultTuple(encode(tuple.getElement()), Double.valueOf(tuple.getScore())));
}
return value;
}
static Map<byte[], byte[]> encodeMap(Map<String, String> map) {
Map<byte[], byte[]> result = new LinkedHashMap<byte[], byte[]>(map.size());
for (Map.Entry<String, String> entry : map.entrySet()) {
result.put(encode(entry.getKey()), encode(entry.getValue()));
}
return result;
}
static Map<String, String> decodeMap(Map<byte[], byte[]> map) {
Map<String, String> result = new LinkedHashMap<String, String>(map.size());
for (Map.Entry<byte[], byte[]> 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;
}
}

View File

@@ -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<K> implements KeyBound<K> {
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;
}
}

View File

@@ -0,0 +1,5 @@
/**
* Connection package for <a href="https://github.com/e-mzungu/rjc">RJC</a> library.
*/
package org.springframework.data.keyvalue.redis.connection.rjc;

View File

@@ -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<ByteArrayWrapper> channels = new ArrayList<ByteArrayWrapper>(2);
private final Collection<ByteArrayWrapper> patterns = new ArrayList<ByteArrayWrapper>(2);
private final AtomicBoolean alive = new AtomicBoolean(true);
private final MessageListener listener;
protected AbstractSubscription(MessageListener listener) {
this(listener, null, null);
}
/**
* Constructs a new <code>AbstractSubscription</code> 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<byte[]> getChannels() {
synchronized (channels) {
return clone(channels);
}
}
@Override
public Collection<byte[]> 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<byte[]> clone(Collection<ByteArrayWrapper> col) {
Collection<byte[]> list = new ArrayList<byte[]>(col.size());
for (ByteArrayWrapper wrapper : col) {
list.add(wrapper.getArray().clone());
}
return list;
}
private static void add(Collection<ByteArrayWrapper> col, byte[]... bytes) {
if (!ObjectUtils.isEmpty(bytes)) {
for (byte[] bs : bytes) {
col.add(new ByteArrayWrapper(bs));
}
}
}
private static void remove(Collection<ByteArrayWrapper> col, byte[]... bytes) {
if (!ObjectUtils.isEmpty(bytes)) {
for (byte[] bs : bytes) {
col.remove(new ByteArrayWrapper(bs));
}
}
}
}

View File

@@ -1,4 +1,4 @@
package org.springframework.data.keyvalue.redis.connection.jredis;
package org.springframework.data.keyvalue.redis.connection.util;
import java.util.Arrays;

View File

@@ -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;
}
}

View File

@@ -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<byte[], byte[]> encodeMap(Map<String, byte[]> map) {
Map<byte[], byte[]> result = new LinkedHashMap<byte[], byte[]>(map.size());
for (Map.Entry<String, byte[]> entry : map.entrySet()) {
result.put(encode(entry.getKey()), entry.getValue());
}
return result;
}
public static Map<String, byte[]> decodeMap(Map<byte[], byte[]> tuple) {
Map<String, byte[]> result = new LinkedHashMap<String, byte[]>(tuple.size());
for (Map.Entry<byte[], byte[]> entry : tuple.entrySet()) {
result.put(decode(entry.getKey()), entry.getValue());
}
return result;
}
public static Set<byte[]> convertToSet(Collection<String> keys) {
Set<byte[]> set = new LinkedHashSet<byte[]>(keys.size());
for (String string : keys) {
set.add(encode(string));
}
return set;
}
public static List<byte[]> convertToList(Collection<String> keys) {
List<byte[]> set = new ArrayList<byte[]>(keys.size());
for (String string : keys) {
set.add(encode(string));
}
return set;
}
}

View File

@@ -0,0 +1,5 @@
/**
* Internal utility package for encoding/decoding Strings to byte[] (using Base64) library.
*/
package org.springframework.data.keyvalue.redis.connection.util;

View File

@@ -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<K, V> {
// utility methods for the template internal methods
abstract class ValueDeserializingRedisCallback implements RedisCallback<V> {
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<K, V> template;
AbstractOperations(RedisTemplate<K, V> template) {
keySerializer = template.getKeySerializer();
valueSerializer = template.getValueSerializer();
hashKeySerializer = template.getHashKeySerializer();
hashValueSerializer = template.getHashValueSerializer();
stringSerializer = template.getStringSerializer();
this.template = template;
}
<T> T execute(RedisCallback<T> callback, boolean b) {
return template.execute(callback, b);
}
public RedisOperations<K, V> 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")
<HK> byte[] rawHashKey(HK hashKey) {
Assert.notNull(hashKey, "non null hash key required");
return hashKeySerializer.serialize(hashKey);
}
@SuppressWarnings("unchecked")
<HV> 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<K> keys) {
return rawKeys(null, keys);
}
byte[][] rawKeys(K key, Collection<K> 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<V> deserializeValues(Set<byte[]> rawValues) {
return SerializationUtils.deserialize(rawValues, valueSerializer);
}
@SuppressWarnings("unchecked")
List<V> deserializeValues(List<byte[]> rawValues) {
return SerializationUtils.deserialize(rawValues, valueSerializer);
}
@SuppressWarnings("unchecked")
<T> Set<T> deserializeHashKeys(Set<byte[]> rawKeys) {
return SerializationUtils.deserialize(rawKeys, hashKeySerializer);
}
@SuppressWarnings("unchecked")
<T> List<T> deserializeHashValues(List<byte[]> rawValues) {
return SerializationUtils.deserialize(rawValues, hashValueSerializer);
}
@SuppressWarnings("unchecked")
<HK, HV> Map<HK, HV> deserializeHashMap(Map<byte[], byte[]> entries) {
// connection in pipeline/multi mode
if (entries == null) {
return null;
}
Map<HK, HV> map = new LinkedHashMap<HK, HV>(entries.size());
for (Map.Entry<byte[], byte[]> 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> HK deserializeHashKey(byte[] value) {
return (HK) hashKeySerializer.deserialize(value);
}
@SuppressWarnings("unchecked")
<HV> HV deserializeHashValue(byte[] value) {
return (HV) hashValueSerializer.deserialize(value);
}
}

View File

@@ -24,7 +24,7 @@ import java.util.Set;
*
* @author Costin Leau
*/
public interface BoundHashOperations<H, HK, HV> extends KeyBound<H> {
public interface BoundHashOperations<H, HK, HV> extends BoundKeyOperations<H> {
RedisOperations<H, ?> getOperations();

View File

@@ -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.
*
* <p>As the rest of the APIs, if the underlying connection is pipelined or queued/in multi mode,
* all methods will return null.
* </p>
* @author Costin Leau
*/
public interface BoundKeyOperations<K> {
/**
* 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);
}

View File

@@ -23,7 +23,7 @@ import java.util.concurrent.TimeUnit;
*
* @author Costin Leau
*/
public interface BoundListOperations<K, V> extends KeyBound<K> {
public interface BoundListOperations<K, V> extends BoundKeyOperations<K> {
RedisOperations<K, V> getOperations();

View File

@@ -24,7 +24,7 @@ import java.util.Set;
*
* @author Costin Leau
*/
public interface BoundSetOperations<K, V> extends KeyBound<K> {
public interface BoundSetOperations<K, V> extends BoundKeyOperations<K> {
RedisOperations<K, V> getOperations();

View File

@@ -22,27 +22,27 @@ import java.util.concurrent.TimeUnit;
*
* @author Costin Leau
*/
public interface BoundValueOperations<K, V> extends KeyBound<K> {
public interface BoundValueOperations<K, V> extends BoundKeyOperations<K> {
RedisOperations<K, V> 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();
}

View File

@@ -25,7 +25,7 @@ import java.util.Set;
*
* @author Costin Leau
*/
public interface BoundZSetOperations<K, V> extends KeyBound<K> {
public interface BoundZSetOperations<K, V> extends BoundKeyOperations<K> {
RedisOperations<K, V> getOperations();

View File

@@ -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();
}
}
}

View File

@@ -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<H, HK, HV> extends DefaultKeyBound<H> implements BoundHashOperations<H, HK, HV> {
class DefaultBoundHashOperations<H, HK, HV> extends DefaultBoundKeyOperations<H> implements BoundHashOperations<H, HK, HV> {
private final HashOperations<H, HK, HV> ops;
@@ -35,7 +37,7 @@ class DefaultBoundHashOperations<H, HK, HV> extends DefaultKeyBound<H> implement
* @param template
*/
public DefaultBoundHashOperations(H key, RedisOperations<H, ?> operations) {
super(key);
super(key, operations);
this.ops = operations.opsForHash();
}
@@ -103,4 +105,9 @@ class DefaultBoundHashOperations<H, HK, HV> extends DefaultKeyBound<H> implement
public Map<HK, HV> entries() {
return ops.entries(getKey());
}
@Override
public DataType getType() {
return DataType.HASH;
}
}

View File

@@ -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<K> implements BoundKeyOperations<K> {
private K key;
private final RedisOperations<K, ?> ops;
public DefaultBoundKeyOperations(K key, RedisOperations<K, ?> 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;
}
}

View File

@@ -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<K, V> extends DefaultKeyBound<K> implements BoundListOperations<K, V> {
class DefaultBoundListOperations<K, V> extends DefaultBoundKeyOperations<K> implements BoundListOperations<K, V> {
private final ListOperations<K, V> ops;
@@ -35,7 +37,7 @@ class DefaultBoundListOperations<K, V> extends DefaultKeyBound<K> implements Bou
* @param operations
*/
public DefaultBoundListOperations(K key, RedisOperations<K, V> operations) {
super(key);
super(key, operations);
this.ops = operations.opsForList();
}
@@ -124,4 +126,9 @@ class DefaultBoundListOperations<K, V> extends DefaultKeyBound<K> implements Bou
public void set(long index, V value) {
ops.set(getKey(), index, value);
}
@Override
public DataType getType() {
return DataType.LIST;
}
}

View File

@@ -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<K, V> extends DefaultKeyBound<K> implements BoundSetOperations<K, V> {
class DefaultBoundSetOperations<K, V> extends DefaultBoundKeyOperations<K> implements BoundSetOperations<K, V> {
private final SetOperations<K, V> ops;
@@ -36,7 +38,7 @@ class DefaultBoundSetOperations<K, V> extends DefaultKeyBound<K> implements Boun
* @param operations
*/
DefaultBoundSetOperations(K key, RedisOperations<K, V> operations) {
super(key);
super(key, operations);
this.ops = operations.opsForSet();
}
@@ -146,4 +148,9 @@ class DefaultBoundSetOperations<K, V> extends DefaultKeyBound<K> implements Boun
public void unionAndStore(Collection<K> keys, K destKey) {
ops.unionAndStore(getKey(), keys, destKey);
}
@Override
public DataType getType() {
return DataType.SET;
}
}

View File

@@ -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<K, V> extends DefaultKeyBound<K> implements BoundValueOperations<K, V> {
class DefaultBoundValueOperations<K, V> extends DefaultBoundKeyOperations<K> implements BoundValueOperations<K, V> {
private final ValueOperations<K, V> ops;
@@ -31,7 +33,7 @@ class DefaultBoundValueOperations<K, V> extends DefaultKeyBound<K> implements Bo
* @param operations
*/
public DefaultBoundValueOperations(K key, RedisOperations<K, V> operations) {
super(key);
super(key, operations);
this.ops = operations.opsForValue();
}
@@ -56,7 +58,7 @@ class DefaultBoundValueOperations<K, V> extends DefaultKeyBound<K> 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<K, V> extends DefaultKeyBound<K> 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<K, V> extends DefaultKeyBound<K> implements Bo
public RedisOperations<K, V> getOperations() {
return ops.getOperations();
}
@Override
public DataType getType() {
return DataType.STRING;
}
}

View File

@@ -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<K, V> extends DefaultKeyBound<K> implements BoundZSetOperations<K, V> {
class DefaultBoundZSetOperations<K, V> extends DefaultBoundKeyOperations<K> implements BoundZSetOperations<K, V> {
private final ZSetOperations<K, V> ops;
@@ -34,9 +36,9 @@ class DefaultBoundZSetOperations<K, V> extends DefaultKeyBound<K> implements Bou
* @param key
* @param oeprations
*/
public DefaultBoundZSetOperations(K key, RedisOperations<K, V> oeprations) {
super(key);
this.ops = oeprations.opsForZSet();
public DefaultBoundZSetOperations(K key, RedisOperations<K, V> operations) {
super(key, operations);
this.ops = operations.opsForZSet();
}
@Override
@@ -128,4 +130,9 @@ class DefaultBoundZSetOperations<K, V> extends DefaultKeyBound<K> implements Bou
public void unionAndStore(Collection<K> otherKeys, K destKey) {
ops.unionAndStore(getKey(), otherKeys, destKey);
}
@Override
public DataType getType() {
return DataType.ZSET;
}
}

View File

@@ -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<K, HK, HV> extends AbstractOperations<K, Object> implements HashOperations<K, HK, HV> {
@SuppressWarnings("unchecked")
DefaultHashOperations(RedisTemplate<K, ?> template) {
super((RedisTemplate<K, Object>) 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<byte[]>() {
@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<Boolean>() {
@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<Long>() {
@Override
public Long doInRedis(RedisConnection connection) {
return connection.hIncrBy(rawKey, rawHashKey, delta);
}
}, true);
}
@Override
public Set<HK> keys(K key) {
final byte[] rawKey = rawKey(key);
Set<byte[]> rawValues = execute(new RedisCallback<Set<byte[]>>() {
@Override
public Set<byte[]> 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<Long>() {
@Override
public Long doInRedis(RedisConnection connection) {
return connection.hLen(rawKey);
}
}, true);
}
@Override
public void putAll(K key, Map<? extends HK, ? extends HV> m) {
if (m.isEmpty()) {
return;
}
final byte[] rawKey = rawKey(key);
final Map<byte[], byte[]> hashes = new LinkedHashMap<byte[], byte[]>(m.size());
for (Map.Entry<? extends HK, ? extends HV> entry : m.entrySet()) {
hashes.put(rawHashKey(entry.getKey()), rawHashValue(entry.getValue()));
}
execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) {
connection.hMSet(rawKey, hashes);
return null;
}
}, true);
}
@Override
public Collection<HV> multiGet(K key, Collection<HK> 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<byte[]> rawValues = execute(new RedisCallback<List<byte[]>>() {
@Override
public List<byte[]> 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<Object>() {
@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<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) {
return connection.hSetNX(rawKey, rawHashKey, rawHashValue);
}
}, true);
}
@Override
public List<HV> values(K key) {
final byte[] rawKey = rawKey(key);
List<byte[]> rawValues = execute(new RedisCallback<List<byte[]>>() {
@Override
public List<byte[]> 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<Object>() {
@Override
public Object doInRedis(RedisConnection connection) {
connection.hDel(rawKey, rawHashKey);
return null;
}
}, true);
}
@Override
public Map<HK, HV> entries(K key) {
final byte[] rawKey = rawKey(key);
Map<byte[], byte[]> entries = execute(new RedisCallback<Map<byte[], byte[]>>() {
@Override
public Map<byte[], byte[]> doInRedis(RedisConnection connection) {
return connection.hGetAll(rawKey);
}
}, true);
return deserializeHashMap(entries);
}
}

View File

@@ -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<K, V> extends AbstractOperations<K, V> implements ListOperations<K, V> {
DefaultListOperations(RedisTemplate<K, V> 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<Long>() {
@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<Long>() {
@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<Long>() {
@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<Long>() {
@Override
public Long doInRedis(RedisConnection connection) {
return connection.lLen(rawKey);
}
}, true);
}
@Override
public List<V> range(K key, final long start, final long end) {
final byte[] rawKey = rawKey(key);
return execute(new RedisCallback<List<V>>() {
@SuppressWarnings("unchecked")
@Override
public List<V> 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<Long>() {
@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<Long>() {
@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<Long>() {
@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<Long>() {
@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);
}
}

View File

@@ -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<K, V> extends AbstractOperations<K, V> implements SetOperations<K, V> {
public DefaultSetOperations(RedisTemplate<K, V> 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<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) {
return connection.sAdd(rawKey, rawValue);
}
}, true);
}
@Override
public Set<V> difference(K key, K otherKey) {
return difference(key, Collections.singleton(otherKey));
}
@SuppressWarnings("unchecked")
@Override
public Set<V> difference(final K key, final Collection<K> otherKeys) {
final byte[][] rawKeys = rawKeys(key, otherKeys);
Set<byte[]> rawValues = execute(new RedisCallback<Set<byte[]>>() {
@Override
public Set<byte[]> 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<K> otherKeys, K destKey) {
final byte[][] rawKeys = rawKeys(key, otherKeys);
final byte[] rawDestKey = rawKey(destKey);
execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) {
connection.sDiffStore(rawDestKey, rawKeys);
return null;
}
}, true);
}
@Override
public Set<V> intersect(K key, K otherKey) {
return intersect(key, Collections.singleton(otherKey));
}
@SuppressWarnings("unchecked")
@Override
public Set<V> intersect(K key, Collection<K> otherKeys) {
final byte[][] rawKeys = rawKeys(key, otherKeys);
Set<byte[]> rawValues = execute(new RedisCallback<Set<byte[]>>() {
@Override
public Set<byte[]> 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<K> otherKeys, K destKey) {
final byte[][] rawKeys = rawKeys(key, otherKeys);
final byte[] rawDestKey = rawKey(destKey);
execute(new RedisCallback<Object>() {
@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<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) {
return connection.sIsMember(rawKey, rawValue);
}
}, true);
}
@SuppressWarnings("unchecked")
@Override
public Set<V> members(K key) {
final byte[] rawKey = rawKey(key);
Set<byte[]> rawValues = execute(new RedisCallback<Set<byte[]>>() {
@Override
public Set<byte[]> 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<Boolean>() {
@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<Boolean>() {
@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<Long>() {
@Override
public Long doInRedis(RedisConnection connection) {
return connection.sCard(rawKey);
}
}, true);
}
@Override
public Set<V> union(K key, K otherKey) {
return union(key, Collections.singleton(otherKey));
}
@SuppressWarnings("unchecked")
@Override
public Set<V> union(K key, Collection<K> otherKeys) {
final byte[][] rawKeys = rawKeys(key, otherKeys);
Set<byte[]> rawValues = execute(new RedisCallback<Set<byte[]>>() {
@Override
public Set<byte[]> 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<K> otherKeys, K destKey) {
final byte[][] rawKeys = rawKeys(key, otherKeys);
final byte[] rawDestKey = rawKey(destKey);
execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) {
connection.sUnionStore(rawDestKey, rawKeys);
return null;
}
}, true);
}
}

View File

@@ -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<K, V> extends AbstractOperations<K, V> implements ValueOperations<K, V> {
DefaultValueOperations(RedisTemplate<K, V> 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<Long>() {
@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<Integer>() {
@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<byte[]>() {
@Override
public byte[] doInRedis(RedisConnection connection) {
return connection.getRange(rawKey, start, end);
}
}, true);
return deserializeString(rawReturn);
}
@SuppressWarnings("unchecked")
@Override
public List<V> multiGet(Collection<K> 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<byte[]> rawValues = execute(new RedisCallback<List<byte[]>>() {
@Override
public List<byte[]> doInRedis(RedisConnection connection) {
return connection.mGet(rawKeys);
}
}, true);
return deserializeValues(rawValues);
}
@Override
public void multiSet(Map<? extends K, ? extends V> m) {
if (m.isEmpty()) {
return;
}
final Map<byte[], byte[]> rawKeys = new LinkedHashMap<byte[], byte[]>(m.size());
for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
rawKeys.put(rawKey(entry.getKey()), rawValue(entry.getValue()));
}
execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) {
connection.mSet(rawKeys);
return null;
}
}, true);
}
@Override
public void multiSetIfAbsent(Map<? extends K, ? extends V> m) {
if (m.isEmpty()) {
return;
}
final Map<byte[], byte[]> rawKeys = new LinkedHashMap<byte[], byte[]>(m.size());
for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
rawKeys.put(rawKey(entry.getKey()), rawValue(entry.getValue()));
}
execute(new RedisCallback<Object>() {
@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<Object>() {
@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<Boolean>() {
@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<Object>() {
@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<Long>() {
@Override
public Long doInRedis(RedisConnection connection) {
return connection.strLen(rawKey);
}
}, true);
}
}

View File

@@ -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<K, V> extends AbstractOperations<K, V> implements ZSetOperations<K, V> {
DefaultZSetOperations(RedisTemplate<K, V> 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<Boolean>() {
@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<Double>() {
@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<K> otherKeys, K destKey) {
final byte[][] rawKeys = rawKeys(key, otherKeys);
final byte[] rawDestKey = rawKey(destKey);
execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) {
connection.zInterStore(rawDestKey, rawKeys);
return null;
}
}, true);
}
@SuppressWarnings("unchecked")
@Override
public Set<V> range(K key, final long start, final long end) {
final byte[] rawKey = rawKey(key);
Set<byte[]> rawValues = execute(new RedisCallback<Set<byte[]>>() {
@Override
public Set<byte[]> doInRedis(RedisConnection connection) {
return connection.zRange(rawKey, start, end);
}
}, true);
return deserializeValues(rawValues);
}
@SuppressWarnings("unchecked")
@Override
public Set<V> rangeByScore(K key, final double min, final double max) {
final byte[] rawKey = rawKey(key);
Set<byte[]> rawValues = execute(new RedisCallback<Set<byte[]>>() {
@Override
public Set<byte[]> 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<Long>() {
@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<Long>() {
@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<Boolean>() {
@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<Object>() {
@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<Object>() {
@Override
public Object doInRedis(RedisConnection connection) {
connection.zRemRangeByScore(rawKey, min, max);
return null;
}
}, true);
}
@SuppressWarnings("unchecked")
@Override
public Set<V> reverseRange(K key, final long start, final long end) {
final byte[] rawKey = rawKey(key);
Set<byte[]> rawValues = execute(new RedisCallback<Set<byte[]>>() {
@Override
public Set<byte[]> 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<Double>() {
@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<Long>() {
@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<Long>() {
@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<K> otherKeys, K destKey) {
final byte[][] rawKeys = rawKeys(key, otherKeys);
final byte[] rawDestKey = rawKey(destKey);
execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) {
connection.zUnionStore(rawDestKey, rawKeys);
return null;
}
}, true);
}
}

View File

@@ -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<K> {
/**
* Returns the key associated with this entity.
*
* @return key associated with the implementing entity
*/
K getKey();
}

View File

@@ -64,6 +64,17 @@ public interface RedisOperations<K, V> {
*/
<T> T execute(SessionCallback<T> session);
// /**
// * Executes the given action object on a pipelined connection, returning the results. Note that the callback <b>cannot</b>
// * return a non-null value as it gets overwritten by the pipeline.
// *
// * @param <T> list element return type
// * @param action callback object to execute
// * @return list of objects returned by the pipeline
// */
// List<V> executePipelined(RedisCallback<?> action);
Boolean hasKey(K key);
void delete(K key);
@@ -84,7 +95,9 @@ public interface RedisOperations<K, V> {
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<K, V> {
void discard();
Object exec();
List<Object> exec();
// pubsub functionality on the template
void convertAndSend(String destination, Object message);
@@ -192,13 +205,15 @@ public interface RedisOperations<K, V> {
List<V> sort(SortQuery<K> query);
<T> List<T> sort(SortQuery<K> query, RedisSerializer<T> resultSerializer);
<T> List<T> sort(SortQuery<K> query, BulkMapper<T, V> bulkMapper);
<T, S> List<T> sort(SortQuery<K> query, BulkMapper<T, S> bulkMapper, RedisSerializer<S> resultSerializer);
Long sort(SortQuery<K> query, K storeKey);
RedisSerializer<?> getValueSerializer();
RedisSerializer<?> getKeySerializer();
}

View File

@@ -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<T> {
* @param operations Redis operations
* @return return value
*/
<K, V> T execute(RedisOperations<K, V> operations);
<K, V> T execute(RedisOperations<K, V> operations) throws DataAccessException;
}

View File

@@ -51,12 +51,9 @@ public class StringRedisTemplate extends RedisTemplate<String, String> {
* @param connectionFactory connection factory for creating new connections
*/
public StringRedisTemplate(RedisConnectionFactory connectionFactory) {
super(connectionFactory);
RedisSerializer<String> stringSerializer = new StringRedisSerializer();
setKeySerializer(stringSerializer);
setValueSerializer(stringSerializer);
setHashKeySerializer(stringSerializer);
setHashValueSerializer(stringSerializer);
this();
setConnectionFactory(connectionFactory);
afterPropertiesSet();
}
@Override

View File

@@ -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<K, V> {
V getAndSet(K key, V value);
Collection<V> multiGet(Collection<K> keys);
List<V> multiGet(Collection<K> 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);

View File

@@ -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 <K> SortParameters convertQuery(SortQuery<K> query, RedisSerializer<String> stringSerializer) {
return new DefaultSortParameters(stringSerializer.serialize(query.getBy()), query.getLimit(), serialize(
query.getGetPattern(), stringSerializer), query.getOrder(), query.isAlphabetic());
}
private static byte[][] serialize(List<String> strings, RedisSerializer<String> stringSerializer) {
List<byte[]> raw = null;
if (strings == null) {
raw = Collections.emptyList();
}
else {
raw = new ArrayList<byte[]>(strings.size());
for (String key : strings) {
raw.add(stringSerializer.serialize(key));
}
}
return raw.toArray(new byte[raw.size()][]);
}
}

View File

@@ -0,0 +1,5 @@
/**
* Query package for Redis template.
*/
package org.springframework.data.keyvalue.redis.core.query;

View File

@@ -30,7 +30,7 @@ public class JacksonHashMapper<T> implements HashMapper<T, String, Object> {
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<T> type) {
this(type, new ObjectMapper());
@@ -47,7 +47,6 @@ public class JacksonHashMapper<T> implements HashMapper<T, String, Object> {
return (T) mapper.convertValue(hash, userType);
}
@SuppressWarnings("unchecked")
@Override
public Map<String, Object> toHash(T object) {
return mapper.convertValue(object, mapType);

View File

@@ -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;

View File

@@ -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<ArrayHolder, Collection<MessageListener>> patternMapping = new ConcurrentHashMap<ArrayHolder, Collection<MessageListener>>();
private final Map<ByteArrayWrapper, Collection<MessageListener>> patternMapping = new ConcurrentHashMap<ByteArrayWrapper, Collection<MessageListener>>();
// lookup map between channels and listeners
private final Map<ArrayHolder, Collection<MessageListener>> channelMapping = new ConcurrentHashMap<ArrayHolder, Collection<MessageListener>>();
private final Map<ByteArrayWrapper, Collection<MessageListener>> channelMapping = new ConcurrentHashMap<ByteArrayWrapper, Collection<MessageListener>>();
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<MessageListener> 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<ArrayHolder> holders) {
private byte[][] unwrap(Collection<ByteArrayWrapper> 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<MessageListener> ch = channelMapping.get(new ArrayHolder(channel));
Collection<MessageListener> ch = channelMapping.get(new ByteArrayWrapper(channel));
Collection<MessageListener> 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;
}
}
}

View File

@@ -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.
* <p>The default converter is a {@link JdkSerializationRedisSerializer}, which is able
* to handle {@link Serializable} objects.
* <p>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);
}
}

View File

@@ -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 <code>ListenerExecutionFailedException</code> instance.
* Constructs a new <code>RedisListenerExecutionFailedException</code> instance.
*
* @param msg
* @param cause
*/
public ListenerExecutionFailedException(String msg, Throwable cause) {
public RedisListenerExecutionFailedException(String msg, Throwable cause) {
super(msg, cause);
}
/**
* Constructs a new <code>ListenerExecutionFailedException</code> instance.
* Constructs a new <code>RedisListenerExecutionFailedException</code> instance.
*
* @param msg
*/
public ListenerExecutionFailedException(String msg) {
public RedisListenerExecutionFailedException(String msg) {
super(msg);
}
}

View File

@@ -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<T extends Number> implements RedisSerializer<T> {
private final Charset charset;
private final Constructor<T> ctor;
public BasicNumberToStringSerializer(Class<T> type) {
this(type, Charset.forName("UTF8"));
}
public BasicNumberToStringSerializer(Class<T> 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);
}
}

View File

@@ -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<T> implements RedisSerializer<T>, BeanFactoryAware {
private final Charset charset;
private Converter converter;
private Converter converter = new Converter(ConversionServiceFactory.createDefaultConversionService());
private Class<T> type;
public GenericToStringSerializer(Class<T> type) {
@@ -65,12 +66,19 @@ public class GenericToStringSerializer<T> implements RedisSerializer<T>, 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);
}

View File

@@ -26,6 +26,8 @@ import org.springframework.util.Assert;
* {@link RedisSerializer} that can read and write JSON using <a href="http://jackson.codehaus.org/">Jackson's</a> {@link ObjectMapper}.
*
* <p>This converter can be used to bind to typed beans, or untyped {@link java.util.HashMap HashMap} instances.
*
* <b>Note:</b>Null objects are serialized as empty arrays and vice versa.
*
* @author Costin Leau
*/
@@ -44,7 +46,7 @@ public class JacksonJsonRedisSerializer<T> implements RedisSerializer<T> {
@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<T> implements RedisSerializer<T> {
@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);

View File

@@ -34,6 +34,10 @@ public class JdkSerializationRedisSerializer implements RedisSerializer<Object>
@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<Object>
@Override
public byte[] serialize(Object object) {
if (object == null) {
return SerializationUtils.EMPTY_ARRAY;
}
try {
return serializer.convert(object);
} catch (Exception ex) {

View File

@@ -31,7 +31,7 @@ import org.springframework.util.Assert;
* Delegates serialization/deserialization to OXM {@link Marshaller} and
* {@link Unmarshaller}.
*
* <b>Note:</b>Null objects are serialized as empty arrays.
* <b>Note:</b>Null objects are serialized as empty arrays and vice versa.
*
* @author Costin Leau
*/
@@ -72,7 +72,7 @@ public class OxmSerializer implements InitializingBean, RedisSerializer<Object>
@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<Object>
@Override
public byte[] serialize(Object t) throws SerializationException {
if (t == null) {
return SerializerUtils.EMPTY_ARRAY;
return SerializationUtils.EMPTY_ARRAY;
}
ByteArrayOutputStream stream = new ByteArrayOutputStream();

View File

@@ -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

View File

@@ -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 extends Collection<?>> T deserializeValues(Collection<byte[]> rawValues, Class<T> type, RedisSerializer<?> redisSerializer) {
// connection in pipeline/multi mode
if (rawValues == null) {
return null;
}
Collection<Object> values = (List.class.isAssignableFrom(type) ? new ArrayList<Object>(rawValues.size())
: new LinkedHashSet<Object>(rawValues.size()));
for (byte[] bs : rawValues) {
values.add(redisSerializer.deserialize(bs));
}
return (T) values;
}
@SuppressWarnings("unchecked")
public static <T> Set<T> deserialize(Set<byte[]> rawValues, RedisSerializer<T> redisSerializer) {
return deserializeValues(rawValues, Set.class, redisSerializer);
}
@SuppressWarnings("unchecked")
public static <T> List<T> deserialize(List<byte[]> rawValues, RedisSerializer<T> redisSerializer) {
return deserializeValues(rawValues, List.class, redisSerializer);
}
@SuppressWarnings("unchecked")
public static <T> Collection<T> deserialize(Collection<byte[]> rawValues, RedisSerializer<T> redisSerializer) {
return deserializeValues(rawValues, List.class, redisSerializer);
}
}

View File

@@ -25,14 +25,12 @@ import org.springframework.util.Assert;
* <p/>
* Useful when the interaction with the Redis happens mainly through Strings.
*
* <p/> Converts null into empty arrays (which get translated into empty strings on deserialization).
* <p/> Does not perform any null conversion since empty strings are valid keys/values.
*
* @author Costin Leau
*/
public class StringRedisSerializer implements RedisSerializer<String> {
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<String> {
@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));
}
}

View File

@@ -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:
*
* <pre>
* return CASUtils.execute(ops, key, new Callable<Integer>() {
* @Override
* public Integer call() throws Exception {
* // check
* int value = get();
* // start MULTI
* ops.multi();
* // set
* ops.increment(key, 1);
* return value;
* }
* });
* </pre>
*
* @author Costin Leau
*/
abstract class CASUtils {
public static <T, K, V> T execute(final RedisOperations<K, V> ops, final K key, final Callable<T> callback) {
return ops.execute(new SessionCallback<T>() {
@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);
}
}
});
}
}

View File

@@ -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<String> {
public class RedisAtomicInteger extends Number implements Serializable, BoundKeyOperations<String> {
private final String key;
private volatile String key;
private ValueOperations<String, Integer> operations;
private RedisOperations<String, Integer> generalOps;
@@ -48,16 +51,7 @@ public class RedisAtomicInteger extends Number implements Serializable, KeyBound
* @param factory connection factory
*/
public RedisAtomicInteger(String redisCounter, RedisConnectionFactory factory) {
RedisTemplate<String, Integer> redisTemplate = new RedisTemplate<String, Integer>(factory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new BasicNumberToStringSerializer<Integer>(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<String, Integer> redisTemplate = new RedisTemplate<String, Integer>(factory);
this(redisCounter, factory, Integer.valueOf(initialValue));
}
private RedisAtomicInteger(String redisCounter, RedisConnectionFactory factory, Integer initialValue) {
RedisTemplate<String, Integer> redisTemplate = new RedisTemplate<String, Integer>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericToStringSerializer<Integer>(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 <code>RedisAtomicInteger</code> 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;
}
}

View File

@@ -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<String> {
public class RedisAtomicLong extends Number implements Serializable, BoundKeyOperations<String> {
private final String key;
private volatile String key;
private ValueOperations<String, Long> operations;
private RedisOperations<String, Long> generalOps;
@@ -48,16 +51,7 @@ public class RedisAtomicLong extends Number implements Serializable, KeyBound<St
* @param factory connection factory
*/
public RedisAtomicLong(String redisCounter, RedisConnectionFactory factory) {
RedisTemplate<String, Long> redisTemplate = new RedisTemplate<String, Long>(factory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new BasicNumberToStringSerializer<Long>(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<St
* @param initialValue
*/
public RedisAtomicLong(String redisCounter, RedisConnectionFactory factory, long initialValue) {
RedisTemplate<String, Long> redisTemplate = new RedisTemplate<String, Long>(factory);
this(redisCounter, factory, Long.valueOf(initialValue));
}
private RedisAtomicLong(String redisCounter, RedisConnectionFactory factory, Long initialValue) {
RedisTemplate<String, Long> redisTemplate = new RedisTemplate<String, Long>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericToStringSerializer<Long>(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 <code>RedisAtomicLong</code> 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, KeyBound<St
/**
* Constructs a new <code>RedisAtomicLong</code> 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<St
this.operations.set(redisCounter, initialValue);
}
@Override
public String getKey() {
return key;
}
/**
* Gets the current value.
*
@@ -255,4 +264,40 @@ public class RedisAtomicLong extends Number implements Serializable, KeyBound<St
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;
}
}

View File

@@ -17,6 +17,8 @@ package org.springframework.data.keyvalue.redis.support.collections;
import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import org.springframework.data.keyvalue.redis.core.RedisOperations;
@@ -30,7 +32,7 @@ public abstract class AbstractRedisCollection<E> extends AbstractCollection<E> i
public static final String ENCODING = "UTF-8";
private final String key;
private volatile String key;
private final RedisOperations<String, E> operations;
public <K> AbstractRedisCollection(String key, RedisOperations<String, E> operations) {
@@ -116,4 +118,30 @@ public abstract class AbstractRedisCollection<E> extends AbstractCollection<E> 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;
}
}

View File

@@ -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 <K> void rename(final K key, final K newKey, RedisOperations<K, ?> operations) {
operations.execute(new SessionCallback<Object>() {
@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 <K> Boolean renameIfAbsent(final K key, final K newKey, RedisOperations<K, ?> operations) {
return operations.execute(new SessionCallback<Boolean>() {
@Override
public Boolean execute(RedisOperations operations) throws DataAccessException {
List<Object> 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;
}
});
}
}

View File

@@ -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<E> extends AbstractRedisCollection<E> implements R
public E takeLast() throws InterruptedException {
return pollLast(0, TimeUnit.SECONDS);
}
@Override
public DataType getType() {
return DataType.LIST;
}
}

View File

@@ -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<K, V> implements RedisMap<K, V> {
return hashOps.increment(key, delta);
}
@Override
public String getKey() {
return hashOps.getKey();
}
@Override
public RedisOperations<String, ?> getOperations() {
return hashOps.getOperations();
@@ -295,4 +293,40 @@ public class DefaultRedisMap<K, V> implements RedisMap<K, V> {
// }
// }
}
@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();
}
}

View File

@@ -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<E> extends AbstractRedisCollection<E> implements Re
public int size() {
return boundSetOps.size().intValue();
}
@Override
public DataType getType() {
return DataType.SET;
}
}

View File

@@ -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<E> extends AbstractRedisCollection<E> implements R
public Double score(Object o) {
return boundZSetOps.score(o);
}
@Override
public DataType getType() {
return DataType.ZSET;
}
}

View File

@@ -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<RedisStore> {
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<String, ?> 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<String, ?> template) {
this.template = template;
}
/**
* Sets the key of the store.
*
* @param key The key to set.
*/
public void setKey(String key) {
this.key = key;
}
}

View File

@@ -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}.
* <p/>
* 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<Object, Object> {
private final BoundHashOperations<String, String, String> hashOps;
private final RedisMap<String, String> delegate;
/**
* Constructs a new <code>RedisProperties</code> instance.
*
*/
public RedisProperties(BoundHashOperations<String, String, String> boundOps) {
this(null, boundOps);
}
/**
* Constructs a new <code>RedisProperties</code> instance.
*
* @param boundOps
*/
public RedisProperties(String key, RedisOperations<String, ?> operations) {
this(null, operations.<String, String> boundHashOps(key));
}
/**
* Constructs a new <code>RedisProperties</code> instance.
*
* @param defaults
*/
public RedisProperties(Properties defaults, BoundHashOperations<String, String, String> boundOps) {
super(defaults);
this.hashOps = boundOps;
this.delegate = new DefaultRedisMap<String, String>(boundOps);
}
/**
* Constructs a new <code>RedisProperties</code> instance.
*
* @param defaults
* @param boundOps
*/
public RedisProperties(Properties defaults, String key, RedisOperations<String, ?> operations) {
this(defaults, operations.<String, String> 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<? extends Object, ? extends Object> t) {
delegate.putAll((Map<? extends String, ? extends String>) t);
}
@Override
public Enumeration<?> propertyNames() {
Set<String> keys = new LinkedHashSet<String>(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<Object> elements() {
Collection values = delegate.values();
return Collections.enumeration(values);
}
@Override
@SuppressWarnings("unchecked")
public Set<Entry<Object, Object>> 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<Object> keys() {
Set<Object> keys = keySet();
return Collections.enumeration(keys);
}
@SuppressWarnings("unchecked")
@Override
public Set<Object> 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<Object> values() {
Collection vals = delegate.values();
return vals;
}
@Override
public Long increment(Object key, long delta) {
return hashOps.increment((String) key, delta);
}
@Override
public RedisOperations<String, ?> 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();
}
}

View File

@@ -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<String> {
public interface RedisStore extends BoundKeyOperations<String> {
/**
* Returns the underlying Redis operations used by the backing implementation.

View File

@@ -149,4 +149,61 @@ listener method arguments. Default is a StringRedisSerializer.
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:element name="collection">
<xsd:annotation>
<xsd:documentation><![CDATA[
Factory creating collections on top of Redis keys.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="org.springframework.data.keyvalue.redis.support.collections.RedisCollectionFactoryBean"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="id" type="xsd:ID">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the Redis collection.]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="key" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Redis key of the created collection. Defaults to bean id.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="template" type="xsd:string" default="redisTemplate">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to a RedisTemplate bean.Default is "redisTemplate".
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.data.keyvalue.redis.core.RedisTemplate"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="type" default="LIST" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The collection type (default is list).
If the key exists, its type takes priority. The type is used to disambiguate the collection type (map vs properties) or
specify one in case the key is missing.]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="LIST"/>
<xsd:enumeration value="SET"/>
<xsd:enumeration value="ZSET"/>
<xsd:enumeration value="MAP"/>
<xsd:enumeration value="PROPERTIES"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@@ -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);
}

View File

@@ -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<String> mGet = connection.mGet(keys);
assertEquals(2, mGet.size());
assertNull(mGet.get(0));
assertNull(mGet.get(1));
StringRedisTemplate stringTemplate = new StringRedisTemplate(getConnectionFactory());
List<String> 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<Message> queue = new LinkedBlockingDeque<Message>();
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);
}
}

View File

@@ -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();

View File

@@ -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() {
}
}

View File

@@ -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"));
}
}

View File

@@ -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<Object>() {
@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<Object>() {
@Override

View File

@@ -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<Object> objFactory;
private RedisTemplate template;
public TemplateTest(ObjectFactory<Object> objFactory, RedisTemplate template) {
this.objFactory = objFactory;
this.template = template;
ConnectionFactoryTracker.add(template.getConnectionFactory());
}
@AfterClass
public static void cleanUp() {
ConnectionFactoryTracker.cleanUp();
}
@Parameters
public static Collection<Object[]> testParams() {
return CollectionTestParams.testParams();
}
@Test
public void testKeys() throws Exception {
assertTrue(template.keys("*") != null);
}
}

View File

@@ -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<Person> 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<String, String> stringTemplate = new StringRedisTemplate(jedisConnFactory);
RedisTemplate<String, Person> personTemplate = new RedisTemplate<String, Person>(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<String, String> stringTemplateRJC = new StringRedisTemplate(rjcConnFactory);
RedisTemplate<String, Person> personTemplateRJC = new RedisTemplate<String, Person>(rjcConnFactory);
return Arrays.asList(new Object[][] { { stringFactory, stringTemplate }, { personFactory, personTemplate },
{ stringFactory, stringTemplateRJC }, { personFactory, personTemplateRJC }
});
}
}

View File

@@ -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<T> {
protected RedisMessageListenerContainer container;
protected ObjectFactory<T> factory;
protected RedisTemplate template;
private static Set<RedisConnectionFactory> connFactories = new LinkedHashSet<RedisConnectionFactory>();
private final BlockingDeque<String> bag = new LinkedBlockingDeque<String>(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<T> {
container.addMessageListener(adapter, Arrays.asList(new ChannelTopic(CHANNEL)));
container.afterPropertiesSet();
Thread.sleep(500);
Thread.sleep(1000);
}
@After
@@ -85,21 +82,12 @@ public class PubSubTests<T> {
public PubSubTests(ObjectFactory<T> 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<T> {
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));
}

Some files were not shown because too many files have changed in this diff Show More