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 7947da1f8..e73f80a86 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisClusterConfiguration.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisClusterConfiguration.java @@ -23,7 +23,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; -import java.util.Optional; import java.util.Set; import org.springframework.core.env.MapPropertySource; @@ -50,7 +49,7 @@ public class RedisClusterConfiguration implements RedisConfiguration, ClusterCon private Set clusterNodes; private @Nullable Integer maxRedirects; - private Optional username = Optional.empty(); + private @Nullable String username = null; private RedisPassword password = RedisPassword.none(); /** @@ -189,16 +188,17 @@ public class RedisClusterConfiguration implements RedisConfiguration, ClusterCon * @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#setUsername(String) */ @Override - public void setUsername(String username) { - this.username = Optional.of(username); + public void setUsername(@Nullable String username) { + this.username = username; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#getUsername() */ + @Nullable @Override - public Optional getUsername() { + public String getUsername() { return this.username; } diff --git a/src/main/java/org/springframework/data/redis/connection/RedisConfiguration.java b/src/main/java/org/springframework/data/redis/connection/RedisConfiguration.java index e860cf881..93c440fab 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisConfiguration.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisConfiguration.java @@ -17,7 +17,6 @@ package org.springframework.data.redis.connection; import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.function.IntSupplier; import java.util.function.Supplier; @@ -138,11 +137,11 @@ public interface RedisConfiguration { * @param configuration can be {@literal null}. * @param other a {@code Supplier} whose result is returned if given {@link RedisConfiguration} is not * {@link #isAuthenticationAware(RedisConfiguration) password aware}. - * @return never {@literal null}. + * @return can be {@literal null}. * @throws IllegalArgumentException if {@code other} is {@literal null}. */ - static Optional getUsernameOrElse(@Nullable RedisConfiguration configuration, - Supplier> other) { + @Nullable + static String getUsernameOrElse(@Nullable RedisConfiguration configuration, Supplier other) { Assert.notNull(other, "Other must not be null!"); return isAuthenticationAware(configuration) ? ((WithAuthentication) configuration).getUsername() : other.get(); @@ -203,7 +202,7 @@ public interface RedisConfiguration { * * @param username the username. */ - void setUsername(String username); + void setUsername(@Nullable String username); /** * Create and set a {@link RedisPassword} for given {@link String}. @@ -233,9 +232,10 @@ public interface RedisConfiguration { /** * Get the username to use when connecting. * - * @return {@link Optional#empty()} if none set. + * @return {@literal null} if none set. */ - Optional getUsername(); + @Nullable + String getUsername(); /** * Get the RedisPassword to use when connecting. @@ -381,10 +381,11 @@ public interface RedisConfiguration { /** * Get the username used when authenticating with a Redis Server. * - * @return never {@literal null}. + * @return can be {@literal null} if not set. * @since 2.4 */ - default Optional getDataNodeUsername() { + @Nullable + default String getDataNodeUsername() { return getUsername(); } 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 8313b7a76..53ccbe478 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisSentinelConfiguration.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisSentinelConfiguration.java @@ -21,7 +21,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; -import java.util.Optional; import java.util.Set; import org.springframework.core.env.MapPropertySource; @@ -51,7 +50,7 @@ public class RedisSentinelConfiguration implements RedisConfiguration, SentinelC private Set sentinels; private int database; - private Optional dataNodeUsername = Optional.empty(); + private @Nullable String dataNodeUsername = null; private RedisPassword dataNodePassword = RedisPassword.none(); private RedisPassword sentinelPassword = RedisPassword.none(); @@ -236,16 +235,17 @@ public class RedisSentinelConfiguration implements RedisConfiguration, SentinelC * @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#setUsername(String) */ @Override - public void setUsername(String username) { - this.dataNodeUsername = Optional.of(username); + public void setUsername(@Nullable String username) { + this.dataNodeUsername = username; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#getUsername() */ + @Nullable @Override - public Optional getUsername() { + public String getUsername() { return this.dataNodeUsername; } diff --git a/src/main/java/org/springframework/data/redis/connection/RedisSocketConfiguration.java b/src/main/java/org/springframework/data/redis/connection/RedisSocketConfiguration.java index 93118a572..09d930d88 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisSocketConfiguration.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisSocketConfiguration.java @@ -15,9 +15,8 @@ */ package org.springframework.data.redis.connection; -import java.util.Optional; - import org.springframework.data.redis.connection.RedisConfiguration.DomainSocketConfiguration; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -34,7 +33,7 @@ public class RedisSocketConfiguration implements RedisConfiguration, DomainSocke private String socket = DEFAULT_SOCKET; private int database; - private Optional username = Optional.empty(); + private @Nullable String username = null; private RedisPassword password = RedisPassword.none(); /** @@ -100,16 +99,17 @@ public class RedisSocketConfiguration implements RedisConfiguration, DomainSocke * @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#setUsername(String) */ @Override - public void setUsername(String username) { - this.username = Optional.of(username); + public void setUsername(@Nullable String username) { + this.username = username; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#getUsername() */ + @Nullable @Override - public Optional getUsername() { + public String getUsername() { return this.username; } diff --git a/src/main/java/org/springframework/data/redis/connection/RedisStandaloneConfiguration.java b/src/main/java/org/springframework/data/redis/connection/RedisStandaloneConfiguration.java index 058b32af1..7dbf86650 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisStandaloneConfiguration.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisStandaloneConfiguration.java @@ -15,11 +15,10 @@ */ package org.springframework.data.redis.connection; -import java.util.Optional; - import org.springframework.data.redis.connection.RedisConfiguration.WithDatabaseIndex; import org.springframework.data.redis.connection.RedisConfiguration.WithHostAndPort; import org.springframework.data.redis.connection.RedisConfiguration.WithPassword; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -39,7 +38,7 @@ public class RedisStandaloneConfiguration private String hostName = DEFAULT_HOST; private int port = DEFAULT_PORT; private int database; - private Optional username = Optional.empty(); + private @Nullable String username = null; private RedisPassword password = RedisPassword.none(); /** @@ -133,16 +132,17 @@ public class RedisStandaloneConfiguration * @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#setUsername(String) */ @Override - public void setUsername(String username) { - this.username = Optional.of(username); + public void setUsername(@Nullable String username) { + this.username = username; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#getUsername() */ + @Nullable @Override - public Optional getUsername() { + public String getUsername() { return this.username; } diff --git a/src/main/java/org/springframework/data/redis/connection/RedisStaticMasterReplicaConfiguration.java b/src/main/java/org/springframework/data/redis/connection/RedisStaticMasterReplicaConfiguration.java index d31f5939e..3e6f14fe0 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisStaticMasterReplicaConfiguration.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisStaticMasterReplicaConfiguration.java @@ -18,17 +18,17 @@ package org.springframework.data.redis.connection; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Optional; import org.springframework.data.redis.connection.RedisConfiguration.StaticMasterReplicaConfiguration; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** * Configuration class used for setting up {@link RedisConnection} via {@link RedisConnectionFactory} using the provided * Master / Replica configuration to nodes know to not change address. Eg. when connecting to * AWS ElastiCache with Read Replicas.
- * Note: Redis is undergoing a nomenclature change where the term replica is used synonymously to slave. - * Please also note that a Master/Replica connection cannot be used for Pub/Sub operations. + * Note: Redis is undergoing a nomenclature change where the term replica is used synonymously to slave. Please also + * note that a Master/Replica connection cannot be used for Pub/Sub operations. * * @author Mark Paluch * @author Christoph Strobl @@ -41,7 +41,7 @@ public class RedisStaticMasterReplicaConfiguration implements RedisConfiguration private List nodes = new ArrayList<>(); private int database; - private Optional username = Optional.empty(); + private @Nullable String username = null; private RedisPassword password = RedisPassword.none(); /** @@ -137,16 +137,17 @@ public class RedisStaticMasterReplicaConfiguration implements RedisConfiguration * @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#setUsername(String) */ @Override - public void setUsername(String username) { - this.username = Optional.of(username); + public void setUsername(@Nullable String username) { + this.username = username; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#getUsername() */ + @Nullable @Override - public Optional getUsername() { + public String getUsername() { return this.username; } 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 8cb3fbb6c..26d52715f 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 @@ -58,6 +58,7 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; /** * Connection factory creating Jedis based connections. @@ -328,7 +329,10 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, clientConfiguration.getHostnameVerifier().orElse(null)); getRedisPassword().map(String::new).ifPresent(shardInfo::setPassword); - getRedisUsername().ifPresent(shardInfo::setUser); + String username = getRedisUsername(); + if (StringUtils.hasText(username)) { + shardInfo.setUser(username); + } int readTimeout = getReadTimeout(); @@ -375,8 +379,8 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, String sentinelPassword = config.getSentinelPassword().toOptional().map(String::new).orElse(null); return new JedisSentinelPool(config.getMaster().getName(), convertToJedisSentinelSet(config.getSentinels()), - poolConfig, getConnectTimeout(), getReadTimeout(), getUsername(), getPassword(), getDatabase(), - getClientName(), getConnectTimeout(), getReadTimeout(), sentinelUser, sentinelPassword, getClientName()); + poolConfig, getConnectTimeout(), getReadTimeout(), getUsername(), getPassword(), getDatabase(), getClientName(), + getConnectTimeout(), getReadTimeout(), sentinelUser, sentinelPassword, getClientName()); } /** @@ -556,7 +560,7 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, */ @Nullable private String getUsername() { - return getRedisUsername().orElse(null); + return getRedisUsername(); } /** @@ -569,7 +573,8 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, return getRedisPassword().map(String::new).orElse(null); } - private Optional getRedisUsername() { + @Nullable + private String getRedisUsername() { return RedisConfiguration.getUsernameOrElse(this.configuration, standaloneConfig::getUsername); } 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 ddc53b590..d9e3e40f7 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 @@ -44,7 +44,6 @@ import java.util.stream.Collectors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.dao.DataAccessException; @@ -62,6 +61,7 @@ import org.springframework.data.util.Optionals; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; /** * Connection factory creating Lettuce-based connections. @@ -788,9 +788,11 @@ public class LettuceConnectionFactory this.getMutableConfiguration().setClientName(clientName); } - private Optional getRedisUsername() { + @Nullable + private String getRedisUsername() { return RedisConfiguration.getUsernameOrElse(configuration, standaloneConfig::getUsername); } + /** * Returns the password used for authenticating with the Redis server. * @@ -1164,11 +1166,10 @@ public class LettuceConnectionFactory private void applyAuthentication(RedisURI.Builder builder) { - Optional username = getRedisUsername(); - if (username.isPresent()) { + String username = getRedisUsername(); + if (StringUtils.hasText(username)) { // See https://github.com/lettuce-io/lettuce-core/issues/1404 - username.ifPresent( - it -> builder.withAuthentication(it, new String(getRedisPassword().toOptional().orElse(new char[0])))); + builder.withAuthentication(username, new String(getRedisPassword().toOptional().orElse(new char[0]))); } else { getRedisPassword().toOptional().ifPresent(builder::withPassword); } diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java index 2d90e28b1..acd8442bf 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java @@ -309,7 +309,6 @@ abstract public class LettuceConverters extends Converters { TRANSACTION_RESULT_UNWRAPPER = transactionResult -> transactionResult.stream().collect(Collectors.toList()); - } public static List toTuple(List list) { @@ -653,12 +652,12 @@ abstract public class LettuceConverters extends Converters { builder.withSentinel(sentinelBuilder.build()); } - Optional username = sentinelConfiguration.getUsername(); + String username = sentinelConfiguration.getUsername(); RedisPassword password = sentinelConfiguration.getPassword(); - if (username.isPresent()) { + if (StringUtils.hasText(username)) { // See https://github.com/lettuce-io/lettuce-core/issues/1404 - builder.withAuthentication(username.get(), new String(password.toOptional().orElse(new char[0]))); + builder.withAuthentication(username, new String(password.toOptional().orElse(new char[0]))); } else { password.toOptional().ifPresent(builder::withPassword); } diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisAclIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisAclIntegrationTests.java index 4d52f83dc..9ddb8d476 100644 --- a/src/test/java/org/springframework/data/redis/connection/jedis/JedisAclIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisAclIntegrationTests.java @@ -16,31 +16,32 @@ package org.springframework.data.redis.connection.jedis; import static org.assertj.core.api.Assertions.*; -import static org.junit.Assume.*; import java.io.IOException; import java.util.Collections; -import org.junit.Before; +import org.junit.Assume; +import org.junit.ClassRule; import org.junit.Test; - -import org.springframework.data.redis.RedisTestProfileValueSource; +import org.junit.rules.RuleChain; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisSentinelConfiguration; import org.springframework.data.redis.connection.RedisSentinelConnection; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.test.util.MinimumRedisVersionRule; +import org.springframework.data.redis.test.util.ServerAvailable; +import org.springframework.test.annotation.IfProfileValue; /** * Integration tests for Redis 6 ACL. * * @author Mark Paluch */ +@IfProfileValue(name = "redisVersion", value = "6.0+") public class JedisAclIntegrationTests { - @Before - public void before() { - assumeTrue(RedisTestProfileValueSource.atLeast("redisVersion", "6.0")); - } + @ClassRule public static RuleChain requirements = RuleChain.outerRule(ServerAvailable.runningAtLocalhost(6382)) + .around(new MinimumRedisVersionRule()); @Test public void shouldConnectWithDefaultAuthentication() { @@ -80,6 +81,9 @@ public class JedisAclIntegrationTests { @Test // DATAREDIS-1145 public void shouldConnectSentinelWithAclAuthentication() throws IOException { + Assume.assumeTrue("Redis Sentinel at localhost:26382 did not answer.", + ServerAvailable.runningAtLocalhost(26382).isAvailable()); + // Note: As per https://github.com/redis/redis/issues/7708, Sentinel does not support ACL authentication yet. RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration("mymaster", diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/AuthenticatingRedisClientTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/AuthenticatingRedisClientTests.java index cfe62e23a..3fc9310bf 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/AuthenticatingRedisClientTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/AuthenticatingRedisClientTests.java @@ -22,7 +22,9 @@ import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; import org.junit.After; import org.junit.Before; +import org.junit.ClassRule; import org.junit.Test; +import org.springframework.data.redis.test.util.ServerAvailable; /** * Integration test of {@link AuthenticatingRedisClient}. @@ -35,6 +37,8 @@ public class AuthenticatingRedisClientTests { private RedisClient client; + @ClassRule public static ServerAvailable serverAvailable = ServerAvailable.runningAtLocalhost(6382); + @Before public void setUp() { client = new AuthenticatingRedisClient("localhost", 6382, "foobared"); diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceAclIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceAclIntegrationTests.java index 7a1a056fd..9b2bf508f 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceAclIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceAclIntegrationTests.java @@ -16,7 +16,6 @@ package org.springframework.data.redis.connection.lettuce; import static org.assertj.core.api.Assertions.*; -import static org.junit.Assume.*; import io.lettuce.core.ClientOptions; import io.lettuce.core.protocol.ProtocolVersion; @@ -24,28 +23,29 @@ import io.lettuce.core.protocol.ProtocolVersion; import java.io.IOException; import java.util.Collections; -import org.junit.Before; +import org.junit.Assume; +import org.junit.ClassRule; import org.junit.Ignore; import org.junit.Test; - -import org.springframework.data.redis.RedisTestProfileValueSource; +import org.junit.rules.RuleChain; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisSentinelConfiguration; import org.springframework.data.redis.connection.RedisSentinelConnection; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.RedisStaticMasterReplicaConfiguration; +import org.springframework.data.redis.test.util.MinimumRedisVersionRule; +import org.springframework.data.redis.test.util.ServerAvailable; /** * Integration tests for Redis 6 ACL. * * @author Mark Paluch + * @author Christoph Strobl */ public class LettuceAclIntegrationTests { - @Before - public void before() { - assumeTrue(RedisTestProfileValueSource.atLeast("redisVersion", "6.0")); - } + @ClassRule public static RuleChain requirements = RuleChain.outerRule(ServerAvailable.runningAtLocalhost(6382)) + .around(new MinimumRedisVersionRule()); @Test // DATAREDIS-1046 public void shouldConnectWithDefaultAuthentication() { @@ -87,6 +87,9 @@ public class LettuceAclIntegrationTests { @Test // DATAREDIS-1145 public void shouldConnectSentinelWithAuthentication() throws IOException { + Assume.assumeTrue("Redis Sentinel at localhost:26382 did not answer.", + ServerAvailable.runningAtLocalhost(26382).isAvailable()); + // Note: As per https://github.com/redis/redis/issues/7708, Sentinel does not support ACL authentication yet. LettuceClientConfiguration configuration = LettuceClientConfiguration.builder() diff --git a/src/test/java/org/springframework/data/redis/test/util/ServerAvailable.java b/src/test/java/org/springframework/data/redis/test/util/ServerAvailable.java new file mode 100644 index 000000000..460c7c90c --- /dev/null +++ b/src/test/java/org/springframework/data/redis/test/util/ServerAvailable.java @@ -0,0 +1,81 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.redis.test.util; + +import java.net.InetSocketAddress; +import java.net.Socket; + +import org.junit.AssumptionViolatedException; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * @author Christoph Strobl + */ +public class ServerAvailable implements TestRule { + + private final String host; + private final int port; + + ServerAvailable(String host, int port) { + + this.host = host; + this.port = port; + } + + public static ServerAvailable runningAtLocalhost() { + return runningAtLocalhost(6379); + } + + public static ServerAvailable runningAtLocalhost(int port) { + return runningAt("localhost", port); + } + + public static ServerAvailable runningAt(String host, int port) { + return new ServerAvailable(host, port); + } + + @Override + public Statement apply(Statement base, Description description) { + + return new Statement() { + + @Override + public void evaluate() throws Throwable { + + if (!isAvailable()) { + + throw new AssumptionViolatedException( + String.format("Redis Server (%s:%s) did not answer. Is it up and running?", host, port)); + } + + base.evaluate(); + } + }; + } + + public boolean isAvailable() { + + try (Socket socket = new Socket()) { + socket.connect(new InetSocketAddress(host, port), 100); + return true; + } catch (Exception e) { + return false; + } + + } +}