package com.lambdaworks.redis.cluster.models.partitions;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import com.lambdaworks.redis.LettuceStrings;
import com.lambdaworks.redis.RedisException;
import com.lambdaworks.redis.RedisURI;
import com.lambdaworks.redis.internal.HostAndPort;
import com.lambdaworks.redis.internal.LettuceLists;
/**
* Parser for node information output of {@code CLUSTER NODES} and {@code CLUSTER SLAVES}.
*
* @author Mark Paluch
* @since 3.0
*/
public class ClusterPartitionParser {
public static final String CONNECTED = "connected";
private static final String TOKEN_SLOT_IN_TRANSITION = "[";
private static final char TOKEN_NODE_SEPARATOR = '\n';
private static final Pattern TOKEN_PATTERN = Pattern.compile(Character.toString(TOKEN_NODE_SEPARATOR));
private static final Pattern SPACE_PATTERN = Pattern.compile(" ");
private static final Pattern DASH_PATTERN = Pattern.compile("\\-");
private static final Map<String, RedisClusterNode.NodeFlag> FLAG_MAPPING;
static {
Map<String, RedisClusterNode.NodeFlag> map = new HashMap<>();
map.put("noflags", RedisClusterNode.NodeFlag.NOFLAGS);
map.put("myself", RedisClusterNode.NodeFlag.MYSELF);
map.put("master", RedisClusterNode.NodeFlag.MASTER);
map.put("slave", RedisClusterNode.NodeFlag.SLAVE);
map.put("fail?", RedisClusterNode.NodeFlag.EVENTUAL_FAIL);
map.put("fail", RedisClusterNode.NodeFlag.FAIL);
map.put("handshake", RedisClusterNode.NodeFlag.HANDSHAKE);
map.put("noaddr", RedisClusterNode.NodeFlag.NOADDR);
FLAG_MAPPING = Collections.unmodifiableMap(map);
}
/**
* Utility constructor.
*/
private ClusterPartitionParser() {
}
/**
* Parse partition lines into Partitions object.
*
* @param nodes output of CLUSTER NODES
* @return the partitions object.
*/
public static Partitions parse(String nodes) {
Partitions result = new Partitions();
try {
List<RedisClusterNode> mappedNodes = TOKEN_PATTERN.splitAsStream(nodes).filter(s -> !s.isEmpty())
.map(ClusterPartitionParser::parseNode)
.collect(Collectors.toList());
result.addAll(mappedNodes);
} catch (Exception e) {
throw new RedisException("Cannot parse " + nodes, e);
}
return result;
}
private static RedisClusterNode parseNode(String nodeInformation) {
Iterator<String> iterator = SPACE_PATTERN.splitAsStream(nodeInformation).iterator();
String nodeId = iterator.next();
boolean connected = false;
RedisURI uri = null;
String hostAndPortPart = iterator.next();
if(hostAndPortPart.contains("@")) {
hostAndPortPart = hostAndPortPart.substring(0, hostAndPortPart.indexOf('@'));
}
HostAndPort hostAndPort = HostAndPort.parseCompat(hostAndPortPart);
if (LettuceStrings.isNotEmpty(hostAndPort.getHostText())) {
uri = RedisURI.Builder.redis(hostAndPort.getHostText(), hostAndPort.getPort()).build();
}
String flags = iterator.next();
List<String> flagStrings = LettuceLists.newList(flags.split("\\,"));
Set<RedisClusterNode.NodeFlag> nodeFlags = readFlags(flagStrings);
String slaveOfString = iterator.next(); // (nodeId or -)
String slaveOf = "-".equals(slaveOfString) ? null : slaveOfString;
long pingSentTs = getLongFromIterator(iterator, 0);
long pongReceivedTs = getLongFromIterator(iterator, 0);
long configEpoch = getLongFromIterator(iterator, 0);
String connectedFlags = iterator.next(); // "connected" : "disconnected"
if (CONNECTED.equals(connectedFlags)) {
connected = true;
}
List<String> slotStrings = LettuceLists.newList(iterator); // slot, from-to [slot->-nodeID] [slot-<-nodeID]
List<Integer> slots = readSlots(slotStrings);
RedisClusterNode partition = new RedisClusterNode(uri, nodeId, connected, slaveOf, pingSentTs, pongReceivedTs,
configEpoch, slots, nodeFlags);
return partition;
}
private static Set<RedisClusterNode.NodeFlag> readFlags(List<String> flagStrings) {
Set<RedisClusterNode.NodeFlag> flags = new HashSet<>();
for (String flagString : flagStrings) {
if (FLAG_MAPPING.containsKey(flagString)) {
flags.add(FLAG_MAPPING.get(flagString));
}
}
return Collections.unmodifiableSet(flags);
}
private static List<Integer> readSlots(List<String> slotStrings) {
List<Integer> slots = new ArrayList<>();
for (String slotString : slotStrings) {
if (slotString.startsWith(TOKEN_SLOT_IN_TRANSITION)) {
// not interesting
continue;
}
if (slotString.contains("-")) {
// slot range
Iterator<String> it = DASH_PATTERN.splitAsStream(slotString).iterator();
int from = Integer.parseInt(it.next());
int to = Integer.parseInt(it.next());
for (int slot = from; slot <= to; slot++) {
slots.add(slot);
}
continue;
}
slots.add(Integer.parseInt(slotString));
}
return Collections.unmodifiableList(slots);
}
private static long getLongFromIterator(Iterator<?> iterator, long defaultValue) {
if (iterator.hasNext()) {
Object object = iterator.next();
if (object instanceof String) {
return Long.parseLong((String) object);
}
}
return defaultValue;
}
}