package com.lambdaworks.redis; import static com.lambdaworks.redis.LettuceStrings.isEmpty; import static com.lambdaworks.redis.LettuceStrings.isNotEmpty; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.URI; import java.net.URLEncoder; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import com.lambdaworks.redis.internal.HostAndPort; import com.lambdaworks.redis.internal.LettuceAssert; import com.lambdaworks.redis.internal.LettuceSets; import com.lambdaworks.redis.protocol.LettuceCharsets; /** * Redis URI. Contains connection details for the Redis/Sentinel connections. You can provide the database, password and * timeouts within the RedisURI. * * You have following possibilities to create a {@link RedisURI}: * * <ul> * <li>Use an URI: * <p> * {@code RedisURI.create("redis://localhost/");} * </p> * See {@link #create(String)} for more options</li> * <li>Use the Builder: * <p> * {@code RedisURI.Builder.redis("localhost", 6379).withPassword("password").withDatabase(1).build(); } * </p> * See {@link com.lambdaworks.redis.RedisURI.Builder#redis(String)} and * {@link com.lambdaworks.redis.RedisURI.Builder#sentinel(String)} for more options.</li> * <li>Construct your own instance: * <p> * {@code new RedisURI("localhost", 6379, 60, TimeUnit.SECONDS);} * </p> * or * <p> * {@code RedisURI uri = new RedisURI(); * uri.setHost("localhost"); * } * </p> * </li> * </ul> * * <h3>URI syntax</h3> * * <b>Redis Standalone</b> <blockquote> <i>redis</i><b>{@code ://}</b>[<i>password@</i>]<i>host</i> [<b>{@code :} </b> * <i>port</i>][<b>{@code /}</b><i>database</i>][<b>{@code ?}</b> [<i>timeout=timeout</i>[<i>d|h|m|s|ms|us|ns</i>]] [ * <i>&database=database</i>]] </blockquote> * * <b>Redis Standalone (SSL)</b> <blockquote> <i>rediss</i><b>{@code ://}</b>[<i>password@</i>]<i>host</i> [<b>{@code :} </b> * <i>port</i>][<b>{@code /}</b><i>database</i>][<b>{@code ?}</b> [<i>timeout=timeout</i>[<i>d|h|m|s|ms|us|ns</i>]] [ * <i>&database=database</i>]] </blockquote> * * Redis Standalone (Unix Domain Sockets)</b> <blockquote> <i>redis-socket</i><b>{@code ://} </b>[<i>password@</i>]<i>path</i>[ * <b>{@code ?}</b>[<i>timeout=timeout</i>[<i>d|h|m|s|ms|us|ns</i>]][<i>&database=database</i>]] </blockquote> * * <b>Redis Sentinel</b> <blockquote> <i>redis-sentinel</i><b>{@code ://}</b>[<i>password@</i>]<i>host1</i> [<b>{@code :} </b> * <i>port1</i>][, <i>host2</i> [<b>{@code :}</b><i>port2</i>]][, <i>hostN</i> [<b>{@code :}</b><i>portN</i>]][<b>{@code /} </b> * <i>database</i>][<b>{@code ?} </b>[<i>timeout=timeout</i>[<i>d|h|m|s|ms|us|ns</i>]] [ * <i>&sentinelMasterId=sentinelMasterId</i>] [<i>&database=database</i>]] </blockquote> * * <p> * <b>Schemes</b> * </p> * <ul> * <li><b>redis</b> Redis Standalone</li> * <li><b>rediss</b> Redis Standalone SSL</li> * <li><b>redis-socket</b> Redis Standalone Unix Domain Socket</li> * <li><b>redis-sentinel</b> Redis Sentinel</li> * </ul> * * <p> * <b>Timeout units</b> * </p> * <ul> * <li><b>d</b> Days</li> * <li><b>h</b> Hours</li> * <li><b>m</b> Minutes</li> * <li><b>s</b> Seconds</li> * <li><b>ms</b> Milliseconds</li> * <li><b>us</b> Microseconds</li> * <li><b>ns</b> Nanoseconds</li> * </ul> * * <p> * Hint: The database parameter within the query part has higher precedence than the database in the path. * </p> * * * RedisURI supports Redis Standalone, Redis Sentinel and Redis Cluster with plain, SSL, TLS and unix domain socket connections. * * @author Mark Paluch * @since 3.0 */ @SuppressWarnings("serial") public class RedisURI implements Serializable, ConnectionPoint { public static final String URI_SCHEME_REDIS_SENTINEL = "redis-sentinel"; public static final String URI_SCHEME_REDIS = "redis"; public static final String URI_SCHEME_REDIS_SECURE = "rediss"; public static final String URI_SCHEME_REDIS_SECURE_ALT = "redis+ssl"; public static final String URI_SCHEME_REDIS_TLS_ALT = "redis+tls"; public static final String URI_SCHEME_REDIS_SOCKET = "redis-socket"; public static final String URI_SCHEME_REDIS_SOCKET_ALT = "redis+socket"; public static final String PARAMETER_NAME_TIMEOUT = "timeout"; public static final String PARAMETER_NAME_DATABASE = "database"; public static final String PARAMETER_NAME_DATABASE_ALT = "db"; public static final String PARAMETER_NAME_SENTINEL_MASTER_ID = "sentinelMasterId"; public static final Map<String, TimeUnit> TIME_UNIT_MAP; static { Map<String, TimeUnit> unitMap = new HashMap<String, TimeUnit>(); unitMap.put("ns", TimeUnit.NANOSECONDS); unitMap.put("us", TimeUnit.MICROSECONDS); unitMap.put("ms", TimeUnit.MILLISECONDS); unitMap.put("s", TimeUnit.SECONDS); unitMap.put("m", TimeUnit.MINUTES); unitMap.put("h", TimeUnit.HOURS); unitMap.put("d", TimeUnit.DAYS); TIME_UNIT_MAP = Collections.unmodifiableMap(unitMap); } /** * The default sentinel port. */ public static final int DEFAULT_SENTINEL_PORT = 26379; /** * The default redis port. */ public static final int DEFAULT_REDIS_PORT = 6379; /** * Default timeout: 60 sec */ public static final long DEFAULT_TIMEOUT = 60; public static final TimeUnit DEFAULT_TIMEOUT_UNIT = TimeUnit.SECONDS; private String host; private String socket; private String sentinelMasterId; private int port; private int database; private char[] password; private boolean ssl = false; private boolean verifyPeer = true; private boolean startTls = false; private long timeout = 60; private TimeUnit unit = TimeUnit.SECONDS; private final List<RedisURI> sentinels = new ArrayList<>(); /** * Default empty constructor. */ public RedisURI() { } /** * Constructor with host/port and timeout. * * @param host the host * @param port the port * @param timeout timeout value * @param unit unit of the timeout value */ public RedisURI(String host, int port, long timeout, TimeUnit unit) { this.host = host; this.port = port; this.timeout = timeout; this.unit = unit; } /** * Returns a new {@link RedisURI.Builder} to construct a {@link RedisURI}. * * @return a new {@link RedisURI.Builder} to construct a {@link RedisURI}. */ public static RedisURI.Builder builder() { return new Builder(); } /** * Create a Redis URI from host and port. * * @param host the host * @param port the port * @return An instance of {@link RedisURI} containing details from the {@code host} and {@code port}. */ public static RedisURI create(String host, int port) { return Builder.redis(host, port).build(); } /** * Create a Redis URI from an URI string. * * The uri must follow conventions of {@link java.net.URI} * * @param uri The URI string. * @return An instance of {@link RedisURI} containing details from the URI. */ public static RedisURI create(String uri) { LettuceAssert.notEmpty(uri, "URI must not be empty"); return create(URI.create(uri)); } /** * Create a Redis URI from an URI string: * * The uri must follow conventions of {@link java.net.URI} * * @param uri The URI. * @return An instance of {@link RedisURI} containing details from the URI. */ public static RedisURI create(URI uri) { return buildRedisUriFromUri(uri); } /** * Returns the host. * * @return the host. */ public String getHost() { return host; } /** * Sets the Redis host. * * @param host the host */ public void setHost(String host) { this.host = host; } /** * Returns the Sentinel Master Id. * * @return the Sentinel Master Id. */ public String getSentinelMasterId() { return sentinelMasterId; } /** * Sets the Sentinel Master Id. * * @param sentinelMasterId the Sentinel Master Id. */ public void setSentinelMasterId(String sentinelMasterId) { this.sentinelMasterId = sentinelMasterId; } /** * Returns the Redis port. * * @return the Redis port */ public int getPort() { return port; } /** * Sets the Redis port. Defaults to {@link #DEFAULT_REDIS_PORT}. * * @param port the Redis port */ public void setPort(int port) { this.port = port; } /** * Returns the Unix Domain Socket path. * * @return the Unix Domain Socket path. */ public String getSocket() { return socket; } /** * Sets the Unix Domain Socket path. * * @param socket the Unix Domain Socket path. */ public void setSocket(String socket) { this.socket = socket; } /** * Returns the password. * * @return the password */ public char[] getPassword() { return password; } /** * Sets the password. Use empty string to skip authentication. * * @param password the password, must not be {@literal null}. */ public void setPassword(String password) { LettuceAssert.notNull(password, "Password must not be null"); this.password = password.toCharArray(); } /** * Returns the command timeout for synchronous command execution. * * @return the Timeout */ public long getTimeout() { return timeout; } /** * Sets the command timeout for synchronous command execution. * * @param timeout the command timeout for synchronous command execution. */ public void setTimeout(long timeout) { this.timeout = timeout; } /** * Returns the {@link TimeUnit} for the command timeout. * * @return the {@link TimeUnit} for the command timeout. */ public TimeUnit getUnit() { return unit; } /** * Sets the {@link TimeUnit} for the command timeout. * * @param unit the {@link TimeUnit} for the command timeout, must not be {@literal null} */ public void setUnit(TimeUnit unit) { LettuceAssert.notNull(unit, "TimeUnit must not be null"); this.unit = unit; } /** * Returns the Redis database number. Databases are only available for Redis Standalone and Redis Master/Slave. * * @return */ public int getDatabase() { return database; } /** * Sets the Redis database number. Databases are only available for Redis Standalone and Redis Master/Slave. * * @param database the Redis database number. */ public void setDatabase(int database) { this.database = database; } /** * Returns {@literal true} if SSL mode is enabled. * * @return {@literal true} if SSL mode is enabled. */ public boolean isSsl() { return ssl; } /** * Sets whether to use SSL model. * * @param ssl */ public void setSsl(boolean ssl) { this.ssl = ssl; } /** * Sets whether to verify peers when using {@link #isSsl() SSL}. * * @return {@literal true} to verify peers when using {@link #isSsl() SSL}. */ public boolean isVerifyPeer() { return verifyPeer; } /** * Sets whether to verify peers when using {@link #isSsl() SSL}. * * @param verifyPeer {@literal true} to verify peers when using {@link #isSsl() SSL}. */ public void setVerifyPeer(boolean verifyPeer) { this.verifyPeer = verifyPeer; } /** * Returns {@literal true} if StartTLS is enabled. * * @return {@literal true} if StartTLS is enabled. */ public boolean isStartTls() { return startTls; } /** * Returns whether StartTLS is enabled. * * @param startTls {@literal true} if StartTLS is enabled. */ public void setStartTls(boolean startTls) { this.startTls = startTls; } /** * * @return the list of {@link RedisURI Redis Sentinel URIs}. */ public List<RedisURI> getSentinels() { return sentinels; } /** * Creates an URI based on the RedisURI. * * @return URI based on the RedisURI */ public URI toURI() { String scheme = getScheme(); String authority = getAuthority(scheme); String queryString = getQueryString(); String uri = scheme + "://" + authority; if (!queryString.isEmpty()) { uri += "?" + queryString; } return URI.create(uri); } private static RedisURI buildRedisUriFromUri(URI uri) { Builder builder; if (uri.getScheme().equals(URI_SCHEME_REDIS_SENTINEL)) { builder = configureSentinel(uri); } else { builder = configureStandalone(uri); } String userInfo = uri.getUserInfo(); if (isEmpty(userInfo) && isNotEmpty(uri.getAuthority()) && uri.getAuthority().indexOf('@') > 0) { userInfo = uri.getAuthority().substring(0, uri.getAuthority().indexOf('@')); } if (isNotEmpty(userInfo)) { String password = userInfo; if (password.startsWith(":")) { password = password.substring(1); } else { int index = password.indexOf(':'); if (index > 0) { password = password.substring(index + 1); } } if (password != null && !password.equals("")) { builder.withPassword(password); } } if (isNotEmpty(uri.getPath()) && builder.socket == null) { String pathSuffix = uri.getPath().substring(1); if (isNotEmpty(pathSuffix)) { builder.withDatabase(Integer.parseInt(pathSuffix)); } } if (isNotEmpty(uri.getQuery())) { StringTokenizer st = new StringTokenizer(uri.getQuery(), "&;"); while (st.hasMoreTokens()) { String queryParam = st.nextToken(); String forStartWith = queryParam.toLowerCase(); if (forStartWith.startsWith(PARAMETER_NAME_TIMEOUT + "=")) { parseTimeout(builder, queryParam.toLowerCase()); } if (forStartWith.startsWith(PARAMETER_NAME_DATABASE + "=") || queryParam.startsWith(PARAMETER_NAME_DATABASE_ALT + "=")) { parseDatabase(builder, queryParam); } if (forStartWith.startsWith(PARAMETER_NAME_SENTINEL_MASTER_ID.toLowerCase() + "=")) { parseSentinelMasterId(builder, queryParam); } } } if (uri.getScheme().equals(URI_SCHEME_REDIS_SENTINEL)) { LettuceAssert.notEmpty(builder.sentinelMasterId, "URI must contain the sentinelMasterId"); } return builder.build(); } private String getAuthority(final String scheme) { String authority = null; if (host != null) { authority = urlEncode(host) + getPortPart(port, scheme); } if (sentinels.size() != 0) { String joinedSentinels = sentinels.stream() .map(redisURI -> urlEncode(redisURI.getHost()) + getPortPart(redisURI.getPort(), scheme)) .collect(Collectors.joining(",")); authority = joinedSentinels; } if (socket != null) { authority = urlEncode(socket); } if (password != null && password.length != 0) { authority = urlEncode(new String(password)) + "@" + authority; } return authority; } private String getQueryString() { List<String> queryPairs = new ArrayList<>(); if (database != 0) { queryPairs.add(PARAMETER_NAME_DATABASE + "=" + database); } if (sentinelMasterId != null) { queryPairs.add(PARAMETER_NAME_SENTINEL_MASTER_ID + "=" + urlEncode(sentinelMasterId)); } if (timeout != 0 && unit != null && (timeout != DEFAULT_TIMEOUT && !unit.equals(DEFAULT_TIMEOUT_UNIT))) { queryPairs.add(PARAMETER_NAME_TIMEOUT + "=" + timeout + toQueryParamUnit(unit)); } return queryPairs.stream().collect(Collectors.joining("&")); } private String getPortPart(int port, String scheme) { if (URI_SCHEME_REDIS_SENTINEL.equals(scheme) && port == DEFAULT_SENTINEL_PORT) { return ""; } if (URI_SCHEME_REDIS.equals(scheme) && port == DEFAULT_REDIS_PORT) { return ""; } return ":" + port; } private String getScheme() { String scheme = URI_SCHEME_REDIS; if (isSsl()) { if (isStartTls()) { scheme = URI_SCHEME_REDIS_TLS_ALT; } else { scheme = URI_SCHEME_REDIS_SECURE; } } if (socket != null) { scheme = URI_SCHEME_REDIS_SOCKET; } if (host == null && !sentinels.isEmpty()) { scheme = URI_SCHEME_REDIS_SENTINEL; } return scheme; } private String toQueryParamUnit(TimeUnit unit) { for (Map.Entry<String, TimeUnit> entry : TIME_UNIT_MAP.entrySet()) { if (entry.getValue().equals(unit)) { return entry.getKey(); } } return ""; } /** * URL encode the {@code str} without slash escaping {@code %2F} * * @param str * @return the URL-encoded string */ private String urlEncode(String str) { try { return URLEncoder.encode(str, LettuceCharsets.UTF8.name()).replaceAll("%2F", "/"); } catch (UnsupportedEncodingException e) { throw new IllegalStateException(e); } } /** * * @return the resolved {@link SocketAddress} based either on host/port or the socket. */ public SocketAddress getResolvedAddress() { if (getSocket() != null) { return EpollProvider.newSocketAddress(getSocket()); } InetSocketAddress socketAddress = new InetSocketAddress(host, port); return socketAddress; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append(getClass().getSimpleName()); sb.append(" ["); if (host != null) { sb.append("host='").append(host).append('\''); sb.append(", port=").append(port); } if (socket != null) { sb.append("socket='").append(socket).append('\''); } if (sentinelMasterId != null) { sb.append("sentinels=").append(getSentinels()); sb.append(", sentinelMasterId=").append(sentinelMasterId); } sb.append(']'); return sb.toString(); } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof RedisURI)) return false; RedisURI redisURI = (RedisURI) o; if (port != redisURI.port) return false; if (database != redisURI.database) return false; if (host != null ? !host.equals(redisURI.host) : redisURI.host != null) return false; if (socket != null ? !socket.equals(redisURI.socket) : redisURI.socket != null) return false; if (sentinelMasterId != null ? !sentinelMasterId.equals(redisURI.sentinelMasterId) : redisURI.sentinelMasterId != null) return false; return !(sentinels != null ? !sentinels.equals(redisURI.sentinels) : redisURI.sentinels != null); } @Override public int hashCode() { int result = host != null ? host.hashCode() : 0; result = 31 * result + (socket != null ? socket.hashCode() : 0); result = 31 * result + (sentinelMasterId != null ? sentinelMasterId.hashCode() : 0); result = 31 * result + port; result = 31 * result + database; result = 31 * result + (sentinels != null ? sentinels.hashCode() : 0); return result; } private static void parseTimeout(Builder builder, String queryParam) { int index = queryParam.indexOf('='); if (index < 0) { return; } String timeoutString = queryParam.substring(index + 1); int numbersEnd = 0; while (numbersEnd < timeoutString.length() && Character.isDigit(timeoutString.charAt(numbersEnd))) { numbersEnd++; } if (numbersEnd == 0) { if (timeoutString.startsWith("-")) { builder.withTimeout(0, TimeUnit.MILLISECONDS); } else { // no-op, leave defaults } } else { String timeoutValueString = timeoutString.substring(0, numbersEnd); long timeoutValue = Long.parseLong(timeoutValueString); builder.withTimeout(timeoutValue, TimeUnit.MILLISECONDS); String suffix = timeoutString.substring(numbersEnd); TimeUnit timeoutUnit = TIME_UNIT_MAP.get(suffix); if (timeoutUnit == null) { timeoutUnit = TimeUnit.MILLISECONDS; } builder.withTimeout(timeoutValue, timeoutUnit); } } private static void parseDatabase(Builder builder, String queryParam) { int index = queryParam.indexOf('='); if (index < 0) { return; } String databaseString = queryParam.substring(index + 1); int numbersEnd = 0; while (numbersEnd < databaseString.length() && Character.isDigit(databaseString.charAt(numbersEnd))) { numbersEnd++; } if (numbersEnd != 0) { String databaseValueString = databaseString.substring(0, numbersEnd); int value = Integer.parseInt(databaseValueString); builder.withDatabase(value); } } private static void parseSentinelMasterId(Builder builder, String queryParam) { int index = queryParam.indexOf('='); if (index < 0) { return; } String masterIdString = queryParam.substring(index + 1); if (isNotEmpty(masterIdString)) { builder.withSentinelMasterId(masterIdString); } } private static Builder configureStandalone(URI uri) { Builder builder; Set<String> allowedSchemes = LettuceSets.unmodifiableSet(URI_SCHEME_REDIS, URI_SCHEME_REDIS_SECURE, URI_SCHEME_REDIS_SOCKET, URI_SCHEME_REDIS_SOCKET_ALT, URI_SCHEME_REDIS_SECURE_ALT, URI_SCHEME_REDIS_TLS_ALT); if (!allowedSchemes.contains(uri.getScheme())) { throw new IllegalArgumentException("Scheme " + uri.getScheme() + " not supported"); } if (URI_SCHEME_REDIS_SOCKET.equals(uri.getScheme()) || URI_SCHEME_REDIS_SOCKET_ALT.equals(uri.getScheme())) { builder = Builder.socket(uri.getPath()); } else { if (uri.getPort() > 0) { builder = Builder.redis(uri.getHost(), uri.getPort()); } else { builder = Builder.redis(uri.getHost()); } } if (URI_SCHEME_REDIS_SECURE.equals(uri.getScheme()) || URI_SCHEME_REDIS_SECURE_ALT.equals(uri.getScheme())) { builder.withSsl(true); } if (URI_SCHEME_REDIS_TLS_ALT.equals(uri.getScheme())) { builder.withSsl(true); builder.withStartTls(true); } return builder; } private static RedisURI.Builder configureSentinel(URI uri) { String masterId = uri.getFragment(); RedisURI.Builder builder = null; if (isNotEmpty(uri.getHost())) { if (uri.getPort() != -1) { builder = RedisURI.Builder.sentinel(uri.getHost(), uri.getPort()); } else { builder = RedisURI.Builder.sentinel(uri.getHost()); } } if (builder == null && isNotEmpty(uri.getAuthority())) { String authority = uri.getAuthority(); if (authority.indexOf('@') > -1) { authority = authority.substring(authority.indexOf('@') + 1); } String[] hosts = authority.split("\\,"); for (String host : hosts) { HostAndPort hostAndPort = HostAndPort.parse(host); if (builder == null) { if (hostAndPort.hasPort()) { builder = RedisURI.Builder.sentinel(hostAndPort.getHostText(), hostAndPort.getPort()); } else { builder = RedisURI.Builder.sentinel(hostAndPort.getHostText()); } } else { if (hostAndPort.hasPort()) { builder.withSentinel(hostAndPort.getHostText(), hostAndPort.getPort()); } else { builder.withSentinel(hostAndPort.getHostText()); } } } } if (isNotEmpty(masterId)) { builder.withSentinelMasterId(masterId); } LettuceAssert.notNull(builder, "Invalid URI, cannot get host part"); return builder; } /** * Builder for Redis URI. */ public static class Builder { private String host; private String socket; private String sentinelMasterId; private int port; private int database; private char[] password; private boolean ssl = false; private boolean verifyPeer = true; private boolean startTls = false; private long timeout = 60; private TimeUnit unit = TimeUnit.SECONDS; private final List<HostAndPort> sentinels = new ArrayList<>(); /** * @deprecated Use {@link RedisURI#builder()} */ @Deprecated public Builder() { } /** * Set Redis socket. Creates a new builder. * * @param socket the host name * @return New builder with Redis socket. */ public static Builder socket(String socket) { LettuceAssert.notNull(socket, "Socket must not be null"); Builder builder = RedisURI.builder(); builder.socket = socket; return builder; } /** * Set Redis host. Creates a new builder. * * @param host the host name * @return New builder with Redis host/port. */ public static Builder redis(String host) { return redis(host, DEFAULT_REDIS_PORT); } /** * Set Redis host and port. Creates a new builder * * @param host the host name * @param port the port * @return New builder with Redis host/port. */ public static Builder redis(String host, int port) { LettuceAssert.notEmpty(host, "Host must not be empty"); LettuceAssert.isTrue(isValidPort(port), String.format("Port out of range: %s", port)); Builder builder = RedisURI.builder(); return builder.withHost(host).withPort(port); } /** * Set Sentinel host. Creates a new builder. * * @param host the host name * @return New builder with Sentinel host/port. */ public static Builder sentinel(String host) { LettuceAssert.notEmpty(host, "Host must not be empty"); Builder builder = RedisURI.builder(); return builder.withSentinel(host); } /** * Set Sentinel host and port. Creates a new builder. * * @param host the host name * @param port the port * @return New builder with Sentinel host/port. */ public static Builder sentinel(String host, int port) { LettuceAssert.notEmpty(host, "Host must not be empty"); LettuceAssert.isTrue(isValidPort(port), String.format("Port out of range: %s", port)); Builder builder = RedisURI.builder(); return builder.withSentinel(host, port); } /** * Set Sentinel host and master id. Creates a new builder. * * @param host the host name * @param masterId sentinel master id * @return New builder with Sentinel host/port. */ public static Builder sentinel(String host, String masterId) { return sentinel(host, DEFAULT_SENTINEL_PORT, masterId); } /** * Set Sentinel host, port and master id. Creates a new builder. * * @param host the host name * @param port the port * @param masterId sentinel master id * @return New builder with Sentinel host/port. */ public static Builder sentinel(String host, int port, String masterId) { LettuceAssert.notEmpty(host, "Host must not be empty"); LettuceAssert.isTrue(isValidPort(port), String.format("Port out of range: %s", port)); Builder builder = RedisURI.builder(); return builder.withSentinelMasterId(masterId).withSentinel(host, port); } /** * Add a withSentinel host to the existing builder. * * @param host the host name * @return the builder */ public Builder withSentinel(String host) { return withSentinel(host, DEFAULT_SENTINEL_PORT); } /** * Add a withSentinel host/port to the existing builder. * * @param host the host name * @param port the port * @return the builder */ public Builder withSentinel(String host, int port) { LettuceAssert.assertState(this.host == null, "Cannot use with Redis mode."); LettuceAssert.notEmpty(host, "Host must not be empty"); LettuceAssert.isTrue(isValidPort(port), String.format("Port out of range: %s", port)); sentinels.add(HostAndPort.of(host, port)); return this; } /** * Adds host information to the builder. Does only affect Redis URI, cannot be used with Sentinel connections. * * @param host the port * @return the builder */ public Builder withHost(String host) { LettuceAssert.assertState(this.sentinels.isEmpty(), "Sentinels are non-empty. Cannot use in Sentinel mode."); LettuceAssert.notEmpty(host, "Host must not be empty"); this.host = host; return this; } /** * Adds port information to the builder. Does only affect Redis URI, cannot be used with Sentinel connections. * * @param port the port * @return the builder */ public Builder withPort(int port) { LettuceAssert.assertState(this.host != null, "Host is null. Cannot use in Sentinel mode."); LettuceAssert.isTrue(isValidPort(port), String.format("Port out of range: %s", port)); this.port = port; return this; } /** * Adds ssl information to the builder. Does only affect Redis URI, cannot be used with Sentinel connections. * * @param ssl {@literal true} if use SSL * @return the builder */ public Builder withSsl(boolean ssl) { LettuceAssert.assertState(this.host != null, "Host is null. Cannot use in Sentinel mode."); this.ssl = ssl; return this; } /** * Enables/disables StartTLS when using SSL. Does only affect Redis URI, cannot be used with Sentinel connections. * * @param startTls {@literal true} if use StartTLS * @return the builder */ public Builder withStartTls(boolean startTls) { LettuceAssert.assertState(this.host != null, "Host is null. Cannot use in Sentinel mode."); this.startTls = startTls; return this; } /** * Enables/disables peer verification. Does only affect Redis URI, cannot be used with Sentinel connections. * * @param verifyPeer {@literal true} to verify hosts when using SSL * @return the builder */ public Builder withVerifyPeer(boolean verifyPeer) { LettuceAssert.assertState(this.host != null, "Host is null. Cannot use in Sentinel mode."); this.verifyPeer = verifyPeer; return this; } /** * Adds database selection. * * @param database the database number * @return the builder */ public Builder withDatabase(int database) { LettuceAssert.isTrue(database >= 0 && database <= 15, "Invalid database number: " + database); this.database = database; return this; } /** * Adds authentication. * * @param password the password * @return the builder */ public Builder withPassword(String password) { LettuceAssert.notNull(password, "Password must not be null"); this.password = password.toCharArray(); return this; } /** * Adds timeout. * * @param timeout must be greater or equal 0" * @param unit the timeout time unit. * @return the builder */ public Builder withTimeout(long timeout, TimeUnit unit) { LettuceAssert.notNull(unit, "TimeUnit must not be null"); LettuceAssert.isTrue(timeout >= 0, "Timeout must be greater or equal 0"); this.timeout = timeout; this.unit = unit; return this; } /** * Adds a sentinel master Id. * * @param sentinelMasterId sentinel master id, must not be empty or {@literal null} * @return the builder */ public Builder withSentinelMasterId(String sentinelMasterId) { LettuceAssert.notEmpty(sentinelMasterId, "Sentinel master id must not empty"); this.sentinelMasterId = sentinelMasterId; return this; } /** * * @return the RedisURI. */ public RedisURI build() { if (sentinels.isEmpty() && LettuceStrings.isEmpty(host) && LettuceStrings.isEmpty(socket)) { throw new IllegalStateException( "Cannot build a RedisURI. One of the following must be provided Host, Socket or Sentinel"); } RedisURI redisURI = new RedisURI(); redisURI.setHost(host); redisURI.setPort(port); redisURI.password = password; redisURI.setDatabase(database); redisURI.setSentinelMasterId(sentinelMasterId); for (HostAndPort sentinel : sentinels) { redisURI.getSentinels().add(new RedisURI(sentinel.getHostText(), sentinel.getPort(), timeout, unit)); } redisURI.setSocket(socket); redisURI.setSsl(ssl); redisURI.setStartTls(startTls); redisURI.setVerifyPeer(verifyPeer); redisURI.setTimeout(timeout); redisURI.setUnit(unit); return redisURI; } } /** Return true for valid port numbers. */ private static boolean isValidPort(int port) { return port >= 0 && port <= 65535; } }