org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory.java

Source

/*
 * Copyright 2011-2019 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
 *
 *      https://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 org.springframework.data.redis.connection.lettuce.LettuceConnection.*;

import io.lettuce.core.AbstractRedisClient;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.ReadFrom;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisException;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulConnection;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import io.lettuce.core.codec.RedisCodec;
import io.lettuce.core.resource.ClientResources;
import lombok.RequiredArgsConstructor;

import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.ExceptionTranslationStrategy;
import org.springframework.data.redis.PassThroughExceptionTranslationStrategy;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.RedisConfiguration.ClusterConfiguration;
import org.springframework.data.redis.connection.RedisConfiguration.DomainSocketConfiguration;
import org.springframework.data.redis.connection.RedisConfiguration.WithDatabaseIndex;
import org.springframework.data.redis.connection.RedisConfiguration.WithPassword;
import org.springframework.data.util.Optionals;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
 * Connection factory creating <a href="https://github.com/mp911de/lettuce">Lettuce</a>-based connections.
 * <p>
 * This factory creates a new {@link LettuceConnection} on each call to {@link #getConnection()}. Multiple
 * {@link LettuceConnection}s share a single thread-safe native connection by default.
 * <p>
 * The shared native connection is never closed by {@link LettuceConnection}, therefore it is not validated by default
 * on {@link #getConnection()}. Use {@link #setValidateConnection(boolean)} to change this behavior if necessary. Inject
 * a {@link Pool} to pool dedicated connections. If shareNativeConnection is true, the pool will be used to select a
 * connection for blocking and tx operations only, which should not share a connection. If native connection sharing is
 * disabled, the selected connection will be used for all operations.
 * <p>
 * {@link LettuceConnectionFactory} should be configured using an environmental configuration and the
 * {@link LettuceConnectionFactory client configuration}. Lettuce supports the following environmental configurations:
 * <ul>
 * <li>{@link RedisStandaloneConfiguration}</li>
 * <li>{@link RedisStaticMasterReplicaConfiguration}</li>
 * <li>{@link RedisSocketConfiguration}</li>
 * <li>{@link RedisSentinelConfiguration}</li>
 * <li>{@link RedisClusterConfiguration}</li>
 * </ul>
 *
 * @author Costin Leau
 * @author Jennifer Hickey
 * @author Thomas Darimont
 * @author Christoph Strobl
 * @author Mark Paluch
 * @author Balzs Nmeth
 * @author Ruben Cervilla
 * @author Luis De Bello
 */
