@Override
  public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command)
      throws Throwable {
    wrapEntriesForPrepare(ctx, command);
    EntryVersionsMap newVersionData = null;
    if (ctx.isOriginLocal()
        && !((LocalTransaction) ctx.getCacheTransaction()).isFromStateTransfer())
      newVersionData =
          cdl.createNewVersionsAndCheckForWriteSkews(
              versionGenerator, ctx, (VersionedPrepareCommand) command);

    Object retval = invokeNextInterceptor(ctx, command);

    if (!ctx.isOriginLocal())
      newVersionData =
          cdl.createNewVersionsAndCheckForWriteSkews(
              versionGenerator, ctx, (VersionedPrepareCommand) command);
    if (command.isOnePhaseCommit())
      ctx.getCacheTransaction()
          .setUpdatedEntryVersions(((VersionedPrepareCommand) command).getVersionsSeen());

    if (newVersionData != null) retval = newVersionData;
    if (command.isOnePhaseCommit()) commitContextEntries(ctx, null);
    return retval;
  }
 @Override
 protected void checkIfKeyRead(InvocationContext context, Object key, VisitableCommand command) {
   if (command instanceof AbstractDataWriteCommand) {
     AbstractDataWriteCommand writeCommand = (AbstractDataWriteCommand) command;
     // keep track is only need in a clustered and transactional environment to perform the write
     // skew check
     if (context.isInTxScope() && context.isOriginLocal()) {
       TxInvocationContext txInvocationContext = (TxInvocationContext) context;
       if (!writeCommand.hasFlag(Flag.PUT_FOR_STATE_TRANSFER) && writeCommand.isConditional()
           || !writeCommand.hasFlag(Flag.IGNORE_RETURN_VALUES)) {
         // State transfer does not show the old value for the application neither with the
         // IGNORE_RETURN_VALUES.
         // on other hand, the conditional always read key!
         txInvocationContext.getCacheTransaction().addReadKey(key);
       }
       writeCommand.setPreviousRead(txInvocationContext.getCacheTransaction().keyRead(key));
     }
   } else if (command instanceof GetKeyValueCommand) {
     if (context.isInTxScope() && context.isOriginLocal()) {
       // always show the value to the application
       TxInvocationContext txInvocationContext = (TxInvocationContext) context;
       txInvocationContext.getCacheTransaction().addReadKey(key);
     }
   }
 }
  @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);
  }
  @Override
  public Object visitCommitCommand(TxInvocationContext ctx, CommitCommand command)
      throws Throwable {
    GMUCommitCommand gmuCommitCommand = convert(command, GMUCommitCommand.class);

    if (ctx.isOriginLocal()) {
      gmuCommitCommand.setCommitVersion(ctx.getTransactionVersion());
    } else {
      ctx.setTransactionVersion(gmuCommitCommand.getCommitVersion());
    }

    transactionCommitManager.commitTransaction(
        ctx.getCacheTransaction(), gmuCommitCommand.getCommitVersion());

    Object retVal = null;
    try {
      retVal = invokeNextInterceptor(ctx, command);
    } catch (Throwable throwable) {
      // let ignore the exception. we cannot have some nodes applying the write set and another not
      // another one
      // receives the rollback and don't applies the write set
    } finally {
      transactionCommitManager.awaitUntilCommitted(
          ctx.getCacheTransaction(), ctx.isOriginLocal() ? null : gmuCommitCommand);
    }
    return ctx.isOriginLocal() ? retVal : RequestHandler.DO_NOT_REPLY;
  }
  @Override
  public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command)
      throws Throwable {
    if (ctx.getCacheTransaction() instanceof RemoteTransaction) {
      ((RemoteTransaction) ctx.getCacheTransaction()).setMissingLookedUpEntries(false);
    }

    return handleTxCommand(ctx, command);
  }
  @Override
  public Object visitCommitCommand(TxInvocationContext ctx, CommitCommand command)
      throws Throwable {
    try {
      if (ctx.isOriginLocal())
        ((VersionedCommitCommand) command)
            .setUpdatedVersions(ctx.getCacheTransaction().getUpdatedEntryVersions());

      return invokeNextInterceptor(ctx, command);
    } finally {
      if (!ctx.isOriginLocal())
        ctx.getCacheTransaction()
            .setUpdatedEntryVersions(((VersionedCommitCommand) command).getUpdatedVersions());
      commitContextEntries(ctx, null);
    }
  }
  @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 EntryVersionsMap totalOrderCreateNewVersionsAndCheckForWriteSkews(
        VersionGenerator versionGenerator,
        TxInvocationContext context,
        VersionedPrepareCommand prepareCommand) {
      if (context.isOriginLocal()) {
        throw new IllegalStateException("This must not be reached");
      }

      EntryVersionsMap updatedVersionMap = new EntryVersionsMap();

      if (!((TotalOrderPrepareCommand) prepareCommand).skipWriteSkewCheck()) {
        updatedVersionMap =
            performTotalOrderWriteSkewCheckAndReturnNewVersions(
                prepareCommand,
                dataContainer,
                persistenceManager,
                versionGenerator,
                context,
                keySpecificLogic,
                timeService);
      }

      for (WriteCommand c : prepareCommand.getModifications()) {
        for (Object k : c.getAffectedKeys()) {
          if (keySpecificLogic.performCheckOnKey(k)) {
            if (!updatedVersionMap.containsKey(k)) {
              updatedVersionMap.put(k, null);
            }
          }
        }
      }

      context.getCacheTransaction().setUpdatedEntryVersions(updatedVersionMap);
      return updatedVersionMap;
    }
  @Override
  public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command)
      throws Throwable {
    GMUPrepareCommand spc = convert(command, GMUPrepareCommand.class);

    if (ctx.isOriginLocal()) {
      spc.setVersion(ctx.getTransactionVersion());
      spc.setReadSet(ctx.getReadSet());
    } else {
      ctx.setTransactionVersion(spc.getPrepareVersion());
    }

    wrapEntriesForPrepare(ctx, command);
    performValidation(ctx, spc);

    Object retVal = invokeNextInterceptor(ctx, command);

    if (ctx.isOriginLocal() && command.getModifications().length > 0) {
      EntryVersion commitVersion =
          calculateCommitVersion(
              ctx.getTransactionVersion(),
              versionGenerator,
              cll.getWriteOwners(ctx.getCacheTransaction()));
      ctx.setTransactionVersion(commitVersion);
    } else {
      retVal = ctx.getTransactionVersion();
    }

    if (command.isOnePhaseCommit()) {
      commitContextEntries.commitContextEntries(ctx);
    }

    return retVal;
  }
 @Override
 public Object visitRollbackCommand(TxInvocationContext ctx, RollbackCommand command)
     throws Throwable {
   try {
     return invokeNextInterceptor(ctx, command);
   } finally {
     transactionCommitManager.rollbackTransaction(ctx.getCacheTransaction());
   }
 }
 @Override
 public Object visitCommitCommand(TxInvocationContext ctx, CommitCommand command)
     throws Throwable {
   Object result = super.visitCommitCommand(ctx, command);
   if (!blockPrepare && !ctx.getCacheTransaction().isFromStateTransfer()) {
     doBlock(ctx, command);
   }
   return result;
 }
  @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 boolean shouldInvokeRemoteTxCommand(TxInvocationContext ctx) {
   // ISPN-2362: For backups, we should only replicate to the remote site if there are
   // modifications to replay.
   boolean shouldBackupRemotely =
       ctx.isOriginLocal()
           && ctx.hasModifications()
           && !ctx.getCacheTransaction().isFromStateTransfer();
   getLog().tracef("Should backup remotely? %s", shouldBackupRemotely);
   return shouldBackupRemotely;
 }
 protected final void wrapEntriesForPrepare(TxInvocationContext ctx, PrepareCommand command)
     throws Throwable {
   if (!ctx.isOriginLocal() || command.isReplayEntryWrapping()) {
     for (WriteCommand c : command.getModifications()) {
       c.acceptVisitor(ctx, entryWrappingVisitor);
       if (c.hasFlag(Flag.PUT_FOR_X_SITE_STATE_TRANSFER)) {
         ctx.getCacheTransaction().setStateTransferFlag(Flag.PUT_FOR_X_SITE_STATE_TRANSFER);
       }
     }
   }
 }
  /**
   * validates the read set and returns the prepare version from the commit queue
   *
   * @param ctx the context
   * @param command the prepare command
   * @throws InterruptedException if interrupted
   */
  protected void performValidation(TxInvocationContext ctx, GMUPrepareCommand command)
      throws InterruptedException {
    boolean hasToUpdateLocalKeys = hasLocalKeysToUpdate(command.getModifications());
    boolean isReadOnly = command.getModifications().length == 0;

    if (!isReadOnly) {
      cll.performReadSetValidation(ctx, command);
      if (hasToUpdateLocalKeys) {
        transactionCommitManager.prepareTransaction(ctx.getCacheTransaction());
      } else {
        transactionCommitManager.prepareReadOnlyTransaction(ctx.getCacheTransaction());
      }
    }

    if (log.isDebugEnabled()) {
      log.debugf(
          "Transaction %s can commit on this node. Prepare Version is %s",
          command.getGlobalTransaction().prettyPrint(), ctx.getTransactionVersion());
    }
  }
  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)));
    }
  }
    private EntryVersionsMap clusteredCreateNewVersionsAndCheckForWriteSkews(
        VersionGenerator versionGenerator,
        TxInvocationContext context,
        VersionedPrepareCommand prepareCommand) {
      // Perform a write skew check on mapped entries.
      EntryVersionsMap uv =
          performWriteSkewCheckAndReturnNewVersions(
              prepareCommand,
              dataContainer,
              persistenceManager,
              versionGenerator,
              context,
              keySpecificLogic,
              timeService);

      CacheTransaction cacheTransaction = context.getCacheTransaction();
      EntryVersionsMap uvOld = cacheTransaction.getUpdatedEntryVersions();
      if (uvOld != null && !uvOld.isEmpty()) {
        uvOld.putAll(uv);
        uv = uvOld;
      }
      cacheTransaction.setUpdatedEntryVersions(uv);
      return (uv.isEmpty()) ? null : uv;
    }