Package org.springframework.data.redis.connection.lettuce

Source Code of org.springframework.data.redis.connection.lettuce.LettuceConnection$LettuceTxStatusResult

/*
* Copyright 2011-2014 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 com.lambdaworks.redis.protocol.CommandType.*;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Queue;
import java.util.Set;
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.AbstractRedisConnection;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.FutureResult;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisPipelineException;
import org.springframework.data.redis.connection.RedisSubscribedConnectionException;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.connection.SortParameters;
import org.springframework.data.redis.connection.Subscription;
import org.springframework.data.redis.connection.convert.Converters;
import org.springframework.data.redis.connection.convert.TransactionResultConverter;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.KeyBoundCursor;
import org.springframework.data.redis.core.RedisCommand;
import org.springframework.data.redis.core.ScanCursor;
import org.springframework.data.redis.core.ScanIteration;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;

import com.lambdaworks.redis.RedisAsyncConnection;
import com.lambdaworks.redis.RedisClient;
import com.lambdaworks.redis.RedisException;
import com.lambdaworks.redis.ScriptOutputType;
import com.lambdaworks.redis.SortArgs;
import com.lambdaworks.redis.ZStoreArgs;
import com.lambdaworks.redis.codec.RedisCodec;
import com.lambdaworks.redis.output.BooleanOutput;
import com.lambdaworks.redis.output.ByteArrayOutput;
import com.lambdaworks.redis.output.DateOutput;
import com.lambdaworks.redis.output.DoubleOutput;
import com.lambdaworks.redis.output.IntegerOutput;
import com.lambdaworks.redis.output.KeyListOutput;
import com.lambdaworks.redis.output.KeyValueOutput;
import com.lambdaworks.redis.output.MapOutput;
import com.lambdaworks.redis.output.MultiOutput;
import com.lambdaworks.redis.output.StatusOutput;
import com.lambdaworks.redis.output.ValueListOutput;
import com.lambdaworks.redis.output.ValueOutput;
import com.lambdaworks.redis.output.ValueSetOutput;
import com.lambdaworks.redis.protocol.Command;
import com.lambdaworks.redis.protocol.CommandArgs;
import com.lambdaworks.redis.protocol.CommandOutput;
import com.lambdaworks.redis.protocol.CommandType;
import com.lambdaworks.redis.pubsub.RedisPubSubConnection;

/**
* {@code RedisConnection} implementation on top of <a href="https://github.com/wg/lettuce">Lettuce</a> Redis client.
*
* @author Costin Leau
* @author Jennifer Hickey
* @author Christoph Strobl
* @author Thomas Darimont
* @author David Liu
*/
public class LettuceConnection extends AbstractRedisConnection {

  private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new FallbackExceptionTranslationStrategy(
      LettuceConverters.exceptionConverter());

  static final RedisCodec<byte[], byte[]> CODEC = new BytesRedisCodec();
  private static final TypeHints typeHints = new TypeHints();

  private final com.lambdaworks.redis.RedisAsyncConnection<byte[], byte[]> asyncSharedConn;
  private final com.lambdaworks.redis.RedisConnection<byte[], byte[]> sharedConn;
  private com.lambdaworks.redis.RedisAsyncConnection<byte[], byte[]> asyncDedicatedConn;
  private com.lambdaworks.redis.RedisConnection<byte[], byte[]> dedicatedConn;

  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<FutureResult<?>>();
  private RedisClient 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")
  private class LettuceResult extends FutureResult<Command<?, ?, ?>> {
    public <T> LettuceResult(Future<T> resultHolder, Converter<T, ?> converter) {
      super((Command) resultHolder, converter);
    }

    public LettuceResult(Future resultHolder) {
      super((Command) resultHolder);
    }

    @SuppressWarnings("unchecked")
    @Override
    public Object get() {
      if (convertPipelineAndTxResults && converter != null) {
        return converter.convert(resultHolder.get());
      }
      return resultHolder.get();
    }
  }

  private class LettuceStatusResult extends LettuceResult {
    @SuppressWarnings("rawtypes")
    public LettuceStatusResult(Future resultHolder) {
      super(resultHolder);
      setStatus(true);
    }
  }

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

  private class LettuceTxStatusResult extends LettuceTxResult {
    public LettuceTxStatusResult(Object resultHolder) {
      super(resultHolder);
      setStatus(true);
    }
  }

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

  private class LettuceEvalResultsConverter<T> implements Converter<Object, T> {
    private ReturnType returnType;

    public LettuceEvalResultsConverter(ReturnType returnType) {
      this.returnType = returnType;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public T convert(Object source) {
      if (returnType == ReturnType.MULTI) {
        List resultList = (List) source;
        for (Object obj : resultList) {
          if (obj instanceof Exception) {
            throw convertLettuceAccessException((Exception) obj);
          }
        }
      }
      return (T) source;
    }
  }

