@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 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;
  }
  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)));
    }
  }
  /**
   * 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());
    }
  }