+ ** clusterHostAndPorts[0] = 127.0.0.1:23679 - * clusterHostAndPorts[1] = 127.0.0.1:23680 - * ... - * + * clusterHostAndPorts[1] = 127.0.0.1:23680 ... + **- * - * @param cluster must not be - * {@literal null}. + * + * @param clusterNodes must not be {@literal null}. */ public RedisClusterConfiguration(CollectionclusterNodes) { this(new MapPropertySource("RedisClusterConfiguration", asMap(clusterNodes, -1, -1, null))); @@ -74,7 +74,7 @@ public class RedisClusterConfiguration { /** * Creates {@link RedisClusterConfiguration} looking up values in given {@link PropertySource}. - * + * * *- * + * * @param propertySource must not be {@literal null}. */ public RedisClusterConfiguration(PropertySource> propertySource) { @@ -104,7 +104,7 @@ public class RedisClusterConfiguration { /** * Set {@literal cluster nodes} to connect to. - * + * * @param nodes must not be {@literal null}. */ public void setClusterNodes(Iterable* spring.redis.cluster.nodes=127.0.0.1:23679,127.0.0.1:23680,127.0.0.1:23681 @@ -83,7 +83,7 @@ public class RedisClusterConfiguration { * spring.redis.cluster.password=foobar **nodes) { @@ -120,7 +120,7 @@ public class RedisClusterConfiguration { /** * Returns an {@link Collections#unmodifiableSet(Set)} of {@literal cluster nodes}. - * + * * @return {@link Set} of nodes. Never {@literal null}. */ public Set getClusterNodes() { @@ -129,7 +129,7 @@ public class RedisClusterConfiguration { /** * Add a cluster node to configuration. - * + * * @param node must not be {@literal null}. */ public void addClusterNode(RedisNode node) { @@ -179,6 +179,22 @@ public class RedisClusterConfiguration { } } + /** + * @return + * @since 2.0 + */ + public String getPassword() { + return password; + } + + /** + * @param password + * @since 2.0 + */ + public void setPassword(String password) { + this.password = password; + } + private RedisNode readHostAndPortFromString(String hostAndPort) { String[] args = split(hostAndPort, ":"); diff --git a/src/main/java/org/springframework/data/redis/connection/RedisSentinelConfiguration.java b/src/main/java/org/springframework/data/redis/connection/RedisSentinelConfiguration.java index e9f22fa2c..248f175d7 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisSentinelConfiguration.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisSentinelConfiguration.java @@ -16,6 +16,7 @@ package org.springframework.data.redis.connection; import static org.springframework.util.Assert.*; +import static org.springframework.util.Assert.hasText; import static org.springframework.util.StringUtils.*; import java.util.Collections; @@ -26,6 +27,7 @@ import java.util.Set; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.PropertySource; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -35,6 +37,7 @@ import org.springframework.util.StringUtils; * * @author Christoph Strobl * @author Thomas Darimont + * @author Mark Paluch * @since 1.4 */ public class RedisSentinelConfiguration { @@ -44,6 +47,8 @@ public class RedisSentinelConfiguration { private NamedNode master; private Set sentinels; + private int database; + private String password; /** * Creates new {@link RedisSentinelConfiguration}. @@ -56,9 +61,7 @@ public class RedisSentinelConfiguration { * Creates {@link RedisSentinelConfiguration} for given hostPort combinations. * * - * sentinelHostAndPorts[0] = 127.0.0.1:23679 - * sentinelHostAndPorts[1] = 127.0.0.1:23680 - * ... + * sentinelHostAndPorts[0] = 127.0.0.1:23679 sentinelHostAndPorts[1] = 127.0.0.1:23680 ... * ** @@ -93,8 +96,8 @@ public class RedisSentinelConfiguration { } if (propertySource.containsProperty(REDIS_SENTINEL_NODES_CONFIG_PROPERTY)) { - appendSentinels(commaDelimitedListToSet(propertySource.getProperty(REDIS_SENTINEL_NODES_CONFIG_PROPERTY) - .toString())); + appendSentinels( + commaDelimitedListToSet(propertySource.getProperty(REDIS_SENTINEL_NODES_CONFIG_PROPERTY).toString())); } } @@ -218,6 +221,43 @@ public class RedisSentinelConfiguration { } } + /** + * @return + * @since 2.0 + */ + public int getDatabase() { + return database; + } + + /** + * Sets the index of the database used by this connection factory. Default is 0. + * + * @param index database index. + * @since 2.0 + */ + public void setDatabase(int index) { + + Assert.isTrue(index >= 0, "invalid DB index (a positive index required)"); + + this.database = index; + } + + /** + * @return + * @since 2.0 + */ + public String getPassword() { + return password; + } + + /** + * @param password + * @since 2.0 + */ + public void setPassword(String password) { + this.password = password; + } + private RedisNode readHostAndPortFromString(String hostAndPort) { String[] args = split(hostAndPort, ":"); diff --git a/src/main/java/org/springframework/data/redis/connection/RedisStandaloneConfiguration.java b/src/main/java/org/springframework/data/redis/connection/RedisStandaloneConfiguration.java new file mode 100644 index 000000000..bc31f57e0 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/RedisStandaloneConfiguration.java @@ -0,0 +1,127 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.redis.connection; + +import org.springframework.util.Assert; + +/** + * Configuration class used for setting up {@link RedisConnection} via {@link RedisConnectionFactory} using connecting + * to Redis. + * + * @author Mark Paluch + * @since 2.0 + */ +public class RedisStandaloneConfiguration { + + public static final String DEFAULT_HOST = "localhost"; + public static final int DEFAULT_PORT = 6379; + + private String hostName = DEFAULT_HOST; + private int port = DEFAULT_PORT; + private int database; + private String password; + + /** + * Create a new default {@link RedisStandaloneConfiguration}. + */ + public RedisStandaloneConfiguration() {} + + /** + * Create a new {@link RedisStandaloneConfiguration} given {@code hostName}. + * + * @param hostName must not be {@literal null} or empty. + */ + public RedisStandaloneConfiguration(String hostName) { + this(hostName, DEFAULT_PORT); + } + + /** + * Create a new {@link RedisStandaloneConfiguration} given {@code hostName} and {@code port}. + * + * @param hostName must not be {@literal null} or empty. + * @param port a valid TCP port (1-65535). + */ + public RedisStandaloneConfiguration(String hostName, int port) { + + Assert.hasText(hostName, "Host name must not be null or empty!"); + Assert.isTrue(port >= 1 && port <= 65535, + () -> String.format("Port %d must be a valid TCP port in the range between 1-65535!", port)); + + this.hostName = hostName; + this.port = port; + } + + /** + * @return + */ + public String getHostName() { + return hostName; + } + + /** + * @return + */ + public int getPort() { + return port; + } + + /** + * @param hostName + */ + public void setHostName(String hostName) { + this.hostName = hostName; + } + + /** + * @param port + */ + public void setPort(int port) { + this.port = port; + } + + /** + * @return + */ + public int getDatabase() { + return database; + } + + /** + * Sets the index of the database used by this connection factory. Default is 0. + * + * @param index database index. + */ + public void setDatabase(int index) { + + Assert.isTrue(index >= 0, "invalid DB index (a positive index required)"); + + this.database = index; + } + + /** + * @return + */ + public String getPassword() { + return password; + } + + /** + * @param password + */ + public void setPassword(String password) { + this.password = password; + } +} diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/DefaultJedisClientConfiguration.java b/src/main/java/org/springframework/data/redis/connection/jedis/DefaultJedisClientConfiguration.java new file mode 100644 index 000000000..529f97e4f --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/jedis/DefaultJedisClientConfiguration.java @@ -0,0 +1,131 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.redis.connection.jedis; + +import java.time.Duration; +import java.util.Optional; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocketFactory; + +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; + +/** + * Default implementation of {@literal JedisClientConfiguration}. + * + * @author Mark Paluch + * @since 2.0 + */ +class DefaultJedisClientConfiguration implements JedisClientConfiguration { + + private final boolean useSsl; + private final OptionalsslSocketFactory; + private final Optional sslParameters; + private final Optional hostnameVerifier; + private final boolean usePooling; + private final Optional poolConfig; + private final Optional clientName; + private final Duration readTimeout; + private final Duration connectTimeout; + + DefaultJedisClientConfiguration(boolean useSsl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters, + HostnameVerifier hostnameVerifier, boolean usePooling, GenericObjectPoolConfig poolConfig, String clientName, + Duration readTimeout, Duration connectTimeout) { + + this.useSsl = useSsl; + this.sslSocketFactory = Optional.ofNullable(sslSocketFactory); + this.sslParameters = Optional.ofNullable(sslParameters); + this.hostnameVerifier = Optional.ofNullable(hostnameVerifier); + this.usePooling = usePooling; + this.poolConfig = Optional.ofNullable(poolConfig); + this.clientName = Optional.ofNullable(clientName); + this.readTimeout = readTimeout; + this.connectTimeout = connectTimeout; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration#useSsl() + */ + @Override + public boolean useSsl() { + return useSsl; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration#getSslSocketFactory() + */ + @Override + public Optional getSslSocketFactory() { + return sslSocketFactory; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration#getSslParameters() + */ + @Override + public Optional getSslParameters() { + return sslParameters; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration#getHostnameVerifier() + */ + @Override + public Optional getHostnameVerifier() { + return hostnameVerifier; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration#usePooling() + */ + @Override + public boolean usePooling() { + return usePooling; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration#getPoolConfig() + */ + @Override + public Optional getPoolConfig() { + return poolConfig; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration#getClientName() + */ + @Override + public Optional getClientName() { + return clientName; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration#getReadTimeout() + */ + @Override + public Duration getReadTimeout() { + return readTimeout; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration#getConnectTimeout() + */ + @Override + public Duration getConnectTimeout() { + return connectTimeout; + } +} diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClientConfiguration.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClientConfiguration.java new file mode 100644 index 000000000..8beec65cf --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClientConfiguration.java @@ -0,0 +1,414 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.redis.connection.jedis; + +import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.Protocol; + +import java.net.SocketAddress; +import java.time.Duration; +import java.util.Optional; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocketFactory; + +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.springframework.util.Assert; + +/** + * Redis client configuration for jedis. This configuration provides optional configuration elements such as + * {@link SSLSocketFactory} and {@link JedisPoolConfig} specific to jedis client features. + * + * Providing optional elements allows a more specific configuration of the client: + *
+ *
+ * + * @author Mark Paluch + * @since 2.0 + * @see redis.clients.jedis.Jedis + * @see org.springframework.data.redis.connection.RedisStandaloneConfiguration + * @see org.springframework.data.redis.connection.RedisSentinelConfiguration + * @see org.springframework.data.redis.connection.RedisClusterConfiguration + */ +public interface JedisClientConfiguration { + + /** + * @return {@literal true} to use SSL, {@literal false} to use unencrypted connections. + */ + boolean useSsl(); + + /** + * @return the optional {@link SSLSocketFactory}. + */ + Optional- Whether to use SSL
+ *- Optional {@link SSLSocketFactory}
+ *- Optional {@link SSLParameters}
+ *- Optional {@link HostnameVerifier}
+ *- Whether to use connection-pooling
+ *- Optional {@link GenericObjectPoolConfig}
+ *- Connect {@link Duration timeout}
+ *- Read {@link Duration timeout}
+ *getSslSocketFactory(); + + /** + * @return the optional {@link SSLParameters}. + */ + Optional getSslParameters(); + + /** + * @return the optional {@link HostnameVerifier}. + */ + Optional getHostnameVerifier(); + + /** + * @return {@literal true} to use connection-pooling. + */ + boolean usePooling(); + + /** + * @return the optional {@link GenericObjectPoolConfig}. + */ + Optional getPoolConfig(); + + /** + * @return the optional client name to be set with {@code CLIENT SETNAME}. + */ + Optional getClientName(); + + /** + * @return the connection timeout. + * @see java.net.Socket#connect(SocketAddress, int) + */ + Duration getConnectTimeout(); + + /** + * @return the read timeout. + * @see java.net.Socket#setSoTimeout(int) + */ + Duration getReadTimeout(); + + /** + * Creates a new {@link JedisClientConfigurationBuilder} to build {@link JedisClientConfiguration} to be used with the + * jedis client. + * + * @return a new {@link JedisClientConfigurationBuilder} to build {@link JedisClientConfiguration}. + */ + static JedisClientConfigurationBuilder builder() { + return new DefaultJedisClientConfigurationBuilder(); + } + + /** + * Creates an empty {@link JedisClientConfiguration}. + * + * @return an empty {@link JedisClientConfiguration}. + */ + static JedisClientConfiguration create() { + return builder().build(); + } + + /** + * Builder for {@link JedisClientConfiguration}. + */ + interface JedisClientConfigurationBuilder { + + /** + * Enable SSL connections. + * + * @return {@link JedisSslClientConfigurationBuilder}. + */ + JedisSslClientConfigurationBuilder useSsl(); + + /** + * Use plaintext connections instead of SSL. + * + * @return {@link JedisSslClientConfigurationBuilder}. + */ + JedisClientConfigurationBuilder usePlaintext(); + + /** + * Enable connection-pooling. + * + * @return {@link JedisPoolingClientConfigurationBuilder}. + */ + JedisPoolingClientConfigurationBuilder usePooling(); + + /** + * Disable connection-pooling. + * + * @return {@link JedisClientConfigurationBuilder}. + */ + JedisClientConfigurationBuilder useUnpooledConnections(); + + /** + * Configure a {@code clientName} to be set with {@code CLIENT SETNAME}. + * + * @param clientName must not be {@literal null}. + * @return {@literal this} builder. + */ + JedisClientConfigurationBuilder clientName(String clientName); + + /** + * Configure a read timeout. + * + * @param readTimeout must not be {@literal null}. + * @return {@literal this} builder. + */ + JedisClientConfigurationBuilder readTimeout(Duration readTimeout); + + /** + * Configure a connection timeout. + * + * @param connectTimeout must not be {@literal null}. + * @return {@literal this} builder. + */ + JedisClientConfigurationBuilder connectTimeout(Duration connectTimeout); + + /** + * Build the {@link JedisClientConfiguration} with the configuration applied from this builder. + * + * @return a new {@link JedisClientConfiguration} object. + */ + JedisClientConfiguration build(); + } + + /** + * Builder for Pooling-related {@link JedisClientConfiguration}. + */ + interface JedisPoolingClientConfigurationBuilder { + + /** + * @param poolConfig must not be {@literal null}. + * @return {@literal this} builder. + */ + JedisPoolingClientConfigurationBuilder poolConfig(GenericObjectPoolConfig poolConfig); + + /** + * Return to {@link JedisClientConfigurationBuilder}. + * + * @return {@link JedisClientConfigurationBuilder}. + */ + JedisClientConfigurationBuilder and(); + + /** + * Build the {@link JedisClientConfiguration} with the configuration applied from this builder. + * + * @return a new {@link JedisClientConfiguration} object. + */ + JedisClientConfiguration build(); + } + + /** + * Builder for SSL-related {@link JedisClientConfiguration}. + */ + interface JedisSslClientConfigurationBuilder { + + /** + * @param sslSocketFactory must not be {@literal null}. + * @return {@literal this} builder. + */ + JedisSslClientConfigurationBuilder sslSocketFactory(SSLSocketFactory sslSocketFactory); + + /** + * @param sslParameters must not be {@literal null}. + * @return {@literal this} builder. + */ + JedisSslClientConfigurationBuilder sslParameters(SSLParameters sslParameters); + + /** + * @param hostnameVerifier must not be {@literal null}. + * @return {@literal this} builder. + */ + JedisSslClientConfigurationBuilder hostnameVerifier(HostnameVerifier hostnameVerifier); + + /** + * Return to {@link JedisClientConfigurationBuilder}. + * + * @return {@link JedisClientConfigurationBuilder}. + */ + JedisClientConfigurationBuilder and(); + + /** + * Build the {@link JedisClientConfiguration} with the configuration applied from this builder. + * + * @return a new {@link JedisClientConfiguration} object. + */ + JedisClientConfiguration build(); + } + + /** + * Default {@link JedisClientConfigurationBuilder} implementation to build an immutable + * {@link JedisClientConfiguration}. + */ + class DefaultJedisClientConfigurationBuilder implements JedisClientConfigurationBuilder, + JedisPoolingClientConfigurationBuilder, JedisSslClientConfigurationBuilder { + + private boolean useSsl; + private SSLSocketFactory sslSocketFactory; + private SSLParameters sslParameters; + private HostnameVerifier hostnameVerifier; + private boolean usePooling; + private GenericObjectPoolConfig poolConfig = new JedisPoolConfig(); + private String clientName; + private Duration readTimeout = Duration.ofMillis(Protocol.DEFAULT_TIMEOUT); + private Duration connectTimeout = Duration.ofMillis(Protocol.DEFAULT_TIMEOUT); + + private DefaultJedisClientConfigurationBuilder() {} + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder#useSsl() + */ + @Override + public DefaultJedisClientConfigurationBuilder useSsl() { + + this.useSsl = true; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder#usePlaintext() + */ + @Override + public JedisClientConfigurationBuilder usePlaintext() { + + this.useSsl = false; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisSslClientConfigurationBuilder#sslSocketFactory(javax.net.ssl.SSLSocketFactory) + */ + @Override + public DefaultJedisClientConfigurationBuilder sslSocketFactory(SSLSocketFactory sslSocketFactory) { + + Assert.notNull(sslSocketFactory, "SSLSocketFactory must not be null!"); + + this.sslSocketFactory = sslSocketFactory; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisSslClientConfigurationBuilder#sslParameters(javax.net.ssl.SSLParameters) + */ + @Override + public DefaultJedisClientConfigurationBuilder sslParameters(SSLParameters sslParameters) { + + Assert.notNull(sslParameters, "SSLParameters must not be null!"); + + this.sslParameters = sslParameters; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisSslClientConfigurationBuilder#hostnameVerifier(javax.net.ssl.HostnameVerifier) + */ + @Override + public DefaultJedisClientConfigurationBuilder hostnameVerifier(HostnameVerifier hostnameVerifier) { + + Assert.notNull(hostnameVerifier, "HostnameVerifier must not be null!"); + + this.hostnameVerifier = hostnameVerifier; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder#usePooling() + */ + @Override + public DefaultJedisClientConfigurationBuilder usePooling() { + + this.usePooling = true; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder#useUnpooledConnections() + */ + @Override + public JedisClientConfigurationBuilder useUnpooledConnections() { + + this.usePooling = false; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisPoolingClientConfigurationBuilder#poolConfig(org.apache.commons.pool2.impl.GenericObjectPoolConfig) + */ + @Override + public DefaultJedisClientConfigurationBuilder poolConfig(GenericObjectPoolConfig poolConfig) { + + Assert.notNull(poolConfig, "GenericObjectPoolConfig must not be null!"); + + this.poolConfig = poolConfig; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisPoolingClientConfigurationBuilder#and() + */ + @Override + public JedisClientConfigurationBuilder and() { + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder#clientName(java.lang.String) + */ + @Override + public JedisClientConfigurationBuilder clientName(String clientName) { + + Assert.hasText(clientName, "Client name must not be null or empty!"); + + this.clientName = clientName; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder#readTimeout(java.time.Duration) + */ + @Override + public JedisClientConfigurationBuilder readTimeout(Duration readTimeout) { + + Assert.notNull(readTimeout, "Duration must not be null!"); + + this.readTimeout = readTimeout; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder#connectTimeout(java.time.Duration) + */ + @Override + public JedisClientConfigurationBuilder connectTimeout(Duration connectTimeout) { + + Assert.notNull(connectTimeout, "Duration must not be null!"); + + this.connectTimeout = connectTimeout; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder#build() + */ + @Override + public JedisClientConfiguration build() { + + return new DefaultJedisClientConfiguration(useSsl, sslSocketFactory, sslParameters, hostnameVerifier, usePooling, + poolConfig, clientName, readTimeout, connectTimeout); + } + } + +} diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactory.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactory.java index 023f281e0..be4f2a4de 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactory.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactory.java @@ -15,6 +15,7 @@ */ package org.springframework.data.redis.connection.jedis; +import redis.clients.jedis.Client; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisCluster; @@ -26,12 +27,18 @@ import redis.clients.jedis.Protocol; import redis.clients.util.Pool; import java.lang.reflect.Method; +import java.time.Duration; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.Optional; import java.util.Set; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocketFactory; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; @@ -51,19 +58,31 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisNode; import org.springframework.data.redis.connection.RedisSentinelConfiguration; import org.springframework.data.redis.connection.RedisSentinelConnection; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** * Connection factory creating Jedis based connections. + * + * {@link JedisConnectionFactory} should be configured using an environmental configuration and the + * {@link JedisClientConfiguration client configuration}. Jedis supports the following environmental configurations: + *
+ *
* * @author Costin Leau * @author Thomas Darimont * @author Christoph Strobl * @author Mark Paluch * @author Fu Jian + * @see JedisClientConfiguration + * @see Jedis */ public class JedisConnectionFactory implements InitializingBean, DisposableBean, RedisConnectionFactory { @@ -72,7 +91,6 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, JedisConverters.exceptionConverter()); private static final Method SET_TIMEOUT_METHOD; - private static final Method GET_TIMEOUT_METHOD; static { @@ -83,27 +101,15 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, setTimeoutMethodCandidate = ReflectionUtils.findMethod(JedisShardInfo.class, "setSoTimeout", int.class); } SET_TIMEOUT_METHOD = setTimeoutMethodCandidate; - - Method getTimeoutMethodCandidate = ReflectionUtils.findMethod(JedisShardInfo.class, "getTimeout"); - if (getTimeoutMethodCandidate == null) { - getTimeoutMethodCandidate = ReflectionUtils.findMethod(JedisShardInfo.class, "getSoTimeout"); - } - - GET_TIMEOUT_METHOD = getTimeoutMethodCandidate; } + private final JedisClientConfiguration clientConfiguration; private JedisShardInfo shardInfo; - private String hostName = "localhost"; - private int port = Protocol.DEFAULT_PORT; - private int timeout = Protocol.DEFAULT_TIMEOUT; - private String password; - private boolean usePool = true; - private boolean useSsl = false; + private boolean providedShardInfo = false; private Pool- {@link RedisStandaloneConfiguration}
+ *- {@link RedisSentinelConfiguration}
+ *- {@link RedisClusterConfiguration}
+ *pool; - private JedisPoolConfig poolConfig = new JedisPoolConfig(); - private int dbIndex = 0; - private String clientName; private boolean convertPipelineAndTxResults = true; + private RedisStandaloneConfiguration standaloneConfig = new RedisStandaloneConfiguration("localhost", + Protocol.DEFAULT_PORT); private RedisSentinelConfiguration sentinelConfig; private RedisClusterConfiguration clusterConfig; private JedisCluster cluster; @@ -113,16 +119,38 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, * Constructs a new JedisConnectionFactoryinstance with default settings (default connection pooling, no * shard information). */ - public JedisConnectionFactory() {} + public JedisConnectionFactory() { + this(new MutableJedisClientConfiguration()); + } + + /** + * Constructs a new {@link JedisConnectionFactory} instance given {@link JedisClientConfiguration}. + * + * @param clientConfig must not be {@literal null} + * @since 2.0 + */ + private JedisConnectionFactory(JedisClientConfiguration clientConfig) { + + Assert.notNull(clientConfig, "JedisClientConfiguration must not be null!"); + + this.clientConfiguration = clientConfig; + } /** * Constructs a newJedisConnectionFactoryinstance. Will override the other connection parameters passed * to the factory. * * @param shardInfo shard information + * @deprecated since 2.0, configure Jedis with {@link JedisClientConfiguration} and + * {@link RedisStandaloneConfiguration}. */ + @Deprecated public JedisConnectionFactory(JedisShardInfo shardInfo) { + + this(MutableJedisClientConfiguration.create(shardInfo)); + this.shardInfo = shardInfo; + this.providedShardInfo = true; } /** @@ -138,11 +166,11 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, * Constructs a new {@link JedisConnectionFactory} instance using the given {@link JedisPoolConfig} applied to * {@link JedisSentinelPool}. * - * @param sentinelConfig + * @param sentinelConfig must not be {@literal null}. * @since 1.4 */ public JedisConnectionFactory(RedisSentinelConfiguration sentinelConfig) { - this(sentinelConfig, null); + this(sentinelConfig, new MutableJedisClientConfiguration()); } /** @@ -154,31 +182,97 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, * @since 1.4 */ public JedisConnectionFactory(RedisSentinelConfiguration sentinelConfig, JedisPoolConfig poolConfig) { + this.sentinelConfig = sentinelConfig; - this.poolConfig = poolConfig != null ? poolConfig : new JedisPoolConfig(); + this.clientConfiguration = MutableJedisClientConfiguration + .create(poolConfig != null ? poolConfig : new JedisPoolConfig()); } /** * Constructs a new {@link JedisConnectionFactory} instance using the given {@link RedisClusterConfiguration} applied * to create a {@link JedisCluster}. * - * @param clusterConfig + * @param clusterConfig must not be {@literal null}. * @since 1.7 */ public JedisConnectionFactory(RedisClusterConfiguration clusterConfig) { - this.clusterConfig = clusterConfig; + this(clusterConfig, new MutableJedisClientConfiguration()); } /** * Constructs a new {@link JedisConnectionFactory} instance using the given {@link RedisClusterConfiguration} applied * to create a {@link JedisCluster}. * - * @param clusterConfig + * @param clusterConfig must not be {@literal null}. * @since 1.7 */ public JedisConnectionFactory(RedisClusterConfiguration clusterConfig, JedisPoolConfig poolConfig) { + + Assert.notNull(clusterConfig, "RedisClusterConfiguration must not be null!"); + + this.clusterConfig = clusterConfig; + this.clientConfiguration = MutableJedisClientConfiguration.create(poolConfig); + } + + /** + * Constructs a new {@link JedisConnectionFactory} instance using the given {@link RedisStandaloneConfiguration}. + * + * @param standaloneConfig must not be {@literal null}. + * @since 2.0 + */ + public JedisConnectionFactory(RedisStandaloneConfiguration standaloneConfig) { + this(standaloneConfig, new MutableJedisClientConfiguration()); + } + + /** + * Constructs a new {@link JedisConnectionFactory} instance using the given {@link RedisStandaloneConfiguration} and + * {@link JedisClientConfiguration}. + * + * @param standaloneConfig must not be {@literal null}. + * @param clientConfig must not be {@literal null}. + * @since 2.0 + */ + public JedisConnectionFactory(RedisStandaloneConfiguration standaloneConfig, JedisClientConfiguration clientConfig) { + + this(clientConfig); + + Assert.notNull(standaloneConfig, "RedisStandaloneConfiguration must not be null!"); + + this.standaloneConfig = standaloneConfig; + } + + /** + * Constructs a new {@link JedisConnectionFactory} instance using the given {@link RedisSentinelConfiguration} and + * {@link JedisClientConfiguration}. + * + * @param sentinelConfig must not be {@literal null}. + * @param clientConfig must not be {@literal null}. + * @since 2.0 + */ + public JedisConnectionFactory(RedisSentinelConfiguration sentinelConfig, JedisClientConfiguration clientConfig) { + + this(clientConfig); + + Assert.notNull(sentinelConfig, "RedisSentinelConfiguration must not be null!"); + + this.sentinelConfig = sentinelConfig; + } + + /** + * Constructs a new {@link JedisConnectionFactory} instance using the given {@link RedisClusterConfiguration} and + * {@link JedisClientConfiguration}. + * + * @param clusterConfig must not be {@literal null}. + * @param clientConfig must not be {@literal null}. + * @since 2.0 + */ + public JedisConnectionFactory(RedisClusterConfiguration clusterConfig, JedisClientConfiguration clientConfig) { + + this(clientConfig); + + Assert.notNull(clusterConfig, "RedisClusterConfiguration must not be null!"); + this.clusterConfig = clusterConfig; - this.poolConfig = poolConfig; } /** @@ -190,11 +284,11 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, protected Jedis fetchJedisConnector() { try { - if (usePool && pool != null) { + if (getUsePool() && pool != null) { return pool.getResource(); } - Jedis jedis = new Jedis(getShardInfo()); + Jedis jedis = createJedis(); // force initialization (see Jedis issue #82) jedis.connect(); @@ -205,6 +299,25 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, } } + private Jedis createJedis() { + + if (providedShardInfo) { + return new Jedis(getShardInfo()); + } + + Jedis jedis = new Jedis(getHostName(), getPort(), getConnectTimeout(), getReadTimeout(), isUseSsl(), + clientConfiguration.getSslSocketFactory().orElse(null), // + clientConfiguration.getSslParameters().orElse(null), // + clientConfiguration.getHostnameVerifier().orElse(null)); + + Client client = jedis.getClient(); + + client.setPassword(getPassword()); + client.setDb(getDatabase()); + + return jedis; + } + /** * Post process a newly retrieved connection. Useful for decorating or executing initialization commands on a new * connection. This implementation simply returns the connection. @@ -221,23 +334,33 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() */ public void afterPropertiesSet() { - if (shardInfo == null) { - shardInfo = new JedisShardInfo(hostName, port); - if (StringUtils.hasLength(password)) { - shardInfo.setPassword(password); + if (shardInfo == null && clientConfiguration instanceof MutableJedisClientConfiguration) { + + providedShardInfo = false; + shardInfo = new JedisShardInfo(getHostName(), getPort(), isUseSsl(), // + clientConfiguration.getSslSocketFactory().orElse(null), // + clientConfiguration.getSslParameters().orElse(null), // + clientConfiguration.getHostnameVerifier().orElse(null)); + + if (StringUtils.hasLength(getPassword())) { + shardInfo.setPassword(getPassword()); } - if (timeout > 0) { - setTimeoutOn(shardInfo, timeout); + int readTimeout = getReadTimeout(); + + if (readTimeout > 0) { + setTimeoutOn(shardInfo, readTimeout); } + + getMutableConfiguration().setShardInfo(shardInfo); } - if (usePool && clusterConfig == null) { + if (getUsePool() && !isRedisClusterAware()) { this.pool = createPool(); } - if (clusterConfig != null) { + if (isRedisClusterAware()) { this.cluster = createCluster(); } } @@ -258,9 +381,10 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, * @since 1.4 */ protected PoolcreateRedisSentinelPool(RedisSentinelConfiguration config) { + + GenericObjectPoolConfig poolConfig = getPoolConfig() != null ? getPoolConfig() : new JedisPoolConfig(); return new JedisSentinelPool(config.getMaster().getName(), convertToJedisSentinelSet(config.getSentinels()), - getPoolConfig() != null ? getPoolConfig() : new JedisPoolConfig(), getTimeoutFrom(getShardInfo()), - getShardInfo().getPassword(), Protocol.DEFAULT_DATABASE, clientName); + poolConfig, getConnectTimeout(), getReadTimeout(), getPassword(), Protocol.DEFAULT_DATABASE, getClientName()); } /** @@ -271,13 +395,16 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, */ protected Pool createRedisPool() { - return new JedisPool(getPoolConfig(), getShardInfo().getHost(), getShardInfo().getPort(), - getTimeoutFrom(getShardInfo()), getShardInfo().getPassword(), Protocol.DEFAULT_DATABASE, clientName, useSsl); + return new JedisPool(getPoolConfig(), getHostName(), getPort(), getConnectTimeout(), getReadTimeout(), + getPassword(), Protocol.DEFAULT_DATABASE, getClientName(), isUseSsl(), + clientConfiguration.getSslSocketFactory().orElse(null), // + clientConfiguration.getSslParameters().orElse(null), // + clientConfiguration.getHostnameVerifier().orElse(null)); } private JedisCluster createCluster() { - JedisCluster cluster = createCluster(this.clusterConfig, this.poolConfig); + JedisCluster cluster = createCluster(this.clusterConfig, getPoolConfig()); this.clusterCommandExecutor = new ClusterCommandExecutor( new JedisClusterConnection.JedisClusterTopologyProvider(cluster), new JedisClusterConnection.JedisClusterNodeResourceProvider(cluster), EXCEPTION_TRANSLATION); @@ -301,11 +428,14 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, hostAndPort.add(new HostAndPort(node.getHost(), node.getPort())); } - int redirects = clusterConfig.getMaxRedirects() != null ? clusterConfig.getMaxRedirects().intValue() : 5; + int redirects = clusterConfig.getMaxRedirects() != null ? clusterConfig.getMaxRedirects() : 5; + + int connectTimeout = getConnectTimeout(); + int readTimeout = getReadTimeout(); return StringUtils.hasText(getPassword()) - ? new JedisCluster(hostAndPort, timeout, timeout, redirects, password, poolConfig) - : new JedisCluster(hostAndPort, timeout, redirects, poolConfig); + ? new JedisCluster(hostAndPort, connectTimeout, readTimeout, redirects, getPassword(), poolConfig) + : new JedisCluster(hostAndPort, connectTimeout, readTimeout, redirects, poolConfig); } /* @@ -313,7 +443,9 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, * @see org.springframework.beans.factory.DisposableBean#destroy() */ public void destroy() { - if (usePool && pool != null) { + + if (getUsePool() && pool != null) { + try { pool.destroy(); } catch (Exception ex) { @@ -321,12 +453,15 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, } pool = null; } + if (cluster != null) { + try { cluster.close(); } catch (Exception ex) { log.warn("Cannot properly close Jedis cluster", ex); } + try { clusterCommandExecutor.destroy(); } catch (Exception ex) { @@ -341,13 +476,14 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, */ public RedisConnection getConnection() { - if (cluster != null) { + if (isRedisClusterAware()) { return getClusterConnection(); } Jedis jedis = fetchJedisConnector(); - JedisConnection connection = (usePool ? new JedisConnection(jedis, pool, dbIndex, clientName) - : new JedisConnection(jedis, null, dbIndex, clientName)); + String clientName = clientConfiguration.getClientName().orElse(null); + JedisConnection connection = (getUsePool() ? new JedisConnection(jedis, pool, getDatabase(), clientName) + : new JedisConnection(jedis, null, getDatabase(), clientName)); connection.setConvertPipelineAndTxResults(convertPipelineAndTxResults); return postProcessConnection(connection); } @@ -359,7 +495,7 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, @Override public RedisClusterConnection getClusterConnection() { - if (cluster == null) { + if (!isRedisClusterAware()) { throw new InvalidDataAccessApiUsageException("Cluster is not configured!"); } return new JedisClusterConnection(cluster, clusterCommandExecutor); @@ -374,31 +510,23 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, } /** - * Returns the Redis hostName. + * Returns the Redis hostname. * * @return the hostName. */ public String getHostName() { - return hostName; + return standaloneConfig.getHostName(); } /** - * Sets the Redis hostName. + * Sets the Redis hostname. * - * @param hostName the hostName to set. + * @param hostName the hostname to set. + * @deprecated since 2.0, configure the hostname using {@link RedisStandaloneConfiguration}. */ + @Deprecated public void setHostName(String hostName) { - this.hostName = hostName; - } - - /** - * Sets whether to use SSL. - * - * @param useSsl {@literal true} to use SSL. - * @since 1.8 - */ - public void setUseSsl(boolean useSsl) { - this.useSsl = useSsl; + standaloneConfig.setHostName(hostName); } /** @@ -408,7 +536,20 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, * @since 1.8 */ public boolean isUseSsl() { - return useSsl; + return clientConfiguration.useSsl(); + } + + /** + * Sets whether to use SSL. + * + * @param useSsl {@literal true} to use SSL. + * @since 1.8 + * @deprecated since 2.0, configure the SSL usage with {@link JedisClientConfiguration}. + * @throws IllegalStateException if {@link JedisClientConfiguration} is immutable. + */ + @Deprecated + public void setUseSsl(boolean useSsl) { + getMutableConfiguration().setUseSsl(useSsl); } /** @@ -417,16 +558,39 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, * @return password for authentication. */ public String getPassword() { - return password; + + if (isRedisSentinelAware()) { + return sentinelConfig.getPassword(); + } + + if (isRedisClusterAware()) { + return clusterConfig.getPassword(); + } + + return 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) { - this.password = password; + + if (isRedisSentinelAware()) { + sentinelConfig.setPassword(password); + return; + } + + if (isRedisClusterAware()) { + clusterConfig.setPassword(password); + return; + } + + standaloneConfig.setPassword(password); } /** @@ -435,23 +599,27 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, * @return the Redis port. */ public int getPort() { - return port; + return standaloneConfig.getPort(); } /** * Sets the port used to connect to the Redis instance. * * @param port the Redis port. + * @deprecated since 2.0, configure the port using {@link RedisStandaloneConfiguration}. */ + @Deprecated public void setPort(int port) { - this.port = port; + standaloneConfig.setPort(port); } /** * Returns the shardInfo. * * @return the shardInfo. + * @deprecated since 2.0. */ + @Deprecated public JedisShardInfo getShardInfo() { return shardInfo; } @@ -460,9 +628,16 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, * Sets the shard info for this factory. * * @param shardInfo the shardInfo to set. + * @deprecated since 2.0, configure the individual properties from {@link JedisShardInfo} using + * {@link JedisClientConfiguration}. + * @throws IllegalStateException if {@link JedisClientConfiguration} is immutable. */ + @Deprecated public void setShardInfo(JedisShardInfo shardInfo) { + this.shardInfo = shardInfo; + this.providedShardInfo = true; + getMutableConfiguration().setShardInfo(shardInfo); } /** @@ -471,16 +646,21 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, * @return the timeout. */ public int getTimeout() { - return timeout; + return getReadTimeout(); } /** * Sets the timeout. * * @param timeout the timeout to set. + * @deprecated since 2.0, configure the timeout using {@link JedisClientConfiguration}. + * @throws IllegalStateException if {@link JedisClientConfiguration} is immutable. */ + @Deprecated public void setTimeout(int timeout) { - this.timeout = timeout; + + getMutableConfiguration().setReadTimeout(Duration.ofMillis(timeout)); + getMutableConfiguration().setConnectTimeout(Duration.ofMillis(timeout)); } /** @@ -489,16 +669,19 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, * @return the use of connection pooling. */ public boolean getUsePool() { - return usePool; + return clientConfiguration.usePooling(); } /** * Turns on or off the use of connection pooling. * * @param usePool the usePool to set. + * @deprecated since 2.0, configure pooling usage with {@link JedisClientConfiguration}. + * @throws IllegalStateException if {@link JedisClientConfiguration} is immutable. */ + @Deprecated public void setUsePool(boolean usePool) { - this.usePool = usePool; + getMutableConfiguration().setUsePooling(usePool); } /** @@ -506,17 +689,20 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, * * @return the poolConfig */ - public JedisPoolConfig getPoolConfig() { - return poolConfig; + public GenericObjectPoolConfig getPoolConfig() { + return clientConfiguration.getPoolConfig().orElse(null); } /** * Sets the pool configuration for this factory. * * @param poolConfig the poolConfig to set. + * @deprecated since 2.0, configure {@link JedisPoolConfig} using {@link JedisClientConfiguration}. + * @throws IllegalStateException if {@link JedisClientConfiguration} is immutable. */ + @Deprecated public void setPoolConfig(JedisPoolConfig poolConfig) { - this.poolConfig = poolConfig; + getMutableConfiguration().setPoolConfig(poolConfig); } /** @@ -525,17 +711,32 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, * @return the database index. */ public int getDatabase() { - return dbIndex; + + if (isRedisSentinelAware()) { + return sentinelConfig.getDatabase(); + } + + return standaloneConfig.getDatabase(); } /** * Sets the index of the database used by this connection factory. Default is 0. * * @param index database index. + * @deprecated since 2.0, configure the client name using {@link RedisSentinelConfiguration} or + * {@link RedisStandaloneConfiguration}. */ + @Deprecated public void setDatabase(int index) { + Assert.isTrue(index >= 0, "invalid DB index (a positive index required)"); - this.dbIndex = index; + + if (isRedisSentinelAware()) { + sentinelConfig.setDatabase(index); + return; + } + + standaloneConfig.setDatabase(index); } /** @@ -545,7 +746,7 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, * @since 1.8 */ public String getClientName() { - return clientName; + return clientConfiguration.getClientName().orElse(null); } /** @@ -553,9 +754,44 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, * * @param clientName the client name. * @since 1.8 + * @deprecated since 2.0, configure the client name using {@link JedisClientConfiguration}. + * @throws IllegalStateException if {@link JedisClientConfiguration} is immutable. */ + @Deprecated public void setClientName(String clientName) { - this.clientName = clientName; + this.getMutableConfiguration().setClientName(clientName); + } + + /** + * @return the {@link JedisClientConfiguration}. + * @since 2.0 + */ + public JedisClientConfiguration getClientConfiguration() { + return clientConfiguration; + } + + /** + * @return the {@link RedisStandaloneConfiguration}. + * @since 2.0 + */ + public RedisStandaloneConfiguration getStandaloneConfiguration() { + return standaloneConfig; + } + + /** + * @return the {@link RedisStandaloneConfiguration}, may be {@literal null}. + * @since 2.0 + */ + public RedisSentinelConfiguration getSentinelConfiguration() { + return sentinelConfig; + } + + /** + * @return the {@link RedisClusterConfiguration}, may be {@literal null}. + * @since 2.0 + */ + public RedisClusterConfiguration getClusterConfiguration() { + return clusterConfig; } /** @@ -588,6 +824,14 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, return sentinelConfig != null; } + /** + * @return true when {@link RedisClusterConfiguration} is present. + * @since 2.0 + */ + public boolean isRedisClusterAware() { + return clusterConfig != null; + } + /* (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnectionFactory#getSentinelConnection() */ @@ -607,7 +851,7 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, for (RedisNode node : this.sentinelConfig.getSentinels()) { - Jedis jedis = new Jedis(node.getHost(), node.getPort()); + Jedis jedis = new Jedis(node.getHost(), node.getPort(), getConnectTimeout(), getReadTimeout()); if (jedis.ping().equalsIgnoreCase("pong")) { @@ -635,18 +879,176 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, } private void potentiallySetClientName(Jedis jedis) { - - if (StringUtils.hasText(clientName)) { - jedis.clientSetname(clientName); - } + clientConfiguration.getClientName().ifPresent(jedis::clientSetname); } private void setTimeoutOn(JedisShardInfo shardInfo, int timeout) { ReflectionUtils.invokeMethod(SET_TIMEOUT_METHOD, shardInfo, timeout); } - private int getTimeoutFrom(JedisShardInfo shardInfo) { - return (Integer) ReflectionUtils.invokeMethod(GET_TIMEOUT_METHOD, shardInfo); + private int getReadTimeout() { + return Math.toIntExact(clientConfiguration.getReadTimeout().toMillis()); } + private int getConnectTimeout() { + return Math.toIntExact(clientConfiguration.getConnectTimeout().toMillis()); + } + + private MutableJedisClientConfiguration getMutableConfiguration() { + + Assert.state(clientConfiguration instanceof MutableJedisClientConfiguration, + () -> String.format("Client configuration must be instance of MutableJedisClientConfiguration but is %s", + ClassUtils.getShortName(clientConfiguration.getClass()))); + + return (MutableJedisClientConfiguration) clientConfiguration; + } + + static class MutableJedisClientConfiguration implements JedisClientConfiguration { + + private boolean useSsl; + private SSLSocketFactory sslSocketFactory; + private SSLParameters sslParameters; + private HostnameVerifier hostnameVerifier; + private boolean usePooling = true; + private GenericObjectPoolConfig poolConfig = new JedisPoolConfig(); + private String clientName; + private Duration readTimeout = Duration.ofMillis(Protocol.DEFAULT_TIMEOUT); + private Duration connectTimeout = Duration.ofMillis(Protocol.DEFAULT_TIMEOUT); + + public static JedisClientConfiguration create(JedisShardInfo shardInfo) { + + MutableJedisClientConfiguration configuration = new MutableJedisClientConfiguration(); + + configuration.setShardInfo(shardInfo); + + return configuration; + } + + public static JedisClientConfiguration create(GenericObjectPoolConfig jedisPoolConfig) { + + MutableJedisClientConfiguration configuration = new MutableJedisClientConfiguration(); + + configuration.setPoolConfig(jedisPoolConfig); + + return configuration; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration#useSsl() + */ + @Override + public boolean useSsl() { + return useSsl; + } + + public void setUseSsl(boolean useSsl) { + this.useSsl = useSsl; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration#getSslSocketFactory() + */ + @Override + public Optional getSslSocketFactory() { + return Optional.ofNullable(sslSocketFactory); + } + + public void setSslSocketFactory(SSLSocketFactory sslSocketFactory) { + this.sslSocketFactory = sslSocketFactory; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration#getSslParameters() + */ + @Override + public Optional getSslParameters() { + return Optional.ofNullable(sslParameters); + } + + public void setSslParameters(SSLParameters sslParameters) { + this.sslParameters = sslParameters; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration#getHostnameVerifier() + */ + @Override + public Optional getHostnameVerifier() { + return Optional.ofNullable(hostnameVerifier); + } + + public void setHostnameVerifier(HostnameVerifier hostnameVerifier) { + this.hostnameVerifier = hostnameVerifier; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration#usePooling() + */ + @Override + public boolean usePooling() { + return usePooling; + } + + public void setUsePooling(boolean usePooling) { + this.usePooling = usePooling; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration#getPoolConfig() + */ + @Override + public Optional getPoolConfig() { + return Optional.ofNullable(poolConfig); + } + + public void setPoolConfig(GenericObjectPoolConfig poolConfig) { + this.poolConfig = poolConfig; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration#getClientName() + */ + @Override + public Optional getClientName() { + return Optional.ofNullable(clientName); + } + + public void setClientName(String clientName) { + this.clientName = clientName; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration#getReadTimeout() + */ + @Override + public Duration getReadTimeout() { + return readTimeout; + } + + public void setReadTimeout(Duration readTimeout) { + this.readTimeout = readTimeout; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.jedis.JedisClientConfiguration#getConnectTimeout() + */ + @Override + public Duration getConnectTimeout() { + return connectTimeout; + } + + public void setConnectTimeout(Duration connectTimeout) { + this.connectTimeout = connectTimeout; + } + + public void setShardInfo(JedisShardInfo shardInfo) { + + setSslSocketFactory(shardInfo.getSslSocketFactory()); + setSslParameters(shardInfo.getSslParameters()); + setHostnameVerifier(shardInfo.getHostnameVerifier()); + setUseSsl(shardInfo.getSsl()); + setConnectTimeout(Duration.ofMillis(shardInfo.getConnectionTimeout())); + setReadTimeout(Duration.ofMillis(shardInfo.getSoTimeout())); + } + } } diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/DefaultLettuceClientConfiguration.java b/src/main/java/org/springframework/data/redis/connection/lettuce/DefaultLettuceClientConfiguration.java new file mode 100644 index 000000000..993c4d813 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/DefaultLettuceClientConfiguration.java @@ -0,0 +1,107 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.redis.connection.lettuce; + +import io.lettuce.core.ClientOptions; +import io.lettuce.core.resource.ClientResources; + +import java.time.Duration; +import java.util.Optional; + +/** + * Default implementation of {@literal LettuceClientConfiguration}. + * + * @author Mark Paluch + * @since 2.0 + */ +class DefaultLettuceClientConfiguration implements LettuceClientConfiguration { + + private final boolean useSsl; + private final boolean verifyPeer; + private final boolean startTls; + private final Optional clientResources; + private final Optional clientOptions; + private final Duration timeout; + private final Duration shutdownTimeout; + + DefaultLettuceClientConfiguration(boolean useSsl, boolean verifyPeer, boolean startTls, + ClientResources clientResources, ClientOptions clientOptions, Duration timeout, Duration shutdownTimeout) { + + this.useSsl = useSsl; + this.verifyPeer = verifyPeer; + this.startTls = startTls; + this.clientResources = Optional.ofNullable(clientResources); + this.clientOptions = Optional.ofNullable(clientOptions); + this.timeout = timeout; + this.shutdownTimeout = shutdownTimeout; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#useSsl() + */ + @Override + public boolean useSsl() { + return useSsl; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#isVerifyPeer() + */ + @Override + public boolean isVerifyPeer() { + return verifyPeer; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#isStartTls() + */ + @Override + public boolean isStartTls() { + return startTls; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getClientResources() + */ + @Override + public Optional getClientResources() { + return clientResources; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getClientOptions() + */ + @Override + public Optional getClientOptions() { + return clientOptions; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getTimeout() + */ + @Override + public Duration getTimeout() { + return timeout; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getShutdownTimeout() + */ + @Override + public Duration getShutdownTimeout() { + return shutdownTimeout; + } +} diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClientConfiguration.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClientConfiguration.java new file mode 100644 index 000000000..c0533cea3 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClientConfiguration.java @@ -0,0 +1,339 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.redis.connection.lettuce; + +import io.lettuce.core.ClientOptions; +import io.lettuce.core.RedisURI; +import io.lettuce.core.resource.ClientResources; + +import java.time.Duration; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +import org.springframework.util.Assert; + +/** + * Redis client configuration for lettuce. This configuration provides optional configuration elements such as + * {@link ClientResources} and {@link ClientOptions} specific to Lettuce client features. + * + * Providing optional elements allows a more specific configuration of the client: + *
+ *
+ * + * @author Mark Paluch + * @since 2.0 + * @see org.springframework.data.redis.connection.RedisStandaloneConfiguration + * @see org.springframework.data.redis.connection.RedisSentinelConfiguration + * @see org.springframework.data.redis.connection.RedisClusterConfiguration + */ +public interface LettuceClientConfiguration { + + /** + * @return {@literal true} to use SSL, {@literal false} to use unencrypted connections. + */ + boolean useSsl(); + + /** + * @return {@literal true} to verify peers when using {@link #useSsl() SSL}. + */ + boolean isVerifyPeer(); + + /** + * @return {@literal true} to use Start TLS ({@code true} if the first write request shouldn't be encrypted). + */ + boolean isStartTls(); + + /** + * @return the optional {@link ClientResources}. + */ + Optional- Whether to use SSL
+ *- Whether to verify peers using SSL
+ *- Whether to use StartTLS
+ *- Optional {@link ClientResources}
+ *- Optional {@link ClientOptions}
+ *- Client {@link Duration timeout}
+ *- Shutdown {@link Duration timeout}
+ *getClientResources(); + + /** + * @return the optional {@link io.lettuce.core.ClientOptions}. + */ + Optional getClientOptions(); + + /** + * @return the timeout. + */ + Duration getTimeout(); + + /** + * @return the shutdown timeout used to close the client. + * @see io.lettuce.core.AbstractRedisClient#shutdown(long, long, TimeUnit) + */ + Duration getShutdownTimeout(); + + /** + * Creates a new {@link LettuceClientConfigurationBuilder} to build {@link LettuceClientConfiguration} to be used with + * the Lettuce client. + * + * @return a new {@link LettuceClientConfigurationBuilder} to build {@link LettuceClientConfiguration}. + */ + static LettuceClientConfigurationBuilder builder() { + return new DefaultLettuceClientConfigurationBuilder(); + } + + /** + * Creates an empty {@link LettuceClientConfiguration}. + * + * @return an empty {@link LettuceClientConfiguration}. + */ + static LettuceClientConfiguration create() { + return builder().build(); + } + + /** + * Builder for {@link LettuceClientConfiguration}. + */ + interface LettuceClientConfigurationBuilder { + + /** + * Enable SSL connections. + * + * @return {@link LettuceSslClientConfigurationBuilder}. + */ + LettuceSslClientConfigurationBuilder useSsl(); + + /** + * Use plaintext connections instead of SSL. + * + * @return {@link LettuceClientConfigurationBuilder}. + */ + LettuceClientConfigurationBuilder usePlaintext(); + + /** + * Configure {@link ClientResources}. + * + * @param clientResources must not be {@literal null}. + * @return {@literal this} builder. + */ + LettuceClientConfigurationBuilder clientResources(ClientResources clientResources); + + /** + * Configure {@link ClientOptions}. + * + * @param clientOptions must not be {@literal null}. + * @return {@literal this} builder. + * @see ClientOptions + * @see io.lettuce.core.cluster.ClusterClientOptions + */ + LettuceClientConfigurationBuilder clientOptions(ClientOptions clientOptions); + + /** + * Configure a timeout. + * + * @param timeout must not be {@literal null}. + * @return {@literal this} builder. + */ + LettuceClientConfigurationBuilder timeout(Duration timeout); + + /** + * Configure a shutdown timeout. + * + * @param shutdownTimeout must not be {@literal null}. + * @return {@literal this} builder. + */ + LettuceClientConfigurationBuilder shutdownTimeout(Duration shutdownTimeout); + + /** + * Build the {@link LettuceClientConfiguration} with the configuration applied from this builder. + * + * @return a new {@link LettuceClientConfiguration} object. + */ + LettuceClientConfiguration build(); + } + + /** + * Builder for SSL-related {@link LettuceClientConfiguration}. + */ + interface LettuceSslClientConfigurationBuilder { + + /** + * Enable peer verification. + * + * @return {@literal this} builder. + */ + LettuceSslClientConfigurationBuilder verifyPeer(); + + /** + * Enable/disable peer verification. + * + * @param verifyPeer {@literal true} to enable peer verification, {@literal false} to skip peer verification. + * @return {@literal this} builder. + */ + LettuceSslClientConfigurationBuilder verifyPeer(boolean verifyPeer); + + /** + * Enable Start TLS to send the first bytes unencrypted. + * + * @return {@literal this} builder. + */ + LettuceSslClientConfigurationBuilder startTls(); + + /** + * Return to {@link LettuceClientConfigurationBuilder}. + * + * @return {@link LettuceClientConfigurationBuilder}. + */ + LettuceClientConfigurationBuilder and(); + + /** + * Build the {@link LettuceClientConfiguration} with the configuration applied from this builder. + * + * @return a new {@link LettuceClientConfiguration} object. + */ + LettuceClientConfiguration build(); + } + + /** + * Default {@link LettuceClientConfigurationBuilder} implementation to build an immutable + * {@link LettuceClientConfiguration}. + */ + class DefaultLettuceClientConfigurationBuilder + implements LettuceClientConfigurationBuilder, LettuceSslClientConfigurationBuilder { + + private boolean useSsl; + private boolean verifyPeer = true; + private boolean startTls; + private ClientResources clientResources; + private ClientOptions clientOptions; + private Duration timeout = Duration.ofSeconds(RedisURI.DEFAULT_TIMEOUT); + private Duration shutdownTimeout = Duration.ofSeconds(2); + + private DefaultLettuceClientConfigurationBuilder() {} + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder#useSsl() + */ + @Override + public LettuceSslClientConfigurationBuilder useSsl() { + + this.useSsl = true; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder#usePlaintext() + */ + @Override + public LettuceClientConfigurationBuilder usePlaintext() { + + this.useSsl = false; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceSslClientConfigurationBuilder#verifyPeer() + */ + @Override + public LettuceSslClientConfigurationBuilder verifyPeer() { + return verifyPeer(true); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceSslClientConfigurationBuilder#verifyPeer(boolean) + */ + @Override + public LettuceSslClientConfigurationBuilder verifyPeer(boolean verifyPeer) { + + this.verifyPeer = verifyPeer; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceSslClientConfigurationBuilder#startTls() + */ + @Override + public LettuceSslClientConfigurationBuilder startTls() { + + this.startTls = true; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceSslClientConfigurationBuilder#and() + */ + @Override + public LettuceClientConfigurationBuilder and() { + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder#clientResources(io.lettuce.core.resource.ClientResources) + */ + @Override + public LettuceClientConfigurationBuilder clientResources(ClientResources clientResources) { + + Assert.notNull(clientResources, "ClientResources must not be null!"); + + this.clientResources = clientResources; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder#clientOptions(io.lettuce.core.ClientOptions) + */ + @Override + public LettuceClientConfigurationBuilder clientOptions(ClientOptions clientOptions) { + + Assert.notNull(clientOptions, "ClientOptions must not be null!"); + + this.clientOptions = clientOptions; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder#timeout(java.time.Duration) + */ + @Override + public LettuceClientConfigurationBuilder timeout(Duration timeout) { + + Assert.notNull(timeout, "Duration must not be null!"); + + this.timeout = timeout; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder#shutdownTimeout(java.time.Duration) + */ + @Override + public LettuceClientConfigurationBuilder shutdownTimeout(Duration shutdownTimeout) { + + Assert.notNull(timeout, "Duration must not be null!"); + + this.shutdownTimeout = shutdownTimeout; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder#build() + */ + @Override + public LettuceClientConfiguration build() { + return new DefaultLettuceClientConfiguration(useSsl, verifyPeer, startTls, clientResources, clientOptions, + timeout, shutdownTimeout); + } + } +} diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java index 516df39f6..3f2217228 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java @@ -16,15 +16,19 @@ package org.springframework.data.redis.connection.lettuce; import io.lettuce.core.AbstractRedisClient; +import io.lettuce.core.ClientOptions; import io.lettuce.core.RedisClient; import io.lettuce.core.RedisException; import io.lettuce.core.RedisURI; import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.cluster.ClusterClientOptions; import io.lettuce.core.cluster.RedisClusterClient; import io.lettuce.core.resource.ClientResources; +import java.time.Duration; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; @@ -37,16 +41,7 @@ import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.data.redis.ExceptionTranslationStrategy; import org.springframework.data.redis.PassThroughExceptionTranslationStrategy; import org.springframework.data.redis.RedisConnectionFailureException; -import org.springframework.data.redis.connection.ClusterCommandExecutor; -import org.springframework.data.redis.connection.Pool; -import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; -import org.springframework.data.redis.connection.RedisClusterConfiguration; -import org.springframework.data.redis.connection.RedisClusterConnection; -import org.springframework.data.redis.connection.RedisConnection; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.RedisNode; -import org.springframework.data.redis.connection.RedisSentinelConfiguration; -import org.springframework.data.redis.connection.RedisSentinelConnection; +import org.springframework.data.redis.connection.*; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -62,6 +57,14 @@ import org.springframework.util.StringUtils; * 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. + * + * {@link LettuceConnectionFactory} should be configured using an environmental configuration and the + * {@link LettuceConnectionFactory client configuration}. Lettuce supports the following environmental configurations: + *
+ *
* * @author Costin Leau * @author Jennifer Hickey @@ -79,67 +82,128 @@ public class LettuceConnectionFactory LettuceConverters.exceptionConverter()); private final Log log = LogFactory.getLog(getClass()); + private final LettuceClientConfiguration clientConfiguration; - private String hostName = "localhost"; - private int port = 6379; private AbstractRedisClient client; - private long timeout = TimeUnit.MILLISECONDS.convert(60, TimeUnit.SECONDS); - private long shutdownTimeout = TimeUnit.MILLISECONDS.convert(2, TimeUnit.SECONDS); private boolean validateConnection = false; private boolean shareNativeConnection = true; private StatefulRedisConnection- {@link RedisStandaloneConfiguration}
+ *- {@link RedisSentinelConfiguration}
+ *- {@link RedisClusterConfiguration}
+ *connection; private LettucePool pool; - private int dbIndex = 0; /** Synchronization monitor for the shared Connection */ private final Object connectionMonitor = new Object(); - private String password; private boolean convertPipelineAndTxResults = true; + private RedisStandaloneConfiguration standaloneConfig = new RedisStandaloneConfiguration("localhost", 6379); private RedisSentinelConfiguration sentinelConfiguration; private RedisClusterConfiguration clusterConfiguration; private ClusterCommandExecutor clusterCommandExecutor; - private ClientResources clientResources; - private boolean useSsl = false; - private boolean verifyPeer = true; - private boolean startTls = false; /** - * Constructs a new LettuceConnectionFactoryinstance with default settings. + * Constructs a new {@link LettuceConnectionFactory} instance with default settings. */ - public LettuceConnectionFactory() {} + public LettuceConnectionFactory() { + this(new MutableLettuceClientConfiguration()); + } /** - * Constructs a newLettuceConnectionFactoryinstance with default settings. + * 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; + } + + /** + * Constructs a new {@link LettuceConnectionFactory} instance with default settings. */ public LettuceConnectionFactory(String host, int port) { - this.hostName = host; - this.port = port; + this(new RedisStandaloneConfiguration(host, port), new MutableLettuceClientConfiguration()); } /** * Constructs a new {@link LettuceConnectionFactory} instance using the given {@link RedisSentinelConfiguration} * - * @param sentinelConfiguration + * @param sentinelConfiguration must not be {@literal null}. * @since 1.6 */ public LettuceConnectionFactory(RedisSentinelConfiguration sentinelConfiguration) { - this.sentinelConfiguration = sentinelConfiguration; + this(sentinelConfiguration, new MutableLettuceClientConfiguration()); } /** * Constructs a new {@link LettuceConnectionFactory} instance using the given {@link RedisClusterConfiguration} * applied to create a {@link RedisClusterClient}. * - * @param clusterConfig + * @param clusterConfiguration must not be {@literal null}. * @since 1.7 */ - public LettuceConnectionFactory(RedisClusterConfiguration clusterConfig) { - this.clusterConfiguration = clusterConfig; + public LettuceConnectionFactory(RedisClusterConfiguration clusterConfiguration) { + this(clusterConfiguration, new MutableLettuceClientConfiguration()); } 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; + } + + /** + * 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.sentinelConfiguration = 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.clusterConfiguration = clusterConfiguration; + } + /* * (non-Javadoc) * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() @@ -157,7 +221,8 @@ public class LettuceConnectionFactory resetConnection(); try { - client.shutdown(shutdownTimeout, shutdownTimeout, TimeUnit.MILLISECONDS); + Duration timeout = clientConfiguration.getShutdownTimeout(); + client.shutdown(timeout.toMillis(), timeout.toMillis(), TimeUnit.MILLISECONDS); } catch (Exception e) { if (log.isWarnEnabled()) { @@ -186,7 +251,8 @@ public class LettuceConnectionFactory return getClusterConnection(); } - LettuceConnection connection = new LettuceConnection(getSharedConnection(), timeout, client, pool, dbIndex); + LettuceConnection connection = new LettuceConnection(getSharedConnection(), getTimeout(), client, pool, + getDatabase()); connection.setConvertPipelineAndTxResults(convertPipelineAndTxResults); return connection; } @@ -283,16 +349,18 @@ public class LettuceConnectionFactory * @return the host. */ public String getHostName() { - return hostName; + return standaloneConfig.getHostName(); } /** - * Sets the host. + * Sets the hostname. * - * @param host the host to set. + * @param hostName the hostname to set. + * @deprecated since 2.0, configure the hostname using {@link RedisStandaloneConfiguration}. */ - public void setHostName(String host) { - this.hostName = host; + @Deprecated + public void setHostName(String hostName) { + standaloneConfig.setHostName(hostName); } /** @@ -301,16 +369,18 @@ public class LettuceConnectionFactory * @return the port. */ public int getPort() { - return port; + return 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) { - this.port = port; + standaloneConfig.setPort(port); } /** @@ -319,25 +389,19 @@ public class LettuceConnectionFactory * @return connection timeout. */ public long getTimeout() { - return timeout; + return getClientTimeout(); } /** * Sets the connection timeout (in milliseconds). * - * @param timeout connection timeout. + * @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) { - this.timeout = timeout; - } - - /** - * Sets to use SSL connection. - * - * @param useSsl {@literal true} to use SSL. - */ - public void setUseSsl(boolean useSsl) { - this.useSsl = useSsl; + getMutableConfiguration().setTimeout(Duration.ofMillis(timeout)); } /** @@ -346,25 +410,40 @@ public class LettuceConnectionFactory * @return use of SSL. */ public boolean isUseSsl() { - return useSsl; + return clientConfiguration.useSsl(); + } + + /** + * 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) { - this.verifyPeer = verifyPeer; - } - - /** - * Returns whether to verify certificate validity/hostname check when SSL is used. - * - * @return verify peers when using SSL. - */ - public boolean isVerifyPeer() { - return verifyPeer; + getMutableConfiguration().setVerifyPeer(verifyPeer); } /** @@ -373,16 +452,19 @@ public class LettuceConnectionFactory * @return use of StartTLS. */ public boolean isStartTls() { - return startTls; + 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) { - this.startTls = startTls; + getMutableConfiguration().setStartTls(startTls); } /** @@ -436,7 +518,12 @@ public class LettuceConnectionFactory * @return the database index. */ public int getDatabase() { - return dbIndex; + + if (isRedisSentinelAware()) { + return sentinelConfiguration.getDatabase(); + } + + return standaloneConfig.getDatabase(); } /** @@ -445,8 +532,15 @@ public class LettuceConnectionFactory * @param index database index */ public void setDatabase(int index) { + Assert.isTrue(index >= 0, "invalid DB index (a positive index required)"); - this.dbIndex = index; + + if (isRedisSentinelAware()) { + sentinelConfiguration.setDatabase(index); + return; + } + + standaloneConfig.setDatabase(index); } /** @@ -455,16 +549,39 @@ public class LettuceConnectionFactory * @return password for authentication. */ public String getPassword() { - return password; + + if (isRedisSentinelAware()) { + return sentinelConfiguration.getPassword(); + } + + if (isClusterAware()) { + return clusterConfiguration.getPassword(); + } + + return 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) { - this.password = password; + + if (isRedisSentinelAware()) { + sentinelConfiguration.setPassword(password); + return; + } + + if (isClusterAware()) { + clusterConfiguration.setPassword(password); + return; + } + + standaloneConfig.setPassword(password); } /** @@ -474,7 +591,7 @@ public class LettuceConnectionFactory * @since 1.6 */ public long getShutdownTimeout() { - return shutdownTimeout; + return clientConfiguration.getShutdownTimeout().toMillis(); } /** @@ -482,9 +599,12 @@ public class LettuceConnectionFactory * * @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) { - this.shutdownTimeout = shutdownTimeout; + getMutableConfiguration().setShutdownTimeout(Duration.ofMillis(shutdownTimeout)); } /** @@ -494,7 +614,7 @@ public class LettuceConnectionFactory * @since 1.7 */ public ClientResources getClientResources() { - return clientResources; + return clientConfiguration.getClientResources().orElse(null); } /** @@ -503,9 +623,44 @@ public class LettuceConnectionFactory * * @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) { - this.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 RedisStandaloneConfiguration}, may be {@literal null}. + * @since 2.0 + */ + public RedisSentinelConfiguration getSentinelConfiguration() { + return sentinelConfiguration; + } + + /** + * @return the {@link RedisClusterConfiguration}, may be {@literal null}. + * @since 2.0 + */ + public RedisClusterConfiguration getClusterConfiguration() { + return clusterConfiguration; } /** @@ -568,8 +723,8 @@ public class LettuceConnectionFactory StatefulRedisConnectionconnection = null; if (client instanceof RedisClient) { connection = ((RedisClient) client).connect(LettuceConnection.CODEC); - if (dbIndex > 0) { - connection.sync().select(dbIndex); + if (getDatabase() > 0) { + connection.sync().select(getDatabase()); } } else { connection = null; @@ -585,11 +740,12 @@ public class LettuceConnectionFactory if (isRedisSentinelAware()) { RedisURI redisURI = getSentinelRedisURI(); - if (clientResources == null) { - return RedisClient.create(redisURI); - } + RedisClient redisClient = clientConfiguration.getClientResources() // + .map(clientResources -> RedisClient.create(clientResources, redisURI)) // + .orElseGet(() -> RedisClient.create(redisURI)); - return RedisClient.create(clientResources, redisURI); + clientConfiguration.getClientOptions().ifPresent(redisClient::setOptions); + return redisClient; } if (isClusterAware()) { @@ -599,13 +755,18 @@ public class LettuceConnectionFactory initialUris.add(createRedisURIAndApplySettings(node.getHost(), node.getPort())); } - RedisClusterClient clusterClient = clientResources != null - ? RedisClusterClient.create(clientResources, initialUris) : RedisClusterClient.create(initialUris); + RedisClusterClient clusterClient = clientConfiguration.getClientResources() // + .map(clientResources -> RedisClusterClient.create(clientResources, initialUris)) // + .orElseGet(() -> RedisClusterClient.create(initialUris)); this.clusterCommandExecutor = new ClusterCommandExecutor( new LettuceClusterConnection.LettuceClusterTopologyProvider(clusterClient), new LettuceClusterConnection.LettuceClusterNodeResourceProvider(clusterClient), EXCEPTION_TRANSLATION); + clientConfiguration.getClientOptions() // + .filter(clientOptions -> clientOptions instanceof ClusterClientOptions) // + .ifPresent(clientOptions -> clusterClient.setOptions((ClusterClientOptions) clientOptions)); + return clusterClient; } @@ -613,16 +774,20 @@ public class LettuceConnectionFactory return pool.getClient(); } - RedisURI uri = createRedisURIAndApplySettings(hostName, port); - return clientResources != null ? RedisClient.create(clientResources, uri) : RedisClient.create(uri); + RedisURI uri = 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 RedisURI getSentinelRedisURI() { RedisURI redisUri = LettuceConverters.sentinelConfigurationToRedisURI(sentinelConfiguration); - if (StringUtils.hasText(password)) { - redisUri.setPassword(password); + if (StringUtils.hasText(getPassword())) { + redisUri.setPassword(getPassword()); } return redisUri; @@ -631,14 +796,14 @@ public class LettuceConnectionFactory private RedisURI createRedisURIAndApplySettings(String host, int port) { RedisURI.Builder builder = RedisURI.Builder.redis(host, port); - if (StringUtils.hasText(password)) { - builder.withPassword(password); + if (StringUtils.hasText(getPassword())) { + builder.withPassword(getPassword()); } - builder.withSsl(useSsl); - builder.withVerifyPeer(verifyPeer); - builder.withStartTls(startTls); - builder.withTimeout(timeout, TimeUnit.MILLISECONDS); + builder.withSsl(clientConfiguration.useSsl()); + builder.withVerifyPeer(clientConfiguration.isVerifyPeer()); + builder.withStartTls(clientConfiguration.isStartTls()); + builder.withTimeout(clientConfiguration.getTimeout().toMillis(), TimeUnit.MILLISECONDS); return builder.build(); } @@ -651,4 +816,107 @@ public class LettuceConnectionFactory } return new LettuceSentinelConnection(((RedisClient) client).connectSentinel()); } + + 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.getTimeout().toMillis(); + } + + static class MutableLettuceClientConfiguration implements LettuceClientConfiguration { + + private boolean useSsl; + private boolean verifyPeer = true; + private boolean startTls; + private ClientResources clientResources; + private Duration timeout = Duration.ofSeconds(RedisURI.DEFAULT_TIMEOUT); + private Duration shutdownTimeout = Duration.ofSeconds(RedisURI.DEFAULT_TIMEOUT); + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#useSsl() + */ + @Override + public boolean useSsl() { + return useSsl; + } + + public void setUseSsl(boolean useSsl) { + this.useSsl = useSsl; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#isVerifyPeer() + */ + @Override + public boolean isVerifyPeer() { + return verifyPeer; + } + + public void setVerifyPeer(boolean verifyPeer) { + this.verifyPeer = verifyPeer; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#isStartTls() + */ + @Override + public boolean isStartTls() { + return startTls; + } + + public void setStartTls(boolean startTls) { + this.startTls = startTls; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getClientResources() + */ + @Override + public Optional getClientResources() { + return Optional.ofNullable(clientResources); + } + + public void setClientResources(ClientResources clientResources) { + this.clientResources = clientResources; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getClientOptions() + */ + @Override + public Optional getClientOptions() { + return Optional.empty(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getTimeout() + */ + @Override + public Duration getTimeout() { + return timeout; + } + + public void setTimeout(Duration timeout) { + this.timeout = timeout; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getShutdownTimeout() + */ + @Override + public Duration getShutdownTimeout() { + return shutdownTimeout; + } + + public void setShutdownTimeout(Duration shutdownTimeout) { + this.shutdownTimeout = shutdownTimeout; + } + } } diff --git a/src/test/java/org/springframework/data/redis/ConnectionFactoryTracker.java b/src/test/java/org/springframework/data/redis/ConnectionFactoryTracker.java index b0dfbf83a..d0b03b3bf 100644 --- a/src/test/java/org/springframework/data/redis/ConnectionFactoryTracker.java +++ b/src/test/java/org/springframework/data/redis/ConnectionFactoryTracker.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,9 @@ */ package org.springframework.data.redis; +import java.util.ArrayList; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import org.springframework.beans.factory.DisposableBean; @@ -26,6 +28,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; * factory during setup and then call {@link #cleanUp()} through the @AfterClass method. * * @author Costin Leau + * @author Mark Paluch */ public abstract class ConnectionFactoryTracker { @@ -41,12 +44,14 @@ public abstract class ConnectionFactoryTracker { public static void cleanUp() { if (connFactories != null) { - for (Object connectionFactory : connFactories) { + List