package org.infinispan.interceptors.distribution;
import static org.infinispan.factories.KnownComponentNames.ASYNC_OPERATIONS_EXECUTOR;
import java.util.Iterator;
import java.util.Map;
import java.util.Spliterator;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import org.infinispan.AdvancedCache;
import org.infinispan.Cache;
import org.infinispan.CacheSet;
import org.infinispan.CacheStream;
import org.infinispan.cache.impl.Caches;
import org.infinispan.commands.FlagAffectedCommand;
import org.infinispan.commands.read.AbstractCloseableIteratorCollection;
import org.infinispan.commands.read.EntrySetCommand;
import org.infinispan.commands.read.KeySetCommand;
import org.infinispan.commons.util.CloseableIterator;
import org.infinispan.commons.util.CloseableIteratorMapper;
import org.infinispan.commons.util.CloseableSpliterator;
import org.infinispan.commons.util.Closeables;
import org.infinispan.container.entries.CacheEntry;
import org.infinispan.container.entries.ForwardingCacheEntry;
import org.infinispan.context.Flag;
import org.infinispan.context.InvocationContext;
import org.infinispan.context.impl.FlagBitSets;
import org.infinispan.context.impl.LocalTxInvocationContext;
import org.infinispan.distribution.DistributionManager;
import org.infinispan.factories.ComponentRegistry;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.interceptors.DDAsyncInterceptor;
import org.infinispan.stream.StreamMarshalling;
import org.infinispan.stream.impl.ClusterStreamManager;
import org.infinispan.stream.impl.DistributedCacheStream;
import org.infinispan.stream.impl.RemovableCloseableIterator;
import org.infinispan.stream.impl.RemovableIterator;
import org.infinispan.stream.impl.intops.IntermediateOperation;
import org.infinispan.stream.impl.intops.object.MapOperation;
import org.infinispan.stream.impl.tx.TxClusterStreamManager;
import org.infinispan.stream.impl.tx.TxDistributedCacheStream;
import org.infinispan.util.function.RemovableFunction;
/**
* Interceptor that handles bulk entrySet and keySet commands when using in a distributed/replicated environment.
* This interceptor produces backing collections for either method and a distributed stream for either which leverages
* distributed processing through the cluster.
* @param <K> The key type of entries
* @param <V> The value type of entries
*/
public class DistributionBulkInterceptor<K, V> extends DDAsyncInterceptor {
private Cache<K, V> cache;
@Inject
public void inject(Cache<K, V> cache) {
this.cache = cache;
}
@Override
public Object visitEntrySetCommand(InvocationContext ctx, EntrySetCommand command) throws Throwable {
return invokeNextThenApply(ctx, command, (rCtx, rCommand, rv) -> {
EntrySetCommand entrySetCommand = (EntrySetCommand) rCommand;
if (entrySetCommand.hasAnyFlag(FlagBitSets.CACHE_MODE_LOCAL))
return rv;
CacheSet<CacheEntry<K, V>> entrySet = (CacheSet<CacheEntry<K, V>>) rv;
if (rCtx.isInTxScope()) {
entrySet = new TxBackingEntrySet<>(Caches.getCacheWithFlags(cache, entrySetCommand), entrySet, entrySetCommand,
(LocalTxInvocationContext) rCtx);
} else {
entrySet = new BackingEntrySet<>(Caches.getCacheWithFlags(cache, entrySetCommand), entrySet, entrySetCommand);
}
return entrySet;
});
}
protected static class BackingEntrySet<K, V> extends AbstractCloseableIteratorCollection<CacheEntry<K, V>, K, V>
implements CacheSet<CacheEntry<K, V>> {
protected final CacheSet<CacheEntry<K, V>> entrySet;
protected final FlagAffectedCommand command;
private BackingEntrySet(Cache cache, CacheSet<CacheEntry<K, V>> entrySet, FlagAffectedCommand command) {
super(cache);
this.entrySet = entrySet;
this.command = command;
}
@Override
public CloseableIterator<CacheEntry<K, V>> iterator() {
return new CloseableIteratorMapper<>(new RemovableCloseableIterator<>(Closeables.iterator(stream()), cache,
CacheEntry::getKey), e -> new EntryWrapper<>(cache, e));
}
@Override
public CloseableSpliterator<CacheEntry<K, V>> spliterator() {
return Closeables.spliterator(stream());
}
@Override
public boolean contains(Object o) {
Map.Entry entry = toEntry(o);
if (entry != null) {
V value = cache.get(entry.getKey());
return value != null && value.equals(entry.getValue());
}
return false;
}
@Override
public boolean remove(Object o) {
Map.Entry entry = toEntry(o);
if (entry != null) {
return cache.remove(entry.getKey(), entry.getValue());
}
return false;
}
private Map.Entry<K, V> toEntry(Object obj) {
if (obj instanceof Map.Entry) {
return (Map.Entry) obj;
} else {
return null;
}
}
@Override
public CacheStream<CacheEntry<K, V>> stream() {
AdvancedCache<K, V> advancedCache = cache.getAdvancedCache();
ComponentRegistry registry = advancedCache.getComponentRegistry();
CacheStream<CacheEntry<K, V>> cacheStream = new DistributedCacheStream<CacheEntry<K, V>>(
cache.getCacheManager().getAddress(), false, advancedCache.getDistributionManager(),
entrySet::stream, registry.getComponent(ClusterStreamManager.class),
!command.hasAnyFlag(FlagBitSets.SKIP_CACHE_LOAD),
cache.getCacheConfiguration().clustering().stateTransfer().chunkSize(),
registry.getComponent(Executor.class, ASYNC_OPERATIONS_EXECUTOR), registry) {
@Override
public Iterator<CacheEntry<K, V>> iterator() {
int size = intermediateOperations.size();
if (size == 0) {
// If no intermediate operations we can support remove
return new RemovableIterator<>(super.iterator(), cache, CacheEntry::getKey);
}
else if (size == 1) {
IntermediateOperation intOp = intermediateOperations.peek();
if (intOp instanceof MapOperation) {
MapOperation map = (MapOperation) intOp;
if (map.getFunction() instanceof RemovableFunction) {
// If function was removable means we can just use remove as is
return new RemovableIterator<>(super.iterator(), cache, CacheEntry::getKey);
}
}
}
return super.iterator();
}
};
return applyTimeOut(cacheStream, cache);
}
@Override
public CacheStream<CacheEntry<K, V>> parallelStream() {
AdvancedCache<K, V> advancedCache = cache.getAdvancedCache();
ComponentRegistry registry = advancedCache.getComponentRegistry();
CacheStream<CacheEntry<K, V>> cacheStream = new DistributedCacheStream<>(cache.getCacheManager().getAddress(),
true, advancedCache.getDistributionManager(), entrySet::parallelStream,
registry.getComponent(ClusterStreamManager.class), !command.hasAnyFlag(FlagBitSets.SKIP_CACHE_LOAD),
cache.getCacheConfiguration().clustering().stateTransfer().chunkSize(),
registry.getComponent(Executor.class, ASYNC_OPERATIONS_EXECUTOR), registry);
return applyTimeOut(cacheStream, cache);
}
}
protected static class TxBackingEntrySet<K, V> extends BackingEntrySet<K, V> {
private final LocalTxInvocationContext ctx;
private TxBackingEntrySet(Cache cache, CacheSet<CacheEntry<K, V>> entrySet, FlagAffectedCommand command,
LocalTxInvocationContext ctx) {
super(cache, entrySet, command);
this.ctx = ctx;
}
@Override
public CacheStream<CacheEntry<K, V>> stream() {
AdvancedCache<K, V> advancedCache = cache.getAdvancedCache();
DistributionManager dm = advancedCache.getDistributionManager();
ComponentRegistry registry = advancedCache.getComponentRegistry();
ClusterStreamManager<K> realManager = registry.getComponent(ClusterStreamManager.class);
TxClusterStreamManager<K> txManager = new TxClusterStreamManager<>(realManager, ctx, dm.getWriteConsistentHash());
CacheStream<CacheEntry<K, V>> cacheStream = new TxDistributedCacheStream<>(cache.getCacheManager().getAddress(),
false, dm, entrySet::stream, txManager, !command.hasAnyFlag(FlagBitSets.SKIP_CACHE_LOAD),
cache.getCacheConfiguration().clustering().stateTransfer().chunkSize(),
registry.getComponent(Executor.class, ASYNC_OPERATIONS_EXECUTOR), registry, ctx);
return applyTimeOut(cacheStream, cache);
}
@Override
public CacheStream<CacheEntry<K, V>> parallelStream() {
AdvancedCache<K, V> advancedCache = cache.getAdvancedCache();
DistributionManager dm = advancedCache.getDistributionManager();
ComponentRegistry registry = advancedCache.getComponentRegistry();
ClusterStreamManager<K> realManager = registry.getComponent(ClusterStreamManager.class);
TxClusterStreamManager<K> txManager = new TxClusterStreamManager<>(realManager, ctx, dm.getWriteConsistentHash());
CacheStream<CacheEntry<K, V>> cacheStream = new TxDistributedCacheStream<>(cache.getCacheManager().getAddress(),
true, dm, entrySet::parallelStream, txManager, !command.hasAnyFlag(FlagBitSets.SKIP_CACHE_LOAD),
cache.getCacheConfiguration().clustering().stateTransfer().chunkSize(),
registry.getComponent(Executor.class, ASYNC_OPERATIONS_EXECUTOR), registry, ctx);
return applyTimeOut(cacheStream, cache);
}
}
private static <C> CacheStream<C> applyTimeOut(CacheStream<C> stream, Cache<?, ?> cache) {
return stream.timeout(cache.getCacheConfiguration().clustering().remoteTimeout(),
TimeUnit.MILLISECONDS);
}
/**
* Wrapper for CacheEntry(s) that can be used to update the cache when it's value is set.
* @param <K> The key type
* @param <V> The value type
*/
private static class EntryWrapper<K, V> extends ForwardingCacheEntry<K, V> {
private final Cache<K, V> cache;
private final CacheEntry<K, V> entry;
public EntryWrapper(Cache<K, V> cache, CacheEntry<K, V> entry) {
this.cache = cache;
this.entry = entry;
}
@Override
protected CacheEntry<K, V> delegate() {
return entry;
}
@Override
public V setValue(V value) {
cache.put(entry.getKey(), value);
return super.setValue(value);
}
}
@Override
public Object visitKeySetCommand(InvocationContext ctx, KeySetCommand command) throws Throwable {
CacheSet<K> keySet;
if (command.hasAnyFlag(FlagBitSets.CACHE_MODE_LOCAL)) {
return invokeNext(ctx, command);
}
if (ctx.isInTxScope()) {
keySet = new TxBackingKeySet<>(Caches.getCacheWithFlags(cache, command),
cache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL).cacheEntrySet(), command,
(LocalTxInvocationContext) ctx);
} else {
keySet = new BackingKeySet<>(Caches.getCacheWithFlags(cache, command), cache.getAdvancedCache().withFlags(
Flag.CACHE_MODE_LOCAL).cacheEntrySet(), command);
}
return keySet;
}
protected static class BackingKeySet<K, V> extends AbstractCloseableIteratorCollection<K, K, V>
implements CacheSet<K> {
protected final CacheSet<CacheEntry<K, V>> entrySet;
protected final FlagAffectedCommand command;
public BackingKeySet(Cache<K, V> cache, CacheSet<CacheEntry<K, V>> entrySet, FlagAffectedCommand command) {
super(cache);
this.entrySet = entrySet;
this.command = command;
}
@Override
public CloseableIterator<K> iterator() {
return new RemovableCloseableIterator(Closeables.iterator(stream()), cache, Function.identity());
}
@Override
public boolean contains(Object o) {
return cache.containsKey(o);
}
@Override
public boolean remove(Object o) {
return cache.remove(o) != null;
}
@Override
public CloseableSpliterator<K> spliterator() {
return Closeables.spliterator(iterator(), Long.MAX_VALUE,
Spliterator.CONCURRENT | Spliterator.DISTINCT | Spliterator.NONNULL);
}
@Override
public CacheStream<K> stream() {
AdvancedCache<K, V> advancedCache = cache.getAdvancedCache();
ComponentRegistry registry = advancedCache.getComponentRegistry();
return new DistributedCacheStream<K>(cache.getCacheManager().getAddress(), false,
advancedCache.getDistributionManager(), entrySet::stream,
registry.getComponent(ClusterStreamManager.class), !command.hasAnyFlag(FlagBitSets.SKIP_CACHE_LOAD),
cache.getCacheConfiguration().clustering().stateTransfer().chunkSize(),
registry.getComponent(Executor.class, ASYNC_OPERATIONS_EXECUTOR), registry,
StreamMarshalling.entryToKeyFunction()) {
@Override
public Iterator<K> iterator() {
int size = intermediateOperations.size();
// The act of mapping to key requires 1 intermediate operation
if (size == 1) {
return new RemovableIterator<>(super.iterator(), cache, Function.identity());
} else if (size == 2) {
Iterator<IntermediateOperation> iter = intermediateOperations.iterator();
iter.next();
IntermediateOperation intOp = iter.next();
if (intOp instanceof MapOperation) {
MapOperation map = (MapOperation) intOp;
if (map.getFunction() instanceof RemovableFunction) {
// If function was removable means we can just use remove as is
return new RemovableIterator<>(super.iterator(), cache, Function.identity());
}
}
}
return super.iterator();
}
};
}
@Override
public CacheStream<K> parallelStream() {
AdvancedCache<K, V> advancedCache = cache.getAdvancedCache();
ComponentRegistry registry = advancedCache.getComponentRegistry();
return new DistributedCacheStream<>(cache.getCacheManager().getAddress(), true,
advancedCache.getDistributionManager(), entrySet::parallelStream,
registry.getComponent(ClusterStreamManager.class), !command.hasAnyFlag(FlagBitSets.SKIP_CACHE_LOAD),
cache.getCacheConfiguration().clustering().stateTransfer().chunkSize(),
registry.getComponent(Executor.class, ASYNC_OPERATIONS_EXECUTOR), registry,
StreamMarshalling.entryToKeyFunction());
}
}
private static class TxBackingKeySet<K, V> extends BackingKeySet<K, V> {
private final LocalTxInvocationContext ctx;
public TxBackingKeySet(Cache<K, V> cache, CacheSet<CacheEntry<K, V>> entrySet, FlagAffectedCommand command,
LocalTxInvocationContext ctx) {
super(cache, entrySet, command);
this.ctx = ctx;
}
@Override
public CacheStream<K> stream() {
AdvancedCache<K, V> advancedCache = cache.getAdvancedCache();
DistributionManager dm = advancedCache.getDistributionManager();
ComponentRegistry registry = advancedCache.getComponentRegistry();
ClusterStreamManager<K> realManager = registry.getComponent(ClusterStreamManager.class);
TxClusterStreamManager<K> txManager = new TxClusterStreamManager<>(realManager, ctx, dm.getWriteConsistentHash());
return new TxDistributedCacheStream<>(cache.getCacheManager().getAddress(), false,
dm, entrySet::stream, txManager, !command.hasAnyFlag(FlagBitSets.SKIP_CACHE_LOAD),
cache.getCacheConfiguration().clustering().stateTransfer().chunkSize(),
registry.getComponent(Executor.class, ASYNC_OPERATIONS_EXECUTOR), registry,
StreamMarshalling.entryToKeyFunction(), ctx);
}
@Override
public CacheStream<K> parallelStream() {
AdvancedCache<K, V> advancedCache = cache.getAdvancedCache();
DistributionManager dm = advancedCache.getDistributionManager();
ComponentRegistry registry = advancedCache.getComponentRegistry();
ClusterStreamManager<K> realManager = registry.getComponent(ClusterStreamManager.class);
TxClusterStreamManager<K> txManager = new TxClusterStreamManager<>(realManager, ctx, dm.getWriteConsistentHash());
return new TxDistributedCacheStream<>(cache.getCacheManager().getAddress(), true,
dm, entrySet::parallelStream, txManager, !command.hasAnyFlag(FlagBitSets.SKIP_CACHE_LOAD),
cache.getCacheConfiguration().clustering().stateTransfer().chunkSize(),
registry.getComponent(Executor.class, ASYNC_OPERATIONS_EXECUTOR), registry,
StreamMarshalling.entryToKeyFunction(), ctx);
}
}
}