@Override
  public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command)
      throws Throwable {
    Object retVal = invokeNextInterceptor(ctx, command);

    boolean sync = isSynchronous(ctx);

    if (shouldInvokeRemoteTxCommand(ctx)) {
      int newCacheViewId = -1;
      stateTransferLock.waitForStateTransferToEnd(ctx, command, newCacheViewId);

      if (command.isOnePhaseCommit())
        flushL1Caches(ctx); // if we are one-phase, don't block on this future.

      Collection<Address> recipients = dm.getAffectedNodes(ctx.getAffectedKeys());
      prepareOnAffectedNodes(ctx, command, recipients, sync);

      ((LocalTxInvocationContext) ctx).remoteLocksAcquired(recipients);
    } else if (isL1CacheEnabled
        && command.isOnePhaseCommit()
        && !ctx.isOriginLocal()
        && !ctx.getLockedKeys().isEmpty()) {
      // We fall into this block if we are a remote node, happen to be the primary data owner and
      // have locked keys.
      // it is still our responsibility to invalidate L1 caches in the cluster.
      flushL1Caches(ctx);
    }
    return retVal;
  }
  private void sendCommitCommand(
      TxInvocationContext ctx, CommitCommand command, Collection<Address> preparedOn)
      throws TimeoutException, InterruptedException {
    // we only send the commit command to the nodes that
    Collection<Address> recipients = dm.getAffectedNodes(ctx.getAffectedKeys());

    // By default, use the configured commit sync settings
    boolean syncCommitPhase = configuration.isSyncCommitPhase();
    for (Address a : preparedOn) {
      if (!recipients.contains(a)) {
        // However if we have prepared on some nodes and are now committing on different nodes, make
        // sure we
        // force sync commit so we can respond to prepare resend requests.
        syncCommitPhase = true;
      }
    }

    Map<Address, Response> responses =
        rpcManager.invokeRemotely(recipients, command, syncCommitPhase, true);
    if (!responses.isEmpty()) {
      List<Address> resendTo = new LinkedList<Address>();
      for (Map.Entry<Address, Response> r : responses.entrySet()) {
        if (needToResendPrepare(r.getValue())) resendTo.add(r.getKey());
      }

      if (!resendTo.isEmpty()) {
        log.debugf(
            "Need to resend prepares for %s to %s", command.getGlobalTransaction(), resendTo);
        PrepareCommand pc = buildPrepareCommandForResend(ctx, command);
        rpcManager.invokeRemotely(resendTo, pc, true, true);
      }
    }
  }
  @Override
  protected void prepareOnAffectedNodes(
      TxInvocationContext<?> ctx, PrepareCommand command, Collection<Address> recipients) {
    if (log.isTraceEnabled()) {
      log.tracef(
          "Total Order Anycast transaction %s with Total Order",
          command.getGlobalTransaction().globalId());
    }

    if (!ctx.hasModifications()) {
      return;
    }

    if (!ctx.isOriginLocal()) {
      throw new IllegalStateException("Expected a local context while TO-Anycast prepare command");
    }

    if (!(command instanceof VersionedPrepareCommand)) {
      throw new IllegalStateException(
          "Expected a Versioned Prepare Command in version aware component");
    }

    try {
      KeysValidateFilter responseFilter =
          ctx.getCacheTransaction().hasModification(ClearCommand.class) || isSyncCommitPhase()
              ? null
              : new KeysValidateFilter(rpcManager.getAddress(), ctx.getAffectedKeys());

      totalOrderPrepare(recipients, command, responseFilter);
    } finally {
      transactionRemotelyPrepared(ctx);
    }
  }
  private Object realRemoteGet(
      InvocationContext ctx, Object key, boolean storeInL1, boolean isWrite) throws Throwable {
    if (trace) log.tracef("Doing a remote get for key %s", key);

    boolean acquireRemoteLock = false;
    if (ctx.isInTxScope()) {
      TxInvocationContext txContext = (TxInvocationContext) ctx;
      acquireRemoteLock =
          isWrite && isPessimisticCache && !txContext.getAffectedKeys().contains(key);
    }
    // attempt a remote lookup
    InternalCacheEntry ice = dm.retrieveFromRemoteSource(key, ctx, acquireRemoteLock);

    if (acquireRemoteLock) {
      ((TxInvocationContext) ctx).addAffectedKey(key);
    }

    if (ice != null) {
      if (storeInL1) {
        if (isL1CacheEnabled) {
          if (trace) log.tracef("Caching remotely retrieved entry for key %s in L1", key);
          // This should be fail-safe
          try {
            long lifespan =
                ice.getLifespan() < 0
                    ? configuration.getL1Lifespan()
                    : Math.min(ice.getLifespan(), configuration.getL1Lifespan());
            PutKeyValueCommand put =
                cf.buildPutKeyValueCommand(
                    ice.getKey(), ice.getValue(), lifespan, -1, ctx.getFlags());
            lockAndWrap(ctx, key, ice);
            invokeNextInterceptor(ctx, put);
          } catch (Exception e) {
            // Couldn't store in L1 for some reason.  But don't fail the transaction!
            log.infof("Unable to store entry %s in L1 cache", key);
            log.debug("Inability to store in L1 caused by", e);
          }
        } else {
          CacheEntry ce = ctx.lookupEntry(key);
          if (ce == null || ce.isNull() || ce.isLockPlaceholder() || ce.getValue() == null) {
            if (ce != null && ce.isChanged()) {
              ce.setValue(ice.getValue());
            } else {
              if (isWrite) lockAndWrap(ctx, key, ice);
              else ctx.putLookedUpEntry(key, ice);
            }
          }
        }
      } else {
        if (trace) log.tracef("Not caching remotely retrieved entry for key %s in L1", key);
      }
      return ice.getValue();
    }
    return null;
  }
  @Override
  public Object visitRollbackCommand(TxInvocationContext ctx, RollbackCommand command)
      throws Throwable {
    if (shouldInvokeRemoteTxCommand(ctx)) {
      rpcManager.invokeRemotely(
          dm.getAffectedNodes(ctx.getAffectedKeys()),
          command,
          configuration.isSyncRollbackPhase(),
          true);
    }

    return invokeNextInterceptor(ctx, command);
  }
  protected Object endInvalidationAndInvokeNextInterceptor(
      TxInvocationContext ctx, VisitableCommand command) throws Throwable {
    try {
      if (ctx.isOriginLocal()) {
        // send async Commit
        Set<Object> affectedKeys = ctx.getAffectedKeys();

        if (log.isTraceEnabled()) {
          log.tracef("Sending end invalidation for keys %s asynchronously", affectedKeys);
        }

        if (!affectedKeys.isEmpty()) {
          EndInvalidationCommand commitCommand =
              cacheCommandInitializer.buildEndInvalidationCommand(
                  cacheName, affectedKeys.toArray(), ctx.getGlobalTransaction());
          rpcManager.invokeRemotely(
              null, commitCommand, rpcManager.getDefaultRpcOptions(false, DeliverOrder.NONE));
        }
      }
    } finally {
      return invokeNextInterceptor(ctx, command);
    }
  }