DATAREDIS-574 - Introduce RedisPassword value object.

Encapsulate Redis passwords within a GC-friendly container that allows mapping and consumption in a functional style.

Original Pull Request: #236
This commit is contained in:
Mark Paluch
2017-05-26 14:30:40 +02:00
committed by Christoph Strobl
parent a403a530d3
commit 3819ded01c
9 changed files with 276 additions and 68 deletions

View File

@@ -47,7 +47,7 @@ public class RedisClusterConfiguration {
private Set<RedisNode> clusterNodes;
private Integer maxRedirects;
private String password;
private RedisPassword password = RedisPassword.none();
/**
* Creates new {@link RedisClusterConfiguration}.
@@ -183,15 +183,18 @@ public class RedisClusterConfiguration {
* @return
* @since 2.0
*/
public String getPassword() {
public RedisPassword getPassword() {
return password;
}
/**
* @param password
* @param password must not be {@literal null}.
* @since 2.0
*/
public void setPassword(String password) {
public void setPassword(RedisPassword password) {
Assert.notNull(password, "RedisPassword must not be null!");
this.password = password;
}

View File

@@ -0,0 +1,150 @@
/*
* 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 lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.Function;
import org.springframework.util.Assert;
/**
* Value object which may or may not contain a Redis password.
* <p/>
* If a password is present, {@code isPresent()} will return {@code true} and {@code get()} will return the value.
* <p/>
* The password is stored as character array.
*
* @author Mark Paluch
* @since 2.0
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@EqualsAndHashCode
public class RedisPassword {
private static final RedisPassword NONE = new RedisPassword(new char[0]);
private final char[] thePassword;
/**
* Create a {@link RedisPassword} from a {@link String}.
*
* @param passwordAsString the password as string.
* @return the {@link RedisPassword} for {@code passwordAsString}.
*/
public static RedisPassword of(String passwordAsString) {
return Optional.ofNullable(passwordAsString) //
.filter(it -> it.length() != 0) //
.map(it -> new RedisPassword(it.toCharArray())) //
.orElseGet(RedisPassword::none);
}
/**
* Create a {@link RedisPassword} from a {@code char} array.
*
* @param passwordAsChars the password as string.
* @return the {@link RedisPassword} for {@code passwordAsChars}.
*/
public static RedisPassword of(char[] passwordAsChars) {
return Optional.ofNullable(passwordAsChars) //
.filter(it -> it.length != 0) //
.map(it -> new RedisPassword(Arrays.copyOf(it, it.length))) //
.orElseGet(RedisPassword::none);
}
/**
* Create an absent {@link RedisPassword}.
*
* @return the absent {@link RedisPassword}.
*/
public static RedisPassword none() {
return NONE;
}
/**
* Return {@code true} if there is a password present, otherwise {@code false}.
*
* @return {@code true} if there is a password present, otherwise {@code false}
*/
public boolean isPresent() {
return thePassword.length != 0;
}
/**
* Return the password value if present. Throws {@link NoSuchElementException} if the password is absent.
*
* @return the password.
* @throws NoSuchElementException if the password is absent.
*/
public char[] get() throws NoSuchElementException {
if (isPresent()) {
return Arrays.copyOf(thePassword, thePassword.length);
}
throw new NoSuchElementException("No password present.");
}
/**
* Map the password using a {@link Function} and return a {@link Optional} containing the mapped value.
* <p/>
* Absent passwords return a {@link Optional#empty()}.
*
* @param mapper must not be {@literal null}.
* @return the mapped result.
*/
public <R> Optional<R> map(Function<char[], R> mapper) {
Assert.notNull(mapper, "Mapper function must not be null!");
if (isPresent()) {
return Optional.ofNullable(mapper.apply(thePassword));
}
return Optional.empty();
}
/**
* Adopt the password to {@link Optional} containing the password value.
* <p/>
* Absent passwords return a {@link Optional#empty()}.
*
* @return the {@link Optional} containing the password value.
*/
public Optional<char[]> toOptional() {
if (isPresent()) {
return Optional.ofNullable(get());
}
return Optional.empty();
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("%s[%s]", getClass().getSimpleName(), isPresent() ? "*****" : "<none>");
}
}

View File

@@ -34,7 +34,7 @@ import org.springframework.util.StringUtils;
* Configuration class used for setting up {@link RedisConnection} via {@link RedisConnectionFactory} using connecting
* to <a href="http://redis.io/topics/sentinel">Redis Sentinel(s)</a>. Useful when setting up a high availability Redis
* environment.
*
*
* @author Christoph Strobl
* @author Thomas Darimont
* @author Mark Paluch
@@ -48,7 +48,7 @@ public class RedisSentinelConfiguration {
private NamedNode master;
private Set<RedisNode> sentinels;
private int database;
private String password;
private RedisPassword password = RedisPassword.none();
/**
* Creates new {@link RedisSentinelConfiguration}.
@@ -59,12 +59,12 @@ public class RedisSentinelConfiguration {
/**
* Creates {@link RedisSentinelConfiguration} for given hostPort combinations.
*
*
* <pre>
* sentinelHostAndPorts[0] = 127.0.0.1:23679 sentinelHostAndPorts[1] = 127.0.0.1:23680 ...
*
*
* <pre>
*
*
* @param sentinelHostAndPorts must not be {@literal null}.
* @since 1.5
*/
@@ -74,14 +74,14 @@ public class RedisSentinelConfiguration {
/**
* Creates {@link RedisSentinelConfiguration} looking up values in given {@link PropertySource}.
*
*
* <pre>
* <code>
* spring.redis.sentinel.master=myMaster
* spring.redis.sentinel.nodes=127.0.0.1:23679,127.0.0.1:23680,127.0.0.1:23681
* </code>
* </pre>
*
*
* @param propertySource must not be {@literal null}.
* @since 1.5
*/
@@ -103,7 +103,7 @@ public class RedisSentinelConfiguration {
/**
* Set {@literal Sentinels} to connect to.
*
*
* @param sentinels must not be {@literal null}.
*/
public void setSentinels(Iterable<RedisNode> sentinels) {
@@ -119,7 +119,7 @@ public class RedisSentinelConfiguration {
/**
* Returns an {@link Collections#unmodifiableSet(Set)} of {@literal Sentinels}.
*
*
* @return {@link Set} of sentinels. Never {@literal null}.
*/
public Set<RedisNode> getSentinels() {
@@ -128,7 +128,7 @@ public class RedisSentinelConfiguration {
/**
* Add sentinel.
*
*
* @param sentinel must not be {@literal null}.
*/
public void addSentinel(RedisNode sentinel) {
@@ -139,7 +139,7 @@ public class RedisSentinelConfiguration {
/**
* Set the master node via its name.
*
*
* @param name must not be {@literal null}.
*/
public void setMaster(final String name) {
@@ -156,7 +156,7 @@ public class RedisSentinelConfiguration {
/**
* Set the master.
*
*
* @param master must not be {@literal null}.
*/
public void setMaster(NamedNode master) {
@@ -167,7 +167,7 @@ public class RedisSentinelConfiguration {
/**
* Get the {@literal Sentinel} master node.
*
*
* @return
*/
public NamedNode getMaster() {
@@ -246,15 +246,18 @@ public class RedisSentinelConfiguration {
* @return
* @since 2.0
*/
public String getPassword() {
public RedisPassword getPassword() {
return password;
}
/**
* @param password
* @param password must not be {@literal null}.
* @since 2.0
*/
public void setPassword(String password) {
public void setPassword(RedisPassword password) {
Assert.notNull(password, "RedisPassword must not be null!");
this.password = password;
}

View File

@@ -32,7 +32,7 @@ public class RedisStandaloneConfiguration {
private String hostName = DEFAULT_HOST;
private int port = DEFAULT_PORT;
private int database;
private String password;
private RedisPassword password = RedisPassword.none();
/**
* Create a new default {@link RedisStandaloneConfiguration}.
@@ -114,14 +114,17 @@ public class RedisStandaloneConfiguration {
/**
* @return
*/
public String getPassword() {
public RedisPassword getPassword() {
return password;
}
/**
* @param password
* @param password must not be {@literal null}.
*/
public void setPassword(String password) {
public void setPassword(RedisPassword password) {
Assert.notNull(password, "RedisPassword must not be null!");
this.password = password;
}
}

View File

@@ -50,15 +50,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.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.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.*;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
@@ -312,7 +304,7 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean,
Client client = jedis.getClient();
client.setPassword(getPassword());
getRedisPassword().map(String::new).ifPresent(client::setPassword);
client.setDb(getDatabase());
return jedis;
@@ -343,9 +335,7 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean,
clientConfiguration.getSslParameters().orElse(null), //
clientConfiguration.getHostnameVerifier().orElse(null));
if (StringUtils.hasLength(getPassword())) {
shardInfo.setPassword(getPassword());
}
getRedisPassword().map(String::new).ifPresent(shardInfo::setPassword);
int readTimeout = getReadTimeout();
@@ -558,6 +548,10 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean,
* @return password for authentication.
*/
public String getPassword() {
return getRedisPassword().map(String::new).orElse(null);
}
private RedisPassword getRedisPassword() {
if (isRedisSentinelAware()) {
return sentinelConfig.getPassword();
@@ -581,16 +575,16 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean,
public void setPassword(String password) {
if (isRedisSentinelAware()) {
sentinelConfig.setPassword(password);
sentinelConfig.setPassword(RedisPassword.of(password));
return;
}
if (isRedisClusterAware()) {
clusterConfig.setPassword(password);
clusterConfig.setPassword(RedisPassword.of(password));
return;
}
standaloneConfig.setPassword(password);
standaloneConfig.setPassword(RedisPassword.of(password));
}
/**

View File

@@ -44,7 +44,6 @@ import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.connection.*;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Connection factory creating <a href="http://github.com/mp911de/lettuce">Lettuce</a>-based connections.
@@ -549,6 +548,10 @@ public class LettuceConnectionFactory
* @return password for authentication.
*/
public String getPassword() {
return getRedisPassword().map(String::new).orElse(null);
}
private RedisPassword getRedisPassword() {
if (isRedisSentinelAware()) {
return sentinelConfiguration.getPassword();
@@ -572,16 +575,16 @@ public class LettuceConnectionFactory
public void setPassword(String password) {
if (isRedisSentinelAware()) {
sentinelConfiguration.setPassword(password);
sentinelConfiguration.setPassword(RedisPassword.of(password));
return;
}
if (isClusterAware()) {
clusterConfiguration.setPassword(password);
clusterConfiguration.setPassword(RedisPassword.of(password));
return;
}
standaloneConfig.setPassword(password);
standaloneConfig.setPassword(RedisPassword.of(password));
}
/**
@@ -786,9 +789,7 @@ public class LettuceConnectionFactory
RedisURI redisUri = LettuceConverters.sentinelConfigurationToRedisURI(sentinelConfiguration);
if (StringUtils.hasText(getPassword())) {
redisUri.setPassword(getPassword());
}
getRedisPassword().toOptional().ifPresent(redisUri::setPassword);
return redisUri;
}
@@ -796,9 +797,8 @@ public class LettuceConnectionFactory
private RedisURI createRedisURIAndApplySettings(String host, int port) {
RedisURI.Builder builder = RedisURI.Builder.redis(host, port);
if (StringUtils.hasText(getPassword())) {
builder.withPassword(getPassword());
}
getRedisPassword().toOptional().ifPresent(builder::withPassword);
builder.withSsl(clientConfiguration.useSsl());
builder.withVerifyPeer(clientConfiguration.isVerifyPeer());