/*
* Copyright 2011-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.connection.lettuce;
import static io.lettuce.core.protocol.CommandType.*;
import io.lettuce.core.AbstractRedisClient;
import io.lettuce.core.LettuceFutures;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisException;
import io.lettuce.core.RedisFuture;
import io.lettuce.core.RedisURI;
import io.lettuce.core.ScanArgs;
import io.lettuce.core.TransactionResult;
import io.lettuce.core.api.StatefulConnection;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.api.sync.RedisCommands;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import io.lettuce.core.cluster.api.async.RedisClusterAsyncCommands;
import io.lettuce.core.cluster.api.sync.RedisClusterCommands;
import io.lettuce.core.codec.ByteArrayCodec;
import io.lettuce.core.codec.RedisCodec;
import io.lettuce.core.output.*;
import io.lettuce.core.protocol.Command;
import io.lettuce.core.protocol.CommandArgs;
import io.lettuce.core.protocol.CommandType;
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
import io.lettuce.core.sentinel.api.StatefulRedisSentinelConnection;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.BeanUtils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.QueryTimeoutException;
import org.springframework.data.redis.ExceptionTranslationStrategy;
import org.springframework.data.redis.FallbackExceptionTranslationStrategy;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.convert.TransactionResultConverter;
import org.springframework.data.redis.core.RedisCommand;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
* {@code RedisConnection} implementation on top of <a href="https://github.com/mp911de/lettuce">Lettuce</a> Redis
* client.
*
* @author Costin Leau
* @author Jennifer Hickey
* @author Christoph Strobl
* @author Thomas Darimont
* @author David Liu
* @author Mark Paluch
* @author Ninad Divadkar
*/
public class LettuceConnection extends AbstractRedisConnection {
static final RedisCodec<byte[], byte[]> CODEC = ByteArrayCodec.INSTANCE;
private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new FallbackExceptionTranslationStrategy(
LettuceConverters.exceptionConverter());
private static final TypeHints typeHints = new TypeHints();
private final int defaultDbIndex;
private int dbIndex;
private final StatefulConnection<byte[], byte[]> asyncSharedConn;
private StatefulConnection<byte[], byte[]> asyncDedicatedConn;
private final long timeout;
// refers only to main connection as pubsub happens on a different one
private boolean isClosed = false;
private boolean isMulti = false;
private boolean isPipelined = false;
private List<LettuceResult> ppline;
private Queue<FutureResult<?>> txResults = new LinkedList<>();
private AbstractRedisClient client;
private volatile LettuceSubscription subscription;
private LettucePool pool;
/** flag indicating whether the connection needs to be dropped or not */
private boolean broken = false;
private boolean convertPipelineAndTxResults = true;
@SuppressWarnings("rawtypes")
class LettuceResult extends FutureResult<io.lettuce.core.protocol.RedisCommand<?, ?, ?>> {
public <T> LettuceResult(Future<T> resultHolder, Converter<T, ?> converter) {
super((io.lettuce.core.protocol.RedisCommand) resultHolder, converter);
}
public LettuceResult(Future resultHolder) {
super((io.lettuce.core.protocol.RedisCommand) resultHolder);
}
@SuppressWarnings("unchecked")
@Override
public Object get() {
try {
if (convertPipelineAndTxResults && converter != null) {
return converter.convert(resultHolder.getOutput().get());
}
return resultHolder.getOutput().get();
} catch (Exception e) {
throw EXCEPTION_TRANSLATION.translate(e);
}
}
}
LettuceResult newLettuceResult(Future resultHolder) {
return new LettuceResult(resultHolder);
}
<T> LettuceResult newLettuceResult(Future resultHolder, Converter<T, ?> converter) {
return new LettuceResult(resultHolder, converter);
}
private class LettuceStatusResult extends LettuceResult {
@SuppressWarnings("rawtypes")
public LettuceStatusResult(Future resultHolder) {
super(resultHolder);
setStatus(true);
}
}
LettuceStatusResult newLettuceStatusResult(Future resultHolder) {
return new LettuceStatusResult(resultHolder);
}
class LettuceTxResult extends FutureResult<Object> {
public LettuceTxResult(Object resultHolder, Converter<?, ?> converter) {
super(resultHolder, converter);
}
public LettuceTxResult(Object resultHolder) {
super(resultHolder);
}
@SuppressWarnings("unchecked")
@Override
public Object get() {
if (convertPipelineAndTxResults && converter != null) {
return converter.convert(resultHolder);
}
return resultHolder;
}
}
LettuceTxResult newLettuceTxResult(Object resultHolder) {
return new LettuceTxResult(resultHolder);
}
LettuceTxResult newLettuceTxResult(Object resultHolder, Converter<?, ?> converter) {
return new LettuceTxResult(resultHolder, converter);
}
private class LettuceTxStatusResult extends LettuceTxResult {
public LettuceTxStatusResult(Object resultHolder) {
super(resultHolder);
setStatus(true);
}
}
LettuceTxStatusResult newLettuceTxStatusResult(Object resultHolder) {
return new LettuceTxStatusResult(resultHolder);
}
private class LettuceTransactionResultConverter<T> extends TransactionResultConverter<T> {
public LettuceTransactionResultConverter(Queue<FutureResult<T>> txResults,
Converter<Exception, DataAccessException> exceptionConverter) {
super(txResults, exceptionConverter);
}
@Override
public List<Object> convert(List<Object> execResults) {
// Lettuce Empty list means null (watched variable modified)
if (execResults.isEmpty()) {
return null;
}
return super.convert(execResults);
}
}
/**
* Instantiates a new lettuce connection.
*
* @param timeout The connection timeout (in milliseconds)
* @param client The {@link RedisClient} to use when instantiating a native connection
*/
public LettuceConnection(long timeout, RedisClient client) {
this(null, timeout, client, null);
}
/**
* Instantiates a new lettuce connection.
*
* @param timeout The connection timeout (in milliseconds) * @param client The {@link RedisClient} to use when
* instantiating a pub/sub connection
* @param pool The connection pool to use for all other native connections
*/
public LettuceConnection(long timeout, RedisClient client, LettucePool pool) {
this(null, timeout, client, pool);
}
/**
* Instantiates a new lettuce connection.
*
* @param sharedConnection A native connection that is shared with other {@link LettuceConnection}s. Will not be used
* for transactions or blocking operations
* @param timeout The connection timeout (in milliseconds)
* @param client The {@link RedisClient} to use when making pub/sub, blocking, and tx connections
*/
public LettuceConnection(StatefulRedisConnection<byte[], byte[]> sharedConnection, long timeout, RedisClient client) {
this(sharedConnection, timeout, client, null);
}
/**
* Instantiates a new lettuce connection.
*
* @param sharedConnection A native connection that is shared with other {@link LettuceConnection}s. Should not be
* used for transactions or blocking operations
* @param timeout The connection timeout (in milliseconds)
* @param client The {@link RedisClient} to use when making pub/sub connections
* @param pool The connection pool to use for blocking and tx operations
*/
public LettuceConnection(StatefulRedisConnection<byte[], byte[]> sharedConnection, long timeout, RedisClient client,
LettucePool pool) {
this(sharedConnection, timeout, client, pool, 0);
}
/**
* @param sharedConnection A native connection that is shared with other {@link LettuceConnection}s. Should not be
* used for transactions or blocking operations
* @param timeout The connection timeout (in milliseconds)
* @param client The {@link RedisClient} to use when making pub/sub connections.
* @param pool The connection pool to use for blocking and tx operations.
* @param defaultDbIndex The db index to use along with {@link RedisClient} when establishing a dedicated connection.
* @since 1.7
*/
public LettuceConnection(StatefulRedisConnection<byte[], byte[]> sharedConnection, long timeout,
AbstractRedisClient client, LettucePool pool, int defaultDbIndex) {
this.asyncSharedConn = sharedConnection;
this.timeout = timeout;
this.client = client;
this.pool = pool;
this.defaultDbIndex = defaultDbIndex;
this.dbIndex = this.defaultDbIndex;
}
protected DataAccessException convertLettuceAccessException(Exception ex) {
DataAccessException exception = EXCEPTION_TRANSLATION.translate(ex);
if (exception instanceof RedisConnectionFailureException) {
broken = true;
}
return exception;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConnection#geoCommands()
*/
@Override
public RedisGeoCommands geoCommands() {
return new LettuceGeoCommands(this);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConnection#hashCommands()
*/
@Override
public RedisHashCommands hashCommands() {
return new LettuceHashCommands(this);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConnection#hyperLogLogCommands()
*/
@Override
public RedisHyperLogLogCommands hyperLogLogCommands() {
return new LettuceHyperLogLogCommands(this);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConnection#keyCommands()
*/
@Override
public RedisKeyCommands keyCommands() {
return new LettuceKeyCommands(this);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConnection#listCommands()
*/
@Override
public RedisListCommands listCommands() {
return new LettuceListCommands(this);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConnection#setCommands()
*/
@Override
public RedisSetCommands setCommands() {
return new LettuceSetCommands(this);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConnection#scriptingCommands()
*/
@Override
public RedisScriptingCommands scriptingCommands() {
return new LettuceScriptingCommands(this);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConnection#stringCommands()
*/
@Override
public RedisStringCommands stringCommands() {
return new LettuceStringCommands(this);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConnection#serverCommands()
*/
@Override
public RedisServerCommands serverCommands() {
return new LettuceServerCommands(this);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConnection#zSetCommands()
*/
@Override
public RedisZSetCommands zSetCommands() {
return new LettuceZSetCommands(this);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private Object await(RedisFuture<?> cmd) {
if (isMulti) {
return null;
}
return LettuceFutures.awaitOrCancel(cmd, timeout, TimeUnit.MILLISECONDS);
}
@Override
public Object execute(String command, byte[]... args) {
return execute(command, null, args);
}
/**
* 'Native' or 'raw' execution of the given command along-side the given arguments.
*
* @param command Command to execute
* @param commandOutputTypeHint Type of Output to use, may be (may be {@literal null}).
* @param args Possible command arguments (may be {@literal null})
* @return execution result.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public Object execute(String command, CommandOutput commandOutputTypeHint, byte[]... args) {
Assert.hasText(command, "a valid command needs to be specified");
try {
String name = command.trim().toUpperCase();
CommandType commandType = CommandType.valueOf(name);
validateCommandIfRunningInTransactionMode(commandType, args);
CommandArgs<byte[], byte[]> cmdArg = new CommandArgs<>(CODEC);
if (!ObjectUtils.isEmpty(args)) {
cmdArg.addKeys(args);
}
RedisClusterAsyncCommands<byte[], byte[]> connectionImpl = getAsyncConnection();
CommandOutput expectedOutput = commandOutputTypeHint != null ? commandOutputTypeHint
: typeHints.getTypeHint(commandType);
Command cmd = new Command(commandType, expectedOutput, cmdArg);
if (isPipelined()) {
pipeline(new LettuceResult(connectionImpl.dispatch(cmd.getType(), cmd.getOutput(), cmd.getArgs())));
return null;
} else if (isQueueing()) {
transaction(new LettuceTxResult(connectionImpl.dispatch(cmd.getType(), cmd.getOutput(), cmd.getArgs())));
return null;
} else {
return await(connectionImpl.dispatch(cmd.getType(), cmd.getOutput(), cmd.getArgs()));
}
} catch (RedisException ex) {
throw convertLettuceAccessException(ex);
}
}
private void returnDedicatedAsyncConnection() {
if (pool != null) {
if (!broken) {
pool.returnResource(this.asyncDedicatedConn);
} else {
pool.returnBrokenResource(this.asyncDedicatedConn);
}
this.asyncDedicatedConn = null;
} else {
try {
asyncDedicatedConn.close();
} catch (RuntimeException ex) {
throw convertLettuceAccessException(ex);
}
}
}
public void close() throws DataAccessException {
super.close();
isClosed = true;
if (asyncDedicatedConn != null) {
returnDedicatedAsyncConnection();
}
if (subscription != null) {
if (subscription.isAlive()) {
subscription.doClose();
}
subscription = null;
}
this.dbIndex = defaultDbIndex;
}
public boolean isClosed() {
return isClosed && !isSubscribed();
}
public RedisClusterAsyncCommands<byte[], byte[]> getNativeConnection() {
return (subscription != null ? subscription.pubsub.async() : getAsyncConnection());
}
public boolean isQueueing() {
return isMulti;
}
public boolean isPipelined() {
return isPipelined;
}
public void openPipeline() {
if (!isPipelined) {
isPipelined = true;
ppline = new ArrayList<>();
}
}
public List<Object> closePipeline() {
if (isPipelined) {
isPipelined = false;
List<io.lettuce.core.protocol.RedisCommand<?, ?, ?>> futures = new ArrayList<>();
for (LettuceResult result : ppline) {
futures.add(result.getResultHolder());
}
try {
boolean done = LettuceFutures.awaitAll(timeout, TimeUnit.MILLISECONDS,
futures.toArray(new RedisFuture[futures.size()]));
List<Object> results = new ArrayList<>(futures.size());
Exception problem = null;
if (done) {
for (LettuceResult result : ppline) {
if (result.getResultHolder().getOutput().hasError()) {
Exception err = new InvalidDataAccessApiUsageException(result.getResultHolder().getOutput().getError());
// remember only the first error
if (problem == null) {
problem = err;
}
results.add(err);
} else if (!convertPipelineAndTxResults || !(result.isStatus())) {
try {
results.add(result.get());
} catch (DataAccessException e) {
if (problem == null) {
problem = e;
}
results.add(e);
}
}
}
}
ppline.clear();
if (problem != null) {
throw new RedisPipelineException(problem, results);
}
if (done) {
return results;
}
throw new RedisPipelineException(new QueryTimeoutException("Redis command timed out"));
} catch (Exception e) {
throw new RedisPipelineException(e);
}
}
return Collections.emptyList();
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisServerCommands#shutdown(org.springframework.data.redis.connection.RedisServerCommands.ShutdownOption)
*/
@Override
public byte[] echo(byte[] message) {
try {
if (isPipelined()) {
pipeline(new LettuceResult(getAsyncConnection().echo(message)));
return null;
}
if (isQueueing()) {
transaction(new LettuceTxResult(getConnection().echo(message)));
return null;
}
return getConnection().echo(message);
} catch (Exception ex) {
throw convertLettuceAccessException(ex);
}
}
public String ping() {
try {
if (isPipelined()) {
pipeline(new LettuceResult(getAsyncConnection().ping()));
return null;
}
if (isQueueing()) {
transaction(new LettuceTxResult(getConnection().ping()));
return null;
}
return getConnection().ping();
} catch (Exception ex) {
throw convertLettuceAccessException(ex);
}
}
public void discard() {
isMulti = false;
try {
if (isPipelined()) {
pipeline(new LettuceStatusResult(((RedisAsyncCommands) getAsyncDedicatedConnection()).discard()));
return;
}
((RedisCommands) getDedicatedConnection()).discard();
} catch (Exception ex) {
throw convertLettuceAccessException(ex);
} finally {
txResults.clear();
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public List<Object> exec() {
isMulti = false;
try {
if (isPipelined()) {
RedisFuture<TransactionResult> exec = ((RedisAsyncCommands) getAsyncDedicatedConnection()).exec();
LettuceTransactionResultConverter resultConverter = new LettuceTransactionResultConverter(
new LinkedList<FutureResult<?>>(txResults), LettuceConverters.exceptionConverter());
pipeline(new LettuceResult(exec,
source -> resultConverter.convert(LettuceConverters.transactionResultUnwrapper().convert(source))));
return null;
}
TransactionResult transactionResult = ((RedisCommands) getDedicatedConnection()).exec();
List<Object> results = LettuceConverters.transactionResultUnwrapper().convert(transactionResult);
return convertPipelineAndTxResults
? new LettuceTransactionResultConverter(txResults, LettuceConverters.exceptionConverter()).convert(results)
: results;
} catch (Exception ex) {
throw convertLettuceAccessException(ex);
} finally {
txResults.clear();
}
}
public void multi() {
if (isQueueing()) {
return;
}
isMulti = true;
try {
if (isPipelined()) {
((RedisAsyncCommands) getAsyncDedicatedConnection()).multi();
return;
}
((RedisCommands) getDedicatedConnection()).multi();
} catch (Exception ex) {
throw convertLettuceAccessException(ex);
}
}
public void select(int dbIndex) {
if (asyncSharedConn != null) {
throw new UnsupportedOperationException("Selecting a new database not supported due to shared connection. "
+ "Use separate ConnectionFactorys to work with multiple databases");
}
if (isPipelined()) {
throw new UnsupportedOperationException("Lettuce blocks for #select");
}
try {
this.dbIndex = dbIndex;
if (isQueueing()) {
transaction(new LettuceTxStatusResult(((RedisCommands) getAsyncConnection()).select(dbIndex)));
return;
}
((RedisCommands) getConnection()).select(dbIndex);
} catch (Exception ex) {
throw convertLettuceAccessException(ex);
}
}
public void unwatch() {
try {
if (isPipelined()) {
pipeline(new LettuceStatusResult(((RedisAsyncCommands) getAsyncDedicatedConnection()).unwatch()));
return;
}
if (isQueueing()) {
transaction(new LettuceTxStatusResult(((RedisAsyncCommands) getDedicatedConnection()).unwatch()));
return;
}
((RedisCommands) getDedicatedConnection()).unwatch();
} catch (Exception ex) {
throw convertLettuceAccessException(ex);
}
}
public void watch(byte[]... keys) {
if (isQueueing()) {
throw new UnsupportedOperationException();
}
try {
if (isPipelined()) {
pipeline(new LettuceStatusResult(((RedisAsyncCommands) getAsyncDedicatedConnection()).watch((Object[]) keys)));
return;
}
if (isQueueing()) {
transaction(new LettuceTxStatusResult(((RedisAsyncCommands) getDedicatedConnection()).watch((Object[]) keys)));
return;
}
((RedisCommands) getDedicatedConnection()).watch((Object[]) keys);
} catch (Exception ex) {
throw convertLettuceAccessException(ex);
}
}
//
// Pub/Sub functionality
//
public Long publish(byte[] channel, byte[] message) {
try {
if (isPipelined()) {
pipeline(new LettuceResult(getAsyncConnection().publish(channel, message)));
return null;
}
if (isQueueing()) {
transaction(new LettuceTxResult(getConnection().publish(channel, message)));
return null;
}
return getConnection().publish(channel, message);
} catch (Exception ex) {
throw convertLettuceAccessException(ex);
}
}
public Subscription getSubscription() {
return subscription;
}
public boolean isSubscribed() {
return (subscription != null && subscription.isAlive());
}
public void pSubscribe(MessageListener listener, byte[]... patterns) {
checkSubscription();
if (isQueueing()) {
throw new UnsupportedOperationException();
}
if (isPipelined()) {
throw new UnsupportedOperationException();
}
try {
subscription = new LettuceSubscription(listener, switchToPubSub());
subscription.pSubscribe(patterns);
} catch (Exception ex) {
throw convertLettuceAccessException(ex);
}
}
public void subscribe(MessageListener listener, byte[]... channels) {
checkSubscription();
if (isPipelined()) {
throw new UnsupportedOperationException();
}
try {
subscription = new LettuceSubscription(listener, switchToPubSub());
subscription.subscribe(channels);
} catch (Exception ex) {
throw convertLettuceAccessException(ex);
}
}
@SuppressWarnings("unchecked")
<T> T failsafeReadScanValues(List<?> source, @SuppressWarnings("rawtypes") Converter converter) {
try {
return (T) (converter != null ? converter.convert(source) : source);
} catch (IndexOutOfBoundsException e) {
// ignore this one
}
return null;
}
/**
* Specifies if pipelined and transaction results should be converted to the expected data type. If false, results of
* {@link #closePipeline()} and {@link #exec()} will be of the type returned by the Lettuce driver
*
* @param convertPipelineAndTxResults Whether or not to convert pipeline and tx results
*/
public void setConvertPipelineAndTxResults(boolean convertPipelineAndTxResults) {
this.convertPipelineAndTxResults = convertPipelineAndTxResults;
}
private void checkSubscription() {
if (isSubscribed()) {
throw new RedisSubscribedConnectionException(
"Connection already subscribed; use the connection Subscription to cancel or add new channels");
}
}
private StatefulRedisPubSubConnection<byte[], byte[]> switchToPubSub() {
close();
// open a pubsub one
return ((RedisClient) client).connectPubSub(CODEC);
// return ((RedisClient) client).connectPubSub(CODEC);
}
void pipeline(LettuceResult result) {
if (isQueueing()) {
transaction(result);
} else {
ppline.add(result);
}
}
void transaction(FutureResult<?> result) {
txResults.add(result);
}
RedisClusterAsyncCommands<byte[], byte[]> getAsyncConnection() {
if (isQueueing()) {
return getAsyncDedicatedConnection();
}
if (asyncSharedConn != null) {
if (asyncSharedConn instanceof StatefulRedisConnection) {
return ((StatefulRedisConnection<byte[], byte[]>) asyncSharedConn).async();
}
}
return getAsyncDedicatedConnection();
}
protected RedisClusterCommands<byte[], byte[]> getConnection() {
if (isQueueing()) {
return getDedicatedConnection();
}
if (asyncSharedConn != null) {
if (asyncSharedConn instanceof StatefulRedisConnection) {
return ((StatefulRedisConnection<byte[], byte[]>) asyncSharedConn).sync();
}
if (asyncSharedConn instanceof StatefulRedisClusterConnection) {
return ((StatefulRedisClusterConnection<byte[], byte[]>) asyncSharedConn).sync();
}
}
return getDedicatedConnection();
}
protected RedisClusterAsyncCommands<byte[], byte[]> getAsyncDedicatedConnection() {
if (asyncDedicatedConn == null) {
asyncDedicatedConn = doGetAsyncDedicatedConnection();
if (this.pool == null) {
if (asyncDedicatedConn instanceof StatefulRedisConnection) {
((StatefulRedisConnection<byte[], byte[]>) asyncDedicatedConn).sync().select(dbIndex);
}
}
}
if (asyncDedicatedConn instanceof StatefulRedisConnection) {
return ((StatefulRedisConnection<byte[], byte[]>) asyncDedicatedConn).async();
}
if (asyncDedicatedConn instanceof StatefulRedisClusterConnection) {
return ((StatefulRedisClusterConnection<byte[], byte[]>) asyncDedicatedConn).async();
}
throw new IllegalStateException(
String.format("%s is not a supported connection type.", asyncDedicatedConn.getClass().getName()));
}
protected StatefulConnection<byte[], byte[]> doGetAsyncDedicatedConnection() {
if (this.pool != null) {
return pool.getResource();
} else {
return ((RedisClient) client).connect(CODEC);
}
}
RedisClusterCommands<byte[], byte[]> getDedicatedConnection() {
if (asyncDedicatedConn == null) {
asyncDedicatedConn = doGetAsyncDedicatedConnection();
if (this.pool == null) {
if (asyncDedicatedConn instanceof StatefulRedisConnection) {
((StatefulRedisConnection<byte[], byte[]>) asyncDedicatedConn).sync().select(dbIndex);
}
}
}
if (asyncDedicatedConn instanceof StatefulRedisConnection) {
return ((StatefulRedisConnection<byte[], byte[]>) asyncDedicatedConn).sync();
}
if (asyncDedicatedConn instanceof StatefulRedisClusterConnection) {
return ((StatefulRedisClusterConnection<byte[], byte[]>) asyncDedicatedConn).sync();
}
throw new IllegalStateException(
String.format("%s is not a supported connection type.", asyncDedicatedConn.getClass().getName()));
}
io.lettuce.core.ScanCursor getScanCursor(long cursorId) {
return io.lettuce.core.ScanCursor.of(Long.toString(cursorId));
}
ScanArgs getScanArgs(ScanOptions options) {
if (options == null) {
return null;
}
ScanArgs scanArgs = new ScanArgs();
if (options.getPattern() != null) {
scanArgs.match(options.getPattern());
}
if (options.getCount() != null) {
scanArgs.limit(options.getCount());
}
return scanArgs;
}
private void validateCommandIfRunningInTransactionMode(CommandType cmd, byte[]... args) {
if (this.isQueueing()) {
validateCommand(cmd, args);
}
}
private void validateCommand(CommandType cmd, byte[]... args) {
RedisCommand redisCommand = RedisCommand.failsafeCommandLookup(cmd.name());
if (!RedisCommand.UNKNOWN.equals(redisCommand) && redisCommand.requiresArguments()) {
try {
redisCommand.validateArgumentCount(args != null ? args.length : 0);
} catch (IllegalArgumentException e) {
throw new InvalidDataAccessApiUsageException(String.format("Validation failed for %s command.", cmd), e);
}
}
}
@Override
protected boolean isActive(RedisNode node) {
if (node == null) {
return false;
}
StatefulRedisConnection<String, String> connection = null;
try {
connection = ((RedisClient) client).connect(getRedisURI(node));
return connection.sync().ping().equalsIgnoreCase("pong");
} catch (Exception e) {
return false;
} finally {
if (connection != null) {
connection.close();
}
}
}
private RedisURI getRedisURI(RedisNode node) {
return RedisURI.Builder.redis(node.getHost(), node.getPort()).build();
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.AbstractRedisConnection#getSentinelConnection(org.springframework.data.redis.connection.RedisNode)
*/
@Override
protected RedisSentinelConnection getSentinelConnection(RedisNode sentinel) {
StatefulRedisSentinelConnection<String, String> connection = ((RedisClient) client)
.connectSentinel(getRedisURI(sentinel));
return new LettuceSentinelConnection(connection);
}
/**
* {@link TypeHints} provide {@link CommandOutput} information for a given {@link CommandType}.
*
* @since 1.2.1
*/
static class TypeHints {
@SuppressWarnings("rawtypes") //
private static final Map<CommandType, Class<? extends CommandOutput>> COMMAND_OUTPUT_TYPE_MAPPING = new HashMap<>();
@SuppressWarnings("rawtypes") //
private static final Map<Class<?>, Constructor<CommandOutput>> CONSTRUCTORS = new ConcurrentHashMap<>();
{
// INTEGER
COMMAND_OUTPUT_TYPE_MAPPING.put(BITCOUNT, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(BITOP, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(DBSIZE, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(DECR, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(DECRBY, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(DEL, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(GETBIT, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(HDEL, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(HINCRBY, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(HLEN, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(INCR, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(INCRBY, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(LINSERT, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(LLEN, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(LPUSH, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(LPUSHX, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(LREM, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(PTTL, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(PUBLISH, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(RPUSH, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(RPUSHX, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SADD, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SCARD, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SDIFFSTORE, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SETBIT, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SETRANGE, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SINTERSTORE, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SREM, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SUNIONSTORE, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(STRLEN, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(TTL, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(ZADD, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(ZCOUNT, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(ZINTERSTORE, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(ZRANK, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(ZREM, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(ZREMRANGEBYRANK, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(ZREMRANGEBYSCORE, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(ZREVRANK, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(ZUNIONSTORE, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(PFCOUNT, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(PFMERGE, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(PFADD, IntegerOutput.class);
// DOUBLE
COMMAND_OUTPUT_TYPE_MAPPING.put(HINCRBYFLOAT, DoubleOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(INCRBYFLOAT, DoubleOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(MGET, ValueListOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(ZINCRBY, DoubleOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(ZSCORE, DoubleOutput.class);
// MAP
COMMAND_OUTPUT_TYPE_MAPPING.put(HGETALL, MapOutput.class);
// KEY LIST
COMMAND_OUTPUT_TYPE_MAPPING.put(HKEYS, KeyListOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(KEYS, KeyListOutput.class);
// KEY VALUE
COMMAND_OUTPUT_TYPE_MAPPING.put(BRPOP, KeyValueOutput.class);
// SINGLE VALUE
COMMAND_OUTPUT_TYPE_MAPPING.put(BRPOPLPUSH, ValueOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(ECHO, ValueOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(GET, ValueOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(GETRANGE, ValueOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(GETSET, ValueOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(HGET, ValueOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(LINDEX, ValueOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(LPOP, ValueOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(RANDOMKEY, ValueOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(RENAME, ValueOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(RPOP, ValueOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(RPOPLPUSH, ValueOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SPOP, ValueOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SRANDMEMBER, ValueOutput.class);
// STATUS VALUE
COMMAND_OUTPUT_TYPE_MAPPING.put(BGREWRITEAOF, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(BGSAVE, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(CLIENT, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(DEBUG, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(DISCARD, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(FLUSHALL, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(FLUSHDB, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(HMSET, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(INFO, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(LSET, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(LTRIM, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(MIGRATE, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(MSET, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(QUIT, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(RESTORE, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SAVE, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SELECT, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SET, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SETEX, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SHUTDOWN, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SLAVEOF, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SYNC, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(TYPE, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(WATCH, StatusOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(UNWATCH, StatusOutput.class);
// VALUE LIST
COMMAND_OUTPUT_TYPE_MAPPING.put(HMGET, ValueListOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(MGET, ValueListOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(HVALS, ValueListOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(LRANGE, ValueListOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SORT, ValueListOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(ZRANGE, ValueListOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(ZRANGEBYSCORE, ValueListOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(ZREVRANGE, ValueListOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(ZREVRANGEBYSCORE, ValueListOutput.class);
// BOOLEAN
COMMAND_OUTPUT_TYPE_MAPPING.put(EXISTS, BooleanOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(EXPIRE, BooleanOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(EXPIREAT, BooleanOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(HEXISTS, BooleanOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(HSET, BooleanOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(HSETNX, BooleanOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(MOVE, BooleanOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(MSETNX, BooleanOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(PERSIST, BooleanOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(PEXPIRE, BooleanOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(PEXPIREAT, BooleanOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(RENAMENX, BooleanOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SETNX, BooleanOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SISMEMBER, BooleanOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SMOVE, BooleanOutput.class);
// MULTI
COMMAND_OUTPUT_TYPE_MAPPING.put(EXEC, MultiOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(MULTI, MultiOutput.class);
// DATE
COMMAND_OUTPUT_TYPE_MAPPING.put(LASTSAVE, DateOutput.class);
// VALUE SET
COMMAND_OUTPUT_TYPE_MAPPING.put(SDIFF, ValueSetOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SINTER, ValueSetOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SMEMBERS, ValueSetOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(SUNION, ValueSetOutput.class);
}
/**
* Returns the {@link CommandOutput} mapped for given {@link CommandType} or {@link ByteArrayOutput} as default.
*
* @param type
* @return {@link ByteArrayOutput} as default when no matching {@link CommandOutput} available.
*/
@SuppressWarnings("rawtypes")
public CommandOutput getTypeHint(CommandType type) {
return getTypeHint(type, new ByteArrayOutput<>(CODEC));
}
/**
* Returns the {@link CommandOutput} mapped for given {@link CommandType} given {@link CommandOutput} as default.
*
* @param type
* @return
*/
@SuppressWarnings("rawtypes")
public CommandOutput getTypeHint(CommandType type, CommandOutput defaultType) {
if (type == null || !COMMAND_OUTPUT_TYPE_MAPPING.containsKey(type)) {
return defaultType;
}
CommandOutput<?, ?, ?> outputType = instanciateCommandOutput(COMMAND_OUTPUT_TYPE_MAPPING.get(type));
return outputType != null ? outputType : defaultType;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private CommandOutput<?, ?, ?> instanciateCommandOutput(Class<? extends CommandOutput> type) {
Assert.notNull(type, "Cannot create instance for 'null' type.");
Constructor<CommandOutput> constructor = CONSTRUCTORS.get(type);
if (constructor == null) {
constructor = (Constructor<CommandOutput>) ClassUtils.getConstructorIfAvailable(type, RedisCodec.class);
CONSTRUCTORS.put(type, constructor);
}
return BeanUtils.instantiateClass(constructor, CODEC);
}
}
}