Add factory method to LettuceConnectionFactory to create RedisConfiguration from RedisURI.

Closes #2116.
Original pull request: #2117.
This commit is contained in:
bono007
2021-07-11 23:30:46 -05:00
committed by Mark Paluch
parent 82ab6e8e6a
commit 232410d4cc
3 changed files with 303 additions and 10 deletions

View File

@@ -61,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.ObjectUtils;
import org.springframework.util.StringUtils;
/**
@@ -97,6 +98,7 @@ import org.springframework.util.StringUtils;
* @author Ruben Cervilla
* @author Luis De Bello
* @author Andrea Como
* @author Chris Bono
*/
public class LettuceConnectionFactory
implements InitializingBean, DisposableBean, RedisConnectionFactory, ReactiveRedisConnectionFactory {
@@ -279,6 +281,32 @@ public class LettuceConnectionFactory
this.configuration = clusterConfiguration;
}
/**
* Creates a {@link RedisConfiguration} based on a {@link RedisURI} according to the following:
* <ul>
* <li>If {@code redisURI} has sentinel info a {@link RedisSentinelConfiguration} is returned</li>
* <li>If {@code redisURI} has socket info a {@link RedisSocketConfiguration} is returned</li>
* <li>Otherwise a {@link RedisStandaloneConfiguration} is returned</li>
* </ul>
*
* @param redisURI the connection info in the format of a RedisURI
* @return an appropriate {@link RedisConfiguration} instance representing the Redis URI.
*/
public static RedisConfiguration createRedisConfiguration(RedisURI redisURI) {
Assert.notNull(redisURI, "RedisURI must not be null");
if (!ObjectUtils.isEmpty(redisURI.getSentinels())) {
return LettuceConverters.redisUriToSentinelConfiguration(redisURI);
}
if (!ObjectUtils.isEmpty(redisURI.getSocket())) {
return LettuceConverters.redisUriToSocketConfiguration(redisURI);
}
return LettuceConverters.redisUriToStandaloneConfiguration(redisURI);
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()

View File

@@ -33,14 +33,12 @@ import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Metric;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldGet;
import org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldIncrBy;
import org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldIncrBy.Overflow;
import org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldSet;
import org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldSubCommand;
import org.springframework.data.redis.connection.DefaultTuple;
import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.connection.RedisClusterNode.Flag;
import org.springframework.data.redis.connection.RedisClusterNode.LinkState;
import org.springframework.data.redis.connection.RedisClusterNode.SlotRange;
@@ -48,17 +46,10 @@ import org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit;
import org.springframework.data.redis.connection.RedisGeoCommands.GeoLocation;
import org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs;
import org.springframework.data.redis.connection.RedisListCommands.Position;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisNode.NodeType;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisServer;
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.connection.RedisZSetCommands.Range.Boundary;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.connection.SortParameters;
import org.springframework.data.redis.connection.SortParameters.Order;
import org.springframework.data.redis.connection.convert.Converters;
import org.springframework.data.redis.connection.convert.ListConverter;
@@ -83,6 +74,7 @@ import org.springframework.util.StringUtils;
* @author Mark Paluch
* @author Ninad Divadkar
* @author dengliming
* @author Chris Bono
*/
public abstract class LettuceConverters extends Converters {
@@ -532,6 +524,86 @@ public abstract class LettuceConverters extends Converters {
return builder.build();
}
/**
* Converts a {@link RedisURI} to its corresponding {@link RedisSentinelConfiguration}.
*
* @param redisURI the uri containing the Redis Sentinel connection info
* @return a {@link RedisSentinelConfiguration} representing the Redis Sentinel information in the Redis URI.
* @since 2.6
*/
static RedisSentinelConfiguration redisUriToSentinelConfiguration(RedisURI redisURI) {
Assert.notNull(redisURI, "RedisURI is required");
RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration();
if (!ObjectUtils.isEmpty(redisURI.getSentinelMasterId())) {
sentinelConfiguration.setMaster(redisURI.getSentinelMasterId());
}
sentinelConfiguration.setDatabase(redisURI.getDatabase());
for (RedisURI sentinelNodeRedisUri : redisURI.getSentinels()) {
RedisNode sentinelNode = new RedisNode(sentinelNodeRedisUri.getHost(), sentinelNodeRedisUri.getPort());
if (sentinelNodeRedisUri.getPassword() != null) {
sentinelConfiguration.setSentinelPassword(sentinelNodeRedisUri.getPassword());
}
sentinelConfiguration.addSentinel(sentinelNode);
}
applyAuthentication(redisURI, sentinelConfiguration);
return sentinelConfiguration;
}
/**
* Converts a {@link RedisURI} to its corresponding {@link RedisSocketConfiguration}.
*
* @param redisURI the uri containing the Redis connection info using a local unix domain socket
* @return a {@link RedisSocketConfiguration} representing the connection information in the Redis URI.
* @since 2.6
*/
static RedisSocketConfiguration redisUriToSocketConfiguration(RedisURI redisURI) {
Assert.notNull(redisURI, "RedisURI is required");
RedisSocketConfiguration socketConfiguration = new RedisSocketConfiguration();
socketConfiguration.setSocket(redisURI.getSocket());
socketConfiguration.setDatabase(redisURI.getDatabase());
applyAuthentication(redisURI, socketConfiguration);
return socketConfiguration;
}
/**
* Converts a {@link RedisURI} to its corresponding {@link RedisStandaloneConfiguration}.
*
* @param redisURI the uri containing the Redis connection info
* @return a {@link RedisStandaloneConfiguration} representing the connection information in the Redis URI.
* @since 2.6
*/
static RedisStandaloneConfiguration redisUriToStandaloneConfiguration(RedisURI redisURI) {
Assert.notNull(redisURI, "RedisURI is required");
RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration();
standaloneConfiguration.setHostName(redisURI.getHost());
standaloneConfiguration.setPort(redisURI.getPort());
standaloneConfiguration.setDatabase(redisURI.getDatabase());
applyAuthentication(redisURI, standaloneConfiguration);
return standaloneConfiguration;
}
private static void applyAuthentication(RedisURI redisURI, RedisConfiguration.WithAuthentication redisConfiguration) {
if (StringUtils.hasText(redisURI.getUsername())) {
redisConfiguration.setUsername(redisURI.getUsername());
}
if (redisURI.getPassword() != null) {
redisConfiguration.setPassword(redisURI.getPassword());
}
}
public static byte[] toBytes(@Nullable String source) {
if (source == null) {
return null;

View File

@@ -0,0 +1,193 @@
/*
* Copyright 2015-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.connection.lettuce;
import static org.assertj.core.api.Assertions.*;
import io.lettuce.core.RedisURI;
import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.data.redis.connection.RedisConfiguration;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisSocketConfiguration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
/**
* Unit tests for the {@link LettuceConnectionFactory#createRedisConfiguration(RedisURI)} factory method.
*
* @author Chris Bono
*/
class LettuceConnectionFactoryCreateRedisConfigurationUnitTests {
@Test
void requiresRedisURI() {
assertThatThrownBy(() -> LettuceConnectionFactory.createRedisConfiguration(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("RedisURI must not be null");
}
@Nested // GH-2117
class CreateRedisSentinelConfiguration {
@Test
void minimalFieldsSetOnRedisURI() {
RedisURI redisURI = RedisURI.create("redis-sentinel://myserver?sentinelMasterId=5150");
RedisSentinelConfiguration expectedSentinelConfiguration = new RedisSentinelConfiguration();
expectedSentinelConfiguration.setMaster("5150");
expectedSentinelConfiguration.addSentinel(new RedisNode("myserver", 26379));
RedisConfiguration sentinelConfiguration = LettuceConnectionFactory.createRedisConfiguration(redisURI);
assertThat(sentinelConfiguration).usingRecursiveComparison(sentinelCompareConfig())
.isInstanceOf(RedisSentinelConfiguration.class)
.isEqualTo(expectedSentinelConfiguration);
}
@Test
void allFieldsSetOnRedisURI() {
RedisURI redisURI = RedisURI.create("redis-sentinel://fooUser:fooPass@myserver1:111,myserver2:222/7?sentinelMasterId=5150");
// Set the passwords directly on the sentinels so that it gets picked up by converter
char[] sentinelPass = "changeme".toCharArray();
redisURI.getSentinels().forEach(sentinelRedisUri -> sentinelRedisUri.setPassword(sentinelPass));
RedisSentinelConfiguration expectedSentinelConfiguration = new RedisSentinelConfiguration();
expectedSentinelConfiguration.setMaster("5150");
expectedSentinelConfiguration.setDatabase(7);
expectedSentinelConfiguration.setUsername("fooUser");
expectedSentinelConfiguration.setPassword("fooPass");
expectedSentinelConfiguration.setSentinelPassword(sentinelPass);
expectedSentinelConfiguration.addSentinel(new RedisNode("myserver1", 111));
expectedSentinelConfiguration.addSentinel(new RedisNode("myserver2", 222));
RedisConfiguration sentinelConfiguration = LettuceConnectionFactory.createRedisConfiguration(redisURI);
assertThat(sentinelConfiguration).usingRecursiveComparison(sentinelCompareConfig())
.isInstanceOf(RedisSentinelConfiguration.class)
.isEqualTo(expectedSentinelConfiguration);
}
// RedisSentinelConfiguration does not provide equals impl
private RecursiveComparisonConfiguration sentinelCompareConfig() {
return RecursiveComparisonConfiguration.builder().withComparedFields(
"master",
"username",
"password",
"sentinelPassword",
"database",
"sentinels")
.build();
}
}
@Nested // GH-2117
class CreateRedisSocketConfiguration {
@Test
void minimalFieldsSetOnRedisURI() {
RedisURI redisURI = RedisURI.builder()
.socket("mysocket")
.build();
RedisSocketConfiguration expectedSocketConfiguration = new RedisSocketConfiguration();
expectedSocketConfiguration.setSocket("mysocket");
RedisConfiguration socketConfiguration = LettuceConnectionFactory.createRedisConfiguration(redisURI);
assertThat(socketConfiguration).usingRecursiveComparison(socketCompareConfig())
.isInstanceOf(RedisSocketConfiguration.class)
.isEqualTo(expectedSocketConfiguration);
}
@Test
void allFieldsSetOnRedisURI() {
RedisURI redisURI = RedisURI.builder()
.socket("mysocket")
.withAuthentication("fooUser", "fooPass".toCharArray())
.withDatabase(7)
.build();
RedisSocketConfiguration expectedSocketConfiguration = new RedisSocketConfiguration();
expectedSocketConfiguration.setSocket("mysocket");
expectedSocketConfiguration.setUsername("fooUser");
expectedSocketConfiguration.setPassword("fooPass");
expectedSocketConfiguration.setDatabase(7);
RedisConfiguration socketConfiguration = LettuceConnectionFactory.createRedisConfiguration(redisURI);
assertThat(socketConfiguration).usingRecursiveComparison(socketCompareConfig())
.isInstanceOf(RedisSocketConfiguration.class)
.isEqualTo(expectedSocketConfiguration);
}
// RedisSocketConfiguration does not provide equals impl
private RecursiveComparisonConfiguration socketCompareConfig() {
return RecursiveComparisonConfiguration.builder().withComparedFields(
"socket",
"username",
"password",
"database")
.build();
}
}
@Nested // GH-2117
class CreateRedisStandaloneConfiguration {
@Test
void minimalFieldsSetOnRedisURI() {
RedisURI redisURI = RedisURI.create("redis://myserver");
RedisStandaloneConfiguration expectedStandaloneConfiguration = new RedisStandaloneConfiguration();
expectedStandaloneConfiguration.setHostName("myserver");
RedisConfiguration StandaloneConfiguration = LettuceConnectionFactory.createRedisConfiguration(redisURI);
assertThat(StandaloneConfiguration).usingRecursiveComparison(standaloneCompareConfig())
.isInstanceOf(RedisStandaloneConfiguration.class)
.isEqualTo(expectedStandaloneConfiguration);
}
@Test
void allFieldsSetOnRedisURI() {
RedisURI redisURI = RedisURI.create("redis://fooUser:fooPass@myserver1:111/7");
RedisStandaloneConfiguration expectedStandaloneConfiguration = new RedisStandaloneConfiguration();
expectedStandaloneConfiguration.setHostName("myserver1");
expectedStandaloneConfiguration.setPort(111);
expectedStandaloneConfiguration.setDatabase(7);
expectedStandaloneConfiguration.setUsername("fooUser");
expectedStandaloneConfiguration.setPassword("fooPass");
RedisConfiguration StandaloneConfiguration = LettuceConnectionFactory.createRedisConfiguration(redisURI);
assertThat(StandaloneConfiguration).usingRecursiveComparison(standaloneCompareConfig())
.isInstanceOf(RedisStandaloneConfiguration.class)
.isEqualTo(expectedStandaloneConfiguration);
}
// RedisStandaloneConfiguration does not provide equals impl
private RecursiveComparisonConfiguration standaloneCompareConfig() {
return RecursiveComparisonConfiguration.builder().withComparedFields(
"host",
"port",
"username",
"password",
"database")
.build();
}
}
}