@Override
  public CompletableFuture<Void> visitReadOnlyKeyCommand(
      InvocationContext ctx, ReadOnlyKeyCommand command) throws Throwable {
    try {
      CacheEntry entry = entryFactory.wrapEntryForReading(ctx, command.getKey(), null);
      // Null entry is often considered to mean that entry is not available
      // locally, but if there's no need to get remote, the read-only
      // function needs to be executed, so force a non-null entry in
      // context with null content
      if (entry == null && cdl.localNodeIsOwner(command.getKey())) {
        entryFactory.wrapEntryForReading(ctx, command.getKey(), NullCacheEntry.getInstance());
      }

      return ctx.shortCircuit(ctx.forkInvocationSync(command));
    } finally {
      // needed because entries might be added in L1
      if (!ctx.isInTxScope()) commitContextEntries(ctx, command, null);
      else {
        CacheEntry entry = ctx.lookupEntry(command.getKey());
        if (entry != null) {
          entry.setSkipLookup(true);
        }
      }
    }
  }
 @Override
 public CompletableFuture<Void> visitGetKeysInGroupCommand(
     final InvocationContext ctx, GetKeysInGroupCommand command) throws Throwable {
   final String groupName = command.getGroupName();
   if (!command.isGroupOwner()) {
     return ctx.continueInvocation();
   }
   final KeyFilter<Object> keyFilter =
       new CompositeKeyFilter<>(
           new GroupFilter<>(groupName, groupManager),
           new CollectionKeyFilter<>(ctx.getLookedUpEntries().keySet()));
   dataContainer.executeTask(
       keyFilter,
       (o, internalCacheEntry) -> {
         synchronized (ctx) {
           // the process can be made in multiple threads, so we need to synchronize in the
           // context.
           entryFactory.wrapExternalEntry(
               ctx,
               internalCacheEntry.getKey(),
               internalCacheEntry,
               EntryFactory.Wrap.STORE,
               false);
         }
       });
   return ctx.continueInvocation();
 }
 private void wrapEntryForPutIfNeeded(InvocationContext ctx, AbstractDataWriteCommand command)
     throws Throwable {
   if (shouldWrap(command.getKey(), ctx, command)) {
     boolean skipRead = command.hasFlag(Flag.IGNORE_RETURN_VALUES) && !command.isConditional();
     entryFactory.wrapEntryForWriting(
         ctx, command.getKey(), EntryFactory.Wrap.WRAP_ALL, skipRead, false);
   }
 }
 @Override
 public CompletableFuture<Void> visitReadWriteManyEntriesCommand(
     InvocationContext ctx, ReadWriteManyEntriesCommand command) throws Throwable {
   for (Object key : command.getEntries().keySet()) {
     if (shouldWrap(key, ctx, command)) {
       entryFactory.wrapEntryForWriting(ctx, key, EntryFactory.Wrap.WRAP_ALL, false, false);
     }
   }
   return setSkipRemoteGetsAndInvokeNextForPutMapCommand(ctx, command);
 }
 private void wrapEntryForRemoveIfNeeded(InvocationContext ctx, RemoveCommand command)
     throws InterruptedException {
   if (shouldWrap(command.getKey(), ctx, command)) {
     boolean forceWrap = command.getValueMatcher().nonExistentEntryCanMatch();
     EntryFactory.Wrap wrap =
         forceWrap ? EntryFactory.Wrap.WRAP_ALL : EntryFactory.Wrap.WRAP_NON_NULL;
     boolean skipRead = command.hasFlag(Flag.IGNORE_RETURN_VALUES) && !command.isConditional();
     entryFactory.wrapEntryForWriting(ctx, command.getKey(), wrap, skipRead, false);
   }
 }
 @Override
 public CompletableFuture<Void> visitWriteOnlyManyCommand(
     InvocationContext ctx, WriteOnlyManyCommand command) throws Throwable {
   for (Object key : command.getKeys()) {
     if (shouldWrap(key, ctx, command)) {
       // the put map never reads the keys
       entryFactory.wrapEntryForWriting(ctx, key, EntryFactory.Wrap.WRAP_ALL, true, false);
     }
   }
   return setSkipRemoteGetsAndInvokeNextForPutMapCommand(ctx, command);
 }
 @Override
 public CompletableFuture<Void> visitInvalidateL1Command(
     InvocationContext ctx, InvalidateL1Command command) throws Throwable {
   for (Object key : command.getKeys()) {
     // for the invalidate command, we need to try to fetch the key from the data container
     // otherwise it may be not removed
     entryFactory.wrapEntryForWriting(ctx, key, EntryFactory.Wrap.WRAP_NON_NULL, false, true);
     if (trace) log.tracef("Entry to be removed: %s", toStr(key));
   }
   return setSkipRemoteGetsAndInvokeNextForDataCommand(ctx, command, null);
 }
 private void wrapEntryForReplaceIfNeeded(InvocationContext ctx, ReplaceCommand command)
     throws InterruptedException {
   if (shouldWrap(command.getKey(), ctx, command)) {
     // When retrying, we might still need to perform the command even if the previous value was
     // removed
     EntryFactory.Wrap wrap =
         command.getValueMatcher().nonExistentEntryCanMatch()
             ? EntryFactory.Wrap.WRAP_ALL
             : EntryFactory.Wrap.WRAP_NON_NULL;
     entryFactory.wrapEntryForWriting(ctx, command.getKey(), wrap, false, false);
   }
 }
 private CompletableFuture<Void> visitDataReadCommand(
     InvocationContext ctx, AbstractDataCommand command) throws Throwable {
   try {
     entryFactory.wrapEntryForReading(ctx, command.getKey(), null);
     return ctx.shortCircuit(ctx.forkInvocationSync(command));
   } finally {
     // needed because entries might be added in L1
     if (!ctx.isInTxScope()) commitContextEntries(ctx, command, null);
     else {
       CacheEntry entry = ctx.lookupEntry(command.getKey());
       if (entry != null) {
         entry.setSkipLookup(true);
       }
     }
   }
 }
 @Override
 public CompletableFuture<Void> visitReadOnlyManyCommand(
     InvocationContext ctx, ReadOnlyManyCommand command) throws Throwable {
   try {
     for (Object key : command.getKeys()) {
       entryFactory.wrapEntryForReading(ctx, key, null);
     }
     return ctx.shortCircuit(ctx.forkInvocationSync(command));
   } finally {
     if (ctx.isInTxScope()) {
       for (Object key : command.getKeys()) {
         CacheEntry entry = ctx.lookupEntry(key);
         if (entry != null) {
           entry.setSkipLookup(true);
         }
       }
     }
   }
 }
 @Override
 public CompletableFuture<Void> visitApplyDeltaCommand(
     InvocationContext ctx, ApplyDeltaCommand command) throws Throwable {
   entryFactory.wrapEntryForDelta(ctx, command.getKey(), command.getDelta());
   return ctx.continueInvocation();
 }
 private void lockAndWrap(InvocationContext ctx, Object key, InternalCacheEntry ice)
     throws InterruptedException {
   lockManager.acquireLock(ctx, key);
   entryFactory.wrapEntryForPut(ctx, key, ice, false);
 }