  /**
   * 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(com.lambdaworks.redis.RedisAsyncConnection<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(com.lambdaworks.redis.RedisAsyncConnection<byte[], byte[]> sharedConnection, long timeout,
      RedisClient client, LettucePool pool) {
    this.asyncSharedConn = sharedConnection;
    this.timeout = timeout;
    this.sharedConn = sharedConnection != null ? new com.lambdaworks.redis.RedisConnection<byte[], byte[]>(
        asyncSharedConn) : null;
    this.client = client;
    this.pool = pool;
  }

  protected DataAccessException convertLettuceAccessException(Exception ex) {

    DataAccessException exception = EXCEPTION_TRANSLATION.translate(ex);

    if (exception instanceof RedisConnectionFailureException) {
      broken = true;
    }
    return exception;
  }

  @SuppressWarnings({ "rawtypes", "unchecked" })
  private Object await(Command cmd) {
    if (isMulti && cmd.type != MULTI) {
      return null;
    }
    return getAsyncConnection().await(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.
   *
   * @see RedisCommands#execute(String, byte[]...)
   * @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 cmd = CommandType.valueOf(name);

      validateCommandIfRunningInTransactionMode(cmd, args);

      CommandArgs<byte[], byte[]> cmdArg = new CommandArgs<byte[], byte[]>(CODEC);
      if (!ObjectUtils.isEmpty(args)) {
        cmdArg.addKeys(args);
      }

      CommandOutput expectedOutput = commandOutputTypeHint != null ? commandOutputTypeHint : typeHints.getTypeHint(cmd);
      if (isPipelined()) {

        pipeline(new LettuceResult(getAsyncConnection().dispatch(cmd, expectedOutput, cmdArg)));
        return null;
      } else if (isQueueing()) {

        transaction(new LettuceTxResult(getAsyncConnection().dispatch(cmd, expectedOutput, cmdArg)));
        return null;
      } else {
        return await(getAsyncConnection().dispatch(cmd, expectedOutput, cmdArg));
      }
    } 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;
    }
  }

  public boolean isClosed() {
    return isClosed && !isSubscribed();
  }

  public RedisAsyncConnection<byte[], byte[]> getNativeConnection() {
    return (subscription != null ? subscription.pubsub : getAsyncConnection());
  }

  public boolean isQueueing() {
    return isMulti;
  }

  public boolean isPipelined() {
    return isPipelined;
  }

  public void openPipeline() {
    if (!isPipelined) {
      isPipelined = true;
      ppline = new ArrayList<LettuceResult>();
    }
  }

  public List<Object> closePipeline() {
    if (isPipelined) {
      isPipelined = false;
      List<Command<?, ?, ?>> futures = new ArrayList<Command<?, ?, ?>>();
      for (LettuceResult result : ppline) {
        futures.add(result.getResultHolder());
      }
      boolean done = getAsyncConnection().awaitAll(futures.toArray(new Command[futures.size()]));
      List<Object> results = new ArrayList<Object>(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"));
    }

    return Collections.emptyList();
  }

  public List<byte[]> sort(byte[] key, SortParameters params) {

    SortArgs args = LettuceConverters.toSortArgs(params);

    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().sort(key, args)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().sort(key, args)));
        return null;
      }
      return getConnection().sort(key, args);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long sort(byte[] key, SortParameters params, byte[] sortKey) {

    SortArgs args = LettuceConverters.toSortArgs(params);

    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().sortStore(key, args, sortKey)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().sortStore(key, args, sortKey)));
        return null;
      }
      return getConnection().sortStore(key, args, sortKey);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long dbSize() {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().dbsize()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().dbsize()));
        return null;
      }
      return getConnection().dbsize();
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void flushDb() {
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().flushdb()));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getConnection().flushdb()));
        return;
      }
      getConnection().flushdb();
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void flushAll() {
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().flushall()));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getConnection().flushall()));
        return;
      }
      getConnection().flushall();
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void bgSave() {
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().bgsave()));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getConnection().bgsave()));
        return;
      }
      getConnection().bgsave();
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void bgReWriteAof() {
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().bgrewriteaof()));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getConnection().bgrewriteaof()));
        return;
      }
      getConnection().bgrewriteaof();
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  /**
   * @deprecated As of 1.3, use {@link #bgReWriteAof}.
   */
  @Deprecated
  public void bgWriteAof() {
    bgReWriteAof();
  }

