package com.lambdaworks.redis.cluster;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
import rx.Observable;
import rx.functions.Func1;
import com.lambdaworks.redis.*;
import com.lambdaworks.redis.cluster.api.StatefulRedisClusterConnection;
import com.lambdaworks.redis.cluster.models.partitions.RedisClusterNode;
import com.lambdaworks.redis.models.role.RedisNodeDescription;
/**
* Methods to support a Cluster-wide SCAN operation over multiple hosts.
*
* @author Mark Paluch
*/
class ClusterScanSupport {
/**
* Map a {@link RedisFuture} of {@link KeyScanCursor} to a {@link RedisFuture} of {@link ClusterKeyScanCursor}.
*/
static final ScanCursorMapper<RedisFuture<KeyScanCursor<?>>> futureKeyScanCursorMapper = new ScanCursorMapper<RedisFuture<KeyScanCursor<?>>>() {
@Override
public RedisFuture<KeyScanCursor<?>> map(List<String> nodeIds, String currentNodeId,
RedisFuture<KeyScanCursor<?>> cursor) {
return new PipelinedRedisFuture<>(cursor, new Function<KeyScanCursor<?>, KeyScanCursor<?>>() {
@Override
public KeyScanCursor<?> apply(KeyScanCursor<?> result) {
return new ClusterKeyScanCursor<>(nodeIds, currentNodeId, result);
}
});
}
};
/**
* Map a {@link RedisFuture} of {@link StreamScanCursor} to a {@link RedisFuture} of {@link ClusterStreamScanCursor}.
*/
static final ScanCursorMapper<RedisFuture<StreamScanCursor>> futureStreamScanCursorMapper = new ScanCursorMapper<RedisFuture<StreamScanCursor>>() {
@Override
public RedisFuture<StreamScanCursor> map(List<String> nodeIds, String currentNodeId,
RedisFuture<StreamScanCursor> cursor) {
return new PipelinedRedisFuture<>(cursor, new Function<StreamScanCursor, StreamScanCursor>() {
@Override
public StreamScanCursor apply(StreamScanCursor result) {
return new ClusterStreamScanCursor(nodeIds, currentNodeId, result);
}
});
}
};
/**
* Map a {@link Observable} of {@link KeyScanCursor} to a {@link Observable} of {@link ClusterKeyScanCursor}.
*/
final static ScanCursorMapper<Observable<KeyScanCursor<?>>> reactiveKeyScanCursorMapper = new ScanCursorMapper<Observable<KeyScanCursor<?>>>() {
@Override
public Observable<KeyScanCursor<?>> map(List<String> nodeIds, String currentNodeId, Observable<KeyScanCursor<?>> cursor) {
return cursor.map(new Func1<KeyScanCursor<?>, KeyScanCursor<?>>() {
@Override
public KeyScanCursor<?> call(KeyScanCursor<?> keyScanCursor) {
return new ClusterKeyScanCursor<>(nodeIds, currentNodeId, keyScanCursor);
}
});
}
};
/**
* Map a {@link Observable} of {@link StreamScanCursor} to a {@link Observable} of {@link ClusterStreamScanCursor}.
*/
final static ScanCursorMapper<Observable<StreamScanCursor>> reactiveStreamScanCursorMapper = new ScanCursorMapper<Observable<StreamScanCursor>>() {
@Override
public Observable<StreamScanCursor> map(List<String> nodeIds, String currentNodeId, Observable<StreamScanCursor> cursor) {
return cursor.map(new Func1<StreamScanCursor, StreamScanCursor>() {
@Override
public StreamScanCursor call(StreamScanCursor streamScanCursor) {
return new ClusterStreamScanCursor(nodeIds, currentNodeId, streamScanCursor);
}
});
}
};
/**
* Retrieve the cursor to continue the scan.
*
* @param scanCursor can be {@literal null}.
* @return
*/
static ScanCursor getContinuationCursor(ScanCursor scanCursor) {
if (ScanCursor.INITIAL.equals(scanCursor)) {
return scanCursor;
}
assertClusterScanCursor(scanCursor);
ClusterScanCursor clusterScanCursor = (ClusterScanCursor) scanCursor;
if (clusterScanCursor.isScanOnCurrentNodeFinished()) {
return ScanCursor.INITIAL;
}
return scanCursor;
}
static <K, V> List<String> getNodeIds(StatefulRedisClusterConnection<K, V> connection, ScanCursor cursor) {
if (ScanCursor.INITIAL.equals(cursor)) {
List<String> nodeIds = getNodeIds(connection);
assertHasNodes(nodeIds);
return nodeIds;
}
assertClusterScanCursor(cursor);
ClusterScanCursor clusterScanCursor = (ClusterScanCursor) cursor;
return clusterScanCursor.getNodeIds();
}
static String getCurrentNodeId(ScanCursor cursor, List<String> nodeIds) {
if (ScanCursor.INITIAL.equals(cursor)) {
assertHasNodes(nodeIds);
return nodeIds.get(0);
}
assertClusterScanCursor(cursor);
return getNodeIdForNextScanIteration(nodeIds, (ClusterScanCursor) cursor);
}
/**
* Retrieve a list of node Ids to use for the SCAN operation.
*
* @param connection
* @return
*/
private static List<String> getNodeIds(StatefulRedisClusterConnection<?, ?> connection) {
List<String> nodeIds = new ArrayList<>();
PartitionAccessor partitionAccessor = new PartitionAccessor(connection.getPartitions());
for (RedisClusterNode redisClusterNode : partitionAccessor.getMasters()) {
if (connection.getReadFrom() != null) {
List<RedisNodeDescription> readCandidates = (List) partitionAccessor.getReadCandidates(redisClusterNode);
List<RedisNodeDescription> selection = connection.getReadFrom().select(new ReadFrom.Nodes() {
@Override
public List<RedisNodeDescription> getNodes() {
return readCandidates;
}
@Override
public Iterator<RedisNodeDescription> iterator() {
return readCandidates.iterator();
}
});
if (!selection.isEmpty()) {
RedisClusterNode selectedNode = (RedisClusterNode) selection.get(0);
nodeIds.add(selectedNode.getNodeId());
continue;
}
}
nodeIds.add(redisClusterNode.getNodeId());
}
return nodeIds;
}
private static String getNodeIdForNextScanIteration(List<String> nodeIds, ClusterScanCursor clusterKeyScanCursor) {
if (clusterKeyScanCursor.isScanOnCurrentNodeFinished()) {
if (clusterKeyScanCursor.isFinished()) {
throw new IllegalStateException("Cluster scan is finished");
}
int nodeIndex = nodeIds.indexOf(clusterKeyScanCursor.getCurrentNodeId());
return nodeIds.get(nodeIndex + 1);
}
return clusterKeyScanCursor.getCurrentNodeId();
}
private static void assertClusterScanCursor(ScanCursor cursor) {
if (!(cursor instanceof ClusterScanCursor)) {
throw new IllegalArgumentException(
"A scan in Redis Cluster mode requires to reuse the resulting cursor from the previous scan invocation");
}
}
private static void assertHasNodes(List<String> nodeIds) {
if (nodeIds.isEmpty()) {
throw new RedisException("No available nodes for a scan");
}
}
static <K> ScanCursorMapper<RedisFuture<KeyScanCursor<K>>> asyncClusterKeyScanCursorMapper() {
return (ScanCursorMapper) futureKeyScanCursorMapper;
}
static ScanCursorMapper<RedisFuture<StreamScanCursor>> asyncClusterStreamScanCursorMapper() {
return futureStreamScanCursorMapper;
}
static <K> ScanCursorMapper<Observable<KeyScanCursor<K>>> reactiveClusterKeyScanCursorMapper() {
return (ScanCursorMapper) reactiveKeyScanCursorMapper;
}
static ScanCursorMapper<Observable<StreamScanCursor>> reactiveClusterStreamScanCursorMapper() {
return reactiveStreamScanCursorMapper;
}
/**
* Mapper between the node operation cursor and the cluster scan cursor.
*
* @param <T>
*/
interface ScanCursorMapper<T> {
T map(List<String> nodeIds, String currentNodeId, T cursor);
}
/**
* Marker for a cluster scan cursor.
*/
interface ClusterScanCursor {
List<String> getNodeIds();
String getCurrentNodeId();
boolean isScanOnCurrentNodeFinished();
boolean isFinished();
}
/**
* State object for a cluster-wide SCAN using Key results.
*
* @param <K>
*/
private static class ClusterKeyScanCursor<K> extends KeyScanCursor<K> implements ClusterScanCursor {
final List<String> nodeIds;
final String currentNodeId;
final KeyScanCursor<K> cursor;
public ClusterKeyScanCursor(List<String> nodeIds, String currentNodeId, KeyScanCursor<K> cursor) {
super();
this.nodeIds = nodeIds;
this.currentNodeId = currentNodeId;
this.cursor = cursor;
setCursor(cursor.getCursor());
getKeys().addAll(cursor.getKeys());
if (cursor.isFinished()) {
int nodeIndex = nodeIds.indexOf(currentNodeId);
if (nodeIndex == -1 || nodeIndex == nodeIds.size() - 1) {
setFinished(true);
}
}
}
@Override
public List<String> getNodeIds() {
return nodeIds;
}
@Override
public String getCurrentNodeId() {
return currentNodeId;
}
public boolean isScanOnCurrentNodeFinished() {
return cursor.isFinished();
}
}
/**
* State object for a cluster-wide SCAN using streaming.
*/
private static class ClusterStreamScanCursor extends StreamScanCursor implements ClusterScanCursor {
final List<String> nodeIds;
final String currentNodeId;
final StreamScanCursor cursor;
public ClusterStreamScanCursor(List<String> nodeIds, String currentNodeId, StreamScanCursor cursor) {
super();
this.nodeIds = nodeIds;
this.currentNodeId = currentNodeId;
this.cursor = cursor;
setCursor(cursor.getCursor());
setCount(cursor.getCount());
if (cursor.isFinished()) {
int nodeIndex = nodeIds.indexOf(currentNodeId);
if (nodeIndex == -1 || nodeIndex == nodeIds.size() - 1) {
setFinished(true);
}
}
}
@Override
public List<String> getNodeIds() {
return nodeIds;
}
@Override
public String getCurrentNodeId() {
return currentNodeId;
}
public boolean isScanOnCurrentNodeFinished() {
return cursor.isFinished();
}
}
}