package org.infinispan.interceptors.distribution; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; import org.infinispan.commands.VisitableCommand; import org.infinispan.commands.functional.ReadWriteKeyCommand; import org.infinispan.commands.functional.ReadWriteKeyValueCommand; import org.infinispan.commands.functional.ReadWriteManyCommand; import org.infinispan.commands.functional.ReadWriteManyEntriesCommand; import org.infinispan.commands.functional.WriteOnlyKeyCommand; import org.infinispan.commands.functional.WriteOnlyKeyValueCommand; import org.infinispan.commands.functional.WriteOnlyManyCommand; import org.infinispan.commands.functional.WriteOnlyManyEntriesCommand; import org.infinispan.commands.read.GetCacheEntryCommand; import org.infinispan.commands.write.PutKeyValueCommand; import org.infinispan.commands.write.PutMapCommand; import org.infinispan.commands.write.RemoveCommand; import org.infinispan.commands.write.ReplaceCommand; import org.infinispan.commands.write.WriteCommand; import org.infinispan.container.entries.CacheEntry; import org.infinispan.context.InvocationContext; import org.infinispan.context.impl.FlagBitSets; import org.infinispan.distribution.LocalizedCacheTopology; import org.infinispan.distribution.ch.ConsistentHash; import org.infinispan.distribution.util.ReadOnlySegmentAwareCollection; import org.infinispan.distribution.util.ReadOnlySegmentAwareMap; import org.infinispan.interceptors.InvocationFinallyAction; import org.infinispan.interceptors.InvocationSuccessFunction; import org.infinispan.remoting.responses.Response; import org.infinispan.remoting.responses.SuccessfulResponse; import org.infinispan.remoting.transport.Address; import org.infinispan.util.concurrent.CompletableFutures; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; /** * Non-transactional interceptor used by distributed caches that support concurrent writes. * It is implemented based on lock forwarding. E.g. * - 'k' is written on node A, owners(k)={B,C} * - A forwards the given command to B * - B acquires a lock on 'k' then it forwards it to the remaining owners: C * - C applies the change and returns to B (no lock acquisition is needed) * - B applies the result as well, releases the lock and returns the result of the operation to A. * <p> * Note that even though this introduces an additional RPC (the forwarding), it behaves very well in * conjunction with * consistent-hash aware hotrod clients which connect directly to the lock owner. * * @author Mircea Markus * @author Dan Berindei * @since 8.1 */ public class NonTxDistributionInterceptor extends BaseDistributionInterceptor { private static Log log = LogFactory.getLog(NonTxDistributionInterceptor.class); private static final boolean trace = log.isTraceEnabled(); private final PutMapHelper putMapHelper = new PutMapHelper(); private final ReadWriteManyHelper readWriteManyHelper = new ReadWriteManyHelper(); private final ReadWriteManyEntriesHelper readWriteManyEntriesHelper = new ReadWriteManyEntriesHelper(); private final WriteOnlyManyEntriesHelper writeOnlyManyEntriesHelper = new WriteOnlyManyEntriesHelper(); private final WriteOnlyManyHelper writeOnlyManyHelper = new WriteOnlyManyHelper(); private static BiConsumer<MergingCompletableFuture<Object>, Object> moveListItemsToFuture(int myOffset) { return (f, rv) -> { Collection<?> items; if (rv == null && f.results == null) { return; } else if (rv instanceof Map) { items = ((Map) rv).entrySet(); } else if (rv instanceof Collection) { items = (Collection<?>) rv; } else { f.completeExceptionally(new IllegalArgumentException("Unexpected result value " + rv)); return; } if (trace) { log.tracef("Copying %d items %s to results (%s), starting offset %d", items.size(), items, Arrays.toString(f.results), myOffset); } Iterator<?> it = items.iterator(); for (int i = 0; it.hasNext(); ++i) { f.results[myOffset + i] = it.next(); } }; } private Map<Address, Set<Integer>> primaryOwnersOfSegments(ConsistentHash ch) { Map<Address, Set<Integer>> map = new HashMap<>(ch.getMembers().size()); for (int segment = 0; segment < ch.getNumSegments(); ++segment) { Address owner = ch.locatePrimaryOwnerForSegment(segment); map.computeIfAbsent(owner, o -> new HashSet<>()).add(segment); } return map; } // we're assuming that this function is ran on primary owner of given segments private Map<Address, Set<Integer>> backupOwnersOfSegments(ConsistentHash ch, Set<Integer> segments) { Map<Address, Set<Integer>> map = new HashMap<>(ch.getMembers().size()); if (ch.isReplicated()) { for (Address member : ch.getMembers()) { map.put(member, segments); } map.remove(rpcManager.getAddress()); } else if (ch.getNumOwners() > 1) { for (Integer segment : segments) { List<Address> owners = ch.locateOwnersForSegment(segment); for (int i = 1; i < owners.size(); ++i) { map.computeIfAbsent(owners.get(i), o -> new HashSet<>()).add(segment); } } } return map; } @Override public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable { return handleNonTxWriteCommand(ctx, command); } @Override public Object visitRemoveCommand(InvocationContext ctx, RemoveCommand command) throws Throwable { return handleNonTxWriteCommand(ctx, command); } @Override public Object visitReplaceCommand(InvocationContext ctx, ReplaceCommand command) throws Throwable { return handleNonTxWriteCommand(ctx, command); } @Override public Object visitReadWriteKeyValueCommand(InvocationContext ctx, ReadWriteKeyValueCommand command) throws Throwable { return handleNonTxWriteCommand(ctx, command); } @Override public Object visitReadWriteKeyCommand(InvocationContext ctx, ReadWriteKeyCommand command) throws Throwable { return handleNonTxWriteCommand(ctx, command); } @Override public Object visitPutMapCommand(InvocationContext ctx, PutMapCommand command) throws Throwable { return handleReadWriteManyCommand(ctx, command, putMapHelper); } @Override public Object visitWriteOnlyManyEntriesCommand(InvocationContext ctx, WriteOnlyManyEntriesCommand command) throws Throwable { return handleWriteOnlyManyCommand(ctx, command, writeOnlyManyEntriesHelper); } @Override public Object visitWriteOnlyManyCommand(InvocationContext ctx, WriteOnlyManyCommand command) throws Throwable { return handleWriteOnlyManyCommand(ctx, command, writeOnlyManyHelper); } @Override public Object visitReadWriteManyCommand(InvocationContext ctx, ReadWriteManyCommand command) throws Throwable { return handleReadWriteManyCommand(ctx, command, readWriteManyHelper); } @Override public Object visitReadWriteManyEntriesCommand(InvocationContext ctx, ReadWriteManyEntriesCommand command) throws Throwable { return handleReadWriteManyCommand(ctx, command, readWriteManyEntriesHelper); } private <C extends WriteCommand, Container, Item> Object handleWriteOnlyManyCommand( InvocationContext ctx, C command, WriteManyCommandHelper<C, Container, Item> helper) throws Exception { // TODO: due to possible repeating of the operation (after OutdatedTopologyException is thrown) // it is possible that the function will be applied multiple times on some of the nodes. // There is no general solution for this ATM; proper solution will probably record CommandInvocationId // in the entry, and implement some housekeeping LocalizedCacheTopology cacheTopology = checkTopologyId(command); ConsistentHash ch = cacheTopology.getWriteConsistentHash(); if (ctx.isOriginLocal()) { Map<Address, Set<Integer>> segmentMap = primaryOwnersOfSegments(ch); CountDownCompletableFuture allFuture = new CountDownCompletableFuture(ctx, segmentMap.size()); // Go through all members, for this node invokeNext (if this node is an owner of some keys), // for the others (that own some keys) issue a remote call. // Everything is finished when allFuture is completed for (Entry<Address, Set<Integer>> pair : segmentMap.entrySet()) { Address member = pair.getKey(); Set<Integer> segments = pair.getValue(); handleSegmentsForWriteOnlyManyCommand(ctx, command, helper, ch, allFuture, member, segments); } return asyncValue(allFuture); } else { // origin is not local // check that we have all the data we need return handleRemoteWriteOnlyManyCommand(ctx, command, helper); } } private <C extends WriteCommand, Container, Item> void handleSegmentsForWriteOnlyManyCommand( InvocationContext ctx, C command, WriteManyCommandHelper<C, Container, Item> helper, ConsistentHash ch, CountDownCompletableFuture allFuture, Address member, Set<Integer> segments) { if (member.equals(rpcManager.getAddress())) { Container myItems = filterAndWrap(ctx, command, segments, helper); C localCommand = helper.copyForLocal(command, myItems); // Local keys are backed up in the handler, and counters on allFuture are decremented when the backup // calls complete. invokeNextAndFinally(ctx, localCommand, createLocalInvocationHandler(ch, allFuture, segments, helper, (f, rv) -> { })); return; } C copy = helper.copyForPrimary(command, ch, segments); int size = helper.getItems(copy).size(); if (size <= 0) { allFuture.countDown(); return; } rpcManager.invokeRemotelyAsync(Collections.singletonList(member), copy, defaultSyncOptions) .whenComplete((responseMap, throwable) -> { if (throwable != null) { allFuture.completeExceptionally(throwable); } else try { // ignore actual response value getSingleSuccessfulResponseOrFail(responseMap, allFuture); allFuture.countDown(); } catch (Throwable t) { allFuture.completeExceptionally(t); } }); } private <C extends WriteCommand, Item> Object handleRemoteWriteOnlyManyCommand( InvocationContext ctx, C command, WriteManyCommandHelper<C, ?, Item> helper) { for (Item item : helper.getItems(command)) { Object key = helper.item2key(item); if (ctx.lookupEntry(key) == null) { entryFactory.wrapExternalEntry(ctx, key, null, false, true); } } if (helper.shouldRegisterRemoteCallback(command, null)) { return invokeNextThenApply(ctx, command, helper); } else { return invokeNext(ctx, command); } } private <C extends WriteCommand, Container, Item> Container filterAndWrap( InvocationContext ctx, C command, Set<Integer> segments, WriteManyCommandHelper<C, Container, Item> helper) { // Filter command keys/entries into the collection, and wrap null for those that are not in context yet Container myItems = helper.newContainer(); for (Item item : helper.getItems(command)) { Object key = helper.item2key(item); if (segments.contains(keyPartitioner.getSegment(key))) { helper.accumulate(myItems, item); CacheEntry entry = ctx.lookupEntry(key); if (entry == null) { // executed only be write-only commands entryFactory.wrapExternalEntry(ctx, key, null, false, true); } } } return myItems; } private <C extends WriteCommand, Container, Item> Object handleReadWriteManyCommand( InvocationContext ctx, C command, WriteManyCommandHelper<C, Item, Container> helper) throws Exception { // TODO: due to possible repeating of the operation (after OutdatedTopologyException is thrown) // it is possible that the function will be applied multiple times on some of the nodes. // There is no general solution for this ATM; proper solution will probably record CommandInvocationId // in the entry, and implement some housekeeping ConsistentHash ch = checkTopologyId(command).getWriteConsistentHash(); if (ctx.isOriginLocal()) { Map<Address, Set<Integer>> segmentMap = primaryOwnersOfSegments(ch); Object[] results = null; if (!command.hasAnyFlag(FlagBitSets.IGNORE_RETURN_VALUES)) { results = new Object[helper.getItems(command).size()]; } MergingCompletableFuture<Object> allFuture = new MergingCompletableFuture<>(ctx, segmentMap.size(), results, helper::transformResult); MutableInt offset = new MutableInt(); // Go through all members, for this node invokeNext (if this node is an owner of some keys), // for the others (that own some keys) issue a remote call. // Everything is finished when allFuture is completed for (Entry<Address, Set<Integer>> pair : segmentMap.entrySet()) { Address member = pair.getKey(); Set<Integer> segments = pair.getValue(); if (member.equals(rpcManager.getAddress())) { handleLocalSegmentsForReadWriteManyCommand(ctx, command, helper, ch, allFuture, offset, segments); } else { handleRemoteSegmentsForReadWriteManyCommand(command, helper, ch, allFuture, offset, member, segments); } } return asyncValue(allFuture); } else { // origin is not local return handleRemoteReadWriteManyCommand(ctx, command, helper, ch); } } private <C extends WriteCommand, Container, Item> void handleLocalSegmentsForReadWriteManyCommand( InvocationContext ctx, C command, WriteManyCommandHelper<C, Container, Item> helper, ConsistentHash ch, MergingCompletableFuture<Object> allFuture, MutableInt offset, Set<Integer> segments) throws Exception { Container myItems = helper.newContainer(); List<CompletableFuture<?>> retrievals = null; // Filter command keys/entries into the collection, and record remote retrieval for those that are not // in the context yet for (Item item : helper.getItems(command)) { Object key = helper.item2key(item); if (segments.contains(keyPartitioner.getSegment(key))) { helper.accumulate(myItems, item); retrievals = addRemoteGet(ctx, command, retrievals, key); } } int size = helper.containerSize(myItems); if (size == 0) { allFuture.countDown(); return; } final int myOffset = offset.value; offset.value += size; C localCommand = helper.copyForLocal(command, myItems); InvocationFinallyAction handler = createLocalInvocationHandler(ch, allFuture, segments, helper, moveListItemsToFuture(myOffset)); if (retrievals == null) { invokeNextAndFinally(ctx, localCommand, handler); } else { // We must wait until all retrievals finish before proceeding with the local command CompletableFuture[] ra = retrievals.toArray(new CompletableFuture[retrievals.size()]); Object result = asyncInvokeNext(ctx, command, CompletableFuture.allOf(ra)); makeStage(result).andFinally(ctx, command, handler); } // Local keys are backed up in the handler, and counters on allFuture are decremented when the backup // calls complete. } private <C extends WriteCommand, Item> void handleRemoteSegmentsForReadWriteManyCommand( C command, WriteManyCommandHelper<C, ?, Item> helper, ConsistentHash ch, MergingCompletableFuture<Object> allFuture, MutableInt offset, Address member, Set<Integer> segments) { final int myOffset = offset.value; // TODO: here we iterate through all entries - is the ReadOnlySegmentAwareMap really worth it? C copy = helper.copyForPrimary(command, ch, segments); int size = helper.getItems(copy).size(); offset.value += size; if (size <= 0) { allFuture.countDown(); return; } // Send the command to primary owner rpcManager.invokeRemotelyAsync(Collections.singletonList(member), copy, defaultSyncOptions) .whenComplete((responses, throwable) -> { if (throwable != null) { allFuture.completeExceptionally(throwable); } else { Response response = getSingleSuccessfulResponseOrFail(responses, allFuture); if (response == null) return; Object responseValue = ((SuccessfulResponse) response).getResponseValue(); moveListItemsToFuture(myOffset).accept(allFuture, responseValue); allFuture.countDown(); } }); } private <C extends WriteCommand, Item> Object handleRemoteReadWriteManyCommand( InvocationContext ctx, C command, WriteManyCommandHelper<C, ?, Item> helper, ConsistentHash ch) throws Exception { List<CompletableFuture<?>> retrievals = null; // check that we have all the data we need for (Item item : helper.getItems(command)) { retrievals = addRemoteGet(ctx, command, retrievals, helper.item2key(item)); } CompletableFuture<Void> delay; if (retrievals != null) { CompletableFuture[] ra = retrievals.toArray(new CompletableFuture[retrievals.size()]); delay = CompletableFuture.allOf(ra); } else { delay = CompletableFutures.completedNull(); } Object result = asyncInvokeNext(ctx, command, delay); if (helper.shouldRegisterRemoteCallback(command, ch)) { return makeStage(result).thenApply(ctx, command, helper); } else { return result; } } private List<CompletableFuture<?>> addRemoteGet(InvocationContext ctx, WriteCommand command, List<CompletableFuture<?>> retrievals, Object key) throws Exception { CacheEntry cacheEntry = ctx.lookupEntry(key); if (cacheEntry == null) { // this should be a rare situation, so we don't mind being a bit ineffective with the remote gets if (command.hasAnyFlag(FlagBitSets.SKIP_REMOTE_LOOKUP) || command.hasAnyFlag(FlagBitSets.CACHE_MODE_LOCAL)) { entryFactory.wrapExternalEntry(ctx, key, null, false, true); } else { if (retrievals == null) { retrievals = new ArrayList<>(); } GetCacheEntryCommand fakeGetCommand = cf.buildGetCacheEntryCommand(key, command.getFlagsBitSet()); CompletableFuture<?> getFuture = remoteGet(ctx, fakeGetCommand, fakeGetCommand.getKey(), true); retrievals.add(getFuture); } } return retrievals; } private <C extends WriteCommand, F extends CountDownCompletableFuture, Item> InvocationFinallyAction createLocalInvocationHandler( ConsistentHash ch, F allFuture, Set<Integer> segments, WriteManyCommandHelper<C, ?, Item> helper, BiConsumer<F, Object> returnValueConsumer) { return (rCtx, rCommand, rv, throwable) -> { if (throwable != null) { allFuture.completeExceptionally(throwable); } else try { returnValueConsumer.accept(allFuture, rv); Map<Address, Set<Integer>> backupOwners = backupOwnersOfSegments(ch, segments); for (Entry<Address, Set<Integer>> backup : backupOwners.entrySet()) { // rCommand is the original command C backupCopy = helper.copyForBackup((C) rCommand, ch, backup.getValue()); if (helper.getItems(backupCopy).isEmpty()) continue; Set<Address> backupOwner = Collections.singleton(backup.getKey()); if (isSynchronous(backupCopy)) { allFuture.increment(); rpcManager.invokeRemotelyAsync(backupOwner, backupCopy, defaultSyncOptions) .whenComplete((responseMap, remoteThrowable) -> { if (remoteThrowable != null) { allFuture.completeExceptionally(remoteThrowable); } else { allFuture.countDown(); } }); } else { rpcManager.invokeRemotelyAsync(backupOwner, backupCopy, defaultAsyncOptions); } } allFuture.countDown(); } catch (Throwable t) { allFuture.completeExceptionally(t); } }; } @Override public Object visitWriteOnlyKeyValueCommand(InvocationContext ctx, WriteOnlyKeyValueCommand command) throws Throwable { return handleNonTxWriteCommand(ctx, command); } @Override public Object visitWriteOnlyKeyCommand(InvocationContext ctx, WriteOnlyKeyCommand command) throws Throwable { return handleNonTxWriteCommand(ctx, command); } private final static class MutableInt { public int value; } private abstract class WriteManyCommandHelper<C extends WriteCommand, Container, Item> implements InvocationSuccessFunction { public abstract C copyForLocal(C cmd, Container container); public abstract C copyForPrimary(C cmd, ConsistentHash ch, Set<Integer> segments); public abstract C copyForBackup(C cmd, ConsistentHash ch, Set<Integer> segments); public abstract Collection<Item> getItems(C cmd); public abstract Object item2key(Item item); public abstract Container newContainer(); public abstract void accumulate(Container container, Item item); public abstract int containerSize(Container container); public abstract boolean shouldRegisterRemoteCallback(C cmd, ConsistentHash ch); public abstract Object transformResult(Object[] results); @Override public Object apply(InvocationContext rCtx, VisitableCommand rCommand, Object rv) throws Throwable { C original = (C) rCommand; ConsistentHash ch = checkTopologyId(original).getWriteConsistentHash(); // We have already checked that the command topology is actual, so we can assume that we really are primary owner Map<Address, Set<Integer>> backups = backupOwnersOfSegments(ch, ch.getPrimarySegmentsForOwner(rpcManager.getAddress())); if (backups.isEmpty()) { return rv; } boolean isSync = isSynchronous(original); CompletableFuture[] futures = isSync ? new CompletableFuture[backups.size()] : null; int future = 0; for (Entry<Address, Set<Integer>> backup : backups.entrySet()) { C copy = copyForBackup(original, ch, backup.getValue()); if (isSync) { futures[future++] = rpcManager.invokeRemotelyAsync(Collections.singleton(backup.getKey()), copy, defaultSyncOptions); } else { rpcManager.invokeRemotelyAsync(Collections.singleton(backup.getKey()), copy, defaultAsyncOptions); } } return isSync ? asyncValue(CompletableFuture.allOf(futures).thenApply(nil -> rv)) : rv; } } private class PutMapHelper extends WriteManyCommandHelper<PutMapCommand, Map<Object, Object>, Entry<Object, Object>> { @Override public PutMapCommand copyForLocal(PutMapCommand cmd, Map<Object, Object> container) { return new PutMapCommand(cmd).withMap(container); } @Override public PutMapCommand copyForPrimary(PutMapCommand cmd, ConsistentHash ch, Set<Integer> segments) { return new PutMapCommand(cmd).withMap(new ReadOnlySegmentAwareMap<>(cmd.getMap(), ch, segments)); } @Override public PutMapCommand copyForBackup(PutMapCommand cmd, ConsistentHash ch, Set<Integer> segments) { PutMapCommand copy = new PutMapCommand(cmd).withMap(new ReadOnlySegmentAwareMap(cmd.getMap(), ch, segments)); copy.setForwarded(true); return copy; } @Override public Collection<Entry<Object, Object>> getItems(PutMapCommand cmd) { return cmd.getMap().entrySet(); } @Override public Object item2key(Entry<Object, Object> entry) { return entry.getKey(); } @Override public Map<Object, Object> newContainer() { return new HashMap<>(); } @Override public void accumulate(Map<Object, Object> map, Entry<Object, Object> entry) { map.put(entry.getKey(), entry.getValue()); } @Override public int containerSize(Map<Object, Object> map) { return map.size(); } @Override public boolean shouldRegisterRemoteCallback(PutMapCommand cmd, ConsistentHash ch) { return !(cmd.isForwarded() || ch.getNumOwners() <= 1); } @Override public Object transformResult(Object[] results) { if (results == null) return null; Map<Object, Object> result = new HashMap<>(); for (Object r : results) { Map.Entry<Object, Object> entry = (Entry<Object, Object>) r; result.put(entry.getKey(), entry.getValue()); } return result; } } private class ReadWriteManyEntriesHelper extends WriteManyCommandHelper<ReadWriteManyEntriesCommand, Map<Object, Object>, Entry<Object, Object>> { @Override public ReadWriteManyEntriesCommand copyForLocal(ReadWriteManyEntriesCommand cmd, Map<Object, Object> entries) { return new ReadWriteManyEntriesCommand(cmd).withEntries(entries); } @Override public ReadWriteManyEntriesCommand copyForPrimary(ReadWriteManyEntriesCommand cmd, ConsistentHash ch, Set<Integer> segments) { return new ReadWriteManyEntriesCommand(cmd) .withEntries(new ReadOnlySegmentAwareMap<>(cmd.getEntries(), ch, segments)); } @Override public ReadWriteManyEntriesCommand copyForBackup(ReadWriteManyEntriesCommand cmd, ConsistentHash ch, Set<Integer> segments) { ReadWriteManyEntriesCommand copy = new ReadWriteManyEntriesCommand(cmd) .withEntries(new ReadOnlySegmentAwareMap(cmd.getEntries(), ch, segments)); copy.setForwarded(true); return copy; } @Override public Collection<Entry<Object, Object>> getItems(ReadWriteManyEntriesCommand cmd) { return cmd.getEntries().entrySet(); } @Override public Object item2key(Entry<Object, Object> entry) { return entry.getKey(); } @Override public Map<Object, Object> newContainer() { return new HashMap<>(); } @Override public void accumulate(Map<Object, Object> map, Entry<Object, Object> entry) { map.put(entry.getKey(), entry.getValue()); } @Override public int containerSize(Map<Object, Object> map) { return map.size(); } @Override public boolean shouldRegisterRemoteCallback(ReadWriteManyEntriesCommand cmd, ConsistentHash ch) { return !(cmd.isForwarded() || ch.getNumOwners() <= 1); } @Override public Object transformResult(Object[] results) { return results == null ? null : Arrays.asList(results); } } private class ReadWriteManyHelper extends WriteManyCommandHelper<ReadWriteManyCommand, Collection<Object>, Object> { @Override public ReadWriteManyCommand copyForLocal(ReadWriteManyCommand cmd, Collection<Object> keys) { return new ReadWriteManyCommand(cmd).withKeys(keys); } @Override public ReadWriteManyCommand copyForPrimary(ReadWriteManyCommand cmd, ConsistentHash ch, Set<Integer> segments) { return new ReadWriteManyCommand(cmd).withKeys(new ReadOnlySegmentAwareCollection(cmd.getAffectedKeys(), ch, segments)); } @Override public ReadWriteManyCommand copyForBackup(ReadWriteManyCommand cmd, ConsistentHash ch, Set<Integer> segments) { ReadWriteManyCommand copy = new ReadWriteManyCommand(cmd).withKeys( new ReadOnlySegmentAwareCollection(cmd.getAffectedKeys(), ch, segments)); copy.setForwarded(true); return copy; } @Override public Collection<Object> getItems(ReadWriteManyCommand cmd) { return cmd.getAffectedKeys(); } @Override public Object item2key(Object key) { return key; } @Override public Collection<Object> newContainer() { return new ArrayList<>(); } @Override public void accumulate(Collection<Object> list, Object key) { list.add(key); } @Override public int containerSize(Collection<Object> list) { return list.size(); } @Override public boolean shouldRegisterRemoteCallback(ReadWriteManyCommand cmd, ConsistentHash ch) { return !(cmd.isForwarded() || ch.getNumOwners() <= 1); } @Override public Object transformResult(Object[] results) { return results == null ? null : Arrays.asList(results); } } private class WriteOnlyManyEntriesHelper extends WriteManyCommandHelper<WriteOnlyManyEntriesCommand, Map<Object, Object>, Entry<Object, Object>> { @Override public WriteOnlyManyEntriesCommand copyForLocal(WriteOnlyManyEntriesCommand cmd, Map<Object, Object> entries) { return new WriteOnlyManyEntriesCommand(cmd).withEntries(entries); } @Override public WriteOnlyManyEntriesCommand copyForPrimary(WriteOnlyManyEntriesCommand cmd, ConsistentHash ch, Set<Integer> segments) { return new WriteOnlyManyEntriesCommand(cmd) .withEntries(new ReadOnlySegmentAwareMap<>(cmd.getEntries(), ch, segments)); } @Override public WriteOnlyManyEntriesCommand copyForBackup(WriteOnlyManyEntriesCommand cmd, ConsistentHash ch, Set<Integer> segments) { WriteOnlyManyEntriesCommand copy = new WriteOnlyManyEntriesCommand(cmd) .withEntries(new ReadOnlySegmentAwareMap(cmd.getEntries(), ch, segments)); copy.setForwarded(true); return copy; } @Override public Collection<Entry<Object, Object>> getItems(WriteOnlyManyEntriesCommand cmd) { return cmd.getEntries().entrySet(); } @Override public Object item2key(Entry<Object, Object> entry) { return entry.getKey(); } @Override public Map<Object, Object> newContainer() { return new HashMap<>(); } @Override public void accumulate(Map<Object, Object> map, Entry<Object, Object> entry) { map.put(entry.getKey(), entry.getValue()); } @Override public int containerSize(Map<Object, Object> map) { return map.size(); } @Override public boolean shouldRegisterRemoteCallback(WriteOnlyManyEntriesCommand cmd, ConsistentHash ch) { return !cmd.isForwarded(); } @Override public Object transformResult(Object[] results) { return results == null ? null : Arrays.asList(results); } } private class WriteOnlyManyHelper extends WriteManyCommandHelper<WriteOnlyManyCommand, Collection<Object>, Object> { @Override public WriteOnlyManyCommand copyForLocal(WriteOnlyManyCommand cmd, Collection<Object> keys) { return new WriteOnlyManyCommand(cmd).withKeys(keys); } @Override public WriteOnlyManyCommand copyForPrimary(WriteOnlyManyCommand cmd, ConsistentHash ch, Set<Integer> segments) { return new WriteOnlyManyCommand(cmd) .withKeys(new ReadOnlySegmentAwareCollection(cmd.getAffectedKeys(), ch, segments)); } @Override public WriteOnlyManyCommand copyForBackup(WriteOnlyManyCommand cmd, ConsistentHash ch, Set<Integer> segments) { WriteOnlyManyCommand copy = new WriteOnlyManyCommand(cmd) .withKeys(new ReadOnlySegmentAwareCollection(cmd.getAffectedKeys(), ch, segments)); copy.setForwarded(true); return copy; } @Override public Collection<Object> getItems(WriteOnlyManyCommand cmd) { return cmd.getAffectedKeys(); } @Override public Object item2key(Object key) { return key; } @Override public Collection<Object> newContainer() { return new ArrayList<>(); } @Override public void accumulate(Collection<Object> list, Object key) { list.add(key); } @Override public int containerSize(Collection<Object> list) { return list.size(); } @Override public boolean shouldRegisterRemoteCallback(WriteOnlyManyCommand cmd, ConsistentHash ch) { return !cmd.isForwarded(); } @Override public Object transformResult(Object[] results) { return results == null ? null : Arrays.asList(results); } } }