diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/JedisConnectionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/JedisConnectionConfiguration.java index 58df1e8366..5110ca7293 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/JedisConnectionConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/JedisConnectionConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -42,6 +42,9 @@ import org.springframework.util.StringUtils; * * @author Mark Paluch * @author Stephane Nicoll + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class }) @@ -52,8 +55,10 @@ class JedisConnectionConfiguration extends RedisConnectionConfiguration { JedisConnectionConfiguration(RedisProperties properties, ObjectProvider standaloneConfigurationProvider, ObjectProvider sentinelConfiguration, - ObjectProvider clusterConfiguration) { - super(properties, standaloneConfigurationProvider, sentinelConfiguration, clusterConfiguration); + ObjectProvider clusterConfiguration, + ObjectProvider connectionDetailsProvider) { + super(properties, standaloneConfigurationProvider, sentinelConfiguration, clusterConfiguration, + connectionDetailsProvider); } @Bean @@ -90,7 +95,9 @@ class JedisConnectionConfiguration extends RedisConnectionConfiguration { private JedisClientConfigurationBuilder applyProperties(JedisClientConfigurationBuilder builder) { PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); - map.from(getProperties().isSsl()).whenTrue().toCall(builder::useSsl); + boolean ssl = (!(getConnectionDetails() instanceof PropertiesRedisConnectionDetails)) ? false + : getProperties().isSsl(); + map.from(ssl).whenTrue().toCall(builder::useSsl); map.from(getProperties().getTimeout()).to(builder::readTimeout); map.from(getProperties().getConnectTimeout()).to(builder::connectTimeout); map.from(getProperties().getClientName()).whenHasText().to(builder::clientName); @@ -117,8 +124,7 @@ class JedisConnectionConfiguration extends RedisConnectionConfiguration { } private void customizeConfigurationFromUrl(JedisClientConfiguration.JedisClientConfigurationBuilder builder) { - ConnectionInfo connectionInfo = parseUrl(getProperties().getUrl()); - if (connectionInfo.isUseSsl()) { + if (urlUsesSsl()) { builder.useSsl(); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.java index ab13a3fe67..276c69d0da 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.java @@ -52,6 +52,8 @@ import org.springframework.util.StringUtils; * * @author Mark Paluch * @author Andy Wilkinson + * @author Moritz Halbritter + * @author Phillip Webb */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(RedisClient.class) @@ -61,8 +63,10 @@ class LettuceConnectionConfiguration extends RedisConnectionConfiguration { LettuceConnectionConfiguration(RedisProperties properties, ObjectProvider standaloneConfigurationProvider, ObjectProvider sentinelConfigurationProvider, - ObjectProvider clusterConfigurationProvider) { - super(properties, standaloneConfigurationProvider, sentinelConfigurationProvider, clusterConfigurationProvider); + ObjectProvider clusterConfigurationProvider, + ObjectProvider connectionDetailsProvider) { + super(properties, standaloneConfigurationProvider, sentinelConfigurationProvider, clusterConfigurationProvider, + connectionDetailsProvider); } @Bean(destroyMethod = "shutdown") @@ -116,7 +120,7 @@ class LettuceConnectionConfiguration extends RedisConnectionConfiguration { private LettuceClientConfigurationBuilder applyProperties( LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) { - if (getProperties().isSsl()) { + if (getConnectionDetails() instanceof PropertiesRedisConnectionDetails && getProperties().isSsl()) { builder.useSsl(); } if (getProperties().getTimeout() != null) { @@ -161,8 +165,7 @@ class LettuceConnectionConfiguration extends RedisConnectionConfiguration { } private void customizeConfigurationFromUrl(LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) { - ConnectionInfo connectionInfo = parseUrl(getProperties().getUrl()); - if (connectionInfo.isUseSsl()) { + if (urlUsesSsl()) { builder.useSsl(); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/PropertiesRedisConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/PropertiesRedisConnectionDetails.java new file mode 100644 index 0000000000..81279785b6 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/PropertiesRedisConnectionDetails.java @@ -0,0 +1,127 @@ +/* + * Copyright 2012-2023 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.boot.autoconfigure.data.redis; + +import java.util.List; + +import org.springframework.boot.autoconfigure.data.redis.RedisConnectionConfiguration.ConnectionInfo; + +/** + * Adapts {@link RedisProperties} to {@link RedisConnectionDetails}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + */ +class PropertiesRedisConnectionDetails implements RedisConnectionDetails { + + private final RedisProperties properties; + + PropertiesRedisConnectionDetails(RedisProperties properties) { + this.properties = properties; + } + + @Override + public String getUsername() { + if (this.properties.getUrl() != null) { + ConnectionInfo connectionInfo = connectionInfo(this.properties.getUrl()); + String userInfo = connectionInfo.getUri().getUserInfo(); + int index = (userInfo != null) ? userInfo.indexOf(':') : -1; + if (index != -1) { + return userInfo.substring(0, index); + } + } + return this.properties.getUsername(); + } + + @Override + public String getPassword() { + if (this.properties.getUrl() != null) { + ConnectionInfo connectionInfo = connectionInfo(this.properties.getUrl()); + String userInfo = connectionInfo.getUri().getUserInfo(); + int index = (userInfo != null) ? userInfo.indexOf(':') : -1; + if (index != -1) { + return userInfo.substring(index + 1); + } + } + return this.properties.getPassword(); + } + + @Override + public Standalone getStandalone() { + if (this.properties.getUrl() != null) { + ConnectionInfo connectionInfo = connectionInfo(this.properties.getUrl()); + return Standalone.of(connectionInfo.getUri().getHost(), connectionInfo.getUri().getPort(), + this.properties.getDatabase()); + } + return Standalone.of(this.properties.getHost(), this.properties.getPort(), this.properties.getDatabase()); + } + + private ConnectionInfo connectionInfo(String url) { + return (url != null) ? RedisConnectionConfiguration.parseUrl(url) : null; + } + + @Override + public Sentinel getSentinel() { + org.springframework.boot.autoconfigure.data.redis.RedisProperties.Sentinel sentinel = this.properties + .getSentinel(); + if (sentinel == null) { + return null; + } + return new Sentinel() { + + @Override + public int getDatabase() { + return PropertiesRedisConnectionDetails.this.properties.getDatabase(); + } + + @Override + public String getMaster() { + return sentinel.getMaster(); + } + + @Override + public List getNodes() { + return sentinel.getNodes().stream().map(PropertiesRedisConnectionDetails.this::asNode).toList(); + } + + @Override + public String getUsername() { + return sentinel.getUsername(); + } + + @Override + public String getPassword() { + return sentinel.getPassword(); + } + + }; + } + + @Override + public Cluster getCluster() { + RedisProperties.Cluster cluster = this.properties.getCluster(); + List nodes = (cluster != null) ? cluster.getNodes().stream().map(this::asNode).toList() : null; + return (nodes != null) ? () -> nodes : null; + } + + private Node asNode(String node) { + String[] components = node.split(":"); + return new Node(components[0], Integer.parseInt(components[1])); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionConfiguration.java index f458d254ea..9626b17557 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -22,6 +22,9 @@ import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails.Cluster; +import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails.Node; +import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails.Sentinel; import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Pool; import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisNode; @@ -29,7 +32,6 @@ import org.springframework.data.redis.connection.RedisPassword; import org.springframework.data.redis.connection.RedisSentinelConfiguration; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; /** * Base Redis connection configuration. @@ -39,6 +41,9 @@ import org.springframework.util.StringUtils; * @author Alen Turkovic * @author Scott Frederick * @author EddĂș MelĂ©ndez + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb */ abstract class RedisConnectionConfiguration { @@ -53,14 +58,19 @@ abstract class RedisConnectionConfiguration { private final RedisClusterConfiguration clusterConfiguration; + private final RedisConnectionDetails connectionDetails; + protected RedisConnectionConfiguration(RedisProperties properties, ObjectProvider standaloneConfigurationProvider, ObjectProvider sentinelConfigurationProvider, - ObjectProvider clusterConfigurationProvider) { + ObjectProvider clusterConfigurationProvider, + ObjectProvider connectionDetailsProvider) { this.properties = properties; this.standaloneConfiguration = standaloneConfigurationProvider.getIfAvailable(); this.sentinelConfiguration = sentinelConfigurationProvider.getIfAvailable(); this.clusterConfiguration = clusterConfigurationProvider.getIfAvailable(); + this.connectionDetails = connectionDetailsProvider + .getIfAvailable(() -> new PropertiesRedisConnectionDetails(properties)); } protected final RedisStandaloneConfiguration getStandaloneConfig() { @@ -68,20 +78,11 @@ abstract class RedisConnectionConfiguration { return this.standaloneConfiguration; } RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(); - if (StringUtils.hasText(this.properties.getUrl())) { - ConnectionInfo connectionInfo = parseUrl(this.properties.getUrl()); - config.setHostName(connectionInfo.getHostName()); - config.setPort(connectionInfo.getPort()); - config.setUsername(connectionInfo.getUsername()); - config.setPassword(RedisPassword.of(connectionInfo.getPassword())); - } - else { - config.setHostName(this.properties.getHost()); - config.setPort(this.properties.getPort()); - config.setUsername(this.properties.getUsername()); - config.setPassword(RedisPassword.of(this.properties.getPassword())); - } - config.setDatabase(this.properties.getDatabase()); + config.setHostName(this.connectionDetails.getStandalone().getHost()); + config.setPort(this.connectionDetails.getStandalone().getPort()); + config.setUsername(this.connectionDetails.getUsername()); + config.setPassword(RedisPassword.of(this.connectionDetails.getPassword())); + config.setDatabase(this.connectionDetails.getStandalone().getDatabase()); return config; } @@ -89,20 +90,21 @@ abstract class RedisConnectionConfiguration { if (this.sentinelConfiguration != null) { return this.sentinelConfiguration; } - RedisProperties.Sentinel sentinelProperties = this.properties.getSentinel(); - if (sentinelProperties != null) { + if (this.connectionDetails.getSentinel() != null) { RedisSentinelConfiguration config = new RedisSentinelConfiguration(); - config.master(sentinelProperties.getMaster()); - config.setSentinels(createSentinels(sentinelProperties)); - config.setUsername(this.properties.getUsername()); - if (this.properties.getPassword() != null) { - config.setPassword(RedisPassword.of(this.properties.getPassword())); + config.master(this.connectionDetails.getSentinel().getMaster()); + config.setSentinels(createSentinels(this.connectionDetails.getSentinel())); + config.setUsername(this.connectionDetails.getUsername()); + String password = this.connectionDetails.getPassword(); + if (password != null) { + config.setPassword(RedisPassword.of(password)); } - config.setSentinelUsername(sentinelProperties.getUsername()); - if (sentinelProperties.getPassword() != null) { - config.setSentinelPassword(RedisPassword.of(sentinelProperties.getPassword())); + config.setSentinelUsername(this.connectionDetails.getSentinel().getUsername()); + String sentinelPassword = this.connectionDetails.getSentinel().getPassword(); + if (sentinelPassword != null) { + config.setSentinelPassword(RedisPassword.of(sentinelPassword)); } - config.setDatabase(this.properties.getDatabase()); + config.setDatabase(this.connectionDetails.getSentinel().getDatabase()); return config; } return null; @@ -116,19 +118,25 @@ abstract class RedisConnectionConfiguration { if (this.clusterConfiguration != null) { return this.clusterConfiguration; } - if (this.properties.getCluster() == null) { - return null; - } RedisProperties.Cluster clusterProperties = this.properties.getCluster(); - RedisClusterConfiguration config = new RedisClusterConfiguration(clusterProperties.getNodes()); - if (clusterProperties.getMaxRedirects() != null) { - config.setMaxRedirects(clusterProperties.getMaxRedirects()); - } - config.setUsername(this.properties.getUsername()); - if (this.properties.getPassword() != null) { - config.setPassword(RedisPassword.of(this.properties.getPassword())); + if (this.connectionDetails.getCluster() != null) { + RedisClusterConfiguration config = new RedisClusterConfiguration( + getNodes(this.connectionDetails.getCluster())); + if (clusterProperties != null && clusterProperties.getMaxRedirects() != null) { + config.setMaxRedirects(clusterProperties.getMaxRedirects()); + } + config.setUsername(this.connectionDetails.getUsername()); + String password = this.connectionDetails.getPassword(); + if (password != null) { + config.setPassword(RedisPassword.of(password)); + } + return config; } - return config; + return null; + } + + private List getNodes(Cluster cluster) { + return cluster.getNodes().stream().map((node) -> "%s:%d".formatted(node.host(), node.port())).toList(); } protected final RedisProperties getProperties() { @@ -140,20 +148,23 @@ abstract class RedisConnectionConfiguration { return (enabled != null) ? enabled : COMMONS_POOL2_AVAILABLE; } - private List createSentinels(RedisProperties.Sentinel sentinel) { + private List createSentinels(Sentinel sentinel) { List nodes = new ArrayList<>(); - for (String node : sentinel.getNodes()) { - try { - nodes.add(RedisNode.fromString(node)); - } - catch (RuntimeException ex) { - throw new IllegalStateException("Invalid redis sentinel property '" + node + "'", ex); - } + for (Node node : sentinel.getNodes()) { + nodes.add(new RedisNode(node.host(), node.port())); } return nodes; } - protected ConnectionInfo parseUrl(String url) { + protected final boolean urlUsesSsl() { + return parseUrl(this.properties.getUrl()).isUseSsl(); + } + + protected final RedisConnectionDetails getConnectionDetails() { + return this.connectionDetails; + } + + static ConnectionInfo parseUrl(String url) { try { URI uri = new URI(url); String scheme = uri.getScheme(); @@ -198,16 +209,12 @@ abstract class RedisConnectionConfiguration { this.password = password; } - boolean isUseSsl() { - return this.useSsl; - } - - String getHostName() { - return this.uri.getHost(); + URI getUri() { + return this.uri; } - int getPort() { - return this.uri.getPort(); + boolean isUseSsl() { + return this.useSsl; } String getUsername() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionDetails.java new file mode 100644 index 0000000000..7aed831582 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionDetails.java @@ -0,0 +1,190 @@ +/* + * Copyright 2012-2023 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.boot.autoconfigure.data.redis; + +import java.util.List; + +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; +import org.springframework.util.Assert; + +/** + * Details required to establish a connection to a Redis service. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @since 3.1.0 + */ +public interface RedisConnectionDetails extends ConnectionDetails { + + /** + * Login username of the redis server. + * @return the login username of the redis server + */ + default String getUsername() { + return null; + } + + /** + * Login password of the redis server. + * @return the login password of the redis server + */ + default String getPassword() { + return null; + } + + /** + * Redis standalone configuration. Mutually exclusive with {@link #getSentinel()} and + * {@link #getCluster()}. + * @return the Redis standalone configuration + */ + default Standalone getStandalone() { + return null; + } + + /** + * Redis sentinel configuration. Mutually exclusive with {@link #getStandalone()} and + * {@link #getCluster()}. + * @return the Redis sentinel configuration + */ + default Sentinel getSentinel() { + return null; + } + + /** + * Redis cluster configuration. Mutually exclusive with {@link #getStandalone()} and + * {@link #getSentinel()}. + * @return the Redis cluster configuration + */ + default Cluster getCluster() { + return null; + } + + /** + * Redis standalone configuration. + */ + interface Standalone { + + /** + * Redis server host. + * @return the redis server host + */ + String getHost(); + + /** + * Redis server port. + * @return the redis server port + */ + int getPort(); + + /** + * Database index used by the connection factory. + * @return the database index used by the connection factory + */ + default int getDatabase() { + return 0; + } + + static Standalone of(String host, int port) { + return of(host, port, 0); + } + + static Standalone of(String host, int port, int database) { + Assert.hasLength(host, "Host must not be empty"); + return new Standalone() { + + @Override + public String getHost() { + return host; + } + + @Override + public int getPort() { + return port; + } + + @Override + public int getDatabase() { + return database; + } + + }; + } + + } + + /** + * Redis sentinel configuration. + */ + interface Sentinel { + + /** + * Database index used by the connection factory. + * @return the database index used by the connection factory + */ + int getDatabase(); + + /** + * Name of the Redis server. + * @return the name of the Redis server + */ + String getMaster(); + + /** + * List of nodes. + * @return the list of nodes + */ + List getNodes(); + + /** + * Login username for authenticating with sentinel(s). + * @return the login username for authenticating with sentinel(s) or {@code null} + */ + String getUsername(); + + /** + * Password for authenticating with sentinel(s). + * @return the password for authenticating with sentinel(s) or {@code null} + */ + String getPassword(); + + } + + /** + * Redis cluster configuration. + */ + interface Cluster { + + /** + * Nodes to bootstrap from. This represents an "initial" list of cluster nodes and + * is required to have at least one entry. + * @return nodes to bootstrap from + */ + List getNodes(); + + } + + /** + * A node in a sentinel or cluster configuration. + * + * @param host the hostname of the node + * @param port the port of the node + */ + record Node(String host, int port) { + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java index 2ac757cbe6..043f5c8ccc 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java @@ -39,6 +39,9 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Mark Paluch * @author Stephane Nicoll * @author Weix Sun + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb */ @ClassPathExclusions("lettuce-core-*.jar") class RedisAutoConfigurationJedisTests { @@ -79,6 +82,14 @@ class RedisAutoConfigurationJedisTests { }); } + @Test + void usesConnectionDetailsIfAvailable() { + this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class).run((context) -> { + JedisConnectionFactory cf = context.getBean(JedisConnectionFactory.class); + assertThat(cf.isUseSsl()).isFalse(); + }); + } + @Test void testRedisUrlConfiguration() { this.contextRunner @@ -240,6 +251,35 @@ class RedisAutoConfigurationJedisTests { } + @Configuration(proxyBeanMethods = false) + static class ConnectionDetailsConfiguration { + + @Bean + RedisConnectionDetails redisConnectionDetails() { + return new RedisConnectionDetails() { + + @Override + public Standalone getStandalone() { + return new Standalone() { + + @Override + public String getHost() { + return "localhost"; + } + + @Override + public int getPort() { + return 6379; + } + + }; + } + + }; + } + + } + @Configuration(proxyBeanMethods = false) static class JedisConnectionFactoryCaptorConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java index e1ffb9baaa..fa1bfbc6ee 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java @@ -42,6 +42,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisNode; +import org.springframework.data.redis.connection.RedisPassword; import org.springframework.data.redis.connection.RedisSentinelConfiguration; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; @@ -71,6 +72,9 @@ import static org.mockito.Mockito.mock; * @author Alen Turkovic * @author Scott Frederick * @author Weix Sun + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb */ class RedisAutoConfigurationTests { @@ -490,6 +494,51 @@ class RedisAutoConfigurationTests { (options) -> assertThat(options.getTopologyRefreshOptions().useDynamicRefreshSources()).isTrue())); } + @Test + void usesStandaloneFromConnectionDetailsIfAvailable() { + this.contextRunner.withUserConfiguration(ConnectionDetailsStandaloneConfiguration.class).run((context) -> { + LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class); + assertThat(cf.isUseSsl()).isFalse(); + RedisStandaloneConfiguration configuration = cf.getStandaloneConfiguration(); + assertThat(configuration.getHostName()).isEqualTo("redis.example.com"); + assertThat(configuration.getPort()).isEqualTo(16379); + assertThat(configuration.getDatabase()).isOne(); + assertThat(configuration.getUsername()).isEqualTo("user-1"); + assertThat(configuration.getPassword()).isEqualTo(RedisPassword.of("password-1")); + }); + } + + @Test + void usesSentinelFromConnectionDetailsIfAvailable() { + this.contextRunner.withUserConfiguration(ConnectionDetailsSentinelConfiguration.class).run((context) -> { + LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class); + assertThat(cf.isUseSsl()).isFalse(); + RedisSentinelConfiguration configuration = cf.getSentinelConfiguration(); + assertThat(configuration).isNotNull(); + assertThat(configuration.getSentinelUsername()).isEqualTo("sentinel-1"); + assertThat(configuration.getSentinelPassword().get()).isEqualTo("secret-1".toCharArray()); + assertThat(configuration.getSentinels()).containsExactly(new RedisNode("node-1", 12345)); + assertThat(configuration.getUsername()).isEqualTo("user-1"); + assertThat(configuration.getPassword()).isEqualTo(RedisPassword.of("password-1")); + assertThat(configuration.getDatabase()).isOne(); + assertThat(configuration.getMaster().getName()).isEqualTo("master.redis.example.com"); + }); + } + + @Test + void usesClusterFromConnectionDetailsIfAvailable() { + this.contextRunner.withUserConfiguration(ConnectionDetailsClusterConfiguration.class).run((context) -> { + LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class); + assertThat(cf.isUseSsl()).isFalse(); + RedisClusterConfiguration configuration = cf.getClusterConfiguration(); + assertThat(configuration).isNotNull(); + assertThat(configuration.getUsername()).isEqualTo("user-1"); + assertThat(configuration.getPassword().get()).isEqualTo("password-1".toCharArray()); + assertThat(configuration.getClusterNodes()).containsExactly(new RedisNode("node-1", 12345), + new RedisNode("node-2", 23456)); + }); + } + private ContextConsumer assertClientOptions( Class expectedType, Consumer options) { return (context) -> { @@ -532,4 +581,136 @@ class RedisAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class ConnectionDetailsStandaloneConfiguration { + + @Bean + RedisConnectionDetails redisConnectionDetails() { + return new RedisConnectionDetails() { + + @Override + public String getUsername() { + return "user-1"; + } + + @Override + public String getPassword() { + return "password-1"; + } + + @Override + public Standalone getStandalone() { + return new Standalone() { + + @Override + public int getDatabase() { + return 1; + } + + @Override + public String getHost() { + return "redis.example.com"; + } + + @Override + public int getPort() { + return 16379; + } + + }; + } + + }; + } + + } + + @Configuration(proxyBeanMethods = false) + static class ConnectionDetailsSentinelConfiguration { + + @Bean + RedisConnectionDetails redisConnectionDetails() { + return new RedisConnectionDetails() { + + @Override + public String getUsername() { + return "user-1"; + } + + @Override + public String getPassword() { + return "password-1"; + } + + @Override + public Sentinel getSentinel() { + return new Sentinel() { + + @Override + public int getDatabase() { + return 1; + } + + @Override + public String getMaster() { + return "master.redis.example.com"; + } + + @Override + public List getNodes() { + return List.of(new Node("node-1", 12345)); + } + + @Override + public String getUsername() { + return "sentinel-1"; + } + + @Override + public String getPassword() { + return "secret-1"; + } + + }; + } + + }; + } + + } + + @Configuration(proxyBeanMethods = false) + static class ConnectionDetailsClusterConfiguration { + + @Bean + RedisConnectionDetails redisConnectionDetails() { + return new RedisConnectionDetails() { + + @Override + public String getUsername() { + return "user-1"; + } + + @Override + public String getPassword() { + return "password-1"; + } + + @Override + public Cluster getCluster() { + return new Cluster() { + + @Override + public List getNodes() { + return List.of(new Node("node-1", 12345), new Node("node-2", 23456)); + } + + }; + } + + }; + } + + } + }