  public void save() {
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().save()));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getConnection().save()));
        return;
      }
      getConnection().save();
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public List<String> getConfig(String param) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().configGet(param)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().configGet(param)));
        return null;
      }
      return getConnection().configGet(param);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Properties info() {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().info(), LettuceConverters.stringToProps()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().info(), LettuceConverters.stringToProps()));
        return null;
      }
      return LettuceConverters.toProperties(getConnection().info());
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Properties info(String section) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().info(section), LettuceConverters.stringToProps()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().info(section), LettuceConverters.stringToProps()));
        return null;
      }
      return LettuceConverters.toProperties(getConnection().info(section));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long lastSave() {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().lastsave(), LettuceConverters.dateToLong()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().lastsave(), LettuceConverters.dateToLong()));
        return null;
      }
      return LettuceConverters.toLong(getConnection().lastsave());
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void setConfig(String param, String value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().configSet(param, value)));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getConnection().configSet(param, value)));
        return;
      }
      getConnection().configSet(param, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void resetConfigStats() {
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().configResetstat()));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getConnection().configResetstat()));
        return;
      }
      getConnection().configResetstat();
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void shutdown() {
    try {
      if (isPipelined()) {
        getAsyncConnection().shutdown(true);
        return;
      }
      getConnection().shutdown(true);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  /*
   * (non-Javadoc)
   * @see org.springframework.data.redis.connection.RedisServerCommands#shutdown(org.springframework.data.redis.connection.RedisServerCommands.ShutdownOption)
   */
  @Override
  public void shutdown(ShutdownOption option) {

    if (option == null) {
      shutdown();
      return;
    }

    boolean save = ShutdownOption.SAVE.equals(option);
    try {
      if (isPipelined()) {
        getAsyncConnection().shutdown(save);
        return;
      }
      getConnection().shutdown(save);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  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 Long del(byte[]... keys) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().del(keys)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().del(keys)));
        return null;
      }
      return getConnection().del(keys);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void discard() {
    isMulti = false;
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncDedicatedConnection().discard()));
        return;
      }
      getDedicatedConnection().discard();
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    } finally {
      txResults.clear();
    }
  }

  @SuppressWarnings({ "unchecked", "rawtypes" })
  public List<Object> exec() {
    isMulti = false;
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncDedicatedConnection().exec(), new LettuceTransactionResultConverter(
            new LinkedList<FutureResult<?>>(txResults), LettuceConverters.exceptionConverter())));
        return null;
      }
      List<Object> results = getDedicatedConnection().exec();
      return convertPipelineAndTxResults ? new LettuceTransactionResultConverter(txResults,
          LettuceConverters.exceptionConverter()).convert(results) : results;
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    } finally {
      txResults.clear();
    }
  }

  public Boolean exists(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().exists(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().exists(key)));
        return null;
      }
      return getConnection().exists(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Boolean expire(byte[] key, long seconds) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().expire(key, seconds)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().expire(key, seconds)));
        return null;
      }
      return getConnection().expire(key, seconds);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Boolean expireAt(byte[] key, long unixTime) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().expireat(key, unixTime)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().expireat(key, unixTime)));
        return null;
      }
      return getConnection().expireat(key, unixTime);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Boolean pExpire(byte[] key, long millis) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().pexpire(key, millis)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().pexpire(key, millis)));
        return null;
      }
      return getConnection().pexpire(key, millis);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Boolean pExpireAt(byte[] key, long unixTimeInMillis) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().pexpireat(key, unixTimeInMillis)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().pexpireat(key, unixTimeInMillis)));
        return null;
      }
      return getConnection().pexpireat(key, unixTimeInMillis);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long pTtl(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().pttl(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().pttl(key)));
        return null;
      }
      return getConnection().pttl(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public byte[] dump(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().dump(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().dump(key)));
        return null;
      }
      return getConnection().dump(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void restore(byte[] key, long ttlInMillis, byte[] serializedValue) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().restore(key, ttlInMillis, serializedValue)));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getConnection().restore(key, ttlInMillis, serializedValue)));
        return;
      }
      getConnection().restore(key, ttlInMillis, serializedValue);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Set<byte[]> keys(byte[] pattern) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().keys(pattern), LettuceConverters.bytesListToBytesSet()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().keys(pattern), LettuceConverters.bytesListToBytesSet()));
        return null;
      }
      return LettuceConverters.toBytesSet(getConnection().keys(pattern));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void multi() {
    if (isQueueing()) {
      return;
    }
    isMulti = true;
    try {
      if (isPipelined()) {
        getAsyncDedicatedConnection().multi();
        return;
      }
      getDedicatedConnection().multi();
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Boolean persist(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().persist(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().persist(key)));
        return null;
      }
      return getConnection().persist(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Boolean move(byte[] key, int dbIndex) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().move(key, dbIndex)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().move(key, dbIndex)));
        return null;
      }
      return getConnection().move(key, dbIndex);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public byte[] randomKey() {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().randomkey()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().randomkey()));
        return null;
      }
      return getConnection().randomkey();
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void rename(byte[] oldName, byte[] newName) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().rename(oldName, newName)));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getConnection().rename(oldName, newName)));
        return;
      }
      getConnection().rename(oldName, newName);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Boolean renameNX(byte[] oldName, byte[] newName) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().renamenx(oldName, newName)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().renamenx(oldName, newName)));
        return null;
      }
      return (getConnection().renamenx(oldName, newName));
    } 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 {
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getConnection().select(dbIndex)));
        return;
      }
      getConnection().select(dbIndex);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long ttl(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().ttl(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().ttl(key)));
        return null;
      }
      return getConnection().ttl(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public DataType type(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().type(key), LettuceConverters.stringToDataType()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().type(key), LettuceConverters.stringToDataType()));
        return null;
      }
      return LettuceConverters.toDataType(getConnection().type(key));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void unwatch() {
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncDedicatedConnection().unwatch()));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getDedicatedConnection().unwatch()));
        return;
      }
      getDedicatedConnection().unwatch();
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void watch(byte[]... keys) {
    if (isQueueing()) {
      throw new UnsupportedOperationException();
    }
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncDedicatedConnection().watch(keys)));
        return;
      }
      getDedicatedConnection().watch(keys);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  //
  // String commands
  //

  public byte[] get(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().get(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().get(key)));
        return null;
      }
      return getConnection().get(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void set(byte[] key, byte[] value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().set(key, value)));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getConnection().set(key, value)));
        return;
      }
      getConnection().set(key, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public byte[] getSet(byte[] key, byte[] value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().getset(key, value)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().getset(key, value)));
        return null;
      }
      return getConnection().getset(key, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long append(byte[] key, byte[] value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().append(key, value)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().append(key, value)));
        return null;
      }
      return getConnection().append(key, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public List<byte[]> mGet(byte[]... keys) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().mget(keys)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().mget(keys)));
        return null;
      }
      return getConnection().mget(keys);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void mSet(Map<byte[], byte[]> tuples) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().mset(tuples)));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getConnection().mset(tuples)));
        return;
      }
      getConnection().mset(tuples);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Boolean mSetNX(Map<byte[], byte[]> tuples) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().msetnx(tuples)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().msetnx(tuples)));
        return null;
      }
      return getConnection().msetnx(tuples);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void setEx(byte[] key, long time, byte[] value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().setex(key, time, value)));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getConnection().setex(key, time, value)));
        return;
      }
      getConnection().setex(key, time, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  /**
   * {@code pSetEx} is not directly supported and therefore emulated via {@literal lua script}.
   *
   * @since 1.3
   * @see org.springframework.data.redis.connection.RedisStringCommands#pSetEx(byte[], long, byte[])
   */
  @Override
  public void pSetEx(byte[] key, long milliseconds, byte[] value) {

    byte[] script = createRedisScriptForPSetEx(key, milliseconds, value);
    byte[][] emptyArgs = new byte[0][0];

    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().eval(script, ScriptOutputType.STATUS, emptyArgs,
            emptyArgs)));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getConnection().eval(script, ScriptOutputType.STATUS, emptyArgs,
            emptyArgs)));
        return;
      }
      this.eval(script, ReturnType.STATUS, 0);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  private byte[] createRedisScriptForPSetEx(byte[] key, long milliseconds, byte[] value) {

    StringBuilder sb = new StringBuilder("return redis.call('PSETEX'");
    sb.append(",'");
    sb.append(new String(key));
    sb.append("',");
    sb.append(milliseconds);
    sb.append(",'");
    sb.append(new String(value));
    sb.append("')");

    return sb.toString().getBytes();
  }

  public Boolean setNX(byte[] key, byte[] value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().setnx(key, value)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().setnx(key, value)));
        return null;
      }
      return getConnection().setnx(key, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public byte[] getRange(byte[] key, long start, long end) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().getrange(key, start, end)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().getrange(key, start, end)));
        return null;
      }
      return getConnection().getrange(key, start, end);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long decr(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().decr(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().decr(key)));
        return null;
      }
      return getConnection().decr(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long decrBy(byte[] key, long value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().decrby(key, value)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().decrby(key, value)));
        return null;
      }
      return getConnection().decrby(key, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long incr(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().incr(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().incr(key)));
        return null;
      }
      return getConnection().incr(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long incrBy(byte[] key, long value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().incrby(key, value)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().incrby(key, value)));
        return null;
      }
      return getConnection().incrby(key, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Double incrBy(byte[] key, double value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().incrbyfloat(key, value)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().incrbyfloat(key, value)));
        return null;
      }
      return getConnection().incrbyfloat(key, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Boolean getBit(byte[] key, long offset) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().getbit(key, offset), LettuceConverters.longToBoolean()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().getbit(key, offset), LettuceConverters.longToBoolean()));
        return null;
      }
      return LettuceConverters.toBoolean(getConnection().getbit(key, offset));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Boolean setBit(byte[] key, long offset, boolean value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().setbit(key, offset, LettuceConverters.toInt(value)),
            LettuceConverters.longToBooleanConverter()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().setbit(key, offset, LettuceConverters.toInt(value)),
            LettuceConverters.longToBooleanConverter()));
        return null;
      }
      return LettuceConverters.longToBooleanConverter().convert(
          getConnection().setbit(key, offset, LettuceConverters.toInt(value)));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void setRange(byte[] key, byte[] value, long start) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().setrange(key, start, value)));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getConnection().setrange(key, start, value)));
        return;
      }
      getConnection().setrange(key, start, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long strLen(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().strlen(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().strlen(key)));
        return null;
      }
      return getConnection().strlen(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long bitCount(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().bitcount(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().bitcount(key)));
        return null;
      }
      return getConnection().bitcount(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long bitCount(byte[] key, long begin, long end) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().bitcount(key, begin, end)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().bitcount(key, begin, end)));
        return null;
      }
      return getConnection().bitcount(key, begin, end);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long bitOp(BitOperation op, byte[] destination, byte[]... keys) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(asyncBitOp(op, destination, keys)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(syncBitOp(op, destination, keys)));
        return null;
      }
      return syncBitOp(op, destination, keys);
    } catch (UnsupportedOperationException ex) {
      throw ex;
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  //
  // List commands
  //

  public Long lPush(byte[] key, byte[]... values) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().lpush(key, values)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().lpush(key, values)));
        return null;
      }
      return getConnection().lpush(key, values);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long rPush(byte[] key, byte[]... values) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().rpush(key, values)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().rpush(key, values)));
        return null;
      }
      return getConnection().rpush(key, values);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public List<byte[]> bLPop(int timeout, byte[]... keys) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncDedicatedConnection().blpop(timeout, keys),
            LettuceConverters.keyValueToBytesList()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getDedicatedConnection().blpop(timeout, keys),
            LettuceConverters.keyValueToBytesList()));
        return null;
      }
      return LettuceConverters.toBytesList(getDedicatedConnection().blpop(timeout, keys));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public List<byte[]> bRPop(int timeout, byte[]... keys) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncDedicatedConnection().brpop(timeout, keys),
            LettuceConverters.keyValueToBytesList()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getDedicatedConnection().brpop(timeout, keys),
            LettuceConverters.keyValueToBytesList()));
        return null;
      }
      return LettuceConverters.toBytesList(getDedicatedConnection().brpop(timeout, keys));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public byte[] lIndex(byte[] key, long index) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().lindex(key, index)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().lindex(key, index)));
        return null;
      }
      return getConnection().lindex(key, index);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long lInsert(byte[] key, Position where, byte[] pivot, byte[] value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().linsert(key, LettuceConverters.toBoolean(where), pivot, value)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().linsert(key, LettuceConverters.toBoolean(where), pivot, value)));
        return null;
      }
      return getConnection().linsert(key, LettuceConverters.toBoolean(where), pivot, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long lLen(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().llen(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().llen(key)));
        return null;
      }
      return getConnection().llen(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public byte[] lPop(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().lpop(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().lpop(key)));
        return null;
      }
      return getConnection().lpop(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public List<byte[]> lRange(byte[] key, long start, long end) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().lrange(key, start, end)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().lrange(key, start, end)));
        return null;
      }
      return getConnection().lrange(key, start, end);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long lRem(byte[] key, long count, byte[] value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().lrem(key, count, value)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().lrem(key, count, value)));
        return null;
      }
      return getConnection().lrem(key, count, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void lSet(byte[] key, long index, byte[] value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().lset(key, index, value)));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getConnection().lset(key, index, value)));
        return;
      }
      getConnection().lset(key, index, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void lTrim(byte[] key, long start, long end) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().ltrim(key, start, end)));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getConnection().ltrim(key, start, end)));
        return;
      }
      getConnection().ltrim(key, start, end);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public byte[] rPop(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().rpop(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().rpop(key)));
        return null;
      }
      return getConnection().rpop(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public byte[] rPopLPush(byte[] srcKey, byte[] dstKey) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().rpoplpush(srcKey, dstKey)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().rpoplpush(srcKey, dstKey)));
        return null;
      }
      return getConnection().rpoplpush(srcKey, dstKey);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public byte[] bRPopLPush(int timeout, byte[] srcKey, byte[] dstKey) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncDedicatedConnection().brpoplpush(timeout, srcKey, dstKey)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getDedicatedConnection().brpoplpush(timeout, srcKey, dstKey)));
        return null;
      }
      return getDedicatedConnection().brpoplpush(timeout, srcKey, dstKey);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long lPushX(byte[] key, byte[] value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().lpushx(key, value)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().lpushx(key, value)));
        return null;
      }
      return getConnection().lpushx(key, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long rPushX(byte[] key, byte[] value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().rpushx(key, value)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().rpushx(key, value)));
        return null;
      }
      return getConnection().rpushx(key, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  //
  // Set commands
  //

  public Long sAdd(byte[] key, byte[]... values) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().sadd(key, values)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().sadd(key, values)));
        return null;
      }
      return getConnection().sadd(key, values);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long sCard(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().scard(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().scard(key)));
        return null;
      }
      return getConnection().scard(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Set<byte[]> sDiff(byte[]... keys) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().sdiff(keys)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().sdiff(keys)));
        return null;
      }
      return getConnection().sdiff(keys);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long sDiffStore(byte[] destKey, byte[]... keys) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().sdiffstore(destKey, keys)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().sdiffstore(destKey, keys)));
        return null;
      }
      return getConnection().sdiffstore(destKey, keys);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Set<byte[]> sInter(byte[]... keys) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().sinter(keys)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().sinter(keys)));
        return null;
      }
      return getConnection().sinter(keys);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long sInterStore(byte[] destKey, byte[]... keys) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().sinterstore(destKey, keys)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().sinterstore(destKey, keys)));
        return null;
      }
      return getConnection().sinterstore(destKey, keys);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Boolean sIsMember(byte[] key, byte[] value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().sismember(key, value)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().sismember(key, value)));
        return null;
      }
      return getConnection().sismember(key, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Set<byte[]> sMembers(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().smembers(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().smembers(key)));
        return null;
      }
      return getConnection().smembers(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Boolean sMove(byte[] srcKey, byte[] destKey, byte[] value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().smove(srcKey, destKey, value)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().smove(srcKey, destKey, value)));
        return null;
      }
      return getConnection().smove(srcKey, destKey, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public byte[] sPop(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().spop(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().spop(key)));
        return null;
      }
      return getConnection().spop(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public byte[] sRandMember(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().srandmember(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().srandmember(key)));
        return null;
      }
      return getConnection().srandmember(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public List<byte[]> sRandMember(byte[] key, long count) {
    if (count < 0) {
      throw new UnsupportedOperationException("sRandMember with a negative count is not supported");
    }
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().srandmember(key, count),
            LettuceConverters.bytesSetToBytesList()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().srandmember(key, count),
            LettuceConverters.bytesSetToBytesList()));
        return null;
      }
      return LettuceConverters.toBytesList(getConnection().srandmember(key, count));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long sRem(byte[] key, byte[]... values) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().srem(key, values)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().srem(key, values)));
        return null;
      }
      return getConnection().srem(key, values);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Set<byte[]> sUnion(byte[]... keys) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().sunion(keys)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().sunion(keys)));
        return null;
      }
      return getConnection().sunion(keys);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long sUnionStore(byte[] destKey, byte[]... keys) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().sunionstore(destKey, keys)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().sunionstore(destKey, keys)));
        return null;
      }
      return getConnection().sunionstore(destKey, keys);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  //
  // ZSet commands
  //

  public Boolean zAdd(byte[] key, double score, byte[] value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zadd(key, score, value), LettuceConverters.longToBoolean()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zadd(key, score, value), LettuceConverters.longToBoolean()));
        return null;
      }
      return LettuceConverters.toBoolean(getConnection().zadd(key, score, value));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long zAdd(byte[] key, Set<Tuple> tuples) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zadd(key, LettuceConverters.toObjects(tuples).toArray())));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zadd(key, LettuceConverters.toObjects(tuples).toArray())));
        return null;
      }
      return getConnection().zadd(key, LettuceConverters.toObjects(tuples).toArray());
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long zCard(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zcard(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zcard(key)));
        return null;
      }
      return getConnection().zcard(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long zCount(byte[] key, double min, double max) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zcount(key, min, max)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zcount(key, min, max)));
        return null;
      }
      return getConnection().zcount(key, min, max);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Double zIncrBy(byte[] key, double increment, byte[] value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zincrby(key, increment, value)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zincrby(key, increment, value)));
        return null;
      }
      return getConnection().zincrby(key, increment, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long zInterStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets) {
    ZStoreArgs storeArgs = zStoreArgs(aggregate, weights);

    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zinterstore(destKey, storeArgs, sets)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zinterstore(destKey, storeArgs, sets)));
        return null;
      }
      return getConnection().zinterstore(destKey, storeArgs, sets);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long zInterStore(byte[] destKey, byte[]... sets) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zinterstore(destKey, sets)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zinterstore(destKey, sets)));
        return null;
      }
      return getConnection().zinterstore(destKey, sets);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Set<byte[]> zRange(byte[] key, long start, long end) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zrange(key, start, end),
            LettuceConverters.bytesListToBytesSet()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zrange(key, start, end),
            LettuceConverters.bytesListToBytesSet()));
        return null;
      }
      return LettuceConverters.toBytesSet(getConnection().zrange(key, start, end));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Set<Tuple> zRangeWithScores(byte[] key, long start, long end) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zrangeWithScores(key, start, end),
            LettuceConverters.scoredValuesToTupleSet()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zrangeWithScores(key, start, end),
            LettuceConverters.scoredValuesToTupleSet()));
        return null;
      }
      return LettuceConverters.toTupleSet(getConnection().zrangeWithScores(key, start, end));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Set<byte[]> zRangeByScore(byte[] key, double min, double max) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zrangebyscore(key, min, max),
            LettuceConverters.bytesListToBytesSet()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zrangebyscore(key, min, max),
            LettuceConverters.bytesListToBytesSet()));
        return null;
      }
      return LettuceConverters.toBytesSet(getConnection().zrangebyscore(key, min, max));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Set<Tuple> zRangeByScoreWithScores(byte[] key, double min, double max) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zrangebyscoreWithScores(key, min, max),
            LettuceConverters.scoredValuesToTupleSet()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zrangebyscoreWithScores(key, min, max),
            LettuceConverters.scoredValuesToTupleSet()));
        return null;
      }
      return LettuceConverters.toTupleSet(getConnection().zrangebyscoreWithScores(key, min, max));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Set<Tuple> zRevRangeWithScores(byte[] key, long start, long end) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zrevrangeWithScores(key, start, end),
            LettuceConverters.scoredValuesToTupleSet()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zrevrangeWithScores(key, start, end),
            LettuceConverters.scoredValuesToTupleSet()));
        return null;
      }
      return LettuceConverters.toTupleSet(getConnection().zrevrangeWithScores(key, start, end));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Set<byte[]> zRangeByScore(byte[] key, double min, double max, long offset, long count) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zrangebyscore(key, min, max, offset, count),
            LettuceConverters.bytesListToBytesSet()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zrangebyscore(key, min, max, offset, count),
            LettuceConverters.bytesListToBytesSet()));
        return null;
      }
      return LettuceConverters.toBytesSet(getConnection().zrangebyscore(key, min, max, offset, count));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Set<Tuple> zRangeByScoreWithScores(byte[] key, double min, double max, long offset, long count) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zrangebyscoreWithScores(key, min, max, offset, count),
            LettuceConverters.scoredValuesToTupleSet()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zrangebyscoreWithScores(key, min, max, offset, count),
            LettuceConverters.scoredValuesToTupleSet()));
        return null;
      }
      return LettuceConverters.toTupleSet(getConnection().zrangebyscoreWithScores(key, min, max, offset, count));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Set<byte[]> zRevRangeByScore(byte[] key, double min, double max, long offset, long count) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zrevrangebyscore(key, max, min, offset, count),
            LettuceConverters.bytesListToBytesSet()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zrevrangebyscore(key, max, min, offset, count),
            LettuceConverters.bytesListToBytesSet()));
        return null;
      }
      return LettuceConverters.toBytesSet(getConnection().zrevrangebyscore(key, max, min, offset, count));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Set<byte[]> zRevRangeByScore(byte[] key, double min, double max) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zrevrangebyscore(key, max, min),
            LettuceConverters.bytesListToBytesSet()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zrevrangebyscore(key, max, min),
            LettuceConverters.bytesListToBytesSet()));
        return null;
      }
      return LettuceConverters.toBytesSet(getConnection().zrevrangebyscore(key, max, min));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Set<Tuple> zRevRangeByScoreWithScores(byte[] key, double min, double max, long offset, long count) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zrevrangebyscoreWithScores(key, max, min, offset, count),
            LettuceConverters.scoredValuesToTupleSet()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zrevrangebyscoreWithScores(key, max, min, offset, count),
            LettuceConverters.scoredValuesToTupleSet()));
        return null;
      }
      return LettuceConverters.toTupleSet(getConnection().zrevrangebyscoreWithScores(key, max, min, offset, count));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Set<Tuple> zRevRangeByScoreWithScores(byte[] key, double min, double max) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zrevrangebyscoreWithScores(key, max, min),
            LettuceConverters.scoredValuesToTupleSet()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zrevrangebyscoreWithScores(key, max, min),
            LettuceConverters.scoredValuesToTupleSet()));
        return null;
      }
      return LettuceConverters.toTupleSet(getConnection().zrevrangebyscoreWithScores(key, max, min));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long zRank(byte[] key, byte[] value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zrank(key, value)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zrank(key, value)));
        return null;
      }
      return getConnection().zrank(key, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long zRem(byte[] key, byte[]... values) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zrem(key, values)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zrem(key, values)));
        return null;
      }
      return getConnection().zrem(key, values);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long zRemRange(byte[] key, long start, long end) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zremrangebyrank(key, start, end)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zremrangebyrank(key, start, end)));
        return null;
      }
      return getConnection().zremrangebyrank(key, start, end);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long zRemRangeByScore(byte[] key, double min, double max) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zremrangebyscore(key, min, max)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zremrangebyscore(key, min, max)));
        return null;
      }
      return getConnection().zremrangebyscore(key, min, max);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Set<byte[]> zRevRange(byte[] key, long start, long end) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zrevrange(key, start, end),
            LettuceConverters.bytesListToBytesSet()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zrevrange(key, start, end),
            LettuceConverters.bytesListToBytesSet()));
        return null;
      }
      return LettuceConverters.toBytesSet(getConnection().zrevrange(key, start, end));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long zRevRank(byte[] key, byte[] value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zrevrank(key, value)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zrevrank(key, value)));
        return null;
      }
      return getConnection().zrevrank(key, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Double zScore(byte[] key, byte[] value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zscore(key, value)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zscore(key, value)));
        return null;
      }
      return getConnection().zscore(key, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long zUnionStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets) {
    ZStoreArgs storeArgs = zStoreArgs(aggregate, weights);

    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zunionstore(destKey, storeArgs, sets)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zunionstore(destKey, storeArgs, sets)));
        return null;
      }
      return getConnection().zunionstore(destKey, storeArgs, sets);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long zUnionStore(byte[] destKey, byte[]... sets) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zunionstore(destKey, sets)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zunionstore(destKey, sets)));
        return null;
      }
      return getConnection().zunionstore(destKey, sets);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  //
  // Hash commands
  //

  public Boolean hSet(byte[] key, byte[] field, byte[] value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().hset(key, field, value)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().hset(key, field, value)));
        return null;
      }
      return getConnection().hset(key, field, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Boolean hSetNX(byte[] key, byte[] field, byte[] value) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().hsetnx(key, field, value)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().hsetnx(key, field, value)));
        return null;
      }
      return getConnection().hsetnx(key, field, value);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long hDel(byte[] key, byte[]... fields) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().hdel(key, fields)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().hdel(key, fields)));
        return null;
      }
      return getConnection().hdel(key, fields);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Boolean hExists(byte[] key, byte[] field) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().hexists(key, field)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().hexists(key, field)));
        return null;
      }
      return getConnection().hexists(key, field);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public byte[] hGet(byte[] key, byte[] field) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().hget(key, field)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().hget(key, field)));
        return null;
      }
      return getConnection().hget(key, field);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Map<byte[], byte[]> hGetAll(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().hgetall(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().hgetall(key)));
        return null;
      }
      return getConnection().hgetall(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long hIncrBy(byte[] key, byte[] field, long delta) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().hincrby(key, field, delta)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().hincrby(key, field, delta)));
        return null;
      }
      return getConnection().hincrby(key, field, delta);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Double hIncrBy(byte[] key, byte[] field, double delta) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().hincrbyfloat(key, field, delta)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().hincrbyfloat(key, field, delta)));
        return null;
      }
      return getConnection().hincrbyfloat(key, field, delta);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Set<byte[]> hKeys(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().hkeys(key), LettuceConverters.bytesListToBytesSet()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().hkeys(key), LettuceConverters.bytesListToBytesSet()));
        return null;
      }
      return LettuceConverters.toBytesSet(getConnection().hkeys(key));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public Long hLen(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().hlen(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().hlen(key)));
        return null;
      }
      return getConnection().hlen(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public List<byte[]> hMGet(byte[] key, byte[]... fields) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().hmget(key, fields)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().hmget(key, fields)));
        return null;
      }
      return getConnection().hmget(key, fields);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void hMSet(byte[] key, Map<byte[], byte[]> tuple) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().hmset(key, tuple)));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getConnection().hmset(key, tuple)));
        return;
      }
      getConnection().hmset(key, tuple);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public List<byte[]> hVals(byte[] key) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().hvals(key)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().hvals(key)));
        return null;
      }
      return getConnection().hvals(key);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  //
  // Scripting commands
  //

  public void scriptFlush() {
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().scriptFlush()));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getConnection().scriptFlush()));
        return;
      }
      getConnection().scriptFlush();
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public void scriptKill() {
    if (isQueueing()) {
      throw new UnsupportedOperationException("Script kill not permitted in a transaction");
    }
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().scriptKill()));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxStatusResult(getConnection().scriptKill()));
        return;
      }
      getConnection().scriptKill();
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public String scriptLoad(byte[] script) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().scriptLoad(script)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().scriptLoad(script)));
        return null;
      }
      return getConnection().scriptLoad(script);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public List<Boolean> scriptExists(String... scriptSha1) {
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().scriptExists(scriptSha1)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().scriptExists(scriptSha1)));
        return null;
      }
      return getConnection().scriptExists(scriptSha1);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public <T> T eval(byte[] script, ReturnType returnType, int numKeys, byte[]... keysAndArgs) {
    try {
      byte[][] keys = extractScriptKeys(numKeys, keysAndArgs);
      byte[][] args = extractScriptArgs(numKeys, keysAndArgs);

      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().eval(script, LettuceConverters.toScriptOutputType(returnType),
            keys, args), new LettuceEvalResultsConverter<T>(returnType)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().eval(script, LettuceConverters.toScriptOutputType(returnType),
            keys, args), new LettuceEvalResultsConverter<T>(returnType)));
        return null;
      }
      return new LettuceEvalResultsConverter<T>(returnType).convert(getConnection().eval(script,
          LettuceConverters.toScriptOutputType(returnType), keys, args));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public <T> T evalSha(String scriptSha1, ReturnType returnType, int numKeys, byte[]... keysAndArgs) {
    try {
      byte[][] keys = extractScriptKeys(numKeys, keysAndArgs);
      byte[][] args = extractScriptArgs(numKeys, keysAndArgs);

      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().evalsha(scriptSha1,
            LettuceConverters.toScriptOutputType(returnType), keys, args), new LettuceEvalResultsConverter<T>(
            returnType)));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().evalsha(scriptSha1,
            LettuceConverters.toScriptOutputType(returnType), keys, args), new LettuceEvalResultsConverter<T>(
            returnType)));
        return null;
      }
      return new LettuceEvalResultsConverter<T>(returnType).convert(getConnection().evalsha(scriptSha1,
          LettuceConverters.toScriptOutputType(returnType), keys, args));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  public <T> T evalSha(byte[] scriptSha1, ReturnType returnType, int numKeys, byte[]... keysAndArgs) {
    return evalSha(LettuceConverters.toString(scriptSha1), returnType, numKeys, keysAndArgs);
  }

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

  /*
   * (non-Javadoc)
   * @see org.springframework.data.redis.connection.RedisServerCommands#time()
   */
  @Override
  public Long time() {

    /*
     * We have to emulate the time command via eval script since the current driver version doesn't
     * support the time command natively.
     * see https://github.com/wg/lettuce/issues/19
     */
    List<byte[]> result = (List<byte[]>) eval("return redis.call('TIME')".getBytes(), ReturnType.MULTI, 0);

    Assert.notEmpty(result, "Received invalid result from server. Expected 2 items in collection.");
    Assert.isTrue(result.size() == 2, "Received invalid nr of arguments from redis server. Expected 2 received "
        + result.size());

    return Converters.toTimeMillis(new String(result.get(0)), new String(result.get(1)));
  }

  @Override
  public void killClient(String host, int port) {

    Assert.hasText(host, "Host for 'CLIENT KILL' must not be 'null' or 'empty'.");

    String client = String.format("%s:%s", host, port);
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().clientKill(client)));
        return;
      }
      getConnection().clientKill(client);
    } catch (Exception e) {
      convertLettuceAccessException(e);
    }
  }

  @Override
  public void setClientName(byte[] name) {

    if (isQueueing()) {
      pipeline(new LettuceStatusResult(getAsyncConnection().clientSetname(name)));
      return;
    }
    if (isQueueing()) {
      transaction(new LettuceTxResult(getConnection().clientSetname(name)));
      return;
    }

    getAsyncConnection().clientSetname(name);
  }

  /*
  * (non-Javadoc)
  * @see org.springframework.data.redis.connection.RedisServerCommands#slaveOf(java.lang.String, int)
  */
  @Override
  public void slaveOf(String host, int port) {

    Assert.hasText(host, "Host must not be null for 'SLAVEOF' command.");
    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().slaveof(host, port)));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().slaveof(host, port)));
        return;
      }
      getConnection().slaveof(host, port);
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  /*
   * (non-Javadoc)
   * @see org.springframework.data.redis.connection.RedisServerCommands#getClientName()
   */
  @Override
  public String getClientName() {

    try {

      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().clientGetname(), LettuceConverters.bytesToString()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().clientGetname(), LettuceConverters.bytesToString()));
        return null;
      }

      return LettuceConverters.toString(getConnection().clientGetname());
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  @Override
  public List<RedisClientInfo> getClientList() {

    if (isPipelined()) {
      throw new UnsupportedOperationException("Cannot be called in pipeline mode.");
    }
    if (isQueueing()) {
      transaction(new LettuceTxResult(getAsyncConnection().clientList(),
          LettuceConverters.stringToRedisClientListConverter()));
      return null;
    }

    return LettuceConverters.toListOfRedisClientInformation(getConnection().clientList());
  }

  /*
    * @see org.springframework.data.redis.connection.RedisServerCommands#slaveOfNoOne()
   */
  @Override
  public void slaveOfNoOne() {

    try {
      if (isPipelined()) {
        pipeline(new LettuceStatusResult(getAsyncConnection().slaveofNoOne()));
        return;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().slaveofNoOne()));
        return;
      }
      getConnection().slaveofNoOne();
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  /**
   * @since 1.4
   * @return
   */
  public Cursor<byte[]> scan() {
    return scan(0, ScanOptions.NONE);
  }

  /*
   * (non-Javadoc)
   * @see org.springframework.data.redis.connection.RedisKeyCommands#scan(org.springframework.data.redis.core.ScanOptions)
   */
  public Cursor<byte[]> scan(ScanOptions options) {
    return scan(0, options != null ? options : ScanOptions.NONE);
  }

  /**
   * @since 1.4
   * @param cursorId
   * @param options
   * @return
   */
  public Cursor<byte[]> scan(long cursorId, ScanOptions options) {

    return new ScanCursor<byte[]>(cursorId, options) {

      @SuppressWarnings("unchecked")
      @Override
      protected ScanIteration<byte[]> doScan(long cursorId, ScanOptions options) {

        if (isQueueing() || isPipelined()) {
          throw new UnsupportedOperationException("'SCAN' cannot be called in pipeline / transaction mode.");
        }

        String params = " ," + cursorId + options.toOptionString();
        String script = "return redis.call('SCAN'" + params + ")";

        List<?> result = eval(script.getBytes(), ReturnType.MULTI, 0);
        String nextCursorId = LettuceConverters.bytesToString().convert((byte[]) result.get(0));

        return new ScanIteration<byte[]>(Long.valueOf(nextCursorId), ((List<byte[]>) result.get(1)));
      }
    }.open();

  }

  /* (non-Javadoc)
   * @see org.springframework.data.redis.connection.RedisHashCommands#hScan(byte[], org.springframework.data.redis.core.ScanOptions)
   */
  @Override
  public Cursor<Entry<byte[], byte[]>> hScan(byte[] key, ScanOptions options) {
    return hScan(key, 0, options);
  }

  /**
   * @since 1.4
   * @param key
   * @param cursorId
   * @param options
   * @return
   */
  public Cursor<Entry<byte[], byte[]>> hScan(byte[] key, long cursorId, ScanOptions options) {

    return new KeyBoundCursor<Entry<byte[], byte[]>>(key, cursorId, options) {

      @Override
      protected ScanIteration<Entry<byte[], byte[]>> doScan(byte[] key, long cursorId, ScanOptions options) {

        if (isQueueing() || isPipelined()) {
          throw new UnsupportedOperationException("'HSCAN' cannot be called in pipeline / transaction mode.");
        }

        byte[] script = createBinaryLuaScriptForScan("HSCAN", key, cursorId, options);
        List<?> result = eval(script, ReturnType.MULTI, 0);
        String nextCursorId = LettuceConverters.bytesToString().convert((byte[]) result.get(0));

        Map<byte[], byte[]> values = failsafeReadScanValues(result, LettuceConverters.bytesListToMapConverter());
        return new ScanIteration<Entry<byte[], byte[]>>(Long.valueOf(nextCursorId), values.entrySet());
      }

    }.open();
  }

  private byte[] createBinaryLuaScriptForScan(String command, byte[] key, long cursorId, ScanOptions options) {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

    try {

      outputStream.write(("return redis.call('" + command + "'").getBytes("UTF-8"));
      outputStream.write(", '".getBytes("UTF-8"));
      writeFilteredKey(key, outputStream);
      outputStream.write("', ".getBytes("UTF-8"));
      outputStream.write(Long.toString(cursorId).getBytes("UTF-8"));
      outputStream.write(options.toOptionString().getBytes("UTF-8"));
      outputStream.write(")".getBytes("UTF-8"));
    } catch (IOException e) {
      e.printStackTrace();
    }

    return outputStream.toByteArray();
  }

  private void writeFilteredKey(byte[] key, OutputStream stream) {
    byte toBeFiltered = (byte) '\r';
    for (byte b : key) {
      try {
        if (toBeFiltered == b) {
          stream.write("\\r".getBytes());
        } else {
          stream.write(b);
        }
      } catch (IOException e) {
        throw new IllegalArgumentException("Cannot convert key to suitable redis key for lua", e);
      }
    }
  }

  /*
   * (non-Javadoc)
   * @see org.springframework.data.redis.connection.RedisSetCommands#sScan(byte[], org.springframework.data.redis.core.ScanOptions)
   */
  @Override
  public Cursor<byte[]> sScan(byte[] key, ScanOptions options) {
    return sScan(key, 0, options);
  }

  /**
   * @since 1.4
   * @param key
   * @param cursorId
   * @param options
   * @return
   */
  public Cursor<byte[]> sScan(byte[] key, long cursorId, ScanOptions options) {

    return new KeyBoundCursor<byte[]>(key, cursorId, options) {

      @Override
      protected ScanIteration<byte[]> doScan(byte[] key, long cursorId, ScanOptions options) {

        if (isQueueing() || isPipelined()) {
          throw new UnsupportedOperationException("'SSCAN' cannot be called in pipeline / transaction mode.");
        }

        byte[] script = createBinaryLuaScriptForScan("SSCAN", key, cursorId, options);
        List<?> result = eval(script, ReturnType.MULTI, 0);
        String nextCursorId = LettuceConverters.bytesToString().convert((byte[]) result.get(0));

        List<byte[]> values = failsafeReadScanValues(result, null);
        return new ScanIteration<byte[]>(Long.valueOf(nextCursorId), values);
      }
    }.open();
  }

  /*
   * (non-Javadoc)
   * @see org.springframework.data.redis.connection.RedisZSetCommands#zScan(byte[], org.springframework.data.redis.core.ScanOptions)
   */
  @Override
  public Cursor<Tuple> zScan(byte[] key, ScanOptions options) {
    return zScan(key, 0L, options);
  }

  /**
   * @since 1.4
   * @param key
   * @param cursorId
   * @param options
   * @return
   */
  public Cursor<Tuple> zScan(byte[] key, long cursorId, ScanOptions options) {

    return new KeyBoundCursor<Tuple>(key, cursorId, options) {

      @Override
      protected ScanIteration<Tuple> doScan(byte[] key, long cursorId, ScanOptions options) {

        if (isQueueing() || isPipelined()) {
          throw new UnsupportedOperationException("'ZSCAN' cannot be called in pipeline / transaction mode.");
        }

        byte[] script = createBinaryLuaScriptForScan("ZSCAN", key, cursorId, options);
        List<?> result = eval(script, ReturnType.MULTI, 0);

        String nextCursorId = LettuceConverters.bytesToString().convert((byte[]) result.get(0));

        List<Tuple> values = failsafeReadScanValues(result, LettuceConverters.bytesListToTupleListConverter());
        return new ScanIteration<Tuple>(Long.valueOf(nextCursorId), values);
      }
    }.open();
  }

  @SuppressWarnings("unchecked")
  private <T> T failsafeReadScanValues(List<?> source, @SuppressWarnings("rawtypes") Converter converter) {

    try {
      return (T) (converter != null ? converter.convert(source.get(1)) : source.get(1));
    } 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 RedisPubSubConnection<byte[], byte[]> switchToPubSub() {
    close();
    // open a pubsub one
    return client.connectPubSub(CODEC);
  }

  private void pipeline(LettuceResult result) {
    if (isQueueing()) {
      transaction(result);
    } else {
      ppline.add(result);
    }
  }

  private void transaction(FutureResult<?> result) {
    txResults.add(result);
  }

  private RedisAsyncConnection<byte[], byte[]> getAsyncConnection() {
    if (isQueueing()) {
      return getAsyncDedicatedConnection();
    }
    if (asyncSharedConn != null) {
      return asyncSharedConn;
    }
    return getAsyncDedicatedConnection();
  }

  private com.lambdaworks.redis.RedisConnection<byte[], byte[]> getConnection() {
    if (isQueueing()) {
      return getDedicatedConnection();
    }
    if (sharedConn != null) {
      return sharedConn;
    }
    return getDedicatedConnection();
  }

  private RedisAsyncConnection<byte[], byte[]> getAsyncDedicatedConnection() {
    if (asyncDedicatedConn == null) {
      if (this.pool != null) {
        this.asyncDedicatedConn = pool.getResource();
      } else {
        this.asyncDedicatedConn = client.connectAsync(CODEC);
      }
    }
    return asyncDedicatedConn;
  }

  private com.lambdaworks.redis.RedisConnection<byte[], byte[]> getDedicatedConnection() {
    if (dedicatedConn == null) {
      this.dedicatedConn = new com.lambdaworks.redis.RedisConnection<byte[], byte[]>(getAsyncDedicatedConnection());
    }
    return dedicatedConn;
  }

  private Future<Long> asyncBitOp(BitOperation op, byte[] destination, byte[]... keys) {
    switch (op) {
      case AND:
        return getAsyncConnection().bitopAnd(destination, keys);
      case OR:
        return getAsyncConnection().bitopOr(destination, keys);
      case XOR:
        return getAsyncConnection().bitopXor(destination, keys);
      case NOT:
        if (keys.length != 1) {
          throw new UnsupportedOperationException("Bitop NOT should only be performed against one key");
        }
        return getAsyncConnection().bitopNot(destination, keys[0]);
      default:
        throw new UnsupportedOperationException("Bit operation " + op + " is not supported");
    }
  }

  private Long syncBitOp(BitOperation op, byte[] destination, byte[]... keys) {
    switch (op) {
      case AND:
        return getConnection().bitopAnd(destination, keys);
      case OR:
        return getConnection().bitopOr(destination, keys);
      case XOR:
        return getConnection().bitopXor(destination, keys);
      case NOT:
        if (keys.length != 1) {
          throw new UnsupportedOperationException("Bitop NOT should only be performed against one key");
        }
        return getConnection().bitopNot(destination, keys[0]);
      default:
        throw new UnsupportedOperationException("Bit operation " + op + " is not supported");
    }
  }

  private byte[][] extractScriptKeys(int numKeys, byte[]... keysAndArgs) {
    if (numKeys > 0) {
      return Arrays.copyOfRange(keysAndArgs, 0, numKeys);
    }
    return new byte[0][0];
  }

  private byte[][] extractScriptArgs(int numKeys, byte[]... keysAndArgs) {
    if (keysAndArgs.length > numKeys) {
      return Arrays.copyOfRange(keysAndArgs, numKeys, keysAndArgs.length);
    }
    return new byte[0][0];
  }

  private ZStoreArgs zStoreArgs(Aggregate aggregate, int[] weights) {
    ZStoreArgs args = new ZStoreArgs();
    if (aggregate != null) {
      switch (aggregate) {
        case MIN:
          args.min();
          break;
        case MAX:
          args.max();
          break;
        default:
          args.sum();
          break;
      }
    }
    long[] lg = new long[weights.length];
    for (int i = 0; i < lg.length; i++) {
      lg[i] = (long) weights[i];
    }
    args.weights(lg);
    return args;
  }

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

  /**
   * {@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<CommandType, Class<? extends CommandOutput>>();

    @SuppressWarnings("rawtypes")//
    private static final Map<Class<?>, Constructor<CommandOutput>> CONSTRUCTORS = new ConcurrentHashMap<Class<?>, Constructor<CommandOutput>>();

    {
      // 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);

      // 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<byte[], byte[]>(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);
    }
  }

  @Override
  public Set<byte[]> zRangeByScore(byte[] key, String min, String max) {
   
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zrangebyscore(key, min, max),
            LettuceConverters.bytesListToBytesSet()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zrangebyscore(key, min, max),
            LettuceConverters.bytesListToBytesSet()));
        return null;
      }
      return LettuceConverters.toBytesSet(getConnection().zrangebyscore(key, min, max));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }

  @Override
  public Set<byte[]> zRangeByScore(byte[] key, String min, String max, long offset, long count) {
   
    try {
      if (isPipelined()) {
        pipeline(new LettuceResult(getAsyncConnection().zrangebyscore(key, min, max, offset, count),
            LettuceConverters.bytesListToBytesSet()));
        return null;
      }
      if (isQueueing()) {
        transaction(new LettuceTxResult(getConnection().zrangebyscore(key, min, max, offset, count),
            LettuceConverters.bytesListToBytesSet()));
        return null;
      }
      return LettuceConverters.toBytesSet(getConnection().zrangebyscore(key, min, max, offset, count));
    } catch (Exception ex) {
      throw convertLettuceAccessException(ex);
    }
  }
 
}
TOP

Related Classes of org.springframework.data.redis.connection.lettuce.LettuceConnection$LettuceTxStatusResult

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.