@Override
  public Object visitCommitCommand(TxInvocationContext ctx, CommitCommand command)
      throws Throwable {
    if (ctx.getCacheTransaction() instanceof RemoteTransaction) {
      // If a commit is received for a transaction that doesn't have its 'lookedUpEntries' populated
      // we know for sure this transaction is 2PC and was received via state transfer but the
      // preceding PrepareCommand
      // was not received by local node because it was executed on the previous key owners. We need
      // to re-prepare
      // the transaction on local node to ensure its locks are acquired and lookedUpEntries is
      // properly populated.
      RemoteTransaction remoteTx = (RemoteTransaction) ctx.getCacheTransaction();
      if (remoteTx.isMissingLookedUpEntries()) {
        remoteTx.setMissingLookedUpEntries(false);

        PrepareCommand prepareCommand;
        if (useVersioning) {
          prepareCommand =
              commandFactory.buildVersionedPrepareCommand(
                  ctx.getGlobalTransaction(), ctx.getModifications(), false);
        } else {
          prepareCommand =
              commandFactory.buildPrepareCommand(
                  ctx.getGlobalTransaction(), ctx.getModifications(), false);
        }
        commandFactory.initializeReplicableCommand(prepareCommand, true);
        prepareCommand.setOrigin(ctx.getOrigin());
        log.tracef(
            "Replaying the transactions received as a result of state transfer %s", prepareCommand);
        prepareCommand.perform(null);
      }
    }

    return handleTxCommand(ctx, command);
  }
  private void updateTransactionVersion(InvocationContext context) {
    if (!context.isInTxScope() && !context.isOriginLocal()) {
      return;
    }

    if (context instanceof SingleKeyNonTxInvocationContext) {
      if (log.isDebugEnabled()) {
        log.debugf(
            "Received a SingleKeyNonTxInvocationContext... This should be a single read operation");
      }
      return;
    }

    TxInvocationContext txInvocationContext = (TxInvocationContext) context;
    List<EntryVersion> entryVersionList = new LinkedList<EntryVersion>();
    entryVersionList.add(txInvocationContext.getTransactionVersion());

    if (log.isTraceEnabled()) {
      log.tracef(
          "[%s] Keys read in this command: %s",
          txInvocationContext.getGlobalTransaction().prettyPrint(),
          txInvocationContext.getKeysReadInCommand());
    }

    for (InternalGMUCacheEntry internalGMUCacheEntry :
        txInvocationContext.getKeysReadInCommand().values()) {
      Object key = internalGMUCacheEntry.getKey();
      boolean local = cll.localNodeIsOwner(key);
      if (log.isTraceEnabled()) {
        log.tracef(
            "[%s] Analyze entry [%s]: local?=%s",
            txInvocationContext.getGlobalTransaction().prettyPrint(), internalGMUCacheEntry, local);
      }
      if (txInvocationContext.hasModifications() && !internalGMUCacheEntry.isMostRecent()) {
        throw new CacheException("Read-Write transaction read an old value and should rollback");
      }

      if (internalGMUCacheEntry.getMaximumTransactionVersion() != null) {
        entryVersionList.add(internalGMUCacheEntry.getMaximumTransactionVersion());
      }
      txInvocationContext.getCacheTransaction().addReadKey(key);
      if (local) {
        txInvocationContext.setAlreadyReadOnThisNode(true);
        txInvocationContext.addReadFrom(cll.getAddress());
      }
    }

    if (entryVersionList.size() > 1) {
      EntryVersion[] txVersionArray = new EntryVersion[entryVersionList.size()];
      txInvocationContext.setTransactionVersion(
          versionGenerator.mergeAndMax(entryVersionList.toArray(txVersionArray)));
    }
  }
 /** Special processing required for transaction commands. */
 private Object handleTxCommand(TxInvocationContext ctx, TransactionBoundaryCommand command)
     throws Throwable {
   // For local commands we may not have a GlobalTransaction yet
   Address address =
       ctx.isOriginLocal() ? ctx.getOrigin() : ctx.getGlobalTransaction().getAddress();
   return handleTopologyAffectedCommand(ctx, command, address, true);
 }
  private void invalidateAcrossCluster(boolean synchronous, Object[] keys, InvocationContext ctx)
      throws Throwable {
    // increment invalidations counter if statistics maintained
    incrementInvalidations();
    final InvalidateCommand invalidateCommand =
        commandsFactory.buildInvalidateCommand(InfinispanCollections.<Flag>emptySet(), keys);
    if (log.isDebugEnabled())
      log.debug("Cache [" + rpcManager.getAddress() + "] replicating " + invalidateCommand);

    ReplicableCommand command = invalidateCommand;
    if (ctx.isInTxScope()) {
      TxInvocationContext txCtx = (TxInvocationContext) ctx;
      // A Prepare command containing the invalidation command in its 'modifications' list is sent
      // to the remote nodes
      // so that the invalidation is executed in the same transaction and locks can be acquired and
      // released properly.
      // This is 1PC on purpose, as an optimisation, even if the current TX is 2PC.
      // If the cache uses 2PC it's possible that the remotes will commit the invalidation and the
      // originator rolls back,
      // but this does not impact consistency and the speed benefit is worth it.
      command =
          commandsFactory.buildPrepareCommand(
              txCtx.getGlobalTransaction(),
              Collections.<WriteCommand>singletonList(invalidateCommand),
              true);
    }
    rpcManager.invokeRemotely(null, command, rpcManager.getDefaultRpcOptions(synchronous));
  }
 @Override
 public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command)
     throws Throwable {
   try {
     return invokeNextInterceptor(ctx, command);
   } catch (TimeoutException te) {
     numLocksAfterTeOnPrepare = lm1.getNumberOfLocksHeld();
     isTxInTableAfterTeOnPrepare = txTable1.containRemoteTx(ctx.getGlobalTransaction());
     teReceived = true;
     throw te;
   }
 }
  @Override
  public final CompletableFuture<Void> visitPrepareCommand(
      TxInvocationContext ctx, PrepareCommand command) throws Throwable {
    if (ctx.isOriginLocal()) {
      ((VersionedPrepareCommand) command)
          .setVersionsSeen(ctx.getCacheTransaction().getVersionsRead());
      // for local mode keys
      ctx.getCacheTransaction().setUpdatedEntryVersions(EMPTY_VERSION_MAP);
      return ctx.onReturn(
          (rCtx, rCommand, rv, throwable) -> {
            if (throwable == null && shouldCommitDuringPrepare((PrepareCommand) rCommand, ctx)) {
              commitContextEntries(ctx, null, null);
            }
            return null;
          });
    }

    // Remote context, delivered in total order

    wrapEntriesForPrepare(ctx, command);

    return ctx.onReturn(
        (rCtx, rCommand, rv, throwable) -> {
          if (throwable != null) throw throwable;

          TxInvocationContext txInvocationContext = (TxInvocationContext) rCtx;
          VersionedPrepareCommand prepareCommand = (VersionedPrepareCommand) rCommand;
          EntryVersionsMap versionsMap =
              cdl.createNewVersionsAndCheckForWriteSkews(
                  versionGenerator, txInvocationContext, prepareCommand);

          if (prepareCommand.isOnePhaseCommit()) {
            commitContextEntries(txInvocationContext, null, null);
          } else {
            if (trace)
              log.tracef(
                  "Transaction %s will be committed in the 2nd phase",
                  txInvocationContext.getGlobalTransaction().globalId());
          }

          return CompletableFuture.completedFuture(
              versionsMap == null ? rv : new ArrayList<Object>(versionsMap.keySet()));
        });
  }
  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);
    }
  }