package org.infinispan.stream.impl.tx; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; import org.infinispan.context.impl.LocalTxInvocationContext; import org.infinispan.distribution.ch.ConsistentHash; import org.infinispan.remoting.transport.Address; import org.infinispan.stream.impl.ClusterStreamManager; import org.infinispan.stream.impl.KeyTrackingTerminalOperation; import org.infinispan.stream.impl.TerminalOperation; import org.infinispan.util.AbstractDelegatingMap; /** * This is a delegating cluster stream manager that sends all calls to the underlying cluster stream manager. However * in the case of performing an operation it adds all entries that are in the provided tx context to the map of * keys to exclude so those values are not processed in the remote nodes. * @param <K> the key type */ public class TxClusterStreamManager<K> implements ClusterStreamManager<K> { private final ClusterStreamManager<K> manager; private final LocalTxInvocationContext ctx; private final ConsistentHash hash; public TxClusterStreamManager(ClusterStreamManager<K> manager, LocalTxInvocationContext ctx, ConsistentHash hash) { this.manager = manager; this.ctx = ctx; this.hash = hash; } @Override public <R> Object remoteStreamOperation(boolean parallelDistribution, boolean parallelStream, ConsistentHash ch, Set<Integer> segments, Set<K> keysToInclude, Map<Integer, Set<K>> keysToExclude, boolean includeLoader, TerminalOperation<R> operation, ResultsCallback<R> callback, Predicate<? super R> earlyTerminatePredicate) { TxExcludedKeys<K> txExcludedKeys = new TxExcludedKeys<>(keysToExclude, ctx, hash); return manager.remoteStreamOperation(parallelDistribution, parallelStream, ch, segments, keysToInclude, txExcludedKeys, includeLoader, operation, callback, earlyTerminatePredicate); } @Override public <R> Object remoteStreamOperationRehashAware(boolean parallelDistribution, boolean parallelStream, ConsistentHash ch, Set<Integer> segments, Set<K> keysToInclude, Map<Integer, Set<K>> keysToExclude, boolean includeLoader, TerminalOperation<R> operation, ResultsCallback<R> callback, Predicate<? super R> earlyTerminatePredicate) { TxExcludedKeys<K> txExcludedKeys = new TxExcludedKeys<>(keysToExclude, ctx, hash); return manager.remoteStreamOperationRehashAware(parallelDistribution, parallelStream, ch, segments, keysToInclude, txExcludedKeys, includeLoader, operation, callback, earlyTerminatePredicate); } @Override public <R> Object remoteStreamOperation(boolean parallelDistribution, boolean parallelStream, ConsistentHash ch, Set<Integer> segments, Set<K> keysToInclude, Map<Integer, Set<K>> keysToExclude, boolean includeLoader, KeyTrackingTerminalOperation<K, R, ?> operation, ResultsCallback<Collection<R>> callback) { TxExcludedKeys<K> txExcludedKeys = new TxExcludedKeys<>(keysToExclude, ctx, hash); return manager.remoteStreamOperation(parallelDistribution, parallelStream, ch, segments, keysToInclude, txExcludedKeys, includeLoader, operation, callback); } @Override public <R2> Object remoteStreamOperationRehashAware(boolean parallelDistribution, boolean parallelStream, ConsistentHash ch, Set<Integer> segments, Set<K> keysToInclude, Map<Integer, Set<K>> keysToExclude, boolean includeLoader, KeyTrackingTerminalOperation<K, ?, R2> operation, ResultsCallback<Map<K, R2>> callback) { TxExcludedKeys<K> txExcludedKeys = new TxExcludedKeys<>(keysToExclude, ctx, hash); return manager.remoteStreamOperationRehashAware(parallelDistribution, parallelStream, ch, segments, keysToInclude, txExcludedKeys, includeLoader, operation, callback); } @Override public boolean isComplete(Object id) { return manager.isComplete(id); } @Override public boolean awaitCompletion(Object id, long time, TimeUnit unit) throws InterruptedException { return manager.awaitCompletion(id, time, unit); } @Override public void forgetOperation(Object id) { manager.forgetOperation(id); } @Override public <R1> boolean receiveResponse(Object id, Address origin, boolean complete, Set<Integer> segments, R1 response) { return manager.receiveResponse(id, origin, complete, segments, response); } private static class TxExcludedKeys<K> extends AbstractDelegatingMap<Integer, Set<K>> { private final Map<Integer, Set<K>> map; private final Map<Integer, Set<K>> ctxMap; private TxExcludedKeys(Map<Integer, Set<K>> map, LocalTxInvocationContext ctx, ConsistentHash hash) { this.map = map; this.ctxMap = contextToMap(ctx, hash); } Map<Integer, Set<K>> contextToMap(LocalTxInvocationContext ctx, ConsistentHash hash) { Map<Integer, Set<K>> contextMap = new HashMap<>(); ctx.getLookedUpEntries().forEach((k, v) -> { Integer segment = hash.getSegment(k); Set<K> innerSet = contextMap.get(segment); if (innerSet == null) { innerSet = new HashSet<K>(); contextMap.put(segment, innerSet); } innerSet.add((K) k); }); return contextMap; } @Override protected Map<Integer, Set<K>> delegate() { return map; } @Override public Set<K> get(Object key) { if (!(key instanceof Integer)) { return null; } Set<K> ctxSet = ctxMap.get(key); Set<K> excludedSet = super.get(key); if (ctxSet != null) { if (excludedSet != null) { ctxSet.addAll(excludedSet); } return ctxSet; } return excludedSet; } @Override public boolean isEmpty() { return ctxMap.isEmpty() && super.isEmpty(); } } }