diff --git a/pom.xml b/pom.xml index f3272d4e9..734a2c4e7 100644 --- a/pom.xml +++ b/pom.xml @@ -208,10 +208,6 @@ **/*Tests.java **/*Test.java - - true - ${basedir}/src/test/resources/trusted.keystore - diff --git a/src/main/java/org/springframework/data/redis/connection/RedisClusterConfiguration.java b/src/main/java/org/springframework/data/redis/connection/RedisClusterConfiguration.java index e7a88c11e..729cf2305 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisClusterConfiguration.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisClusterConfiguration.java @@ -35,7 +35,7 @@ import org.springframework.util.StringUtils; * Configuration class used for setting up {@link RedisConnection} via {@link RedisConnectionFactory} using connecting * to Redis Cluster. Useful when setting up a high availability Redis * environment. - * + * * @author Christoph Strobl * @author Mark Paluch * @since 1.7 @@ -47,6 +47,7 @@ public class RedisClusterConfiguration { private Set clusterNodes; private Integer maxRedirects; + private String password; /** * Creates new {@link RedisClusterConfiguration}. @@ -57,16 +58,15 @@ public class RedisClusterConfiguration { /** * Creates {@link RedisClusterConfiguration} for given hostPort combinations. - * + * *
+	 * 
 	 * 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(Collection clusterNodes) {
 		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}.
-	 * 
+	 *
 	 * 
 	 * 
 	 * 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
 	 * 
 	 * 
- * + * * @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 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 Optional sslSocketFactory;
+	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: + *

    + *
  • 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}
  • + *
+ * + * @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 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: + *

    + *
  • {@link RedisStandaloneConfiguration}
  • + *
  • {@link RedisSentinelConfiguration}
  • + *
  • {@link RedisClusterConfiguration}
  • + *
* * @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 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 JedisConnectionFactory instance 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 new JedisConnectionFactory instance. 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 Pool createRedisSentinelPool(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: + *

    + *
  • 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}
  • + *
+ * + * @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 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: + *

    + *
  • {@link RedisStandaloneConfiguration}
  • + *
  • {@link RedisSentinelConfiguration}
  • + *
  • {@link RedisClusterConfiguration}
  • + *
* * @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 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 LettuceConnectionFactory instance with default settings. + * Constructs a new {@link LettuceConnectionFactory} instance with default settings. */ - public LettuceConnectionFactory() {} + public LettuceConnectionFactory() { + this(new MutableLettuceClientConfiguration()); + } /** - * Constructs a new LettuceConnectionFactory instance 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 StatefulRedisConnection connection = 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 copy = new ArrayList<>(connFactories); + for (Object connectionFactory : copy) { try { if (connectionFactory instanceof DisposableBean) { ((DisposableBean) connectionFactory).destroy(); // System.out.println("Succesfully cleaned up factory " + connectionFactory); } + connFactories.remove(connectionFactory); } catch (Exception ex) { System.err.println("Cannot clean factory " + connectionFactory + ex); } diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisClientConfigurationUnitTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisClientConfigurationUnitTests.java new file mode 100644 index 000000000..4bac761c4 --- /dev/null +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisClientConfigurationUnitTests.java @@ -0,0 +1,105 @@ +/* + * 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 static org.assertj.core.api.Assertions.*; + +import redis.clients.jedis.JedisPoolConfig; + +import java.security.NoSuchAlgorithmException; +import java.time.Duration; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; + +import org.junit.Test; + +/** + * Unit tests for {@link JedisClientConfiguration}. + * + * @author Mark Paluch + */ +public class JedisClientConfigurationUnitTests { + + @Test // DATAREDIS-574 + public void shouldCreateEmptyConfiguration() { + + JedisClientConfiguration configuration = JedisClientConfiguration.create(); + + assertThat(configuration.getClientName()).isEmpty(); + assertThat(configuration.getConnectTimeout()).isEqualTo(Duration.ofSeconds(2)); + assertThat(configuration.getReadTimeout()).isEqualTo(Duration.ofSeconds(2)); + assertThat(configuration.getHostnameVerifier()).isEmpty(); + assertThat(configuration.getPoolConfig()).isPresent(); + assertThat(configuration.getSslParameters()).isEmpty(); + assertThat(configuration.getSslSocketFactory()).isEmpty(); + } + + @Test // DATAREDIS-574 + public void shouldConfigureAllProperties() throws NoSuchAlgorithmException { + + SSLParameters sslParameters = new SSLParameters(); + SSLContext context = SSLContext.getDefault(); + SSLSocketFactory socketFactory = context.getSocketFactory(); + JedisPoolConfig poolConfig = new JedisPoolConfig(); + + JedisClientConfiguration configuration = JedisClientConfiguration.builder().useSsl() // + .hostnameVerifier(MyHostnameVerifier.INSTANCE) // + .sslParameters(sslParameters) // + .sslSocketFactory(socketFactory).and() // + .clientName("my-client") // + .connectTimeout(Duration.ofMinutes(10)) // + .readTimeout(Duration.ofHours(5)) // + .usePooling().poolConfig(poolConfig) // + .build(); + + assertThat(configuration.useSsl()).isTrue(); + assertThat(configuration.getHostnameVerifier()).contains(MyHostnameVerifier.INSTANCE); + assertThat(configuration.getSslParameters()).contains(sslParameters); + assertThat(configuration.getSslSocketFactory()).contains(socketFactory); + + assertThat(configuration.getClientName()).contains("my-client"); + assertThat(configuration.getConnectTimeout()).isEqualTo(Duration.ofMinutes(10)); + assertThat(configuration.getReadTimeout()).isEqualTo(Duration.ofHours(5)); + + assertThat(configuration.getPoolConfig()).contains(poolConfig); + } + + @Test // DATAREDIS-574 + public void shouldAllowsConfigurationOverrides() { + + JedisClientConfiguration configuration = JedisClientConfiguration.builder().useSsl() // + .and().usePlaintext() // + .usePooling().and().useUnpooledConnections() // + .build(); + + assertThat(configuration.useSsl()).isFalse(); + assertThat(configuration.usePooling()).isFalse(); + } + + enum MyHostnameVerifier implements HostnameVerifier { + + INSTANCE; + + @Override + public boolean verify(String s, SSLSession sslSession) { + return false; + } + } +} diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactoryIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactoryIntegrationTests.java new file mode 100644 index 000000000..8b390072a --- /dev/null +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactoryIntegrationTests.java @@ -0,0 +1,68 @@ +/* + * 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 static org.hamcrest.core.IsEqual.*; +import static org.junit.Assert.*; + +import redis.clients.jedis.JedisShardInfo; + +import org.junit.After; +import org.junit.Test; +import org.springframework.data.redis.SettingsUtils; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; + +/** + * Integration tests for {@link JedisConnectionFactory}. + * + * @author Mark Paluch + */ +public class JedisConnectionFactoryIntegrationTests { + + private JedisConnectionFactory factory; + + @After + public void tearDown() { + + if (factory != null) { + factory.destroy(); + } + } + + @Test // DATAREDIS-574 + public void shardInfoShouldOverrideFactorySettings() { + + factory = new JedisConnectionFactory(new JedisShardInfo(SettingsUtils.getHost(), SettingsUtils.getPort())); + factory.setUsePool(false); + factory.setPassword("foo"); + factory.setHostName("bar"); + factory.setPort(1234); + factory.afterPropertiesSet(); + + assertThat(factory.getConnection().ping(), equalTo("PONG")); + } + + @Test // DATAREDIS-574 + public void shouldInitiaizeWithStandaloneConfiguration() { + + factory = new JedisConnectionFactory( + new RedisStandaloneConfiguration(SettingsUtils.getHost(), SettingsUtils.getPort()), + JedisClientConfiguration.create()); + factory.afterPropertiesSet(); + + assertThat(factory.getConnection().ping(), equalTo("PONG")); + } +} diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactoryTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactorySentinelIntegrationTests.java similarity index 72% rename from src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactoryTests.java rename to src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactorySentinelIntegrationTests.java index ece335c32..98e3fb97d 100644 --- a/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactoryTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactorySentinelIntegrationTests.java @@ -19,17 +19,19 @@ import static org.hamcrest.core.IsEqual.*; import static org.junit.Assert.*; import org.junit.After; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.springframework.data.redis.connection.RedisSentinelConfiguration; import org.springframework.data.redis.test.util.RedisSentinelRule; /** + * Sentinel integration tests for {@link JedisConnectionFactory}. + * * @author Christoph Strobl * @author Fu Jian + * @author Mark Paluch */ -public class JedisConnectionFactoryTests { +public class JedisConnectionFactorySentinelIntegrationTests { private static final RedisSentinelConfiguration SENTINEL_CONFIG = new RedisSentinelConfiguration().master("mymaster") .sentinel("127.0.0.1", 26379).sentinel("127.0.0.1", 26380); @@ -37,25 +39,43 @@ public class JedisConnectionFactoryTests { public @Rule RedisSentinelRule sentinelRule = RedisSentinelRule.forConfig(SENTINEL_CONFIG).oneActive(); - @Before - public void setUp() { - factory = new JedisConnectionFactory(SENTINEL_CONFIG); - factory.setClientName("clientName"); - factory.afterPropertiesSet(); - } - @After public void tearDown() { - factory.destroy(); + + if (factory != null) { + factory.destroy(); + } + } + + @Test // DATAREDIS-574 + public void shouldInitiaizeWithSentinelConfiguration() { + + JedisClientConfiguration clientConfiguration = JedisClientConfiguration.builder() // + .clientName("clientName") // + .build(); + + factory = new JedisConnectionFactory(SENTINEL_CONFIG, clientConfiguration); + factory.afterPropertiesSet(); + + assertThat(factory.getConnection().getClientName(), equalTo("clientName")); } @Test // DATAREDIS-324 public void shouldSendCommandCorrectlyViaConnectionFactoryUsingSentinel() { + + factory = new JedisConnectionFactory(SENTINEL_CONFIG); + factory.afterPropertiesSet(); + assertThat(factory.getConnection().ping(), equalTo("PONG")); } @Test // DATAREDIS-552 public void getClientNameShouldEqualWithFactorySetting() { + + factory = new JedisConnectionFactory(SENTINEL_CONFIG); + factory.setClientName("clientName"); + factory.afterPropertiesSet(); + assertThat(factory.getConnection().getClientName(), equalTo("clientName")); } } diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactoryUnitTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactoryUnitTests.java index 427bf64e7..b4fb20140 100644 --- a/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactoryUnitTests.java @@ -15,31 +15,44 @@ */ package org.springframework.data.redis.connection.jedis; +import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import redis.clients.jedis.JedisCluster; +import redis.clients.jedis.JedisPoolConfig; +import sun.net.www.protocol.https.DefaultHostnameVerifier; + import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.time.temporal.ChronoUnit; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocketFactory; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.junit.Test; import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisSentinelConfiguration; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.test.util.ReflectionTestUtils; -import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.JedisPoolConfig; - /** + * Unit tests for {@link JedisConnectionFactory}. + * * @author Christoph Strobl + * @author Mark Paluch */ public class JedisConnectionFactoryUnitTests { private JedisConnectionFactory connectionFactory; - private static final RedisSentinelConfiguration SINGLE_SENTINEL_CONFIG = new RedisSentinelConfiguration().master( - "mymaster").sentinel("127.0.0.1", 26379); + private static final RedisSentinelConfiguration SINGLE_SENTINEL_CONFIG = new RedisSentinelConfiguration() + .master("mymaster").sentinel("127.0.0.1", 26379); - private static final RedisClusterConfiguration CLUSTER_CONFIG = new RedisClusterConfiguration().clusterNode( - "127.0.0.1", 6379).clusterNode("127.0.0.1", 6380); + private static final RedisClusterConfiguration CLUSTER_CONFIG = new RedisClusterConfiguration() + .clusterNode("127.0.0.1", 6379).clusterNode("127.0.0.1", 6380); @Test // DATAREDIS-324 public void shouldInitSentinelPoolWhenSentinelConfigPresent() { @@ -73,7 +86,7 @@ public class JedisConnectionFactoryUnitTests { } @Test // DATAREDIS-315 - public void shouldClostClusterCorrectlyOnFactoryDestruction() throws IOException { + public void shouldCloseClusterCorrectlyOnFactoryDestruction() throws IOException { JedisCluster clusterMock = mock(JedisCluster.class); JedisConnectionFactory factory = new JedisConnectionFactory(); @@ -84,6 +97,196 @@ public class JedisConnectionFactoryUnitTests { verify(clusterMock, times(1)).close(); } + @Test // DATAREDIS-574 + public void shouldReadStandalonePassword() { + + RedisStandaloneConfiguration envConfig = new RedisStandaloneConfiguration(); + envConfig.setPassword("foo"); + + connectionFactory = new JedisConnectionFactory(envConfig, JedisClientConfiguration.create()); + + assertThat(connectionFactory.getPassword()).isEqualTo("foo"); + } + + @Test // DATAREDIS-574 + public void shouldWriteStandalonePassword() { + + RedisStandaloneConfiguration envConfig = new RedisStandaloneConfiguration(); + envConfig.setPassword("foo"); + + connectionFactory = new JedisConnectionFactory(envConfig, JedisClientConfiguration.create()); + connectionFactory.setPassword("bar"); + + assertThat(connectionFactory.getPassword()).isEqualTo("bar"); + assertThat(envConfig.getPassword()).isEqualTo("bar"); + } + + @Test // DATAREDIS-574 + public void shouldReadSentinelPassword() { + + RedisSentinelConfiguration envConfig = new RedisSentinelConfiguration(); + envConfig.setPassword("foo"); + + connectionFactory = new JedisConnectionFactory(envConfig, JedisClientConfiguration.create()); + + assertThat(connectionFactory.getPassword()).isEqualTo("foo"); + } + + @Test // DATAREDIS-574 + public void shouldWriteSentinelPassword() { + + RedisSentinelConfiguration envConfig = new RedisSentinelConfiguration(); + envConfig.setPassword("foo"); + + connectionFactory = new JedisConnectionFactory(envConfig, JedisClientConfiguration.create()); + connectionFactory.setPassword("bar"); + + assertThat(connectionFactory.getPassword()).isEqualTo("bar"); + assertThat(envConfig.getPassword()).isEqualTo("bar"); + } + + @Test // DATAREDIS-574 + public void shouldReadClusterPassword() { + + RedisClusterConfiguration envConfig = new RedisClusterConfiguration(); + envConfig.setPassword("foo"); + + connectionFactory = new JedisConnectionFactory(envConfig, JedisClientConfiguration.create()); + + assertThat(connectionFactory.getPassword()).isEqualTo("foo"); + } + + @Test // DATAREDIS-574 + public void shouldWriteClusterPassword() { + + RedisClusterConfiguration envConfig = new RedisClusterConfiguration(); + envConfig.setPassword("foo"); + + connectionFactory = new JedisConnectionFactory(envConfig, JedisClientConfiguration.create()); + connectionFactory.setPassword("bar"); + + assertThat(connectionFactory.getPassword()).isEqualTo("bar"); + assertThat(envConfig.getPassword()).isEqualTo("bar"); + } + + @Test // DATAREDIS-574 + public void shouldReadStandaloneDatabaseIndex() { + + RedisStandaloneConfiguration envConfig = new RedisStandaloneConfiguration(); + envConfig.setDatabase(2); + + connectionFactory = new JedisConnectionFactory(envConfig, JedisClientConfiguration.create()); + + assertThat(connectionFactory.getDatabase()).isEqualTo(2); + } + + @Test // DATAREDIS-574 + public void shouldWriteStandaloneDatabaseIndex() { + + RedisStandaloneConfiguration envConfig = new RedisStandaloneConfiguration(); + envConfig.setDatabase(2); + + connectionFactory = new JedisConnectionFactory(envConfig, JedisClientConfiguration.create()); + connectionFactory.setDatabase(3); + + assertThat(connectionFactory.getDatabase()).isEqualTo(3); + assertThat(envConfig.getDatabase()).isEqualTo(3); + } + + @Test // DATAREDIS-574 + public void shouldReadSentinelDatabaseIndex() { + + RedisSentinelConfiguration envConfig = new RedisSentinelConfiguration(); + envConfig.setDatabase(2); + + connectionFactory = new JedisConnectionFactory(envConfig, JedisClientConfiguration.create()); + + assertThat(connectionFactory.getDatabase()).isEqualTo(2); + } + + @Test // DATAREDIS-574 + public void shouldWriteSentinelDatabaseIndex() { + + RedisSentinelConfiguration envConfig = new RedisSentinelConfiguration(); + envConfig.setDatabase(2); + + connectionFactory = new JedisConnectionFactory(envConfig, JedisClientConfiguration.create()); + connectionFactory.setDatabase(3); + + assertThat(connectionFactory.getDatabase()).isEqualTo(3); + assertThat(envConfig.getDatabase()).isEqualTo(3); + } + + @Test // DATAREDIS-574 + public void shouldApplyClientConfiguration() throws NoSuchAlgorithmException { + + SSLParameters sslParameters = new SSLParameters(); + SSLContext context = SSLContext.getDefault(); + SSLSocketFactory socketFactory = context.getSocketFactory(); + JedisPoolConfig poolConfig = new JedisPoolConfig(); + + JedisClientConfiguration configuration = JedisClientConfiguration.builder().useSsl() // + .hostnameVerifier(new DefaultHostnameVerifier()) // + .sslParameters(sslParameters) // + .sslSocketFactory(socketFactory).and() // + .clientName("my-client") // + .connectTimeout(Duration.of(10, ChronoUnit.MINUTES)) // + .readTimeout(Duration.of(5, ChronoUnit.DAYS)) // + .usePooling().poolConfig(poolConfig) // + .build(); + + connectionFactory = new JedisConnectionFactory(new RedisStandaloneConfiguration(), configuration); + + assertThat(connectionFactory.getClientConfiguration()).isSameAs(configuration); + assertThat(connectionFactory.isUseSsl()).isTrue(); + assertThat(connectionFactory.getClientName()).isEqualTo("my-client"); + assertThat(connectionFactory.getTimeout()).isEqualTo((int) Duration.of(5, ChronoUnit.DAYS).toMillis()); + assertThat(connectionFactory.getUsePool()).isTrue(); + assertThat(connectionFactory.getPoolConfig()).isSameAs(poolConfig); + } + + @Test // DATAREDIS-574 + public void shouldReturnStandaloneConfiguration() { + + RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(); + connectionFactory = new JedisConnectionFactory(configuration, JedisClientConfiguration.create()); + + assertThat(connectionFactory.getStandaloneConfiguration()).isSameAs(configuration); + assertThat(connectionFactory.getSentinelConfiguration()).isNull(); + assertThat(connectionFactory.getClusterConfiguration()).isNull(); + } + + @Test // DATAREDIS-574 + public void shouldReturnSentinelConfiguration() { + + RedisSentinelConfiguration configuration = new RedisSentinelConfiguration(); + connectionFactory = new JedisConnectionFactory(configuration, JedisClientConfiguration.create()); + + assertThat(connectionFactory.getStandaloneConfiguration()).isNotNull(); + assertThat(connectionFactory.getSentinelConfiguration()).isSameAs(configuration); + assertThat(connectionFactory.getClusterConfiguration()).isNull(); + } + + @Test // DATAREDIS-574 + public void shouldReturnClusterConfiguration() { + + RedisClusterConfiguration configuration = new RedisClusterConfiguration(); + connectionFactory = new JedisConnectionFactory(configuration, JedisClientConfiguration.create()); + + assertThat(connectionFactory.getStandaloneConfiguration()).isNotNull(); + assertThat(connectionFactory.getSentinelConfiguration()).isNull(); + assertThat(connectionFactory.getClusterConfiguration()).isSameAs(configuration); + } + + @Test(expected = IllegalStateException.class) // DATAREDIS-574 + public void shouldDenyChangesToImmutableClientConfiguration() throws NoSuchAlgorithmException { + + connectionFactory = new JedisConnectionFactory(new RedisStandaloneConfiguration(), + JedisClientConfiguration.create()); + + connectionFactory.setClientName("foo"); + } + private JedisConnectionFactory initSpyedConnectionFactory(RedisSentinelConfiguration sentinelConfig, JedisPoolConfig poolConfig) { diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClientConfigurationUnitTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClientConfigurationUnitTests.java new file mode 100644 index 000000000..17191980c --- /dev/null +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClientConfigurationUnitTests.java @@ -0,0 +1,82 @@ +/* + * 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 static org.assertj.core.api.Assertions.*; + +import io.lettuce.core.ClientOptions; +import io.lettuce.core.resource.ClientResources; + +import java.time.Duration; + +import org.junit.Test; + +/** + * Unit tests for {@link LettuceClientConfiguration}. + * + * @author Mark Paluch + */ +public class LettuceClientConfigurationUnitTests { + + @Test // DATAREDIS-574 + public void shouldCreateEmptyConfiguration() { + + LettuceClientConfiguration configuration = LettuceClientConfiguration.create(); + + assertThat(configuration.useSsl()).isFalse(); + assertThat(configuration.isVerifyPeer()).isTrue(); + assertThat(configuration.isStartTls()).isFalse(); + assertThat(configuration.getClientOptions()).isEmpty(); + assertThat(configuration.getClientResources()).isEmpty(); + assertThat(configuration.getTimeout()).isEqualTo(Duration.ofSeconds(60)); + assertThat(configuration.getShutdownTimeout()).isEqualTo(Duration.ofSeconds(2)); + } + + @Test // DATAREDIS-574 + public void shouldConfigureAllProperties() { + + ClientOptions clientOptions = ClientOptions.create(); + ClientResources sharedClientResources = LettuceTestClientResources.getSharedClientResources(); + + LettuceClientConfiguration configuration = LettuceClientConfiguration.builder() // + .useSsl() // + .verifyPeer(false) // + .startTls().and() // + .clientOptions(clientOptions) // + .clientResources(sharedClientResources) // + .timeout(Duration.ofMinutes(5)) // + .shutdownTimeout(Duration.ofHours(2)) // + .build(); + + assertThat(configuration.useSsl()).isTrue(); + assertThat(configuration.isVerifyPeer()).isFalse(); + assertThat(configuration.isStartTls()).isTrue(); + assertThat(configuration.getClientOptions()).contains(clientOptions); + assertThat(configuration.getClientResources()).contains(sharedClientResources); + assertThat(configuration.getTimeout()).isEqualTo(Duration.ofMinutes(5)); + assertThat(configuration.getShutdownTimeout()).isEqualTo(Duration.ofHours(2)); + } + + @Test // DATAREDIS-574 + public void shouldAllowsConfigurationOverrides() { + + LettuceClientConfiguration configuration = LettuceClientConfiguration.builder() // + .useSsl().and().usePlaintext() // + .build(); + + assertThat(configuration.useSsl()).isFalse(); + } +} diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactoryUnitTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactoryUnitTests.java index a57db4708..5baf91c10 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactoryUnitTests.java @@ -18,16 +18,21 @@ package org.springframework.data.redis.connection.lettuce; import static org.hamcrest.core.Is.*; import static org.hamcrest.core.IsEqual.*; import static org.hamcrest.core.IsInstanceOf.*; +import static org.hamcrest.core.IsNull.*; import static org.junit.Assert.*; import static org.springframework.data.redis.connection.ClusterTestVariables.*; import static org.springframework.data.redis.connection.lettuce.LettuceTestClientResources.*; import static org.springframework.test.util.ReflectionTestUtils.*; import io.lettuce.core.AbstractRedisClient; +import io.lettuce.core.ClientOptions; import io.lettuce.core.RedisClient; import io.lettuce.core.RedisURI; import io.lettuce.core.cluster.RedisClusterClient; +import io.lettuce.core.resource.ClientResources; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; import java.util.Collections; import java.util.concurrent.TimeUnit; @@ -37,8 +42,11 @@ import org.junit.Test; import org.springframework.data.redis.ConnectionFactoryTracker; import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisSentinelConfiguration; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; /** + * Unit tests for {@link LettuceConnectionFactory}. + * * @author Christoph Strobl * @author Mark Paluch * @author Balázs Németh @@ -275,4 +283,208 @@ public class LettuceConnectionFactoryUnitTests { assertThat(uri.isVerifyPeer(), is(true)); } } + + @Test // DATAREDIS-574 + public void shouldReadStandalonePassword() { + + RedisStandaloneConfiguration envConfig = new RedisStandaloneConfiguration(); + envConfig.setPassword("foo"); + + LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(envConfig, + LettuceClientConfiguration.create()); + + assertThat(connectionFactory.getPassword(), is(equalTo("foo"))); + } + + @Test // DATAREDIS-574 + public void shouldWriteStandalonePassword() { + + RedisStandaloneConfiguration envConfig = new RedisStandaloneConfiguration(); + envConfig.setPassword("foo"); + + LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(envConfig, + LettuceClientConfiguration.create()); + connectionFactory.setPassword("bar"); + + assertThat(connectionFactory.getPassword(), is(equalTo("bar"))); + assertThat(envConfig.getPassword(), is(equalTo("bar"))); + } + + @Test // DATAREDIS-574 + public void shouldReadSentinelPassword() { + + RedisSentinelConfiguration envConfig = new RedisSentinelConfiguration(); + envConfig.setPassword("foo"); + + LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(envConfig, + LettuceClientConfiguration.create()); + + assertThat(connectionFactory.getPassword(), is(equalTo("foo"))); + } + + @Test // DATAREDIS-574 + public void shouldWriteSentinelPassword() { + + RedisSentinelConfiguration envConfig = new RedisSentinelConfiguration(); + envConfig.setPassword("foo"); + + LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(envConfig, + LettuceClientConfiguration.create()); + connectionFactory.setPassword("bar"); + + assertThat(connectionFactory.getPassword(), is(equalTo("bar"))); + assertThat(envConfig.getPassword(), is(equalTo("bar"))); + } + + @Test // DATAREDIS-574 + public void shouldReadClusterPassword() { + + RedisClusterConfiguration envConfig = new RedisClusterConfiguration(); + envConfig.setPassword("foo"); + + LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(envConfig, + LettuceClientConfiguration.create()); + + assertThat(connectionFactory.getPassword(), is(equalTo("foo"))); + } + + @Test // DATAREDIS-574 + public void shouldWriteClusterPassword() { + + RedisClusterConfiguration envConfig = new RedisClusterConfiguration(); + envConfig.setPassword("foo"); + + LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(envConfig, + LettuceClientConfiguration.create()); + connectionFactory.setPassword("bar"); + + assertThat(connectionFactory.getPassword(), is(equalTo("bar"))); + assertThat(envConfig.getPassword(), is(equalTo("bar"))); + } + + @Test // DATAREDIS-574 + public void shouldReadStandaloneDatabaseIndex() { + + RedisStandaloneConfiguration envConfig = new RedisStandaloneConfiguration(); + envConfig.setDatabase(2); + + LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(envConfig, + LettuceClientConfiguration.create()); + + assertThat(connectionFactory.getDatabase(), is(2)); + } + + @Test // DATAREDIS-574 + public void shouldWriteStandaloneDatabaseIndex() { + + RedisStandaloneConfiguration envConfig = new RedisStandaloneConfiguration(); + envConfig.setDatabase(2); + + LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(envConfig, + LettuceClientConfiguration.create()); + connectionFactory.setDatabase(3); + + assertThat(connectionFactory.getDatabase(), is(3)); + assertThat(envConfig.getDatabase(), is(3)); + } + + @Test // DATAREDIS-574 + public void shouldReadSentinelDatabaseIndex() { + + RedisSentinelConfiguration envConfig = new RedisSentinelConfiguration(); + envConfig.setDatabase(2); + + LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(envConfig, + LettuceClientConfiguration.create()); + + assertThat(connectionFactory.getDatabase(), is(2)); + } + + @Test // DATAREDIS-574 + public void shouldWriteSentinelDatabaseIndex() { + + RedisSentinelConfiguration envConfig = new RedisSentinelConfiguration(); + envConfig.setDatabase(2); + + LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(envConfig, + LettuceClientConfiguration.create()); + connectionFactory.setDatabase(3); + + assertThat(connectionFactory.getDatabase(), is(3)); + assertThat(envConfig.getDatabase(), is(3)); + } + + @Test // DATAREDIS-574 + public void shouldApplyClientConfiguration() { + + ClientOptions clientOptions = ClientOptions.create(); + ClientResources sharedClientResources = LettuceTestClientResources.getSharedClientResources(); + + LettuceClientConfiguration configuration = LettuceClientConfiguration.builder() // + .useSsl() // + .verifyPeer(false) // + .startTls().and() // + .clientOptions(clientOptions) // + .clientResources(sharedClientResources) // + .timeout(Duration.ofMinutes(5)) // + .shutdownTimeout(Duration.ofHours(2)) // + .build(); + + LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(new RedisStandaloneConfiguration(), + configuration); + + assertThat(connectionFactory.getClientConfiguration(), is(configuration)); + + assertThat(connectionFactory.isUseSsl(), is(true)); + assertThat(connectionFactory.isVerifyPeer(), is(false)); + assertThat(connectionFactory.isStartTls(), is(true)); + assertThat(connectionFactory.getClientResources(), is(sharedClientResources)); + assertThat(connectionFactory.getTimeout(), is(Duration.ofMinutes(5).toMillis())); + assertThat(connectionFactory.getShutdownTimeout(), is(Duration.ofHours(2).toMillis())); + } + + @Test // DATAREDIS-574 + public void shouldReturnStandaloneConfiguration() { + + RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(); + LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(configuration, + LettuceClientConfiguration.create()); + + assertThat(connectionFactory.getStandaloneConfiguration(), is(configuration)); + assertThat(connectionFactory.getSentinelConfiguration(), is(nullValue())); + assertThat(connectionFactory.getClusterConfiguration(), is(nullValue())); + } + + @Test // DATAREDIS-574 + public void shouldReturnSentinelConfiguration() { + + RedisSentinelConfiguration configuration = new RedisSentinelConfiguration(); + LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(configuration, + LettuceClientConfiguration.create()); + + assertThat(connectionFactory.getStandaloneConfiguration(), is(notNullValue())); + assertThat(connectionFactory.getSentinelConfiguration(), is(configuration)); + assertThat(connectionFactory.getClusterConfiguration(), is(nullValue())); + } + + @Test // DATAREDIS-574 + public void shouldReturnClusterConfiguration() { + + RedisClusterConfiguration configuration = new RedisClusterConfiguration(); + LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(configuration, + LettuceClientConfiguration.create()); + + assertThat(connectionFactory.getStandaloneConfiguration(), is(notNullValue())); + assertThat(connectionFactory.getSentinelConfiguration(), is(nullValue())); + assertThat(connectionFactory.getClusterConfiguration(), is(configuration)); + } + + @Test(expected = IllegalStateException.class) // DATAREDIS-574 + public void shouldDenyChangesToImmutableClientConfiguration() throws NoSuchAlgorithmException { + + LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(new RedisStandaloneConfiguration(), + LettuceClientConfiguration.create()); + + connectionFactory.setUseSsl(false); + } } diff --git a/src/test/java/org/springframework/data/redis/core/AbstractOperationsTestParams.java b/src/test/java/org/springframework/data/redis/core/AbstractOperationsTestParams.java index 0810090c5..ebf1ff1ad 100644 --- a/src/test/java/org/springframework/data/redis/core/AbstractOperationsTestParams.java +++ b/src/test/java/org/springframework/data/redis/core/AbstractOperationsTestParams.java @@ -26,6 +26,7 @@ import org.springframework.data.redis.PersonObjectFactory; import org.springframework.data.redis.RawObjectFactory; import org.springframework.data.redis.SettingsUtils; import org.springframework.data.redis.StringObjectFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.GenericToStringSerializer; @@ -46,6 +47,9 @@ abstract public class AbstractOperationsTestParams { // DATAREDIS-241 public static Collection testParams() { + RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration(SettingsUtils.getHost(), + SettingsUtils.getPort()); + ObjectFactory stringFactory = new StringObjectFactory(); ObjectFactory longFactory = new LongObjectFactory(); ObjectFactory doubleFactory = new DoubleObjectFactory(); @@ -60,9 +64,7 @@ abstract public class AbstractOperationsTestParams { throw new RuntimeException("Cannot init XStream", ex); } - JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(); - jedisConnectionFactory.setPort(SettingsUtils.getPort()); - jedisConnectionFactory.setHostName(SettingsUtils.getHost()); + JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(standaloneConfiguration); jedisConnectionFactory.afterPropertiesSet(); RedisTemplate stringTemplate = new StringRedisTemplate(); diff --git a/src/test/java/org/springframework/data/redis/core/ReactiveOperationsTestParams.java b/src/test/java/org/springframework/data/redis/core/ReactiveOperationsTestParams.java index 0b142f772..9d8dafd7f 100644 --- a/src/test/java/org/springframework/data/redis/core/ReactiveOperationsTestParams.java +++ b/src/test/java/org/springframework/data/redis/core/ReactiveOperationsTestParams.java @@ -18,6 +18,7 @@ package org.springframework.data.redis.core; import static org.springframework.data.redis.connection.ClusterTestVariables.*; import java.nio.ByteBuffer; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -35,6 +36,8 @@ import org.springframework.data.redis.SettingsUtils; import org.springframework.data.redis.StringObjectFactory; import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisClusterNode; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceTestClientResources; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; @@ -57,6 +60,14 @@ abstract public class ReactiveOperationsTestParams { public static Collection testParams() { + LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder() // + .shutdownTimeout(Duration.ZERO) // + .clientResources(LettuceTestClientResources.getSharedClientResources()) // + .build(); + + RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration(SettingsUtils.getHost(), + SettingsUtils.getPort()); + ObjectFactory stringFactory = new StringObjectFactory(); ObjectFactory clusterKeyStringFactory = new PrefixStringObjectFactory("{u1}.", stringFactory); ObjectFactory longFactory = new LongObjectFactory(); @@ -72,10 +83,8 @@ abstract public class ReactiveOperationsTestParams { throw new RuntimeException("Cannot init XStream", ex); } - LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(); - lettuceConnectionFactory.setClientResources(LettuceTestClientResources.getSharedClientResources()); - lettuceConnectionFactory.setPort(SettingsUtils.getPort()); - lettuceConnectionFactory.setHostName(SettingsUtils.getHost()); + LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(standaloneConfiguration, + clientConfiguration); lettuceConnectionFactory.afterPropertiesSet(); JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer(); @@ -137,9 +146,8 @@ abstract public class ReactiveOperationsTestParams { ReactiveRedisTemplate clusterStringTemplate = null; - LettuceConnectionFactory lettuceClusterConnectionFactory = new LettuceConnectionFactory(clusterConfiguration); - lettuceClusterConnectionFactory.setPort(SettingsUtils.getPort()); - lettuceClusterConnectionFactory.setHostName(SettingsUtils.getHost()); + LettuceConnectionFactory lettuceClusterConnectionFactory = new LettuceConnectionFactory(clusterConfiguration, + clientConfiguration); lettuceClusterConnectionFactory.afterPropertiesSet(); clusterStringTemplate = new ReactiveRedisTemplate<>(lettuceClusterConnectionFactory,