public class LettuceConnectionFactory
        implements InitializingBean, DisposableBean, RedisConnectionFactory, ReactiveRedisConnectionFactory {

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

    private final Log log = LogFactory.getLog(getClass());
    private final LettuceClientConfiguration clientConfiguration;

    private @Nullable AbstractRedisClient client;
    private @Nullable LettuceConnectionProvider connectionProvider;
    private @Nullable LettuceConnectionProvider reactiveConnectionProvider;
    private boolean validateConnection = false;
    private boolean shareNativeConnection = true;
    private boolean eagerInitialization = false;
    private @Nullable SharedConnection<byte[]> connection;
    private @Nullable SharedConnection<ByteBuffer> reactiveConnection;
    private @Nullable LettucePool pool;
    /** Synchronization monitor for the shared Connection */
    private final Object connectionMonitor = new Object();
    private boolean convertPipelineAndTxResults = true;

    private RedisStandaloneConfiguration standaloneConfig = new RedisStandaloneConfiguration("localhost", 6379);

    private @Nullable RedisConfiguration configuration;

    private @Nullable ClusterCommandExecutor clusterCommandExecutor;

    /**
     * Constructs a new {@link LettuceConnectionFactory} instance with default settings.
     */
    public LettuceConnectionFactory() {
        this(new MutableLettuceClientConfiguration());
    }

    /**
     * Constructs a new {@link LettuceConnectionFactory} instance with default settings.
     */
    public LettuceConnectionFactory(RedisStandaloneConfiguration configuration) {
        this(configuration, new MutableLettuceClientConfiguration());
    }

    /**
     * Constructs a new {@link LettuceConnectionFactory} instance given {@link LettuceClientConfiguration}.
     *
     * @param clientConfig must not be {@literal null}
     * @since 2.0
     */
    private LettuceConnectionFactory(LettuceClientConfiguration clientConfig) {

        Assert.notNull(clientConfig, "LettuceClientConfiguration must not be null!");

        this.clientConfiguration = clientConfig;
        this.configuration = this.standaloneConfig;
    }

    /**
     * Constructs a new {@link LettuceConnectionFactory} instance with default settings.
     */
    public LettuceConnectionFactory(String host, int port) {
        this(new RedisStandaloneConfiguration(host, port), new MutableLettuceClientConfiguration());
    }

    /**
     * Constructs a new {@link LettuceConnectionFactory} instance using the given {@link RedisSocketConfiguration}.
     *
     * @param redisConfiguration must not be {@literal null}.
     * @since 2.1
     */
    public LettuceConnectionFactory(RedisConfiguration redisConfiguration) {
        this(redisConfiguration, new MutableLettuceClientConfiguration());
    }

    /**
     * Constructs a new {@link LettuceConnectionFactory} instance using the given {@link RedisSentinelConfiguration}.
     *
     * @param sentinelConfiguration must not be {@literal null}.
     * @since 1.6
     */
    public LettuceConnectionFactory(RedisSentinelConfiguration sentinelConfiguration) {
        this(sentinelConfiguration, new MutableLettuceClientConfiguration());
    }

    /**
     * Constructs a new {@link LettuceConnectionFactory} instance using the given {@link RedisClusterConfiguration}
     * applied to create a {@link RedisClusterClient}.
     *
     * @param clusterConfiguration must not be {@literal null}.
     * @since 1.7
     */
    public LettuceConnectionFactory(RedisClusterConfiguration clusterConfiguration) {
        this(clusterConfiguration, new MutableLettuceClientConfiguration());
    }

    /**
     * @param pool
     * @deprecated since 2.0, use pooling via {@link LettucePoolingClientConfiguration}.
     */
    @Deprecated
    public LettuceConnectionFactory(LettucePool pool) {

        this(new MutableLettuceClientConfiguration());
        this.pool = pool;
    }

    /**
     * Constructs a new {@link LettuceConnectionFactory} instance using the given {@link RedisStandaloneConfiguration} and
     * {@link LettuceClientConfiguration}.
     *
     * @param standaloneConfig must not be {@literal null}.
     * @param clientConfig must not be {@literal null}.
     * @since 2.0
     */
    public LettuceConnectionFactory(RedisStandaloneConfiguration standaloneConfig,
            LettuceClientConfiguration clientConfig) {

        this(clientConfig);

        Assert.notNull(standaloneConfig, "RedisStandaloneConfiguration must not be null!");

        this.standaloneConfig = standaloneConfig;
        this.configuration = this.standaloneConfig;
    }

    /**
     * Constructs a new {@link LettuceConnectionFactory} instance using the given
     * {@link RedisStaticMasterReplicaConfiguration} and {@link LettuceClientConfiguration}.
     *
     * @param redisConfiguration must not be {@literal null}.
     * @param clientConfig must not be {@literal null}.
     * @since 2.1
     */
    public LettuceConnectionFactory(RedisConfiguration redisConfiguration,
            LettuceClientConfiguration clientConfig) {

        this(clientConfig);

        Assert.notNull(redisConfiguration, "RedisConfiguration must not be null!");

        this.configuration = redisConfiguration;
    }

    /**
     * Constructs a new {@link LettuceConnectionFactory} instance using the given {@link RedisSentinelConfiguration} and
     * {@link LettuceClientConfiguration}.
     *
     * @param sentinelConfiguration must not be {@literal null}.
     * @param clientConfig must not be {@literal null}.
     * @since 2.0
     */
    public LettuceConnectionFactory(RedisSentinelConfiguration sentinelConfiguration,
            LettuceClientConfiguration clientConfig) {

        this(clientConfig);

        Assert.notNull(sentinelConfiguration, "RedisSentinelConfiguration must not be null!");

        this.configuration = sentinelConfiguration;
    }

    /**
     * Constructs a new {@link LettuceConnectionFactory} instance using the given {@link RedisClusterConfiguration} and
     * {@link LettuceClientConfiguration}.
     *
     * @param clusterConfiguration must not be {@literal null}.
     * @param clientConfig must not be {@literal null}.
     * @since 2.0
     */
    public LettuceConnectionFactory(RedisClusterConfiguration clusterConfiguration,
            LettuceClientConfiguration clientConfig) {

        this(clientConfig);

        Assert.notNull(clusterConfiguration, "RedisClusterConfiguration must not be null!");

        this.configuration = clusterConfiguration;
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    public void afterPropertiesSet() {

        this.client = createClient();

        this.connectionProvider = createConnectionProvider(client, CODEC);
        this.reactiveConnectionProvider = createConnectionProvider(client, LettuceReactiveRedisConnection.CODEC);

        if (isClusterAware()) {

            this.clusterCommandExecutor = new ClusterCommandExecutor(
                    new LettuceClusterTopologyProvider((RedisClusterClient) client),
                    new LettuceClusterConnection.LettuceClusterNodeResourceProvider(this.connectionProvider),
                    EXCEPTION_TRANSLATION);
        }

        if (getEagerInitialization() && getShareNativeConnection()) {
            initConnection();
        }
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.beans.factory.DisposableBean#destroy()
     */
    public void destroy() {

        resetConnection();

        if (connectionProvider instanceof DisposableBean) {
            try {
                ((DisposableBean) connectionProvider).destroy();
            } catch (Exception e) {

                if (log.isWarnEnabled()) {
                    log.warn(connectionProvider + " did not shut down gracefully.", e);
                }
            }
        }

        try {
            Duration quietPeriod = clientConfiguration.getShutdownQuietPeriod();
            Duration timeout = clientConfiguration.getShutdownTimeout();
            client.shutdown(quietPeriod.toMillis(), timeout.toMillis(), TimeUnit.MILLISECONDS);
        } catch (Exception e) {

            if (log.isWarnEnabled()) {
                log.warn((client != null ? ClassUtils.getShortName(client.getClass()) : "LettuceClient")
                        + " did not shut down gracefully.", e);
            }
        }

        if (clusterCommandExecutor != null) {

            try {
                clusterCommandExecutor.destroy();
            } catch (Exception ex) {
                log.warn("Cannot properly close cluster command executor", ex);
            }
        }
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.redis.connection.RedisConnectionFactory#getConnection()
     */
    public RedisConnection getConnection() {

        if (isClusterAware()) {
            return getClusterConnection();
        }

        LettuceConnection connection;
        connection = doCreateLettuceConnection(getSharedConnection(), connectionProvider, getTimeout(),
                getDatabase());
        connection.setConvertPipelineAndTxResults(convertPipelineAndTxResults);
        return connection;
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.redis.connection.RedisConnectionFactory#getClusterConnection()
     */
    @Override
    public RedisClusterConnection getClusterConnection() {

        if (!isClusterAware()) {
            throw new InvalidDataAccessApiUsageException("Cluster is not configured!");
        }

        RedisClusterClient clusterClient = (RedisClusterClient) client;

        StatefulRedisClusterConnection<byte[], byte[]> sharedConnection = getShareNativeConnection()
                ? (StatefulRedisClusterConnection<byte[], byte[]>) getOrCreateSharedConnection().getConnection()
                : null;

        LettuceClusterTopologyProvider topologyProvider = new LettuceClusterTopologyProvider(clusterClient);
        return doCreateLettuceClusterConnection(sharedConnection, connectionProvider, topologyProvider,
                clusterCommandExecutor, clientConfiguration.getCommandTimeout());
    }

    /**
     * Customization hook for {@link LettuceConnection} creation.
     *
     * @param sharedConnection the shared {@link StatefulRedisConnection} if {@link #getShareNativeConnection()} is
     *          {@literal true}; {@literal null} otherwise.
     * @param connectionProvider the {@link LettuceConnectionProvider} to release connections.
     * @param timeout command timeout in {@link TimeUnit#MILLISECONDS}.
     * @param database database index to operate on.
     * @return the {@link LettuceConnection}.
     * @throws IllegalArgumentException if a required parameter is {@literal null}.
     * @since 2.2
     */
    protected LettuceConnection doCreateLettuceConnection(
            @Nullable StatefulRedisConnection<byte[], byte[]> sharedConnection,
            LettuceConnectionProvider connectionProvider, long timeout, int database) {

        return new LettuceConnection(sharedConnection, connectionProvider, timeout, database);
    }

    /**
     * Customization hook for {@link LettuceClusterConnection} creation.
     *
     * @param sharedConnection the shared {@link StatefulRedisConnection} if {@link #getShareNativeConnection()} is
     *          {@literal true}; {@literal null} otherwise.
     * @param connectionProvider the {@link LettuceConnectionProvider} to release connections.
     * @param topologyProvider the {@link ClusterTopologyProvider}.
     * @param clusterCommandExecutor the {@link ClusterCommandExecutor} to release connections.
     * @param commandTimeout command timeout {@link Duration}.
     * @return the {@link LettuceConnection}.
     * @throws IllegalArgumentException if a required parameter is {@literal null}.
     * @since 2.2
     */
    protected LettuceClusterConnection doCreateLettuceClusterConnection(
            @Nullable StatefulRedisClusterConnection<byte[], byte[]> sharedConnection,
            LettuceConnectionProvider connectionProvider, ClusterTopologyProvider topologyProvider,
            ClusterCommandExecutor clusterCommandExecutor, Duration commandTimeout) {

        return new LettuceClusterConnection(sharedConnection, connectionProvider, topologyProvider,
                clusterCommandExecutor, commandTimeout);
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.redis.connection.ReactiveRedisConnectionFactory#getReactiveConnection()
     */
    @Override
    public LettuceReactiveRedisConnection getReactiveConnection() {

        return getShareNativeConnection()
                ? new LettuceReactiveRedisConnection(getSharedReactiveConnection(), reactiveConnectionProvider)
                : new LettuceReactiveRedisConnection(reactiveConnectionProvider);
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.redis.connection.ReactiveRedisConnectionFactory#getReactiveClusterConnection()
     */
    @Override
    public LettuceReactiveRedisClusterConnection getReactiveClusterConnection() {

        if (!isClusterAware()) {
            throw new InvalidDataAccessApiUsageException("Cluster is not configured!");
        }

        RedisClusterClient client = (RedisClusterClient) this.client;

        return getShareNativeConnection()
                ? new LettuceReactiveRedisClusterConnection(getSharedReactiveConnection(),
                        reactiveConnectionProvider, client)
                : new LettuceReactiveRedisClusterConnection(reactiveConnectionProvider, client);
    }

    /**
     * Initialize the shared connection if {@link #getShareNativeConnection() native connection sharing} is enabled and
     * reset any previously existing connection.
     */
    public void initConnection() {

        resetConnection();

        getSharedConnection();
        getSharedReactiveConnection();
    }

    /**
     * Reset the underlying shared Connection, to be reinitialized on next access.
     */
    public void resetConnection() {

        Optionals.toStream(Optional.ofNullable(connection), Optional.ofNullable(reactiveConnection))
                .forEach(SharedConnection::resetConnection);

        synchronized (this.connectionMonitor) {

            this.connection = null;
            this.reactiveConnection = null;
        }
    }

    /**
     * Validate the shared connections and reinitialize if invalid.
     */
    public void validateConnection() {

        getOrCreateSharedConnection().validateConnection();
        getOrCreateSharedReactiveConnection().validateConnection();
    }

    private SharedConnection<byte[]> getOrCreateSharedConnection() {

        synchronized (this.connectionMonitor) {

            if (this.connection == null) {
                this.connection = new SharedConnection<>(connectionProvider);
            }

            return this.connection;
        }
    }

    private SharedConnection<ByteBuffer> getOrCreateSharedReactiveConnection() {

        synchronized (this.connectionMonitor) {

            if (this.reactiveConnection == null) {
                this.reactiveConnection = new SharedConnection<>(reactiveConnectionProvider);
            }

            return this.reactiveConnection;
        }
    }

    public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
        return EXCEPTION_TRANSLATION.translate(ex);
    }

    /**
     * Returns the current host.
     *
     * @return the host.
     */
    public String getHostName() {
        return RedisConfiguration.getHostOrElse(configuration, standaloneConfig::getHostName);
    }

    /**
     * Sets the hostname.
     *
     * @param hostName the hostname to set.
     * @deprecated since 2.0, configure the hostname using {@link RedisStandaloneConfiguration}.
     */
    @Deprecated
    public void setHostName(String hostName) {
        standaloneConfig.setHostName(hostName);
    }

    /**
     * Returns the current port.
     *
     * @return the port.
     */
    public int getPort() {
        return RedisConfiguration.getPortOrElse(configuration, standaloneConfig::getPort);
    }

    /**
     * Sets the port.
     *
     * @param port the port to set.
     * @deprecated since 2.0, configure the port using {@link RedisStandaloneConfiguration}.
     */
    @Deprecated
    public void setPort(int port) {
        standaloneConfig.setPort(port);
    }

    /**
     * Returns the connection timeout (in milliseconds).
     *
     * @return connection timeout.
     */
    public long getTimeout() {
        return getClientTimeout();
    }

    /**
     * Sets the connection timeout (in milliseconds).
     *
     * @param timeout the timeout.
     * @deprecated since 2.0, configure the timeout using {@link LettuceClientConfiguration}.
     * @throws IllegalStateException if {@link LettuceClientConfiguration} is immutable.
     */
    @Deprecated
    public void setTimeout(long timeout) {
        getMutableConfiguration().setTimeout(Duration.ofMillis(timeout));
    }

    /**
     * Returns whether to use SSL.
     *
     * @return use of SSL.
     */
    public boolean isUseSsl() {
        return clientConfiguration.isUseSsl();
    }

    /**
     * Sets to use SSL connection.
     *
     * @param useSsl {@literal true} to use SSL.
     * @deprecated since 2.0, configure SSL usage using {@link LettuceClientConfiguration}.
     * @throws IllegalStateException if {@link LettuceClientConfiguration} is immutable.
     */
    @Deprecated
    public void setUseSsl(boolean useSsl) {
        getMutableConfiguration().setUseSsl(useSsl);
    }

    /**
     * Returns whether to verify certificate validity/hostname check when SSL is used.
     *
     * @return whether to verify peers when using SSL.
     */
    public boolean isVerifyPeer() {
        return clientConfiguration.isVerifyPeer();
    }

    /**
     * Sets to use verify certificate validity/hostname check when SSL is used.
     *
     * @param verifyPeer {@literal false} not to verify hostname.
     * @deprecated since 2.0, configure peer verification using {@link LettuceClientConfiguration}.
     * @throws IllegalStateException if {@link LettuceClientConfiguration} is immutable.
     */
    @Deprecated
    public void setVerifyPeer(boolean verifyPeer) {
        getMutableConfiguration().setVerifyPeer(verifyPeer);
    }

    /**
     * Returns whether to issue a StartTLS.
     *
     * @return use of StartTLS.
     */
    public boolean isStartTls() {
        return clientConfiguration.isStartTls();
    }

    /**
     * Sets to issue StartTLS.
     *
     * @param startTls {@literal true} to issue StartTLS.
     * @deprecated since 2.0, configure StartTLS using {@link LettuceClientConfiguration}.
     * @throws IllegalStateException if {@link LettuceClientConfiguration} is immutable.
     */
    @Deprecated
    public void setStartTls(boolean startTls) {
        getMutableConfiguration().setStartTls(startTls);
    }

    /**
     * Indicates if validation of the native Lettuce connection is enabled.
     *
     * @return connection validation enabled.
     */
    public boolean getValidateConnection() {
        return validateConnection;
    }

    /**
     * Enables validation of the shared native Lettuce connection on calls to {@link #getConnection()}. A new connection
     * will be created and used if validation fails.
     * <p>
     * Lettuce will automatically reconnect until close is called, which should never happen through
     * {@link LettuceConnection} if a shared native connection is used, therefore the default is {@literal false}.
     * <p>
     * Setting this to {@literal true} will result in a round-trip call to the server on each new connection, so this
     * setting should only be used if connection sharing is enabled and there is code that is actively closing the native
     * Lettuce connection.
     *
     * @param validateConnection enable connection validation.
     */
    public void setValidateConnection(boolean validateConnection) {
        this.validateConnection = validateConnection;
    }

    /**
     * Indicates if multiple {@link LettuceConnection}s should share a single native connection.
     *
     * @return native connection shared.
     */
    public boolean getShareNativeConnection() {
        return shareNativeConnection;
    }

    /**
     * Enables multiple {@link LettuceConnection}s to share a single native connection. If set to {@literal false}, every
     * operation on {@link LettuceConnection} will open and close a socket.
     *
     * @param shareNativeConnection enable connection sharing.
     */
    public void setShareNativeConnection(boolean shareNativeConnection) {
        this.shareNativeConnection = shareNativeConnection;
    }

    /**
     * Indicates {@link #setShareNativeConnection(boolean) shared connections} should be eagerly initialized. Eager
     * initialization requires a running Redis instance during application startup to allow early validation of connection
     * factory configuration. Eager initialization also prevents blocking connect while using reactive API and is
     * recommended for reactive API usage.
     *
     * @return {@link true} if the shared connection is initialized upon {@link #afterPropertiesSet()}.
     * @since 2.2
     */
    public boolean getEagerInitialization() {
        return eagerInitialization;
    }

    /**
     * Enables eager initialization of {@link #setShareNativeConnection(boolean) shared connections}.
     *
     * @param eagerInitialization enable eager connection shared connection initialization upon
     *          {@link #afterPropertiesSet()}.
     * @since 2.2
     */
    public void setEagerInitialization(boolean eagerInitialization) {
        this.eagerInitialization = eagerInitialization;
    }

    /**
     * Returns the index of the database.
     *
     * @return the database index.
     */
    public int getDatabase() {
        return RedisConfiguration.getDatabaseOrElse(configuration, standaloneConfig::getDatabase);
    }

    /**
     * Sets the index of the database used by this connection factory. Default is 0.
     *
     * @param index database index
     */
    public void setDatabase(int index) {

        Assert.isTrue(index >= 0, "invalid DB index (a positive index required)");

        if (RedisConfiguration.isDatabaseIndexAware(configuration)) {

            ((WithDatabaseIndex) configuration).setDatabase(index);
            return;
        }

        standaloneConfig.setDatabase(index);
    }

    /**
     * Returns the client name.
     *
     * @return the client name or {@literal null} if not set.
     * @since 2.1
     */
    @Nullable
    public String getClientName() {
        return clientConfiguration.getClientName().orElse(null);
    }

    /**
     * Sets the client name used by this connection factory.
     *
     * @param clientName the client name. Can be {@literal null}.
     * @since 2.1
     * @deprecated configure the client name using {@link LettuceClientConfiguration}.
     * @throws IllegalStateException if {@link LettuceClientConfiguration} is immutable.
     */
    @Deprecated
    public void setClientName(@Nullable String clientName) {
        this.getMutableConfiguration().setClientName(clientName);
    }

    /**
     * Returns the password used for authenticating with the Redis server.
     *
     * @return password for authentication or {@literal null} if not set.
     */
    @Nullable
    public String getPassword() {
        return getRedisPassword().map(String::new).orElse(null);
    }

    private RedisPassword getRedisPassword() {
        return RedisConfiguration.getPasswordOrElse(configuration, standaloneConfig::getPassword);
    }

    /**
     * Sets the password used for authenticating with the Redis server.
     *
     * @param password the password to set
     * @deprecated since 2.0, configure the password using {@link RedisStandaloneConfiguration},
     *             {@link RedisSentinelConfiguration} or {@link RedisClusterConfiguration}.
     */
    @Deprecated
    public void setPassword(String password) {

        if (RedisConfiguration.isPasswordAware(configuration)) {

            ((WithPassword) configuration).setPassword(password);
            return;
        }

        standaloneConfig.setPassword(RedisPassword.of(password));
    }

    /**
     * Returns the shutdown timeout for shutting down the RedisClient (in milliseconds).
     *
     * @return shutdown timeout.
     * @since 1.6
     */
    public long getShutdownTimeout() {
        return clientConfiguration.getShutdownTimeout().toMillis();
    }

    /**
     * Sets the shutdown timeout for shutting down the RedisClient (in milliseconds).
     *
     * @param shutdownTimeout the shutdown timeout.
     * @since 1.6
     * @deprecated since 2.0, configure the shutdown timeout using {@link LettuceClientConfiguration}.
     * @throws IllegalStateException if {@link LettuceClientConfiguration} is immutable.
     */
    @Deprecated
    public void setShutdownTimeout(long shutdownTimeout) {
        getMutableConfiguration().setShutdownTimeout(Duration.ofMillis(shutdownTimeout));
    }

    /**
     * Get the {@link ClientResources} to reuse infrastructure.
     *
     * @return {@literal null} if not set.
     * @since 1.7
     */
    public ClientResources getClientResources() {
        return clientConfiguration.getClientResources().orElse(null);
    }

    /**
     * Sets the {@link ClientResources} to reuse the client infrastructure. <br />
     * Set to {@literal null} to not share resources.
     *
     * @param clientResources can be {@literal null}.
     * @since 1.7
     * @deprecated since 2.0, configure {@link ClientResources} using {@link LettuceClientConfiguration}.
     * @throws IllegalStateException if {@link LettuceClientConfiguration} is immutable.
     */
    @Deprecated
    public void setClientResources(ClientResources clientResources) {
        getMutableConfiguration().setClientResources(clientResources);
    }

    /**
     * @return the {@link LettuceClientConfiguration}.
     * @since 2.0
     */
    public LettuceClientConfiguration getClientConfiguration() {
        return clientConfiguration;
    }

    /**
     * @return the {@link RedisStandaloneConfiguration}.
     * @since 2.0
     */
    public RedisStandaloneConfiguration getStandaloneConfiguration() {
        return standaloneConfig;
    }

    /**
     * @return the {@link RedisSocketConfiguration} or {@literal null} if not set.
     * @since 2.1
     */
    @Nullable
    public RedisSocketConfiguration getSocketConfiguration() {
        return isDomainSocketAware() ? (RedisSocketConfiguration) configuration : null;
    }

    /**
     * @return the {@link RedisStandaloneConfiguration}, may be {@literal null}.
     * @since 2.0
     */
    @Nullable
    public RedisSentinelConfiguration getSentinelConfiguration() {
        return isRedisSentinelAware() ? (RedisSentinelConfiguration) configuration : null;
    }

    /**
     * @return the {@link RedisClusterConfiguration}, may be {@literal null}.
     * @since 2.0
     */
    @Nullable
    public RedisClusterConfiguration getClusterConfiguration() {
        return isClusterAware() ? (RedisClusterConfiguration) configuration : null;
    }

    /**
     * Specifies if pipelined results should be converted to the expected data type. If false, results of
     * {@link LettuceConnection#closePipeline()} and {LettuceConnection#exec()} will be of the type returned by the
     * Lettuce driver.
     *
     * @return Whether or not to convert pipeline and tx results.
     */
    public boolean getConvertPipelineAndTxResults() {
        return convertPipelineAndTxResults;
    }

    /**
     * Specifies if pipelined and transaction results should be converted to the expected data type. If false, results of
     * {@link LettuceConnection#closePipeline()} and {LettuceConnection#exec()} will be of the type returned by the
     * Lettuce driver.
     *
     * @param convertPipelineAndTxResults Whether or not to convert pipeline and tx results.
     */
    public void setConvertPipelineAndTxResults(boolean convertPipelineAndTxResults) {
        this.convertPipelineAndTxResults = convertPipelineAndTxResults;
    }

    /**
     * @return true when {@link RedisStaticMasterReplicaConfiguration} is present.
     * @since 2.1
     */
    private boolean isStaticMasterReplicaAware() {
        return RedisConfiguration.isStaticMasterReplicaConfiguration(configuration);
    }

    /**
     * @return true when {@link RedisSentinelConfiguration} is present.
     * @since 1.5
     */
    public boolean isRedisSentinelAware() {
        return RedisConfiguration.isSentinelConfiguration(configuration);
    }

    /**
     * @return true when {@link RedisSocketConfiguration} is present.
     * @since 2.1
     */
    private boolean isDomainSocketAware() {
        return RedisConfiguration.isDomainSocketConfiguration(configuration);
    }

    /**
     * @return true when {@link RedisClusterConfiguration} is present.
     * @since 1.7
     */
    public boolean isClusterAware() {
        return RedisConfiguration.isClusterConfiguration(configuration);
    }

    /**
     * @return the shared connection using {@literal byte} array encoding for imperative API use. {@literal null} if
     *         {@link #getShareNativeConnection() connection sharing} is disabled.
     */
    @Nullable
    protected StatefulRedisConnection<byte[], byte[]> getSharedConnection() {
        return shareNativeConnection ? (StatefulRedisConnection) getOrCreateSharedConnection().getConnection()
                : null;
    }

    /**
     * @return the shared connection using {@link ByteBuffer} encoding for reactive API use. {@literal null} if
     *         {@link #getShareNativeConnection() connection sharing} is disabled.
     * @since 2.0.1
     */
    @Nullable
    protected StatefulConnection<ByteBuffer, ByteBuffer> getSharedReactiveConnection() {
        return shareNativeConnection ? getOrCreateSharedReactiveConnection().getConnection() : null;
    }

    private LettuceConnectionProvider createConnectionProvider(AbstractRedisClient client, RedisCodec<?, ?> codec) {

        if (this.pool != null) {
            return new LettucePoolConnectionProvider(this.pool);
        }

        LettuceConnectionProvider connectionProvider = doCreateConnectionProvider(client, codec);

        if (this.clientConfiguration instanceof LettucePoolingClientConfiguration) {
            return new LettucePoolingConnectionProvider(connectionProvider,
                    (LettucePoolingClientConfiguration) this.clientConfiguration);
        }

        return connectionProvider;
    }

    /**
     * Create a {@link LettuceConnectionProvider} given {@link AbstractRedisClient} and {@link RedisCodec}. Configuration
     * of this connection factory specifies the type of the created connection provider. This method creates either a
     * {@link LettuceConnectionProvider} for either {@link RedisClient} or {@link RedisClusterClient}. Subclasses may
     * override this method to decorate the connection provider.
     *
     * @param client either {@link RedisClient} or {@link RedisClusterClient}, must not be {@literal null}.
     * @param codec used for connection creation, must not be {@literal null}. By default, a {@code byte[]} codec.
     *          Reactive connections require a {@link java.nio.ByteBuffer} codec.
     * @return the connection provider.
     * @since 2.1
     */
    protected LettuceConnectionProvider doCreateConnectionProvider(AbstractRedisClient client,
            RedisCodec<?, ?> codec) {

        ReadFrom readFrom = getClientConfiguration().getReadFrom().orElse(null);

        if (isStaticMasterReplicaAware()) {

            List<RedisURI> nodes = ((RedisStaticMasterReplicaConfiguration) configuration).getNodes().stream() //
                    .map(it -> createRedisURIAndApplySettings(it.getHostName(), it.getPort())) //
                    .peek(it -> it.setDatabase(getDatabase())) //
                    .collect(Collectors.toList());

            return new StaticMasterReplicaConnectionProvider((RedisClient) client, codec, nodes, readFrom);
        }

        if (isClusterAware()) {
            return new ClusterConnectionProvider((RedisClusterClient) client, codec, readFrom);
        }

        return new StandaloneConnectionProvider((RedisClient) client, codec, readFrom);
    }

    protected AbstractRedisClient createClient() {

        if (isStaticMasterReplicaAware()) {

            RedisClient redisClient = clientConfiguration.getClientResources() //
                    .map(RedisClient::create) //
                    .orElseGet(RedisClient::create);

            clientConfiguration.getClientOptions().ifPresent(redisClient::setOptions);

            return redisClient;
        }

        if (isRedisSentinelAware()) {

            RedisURI redisURI = getSentinelRedisURI();
            RedisClient redisClient = clientConfiguration.getClientResources() //
                    .map(clientResources -> RedisClient.create(clientResources, redisURI)) //
                    .orElseGet(() -> RedisClient.create(redisURI));

            clientConfiguration.getClientOptions().ifPresent(redisClient::setOptions);
            return redisClient;
        }

        if (isClusterAware()) {

            List<RedisURI> initialUris = new ArrayList<>();
            ClusterConfiguration configuration = (ClusterConfiguration) this.configuration;
            for (RedisNode node : configuration.getClusterNodes()) {
                initialUris.add(createRedisURIAndApplySettings(node.getHost(), node.getPort()));
            }

            RedisClusterClient clusterClient = clientConfiguration.getClientResources() //
                    .map(clientResources -> RedisClusterClient.create(clientResources, initialUris)) //
                    .orElseGet(() -> RedisClusterClient.create(initialUris));

            clusterClient.setOptions(getClusterClientOptions(configuration));

            return clusterClient;
        }

        RedisURI uri = isDomainSocketAware()
                ? createRedisSocketURIAndApplySettings(((DomainSocketConfiguration) configuration).getSocket())
                : createRedisURIAndApplySettings(getHostName(), getPort());

        RedisClient redisClient = clientConfiguration.getClientResources() //
                .map(clientResources -> RedisClient.create(clientResources, uri)) //
                .orElseGet(() -> RedisClient.create(uri));
        clientConfiguration.getClientOptions().ifPresent(redisClient::setOptions);

        return redisClient;
    }

    private ClusterClientOptions getClusterClientOptions(ClusterConfiguration configuration) {

        Optional<ClientOptions> clientOptions = clientConfiguration.getClientOptions();
        ClusterClientOptions clusterClientOptions = clientOptions //
                .filter(ClusterClientOptions.class::isInstance) //
                .map(ClusterClientOptions.class::cast) //
                .orElseGet(() -> {
                    return clientOptions //
                            .map(it -> ClusterClientOptions.builder(it).build()) //
                            .orElseGet(ClusterClientOptions::create);
                });

        if (configuration.getMaxRedirects() != null) {
            return clusterClientOptions.mutate().maxRedirects(configuration.getMaxRedirects()).build();
        }

        return clusterClientOptions;
    }

    private RedisURI getSentinelRedisURI() {

        RedisURI redisUri = LettuceConverters.sentinelConfigurationToRedisURI(
                (org.springframework.data.redis.connection.RedisSentinelConfiguration) configuration);

        getRedisPassword().toOptional().ifPresent(redisUri::setPassword);
        clientConfiguration.getClientName().ifPresent(redisUri::setClientName);

        redisUri.setSsl(clientConfiguration.isUseSsl());
        redisUri.setVerifyPeer(clientConfiguration.isVerifyPeer());
        redisUri.setStartTls(clientConfiguration.isStartTls());
        redisUri.setTimeout(clientConfiguration.getCommandTimeout());
        redisUri.setDatabase(getDatabase());

        return redisUri;
    }

    private RedisURI createRedisURIAndApplySettings(String host, int port) {

        RedisURI.Builder builder = RedisURI.Builder.redis(host, port);

        getRedisPassword().toOptional().ifPresent(builder::withPassword);
        clientConfiguration.getClientName().ifPresent(builder::withClientName);

        builder.withDatabase(getDatabase());
        builder.withSsl(clientConfiguration.isUseSsl());
        builder.withVerifyPeer(clientConfiguration.isVerifyPeer());
        builder.withStartTls(clientConfiguration.isStartTls());
        builder.withTimeout(clientConfiguration.getCommandTimeout());

        return builder.build();
    }

    private RedisURI createRedisSocketURIAndApplySettings(String socketPath) {

        RedisURI.Builder builder = RedisURI.Builder.socket(socketPath);

        getRedisPassword().toOptional().ifPresent(builder::withPassword);
        builder.withDatabase(getDatabase());
        builder.withTimeout(clientConfiguration.getCommandTimeout());

        return builder.build();
    }

    @Override
    public RedisSentinelConnection getSentinelConnection() {
        return new LettuceSentinelConnection(connectionProvider);
    }

    private MutableLettuceClientConfiguration getMutableConfiguration() {

        Assert.state(clientConfiguration instanceof MutableLettuceClientConfiguration,
                () -> String.format(
                        "Client configuration must be instance of MutableLettuceClientConfiguration but is %s",
                        ClassUtils.getShortName(clientConfiguration.getClass())));

        return (MutableLettuceClientConfiguration) clientConfiguration;
    }

    private long getClientTimeout() {
        return clientConfiguration.getCommandTimeout().toMillis();
    }

    /**
     * Wrapper for shared connections. Keeps track of the connection lifecycleThe wrapper is thread-safe as it
     * synchronizes concurrent calls by blocking.
     *
     * @param <E> connection encoding.
     * @author Mark Paluch
     * @author Christoph Strobl
     * @since 2.1
     */
    @RequiredArgsConstructor
    class SharedConnection<E> {

        private final LettuceConnectionProvider connectionProvider;

        /** Synchronization monitor for the shared Connection */
        private final Object connectionMonitor = new Object();

        private @Nullable StatefulConnection<E, E> connection;

        /**
         * Returns a valid Lettuce connection. Initializes and validates the connection if
         * {@link #setValidateConnection(boolean) enabled}.
         *
         * @return the connection.
         */
        @Nullable
        StatefulConnection<E, E> getConnection() {

            synchronized (this.connectionMonitor) {

                if (this.connection == null) {
                    this.connection = getNativeConnection();
                }

                if (getValidateConnection()) {
                    validateConnection();
                }

                return this.connection;
            }
        }

        /**
         * Obtain a connection from the associated {@link LettuceConnectionProvider}.
         *
         * @return the connection.
         */
        private StatefulConnection<E, E> getNativeConnection() {

            try {
                return connectionProvider.getConnection(StatefulConnection.class);
            } catch (RedisException e) {
                throw new RedisConnectionFailureException("Unable to connect to Redis", e);
            }
        }

        /**
         * Validate the connection. Invalid connections will be closed and the connection state will be reset.
         */
        void validateConnection() {

            synchronized (this.connectionMonitor) {

                boolean valid = false;

                if (connection != null && connection.isOpen()) {
                    try {

                        if (connection instanceof StatefulRedisConnection) {
                            ((StatefulRedisConnection) connection).sync().ping();
                        }

                        if (connection instanceof StatefulRedisClusterConnection) {
                            ((StatefulRedisClusterConnection) connection).sync().ping();
                        }
                        valid = true;
                    } catch (Exception e) {
                        log.debug("Validation failed", e);
                    }
                }

                if (!valid) {

                    log.warn("Validation of shared connection failed. Creating a new connection.");
                    resetConnection();
                    this.connection = getNativeConnection();
                }
            }
        }

        /**
         * Reset the underlying shared Connection, to be reinitialized on next access.
         */
        void resetConnection() {

            synchronized (this.connectionMonitor) {

                if (this.connection != null) {
                    this.connectionProvider.release(this.connection);
                }

                this.connection = null;
            }
        }
    }

    /**
     * Mutable implementation of {@link LettuceClientConfiguration}.
     *
     * @author Mark Paluch
     * @author Christoph Strobl
     */
    static class MutableLettuceClientConfiguration implements LettuceClientConfiguration {

        private boolean useSsl;
        private boolean verifyPeer = true;
        private boolean startTls;
        private @Nullable ClientResources clientResources;
        private @Nullable String clientName;
        private Duration timeout = Duration.ofSeconds(RedisURI.DEFAULT_TIMEOUT);
        private Duration shutdownTimeout = Duration.ofMillis(100);

        /*
         * (non-Javadoc)
         * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#isUseSsl()
         */
        @Override
        public boolean isUseSsl() {
            return useSsl;
        }

        void setUseSsl(boolean useSsl) {
            this.useSsl = useSsl;
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#isVerifyPeer()
         */
        @Override
        public boolean isVerifyPeer() {
            return verifyPeer;
        }

        void setVerifyPeer(boolean verifyPeer) {
            this.verifyPeer = verifyPeer;
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#isStartTls()
         */
        @Override
        public boolean isStartTls() {
            return startTls;
        }

        void setStartTls(boolean startTls) {
            this.startTls = startTls;
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getClientResources()
         */
        @Override
        public Optional<ClientResources> getClientResources() {
            return Optional.ofNullable(clientResources);
        }

        void setClientResources(ClientResources clientResources) {
            this.clientResources = clientResources;
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getClientOptions()
         */
        @Override
        public Optional<ClientOptions> getClientOptions() {
            return Optional.empty();
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getReadFrom()
         */
        @Override
        public Optional<ReadFrom> getReadFrom() {
            return Optional.empty();
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getClientName()
         */
        @Override
        public Optional<String> getClientName() {
            return Optional.ofNullable(clientName);
        }

        /**
         * @param clientName can be {@literal null}.
         * @since 2.1
         */
        void setClientName(@Nullable String clientName) {
            this.clientName = clientName;
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getTimeout()
         */
        @Override
        public Duration getCommandTimeout() {
            return timeout;
        }

        void setTimeout(Duration timeout) {
            this.timeout = timeout;
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getShutdownTimeout()
         */
        @Override
        public Duration getShutdownTimeout() {
            return shutdownTimeout;
        }

        void setShutdownTimeout(Duration shutdownTimeout) {
            this.shutdownTimeout = shutdownTimeout;
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getShutdownQuietPeriod()
         */
        @Override
        public Duration getShutdownQuietPeriod() {
            return shutdownTimeout;
        }
    }
}