zRangeByLex(byte[] key, Range range, Limit limit) {
- throw new UnsupportedOperationException("ZRANGEBYLEX is no supported for lettuce.");
+
+ Assert.notNull(range, "Range cannot be null for ZRANGEBYLEX.");
+
+ String min = LettuceConverters.boundaryToBytesForZRangeByLex(range.getMin(), LettuceConverters.MINUS_BYTES);
+ String max = LettuceConverters.boundaryToBytesForZRangeByLex(range.getMax(), LettuceConverters.PLUS_BYTES);
+
+ try {
+ if (isPipelined()) {
+ if (limit != null) {
+ pipeline(new LettuceResult(getAsyncConnection().zrangebylex(key, min, max, limit.getOffset(),
+ limit.getCount()), LettuceConverters.bytesListToBytesSet()));
+ } else {
+ pipeline(new LettuceResult(getAsyncConnection().zrangebylex(key, min, max),
+ LettuceConverters.bytesListToBytesSet()));
+ }
+ return null;
+ }
+ if (isQueueing()) {
+ if (limit != null) {
+ transaction(new LettuceTxResult(getConnection().zrangebylex(key, min, max, limit.getOffset(),
+ limit.getCount()), LettuceConverters.bytesListToBytesSet()));
+ } else {
+ transaction(new LettuceTxResult(getConnection().zrangebylex(key, min, max),
+ LettuceConverters.bytesListToBytesSet()));
+ }
+ return null;
+ }
+
+ if (limit != null) {
+ return LettuceConverters.bytesListToBytesSet().convert(
+ getConnection().zrangebylex(key, min, max, limit.getOffset(), limit.getCount()));
+ }
+
+ return LettuceConverters.bytesListToBytesSet().convert(getConnection().zrangebylex(key, min, max));
+ } catch (Exception ex) {
+ throw convertLettuceAccessException(ex);
+ }
}
}
diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java
index 3b28c0bbb..73fe212f9 100644
--- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java
+++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java
@@ -18,6 +18,9 @@ package org.springframework.data.redis.connection.lettuce;
import java.util.concurrent.TimeUnit;
+import com.lambdaworks.redis.LettuceFutures;
+import com.lambdaworks.redis.RedisFuture;
+import com.lambdaworks.redis.RedisURI;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
@@ -29,6 +32,7 @@ import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.connection.Pool;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisSentinelConnection;
import org.springframework.util.Assert;
@@ -37,7 +41,7 @@ import com.lambdaworks.redis.RedisClient;
import com.lambdaworks.redis.RedisException;
/**
- * Connection factory creating Lettuce-based connections.
+ * Connection factory creating Lettuce-based connections.
*
* This factory creates a new {@link LettuceConnection} on each call to {@link #getConnection()}. Multiple
* {@link LettuceConnection}s share a single thread-safe native connection by default.
@@ -54,6 +58,8 @@ import com.lambdaworks.redis.RedisException;
*/
public class LettuceConnectionFactory implements InitializingBean, DisposableBean, RedisConnectionFactory {
+ public static final String PING_REPLY = "PONG";
+
private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new PassThroughExceptionTranslationStrategy(
LettuceConverters.exceptionConverter());
@@ -63,6 +69,7 @@ public class LettuceConnectionFactory implements InitializingBean, DisposableBea
private int port = 6379;
private RedisClient client;
private long timeout = TimeUnit.MILLISECONDS.convert(60, TimeUnit.SECONDS);
+ private long shutdownTimeout = TimeUnit.MILLISECONDS.convert(2, TimeUnit.SECONDS);
private boolean validateConnection = false;
private boolean shareNativeConnection = true;
private RedisAsyncConnection connection;
@@ -72,6 +79,7 @@ public class LettuceConnectionFactory implements InitializingBean, DisposableBea
private final Object connectionMonitor = new Object();
private String password;
private boolean convertPipelineAndTxResults = true;
+ private RedisSentinelConfiguration sentinelConfiguration;
/**
* Constructs a new LettuceConnectionFactory instance with default settings.
@@ -86,6 +94,16 @@ public class LettuceConnectionFactory implements InitializingBean, DisposableBea
this.port = port;
}
+ /**
+ * Constructs a new {@link LettuceConnectionFactory} instance using the given {@link RedisSentinelConfiguration}
+ *
+ * @param sentinelConfiguration
+ * @since 1.5
+ */
+ public LettuceConnectionFactory(RedisSentinelConfiguration sentinelConfiguration) {
+ this.sentinelConfiguration = sentinelConfiguration;
+ }
+
public LettuceConnectionFactory(LettucePool pool) {
this.pool = pool;
}
@@ -96,7 +114,7 @@ public class LettuceConnectionFactory implements InitializingBean, DisposableBea
public void destroy() {
resetConnection();
- client.shutdown();
+ client.shutdown(shutdownTimeout, shutdownTimeout, TimeUnit.MILLISECONDS);
}
public RedisConnection getConnection() {
@@ -131,9 +149,22 @@ public class LettuceConnectionFactory implements InitializingBean, DisposableBea
*/
public void validateConnection() {
synchronized (this.connectionMonitor) {
- try {
- new com.lambdaworks.redis.RedisConnection(connection).ping();
- } catch (RedisException e) {
+
+ boolean valid = false;
+
+ if (connection.isOpen()) {
+ try {
+ RedisFuture ping = connection.ping();
+ LettuceFutures.awaitAll(timeout, TimeUnit.MILLISECONDS, ping);
+ if(PING_REPLY.equalsIgnoreCase(ping.get())) {
+ valid = true;
+ }
+ } catch (Exception e) {
+ log.debug("Validation failed", e);
+ }
+ }
+
+ if (!valid) {
log.warn("Validation of shared connection failed. Creating a new connection.");
initConnection();
}
@@ -280,6 +311,22 @@ public class LettuceConnectionFactory implements InitializingBean, DisposableBea
this.password = password;
}
+ /**
+ * Returns the shutdown timeout for shutting down the RedisClient (in milliseconds).
+ * @return shutdown timeout
+ */
+ public long getShutdownTimeout() {
+ return shutdownTimeout;
+ }
+
+ /**
+ * Sets the shutdown timeout for shutting down the RedisClient (in milliseconds).
+ * @param shutdownTimeout the shutdown timeout
+ */
+ public void setShutdownTimeout(long shutdownTimeout) {
+ this.shutdownTimeout = shutdownTimeout;
+ }
+
/**
* Specifies if pipelined results should be converted to the expected data type. If false, results of
* {@link LettuceConnection#closePipeline()} and {LettuceConnection#exec()} will be of the type returned by the
@@ -331,17 +378,39 @@ public class LettuceConnectionFactory implements InitializingBean, DisposableBea
}
private RedisClient createRedisClient() {
+
+ if(isRedisSentinelAware()) {
+ RedisURI redisURI = getSentinelRedisURI();
+ return new RedisClient(redisURI);
+ }
+
if (pool != null) {
return pool.getClient();
}
- RedisClient client = password != null ? new AuthenticatingRedisClient(hostName, port, password) : new RedisClient(
- hostName, port);
- client.setDefaultTimeout(timeout, TimeUnit.MILLISECONDS);
+
+ RedisURI.Builder builder = RedisURI.Builder.redis(hostName, port);
+ if(password != null) {
+ builder.withPassword(password);
+ }
+ builder.withTimeout(timeout, TimeUnit.MILLISECONDS);
+ RedisClient client = new RedisClient(builder.build());
return client;
}
+ private RedisURI getSentinelRedisURI() {
+ return LettuceConverters.sentinelConfigurationToRedisURI(sentinelConfiguration);
+ }
+
+ /**
+ * @return true when {@link RedisSentinelConfiguration} is present.
+ * @since 1.5
+ */
+ public boolean isRedisSentinelAware() {
+ return sentinelConfiguration != null;
+ }
+
@Override
public RedisSentinelConnection getSentinelConnection() {
- throw new UnsupportedOperationException();
+ return new LettuceSentinelConnection(client.connectSentinelAsync());
}
}
diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java
index a30a72c38..73360394f 100644
--- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java
+++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java
@@ -15,6 +15,7 @@
*/
package org.springframework.data.redis.connection.lettuce;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -30,6 +31,9 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.DefaultTuple;
import org.springframework.data.redis.connection.RedisListCommands.Position;
+import org.springframework.data.redis.connection.RedisNode;
+import org.springframework.data.redis.connection.RedisSentinelConfiguration;
+import org.springframework.data.redis.connection.RedisServer;
import org.springframework.data.redis.connection.RedisZSetCommands.Range.Boundary;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.connection.ReturnType;
@@ -44,10 +48,11 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import com.lambdaworks.redis.KeyValue;
+import com.lambdaworks.redis.RedisURI;
import com.lambdaworks.redis.ScoredValue;
import com.lambdaworks.redis.ScriptOutputType;
import com.lambdaworks.redis.SortArgs;
-import com.lambdaworks.redis.protocol.Charsets;
+import com.lambdaworks.redis.protocol.LettuceCharsets;
/**
* Lettuce type converters
@@ -55,35 +60,40 @@ import com.lambdaworks.redis.protocol.Charsets;
* @author Jennifer Hickey
* @author Christoph Strobl
* @author Thomas Darimont
+ * @author Mark Paluch
*/
abstract public class LettuceConverters extends Converters {
private static final Converter DATE_TO_LONG;
private static final Converter, Set> BYTES_LIST_TO_BYTES_SET;
private static final Converter BYTES_TO_STRING;
+ private static final Converter STRING_TO_BYTES;
private static final Converter, List> BYTES_SET_TO_BYTES_LIST;
private static final Converter, List> KEY_VALUE_TO_BYTES_LIST;
private static final Converter>, Set> SCORED_VALUES_TO_TUPLE_SET;
+ private static final Converter>, List> SCORED_VALUES_TO_TUPLE_LIST;
private static final Converter, Tuple> SCORED_VALUE_TO_TUPLE;
private static final Converter EXCEPTION_CONVERTER = new LettuceExceptionConverter();
private static final Converter LONG_TO_BOOLEAN = new LongToBooleanConverter();
private static final Converter, Map> BYTES_LIST_TO_MAP;
private static final Converter, List> BYTES_LIST_TO_TUPLE_LIST_CONVERTER;
-
private static final Converter> STRING_TO_LIST_OF_CLIENT_INFO = new StringToRedisClientInfoConverter();
+ public static final byte[] PLUS_BYTES;
+ public static final byte[] MINUS_BYTES;
+ public static final byte[] POSITIVE_INFINITY_BYTES;
+ public static final byte[] NEGATIVE_INFINITY_BYTES;
+
static {
DATE_TO_LONG = new Converter() {
public Long convert(Date source) {
return source != null ? source.getTime() : null;
}
-
};
BYTES_LIST_TO_BYTES_SET = new Converter, Set>() {
public Set convert(List results) {
return results != null ? new LinkedHashSet(results) : null;
}
-
};
BYTES_TO_STRING = new Converter() {
@@ -94,7 +104,16 @@ abstract public class LettuceConverters extends Converters {
}
return new String(source);
}
+ };
+ STRING_TO_BYTES = new Converter() {
+ @Override
+ public byte[] convert(String source) {
+ if (source == null) {
+ return null;
+ }
+ return source.getBytes();
+ }
};
BYTES_SET_TO_BYTES_LIST = new Converter, List>() {
public List convert(Set results) {
@@ -130,7 +149,6 @@ abstract public class LettuceConverters extends Converters {
return target;
}
-
};
SCORED_VALUES_TO_TUPLE_SET = new Converter>, Set>() {
public Set convert(List> source) {
@@ -144,11 +162,23 @@ abstract public class LettuceConverters extends Converters {
return tuples;
}
};
+
+ SCORED_VALUES_TO_TUPLE_LIST = new Converter>, List>() {
+ public List convert(List> source) {
+ if (source == null) {
+ return null;
+ }
+ List tuples = new ArrayList(source.size());
+ for (ScoredValue value : source) {
+ tuples.add(LettuceConverters.toTuple(value));
+ }
+ return tuples;
+ }
+ };
SCORED_VALUE_TO_TUPLE = new Converter, Tuple>() {
public Tuple convert(ScoredValue source) {
return source != null ? new DefaultTuple(source.value, Double.valueOf(source.score)) : null;
}
-
};
BYTES_LIST_TO_TUPLE_LIST_CONVERTER = new Converter, List>() {
@@ -169,6 +199,10 @@ abstract public class LettuceConverters extends Converters {
}
};
+ PLUS_BYTES = toBytes("+");
+ MINUS_BYTES = toBytes("-");
+ POSITIVE_INFINITY_BYTES = toBytes("+inf");
+ NEGATIVE_INFINITY_BYTES = toBytes("-inf");
}
public static List toTuple(List list) {
@@ -217,6 +251,10 @@ abstract public class LettuceConverters extends Converters {
return SCORED_VALUES_TO_TUPLE_SET;
}
+ public static Converter>, List> scoredValuesToTupleList() {
+ return SCORED_VALUES_TO_TUPLE_LIST;
+ }
+
public static Converter, Tuple> scoredValueToTuple() {
return SCORED_VALUE_TO_TUPLE;
}
@@ -301,7 +339,7 @@ abstract public class LettuceConverters extends Converters {
return args;
}
if (params.getByPattern() != null) {
- args.by(new String(params.getByPattern(), Charsets.ASCII));
+ args.by(new String(params.getByPattern(), LettuceCharsets.ASCII));
}
if (params.getLimit() != null) {
args.limit(params.getLimit().getStart(), params.getLimit().getCount());
@@ -309,7 +347,7 @@ abstract public class LettuceConverters extends Converters {
if (params.getGetPattern() != null) {
byte[][] pattern = params.getGetPattern();
for (byte[] bs : pattern) {
- args.get(new String(bs, Charsets.ASCII));
+ args.get(new String(bs, LettuceCharsets.ASCII));
}
}
if (params.getOrder() != null) {
@@ -330,6 +368,17 @@ abstract public class LettuceConverters extends Converters {
return stringToRedisClientListConverter().convert(clientList);
}
+ public static byte[][] subarray(byte[][] input, int index) {
+
+ if (input.length > index) {
+ byte[][] output = new byte[input.length - index][];
+ System.arraycopy(input, index, output, 0, output.length);
+ return output;
+ }
+
+ return null;
+ }
+
public static String boundaryToStringForZRange(Boundary boundary, String defaultValue) {
if (boundary == null || boundary.getValue() == null) {
@@ -352,4 +401,126 @@ abstract public class LettuceConverters extends Converters {
return prefix + value;
}
+ /**
+ * @param source List of Maps containing node details from SENTINEL SLAVES or SENTINEL MASTERS. May be empty or
+ * {@literal null}.
+ * @return List of {@link RedisServer}'s. List is empty if List of Maps is empty.
+ * @since 1.5
+ */
+ public static List toListOfRedisServer(List