/**
   * @param nodeId Sender node ID.
   * @param msg Response to prepare request.
   */
  private void processPrepareResponse(UUID nodeId, GridDistributedTxPrepareResponse<K, V> msg) {
    assert nodeId != null;
    assert msg != null;

    GridReplicatedTxLocal<K, V> tx = ctx.tm().tx(msg.version());

    if (tx == null) {
      if (log.isDebugEnabled())
        log.debug(
            "Received prepare response for non-existing transaction [senderNodeId="
                + nodeId
                + ", res="
                + msg
                + ']');

      return;
    }

    GridReplicatedTxPrepareFuture<K, V> future = (GridReplicatedTxPrepareFuture<K, V>) tx.future();

    if (future != null) future.onResult(nodeId, msg);
    else
      U.error(
          log,
          "Received prepare response for transaction with no future [res="
              + msg
              + ", tx="
              + tx
              + ']');
  }
  /**
   * {@inheritDoc}
   *
   * @param ctx
   */
  @Override
  public void prepareMarshal(GridCacheSharedContext ctx) throws IgniteCheckedException {
    super.prepareMarshal(ctx);

    if (ownedVals != null) {
      ownedValKeys = ownedVals.keySet();

      ownedValVals = ownedVals.values();

      for (Map.Entry<IgniteTxKey, CacheVersionedValue> entry : ownedVals.entrySet()) {
        GridCacheContext cacheCtx = ctx.cacheContext(entry.getKey().cacheId());

        entry.getKey().prepareMarshal(cacheCtx);

        entry.getValue().prepareMarshal(cacheCtx.cacheObjectContext());
      }
    }

    if (retVal != null && retVal.cacheId() != 0) {
      GridCacheContext cctx = ctx.cacheContext(retVal.cacheId());

      assert cctx != null : retVal.cacheId();

      retVal.prepareMarshal(cctx);
    }

    if (filterFailedKeys != null) {
      for (IgniteTxKey key : filterFailedKeys) {
        GridCacheContext cctx = ctx.cacheContext(key.cacheId());

        key.prepareMarshal(cctx);
      }
    }
  }
  /** {@inheritDoc} */
  @Override
  public void finishUnmarshal(GridCacheSharedContext ctx, ClassLoader ldr)
      throws IgniteCheckedException {
    super.finishUnmarshal(ctx, ldr);

    if (ownedValKeys != null && ownedVals == null) {
      ownedVals = U.newHashMap(ownedValKeys.size());

      assert ownedValKeys.size() == ownedValVals.size();

      Iterator<IgniteTxKey> keyIter = ownedValKeys.iterator();

      Iterator<CacheVersionedValue> valIter = ownedValVals.iterator();

      while (keyIter.hasNext()) {
        IgniteTxKey key = keyIter.next();

        GridCacheContext cctx = ctx.cacheContext(key.cacheId());

        CacheVersionedValue val = valIter.next();

        key.finishUnmarshal(cctx, ldr);

        val.finishUnmarshal(cctx, ldr);

        ownedVals.put(key, val);
      }
    }

    if (retVal != null && retVal.cacheId() != 0) {
      GridCacheContext cctx = ctx.cacheContext(retVal.cacheId());

      assert cctx != null : retVal.cacheId();

      retVal.finishUnmarshal(cctx, ldr);
    }

    if (filterFailedKeys != null) {
      for (IgniteTxKey key : filterFailedKeys) {
        GridCacheContext cctx = ctx.cacheContext(key.cacheId());

        key.finishUnmarshal(cctx, ldr);
      }
    }
  }
  /**
   * @param nodeId Sender node ID.
   * @param msg Prepare request.
   */
  @SuppressWarnings({"InstanceofCatchParameter"})
  private void processPrepareRequest(UUID nodeId, GridDistributedTxPrepareRequest<K, V> msg) {
    assert nodeId != null;
    assert msg != null;

    GridReplicatedTxRemote<K, V> tx = null;

    GridDistributedTxPrepareResponse<K, V> res;

    try {
      tx =
          new GridReplicatedTxRemote<K, V>(
              ctx.deploy().globalLoader(),
              nodeId,
              msg.threadId(),
              msg.version(),
              msg.commitVersion(),
              msg.concurrency(),
              msg.isolation(),
              msg.isInvalidate(),
              msg.timeout(),
              msg.reads(),
              msg.writes(),
              ctx);

      tx = ctx.tm().onCreated(tx);

      if (tx == null || !ctx.tm().onStarted(tx))
        throw new GridCacheTxRollbackException("Attempt to start a completed transaction: " + tx);

      // Prepare prior to reordering, so the pending locks added
      // in prepare phase will get properly ordered as well.
      tx.prepare();

      // Add remote candidates and reorder completed and uncompleted versions.
      tx.addRemoteCandidates(
          msg.candidatesByKey(), msg.committedVersions(), msg.rolledbackVersions());

      if (msg.concurrency() == EVENTUALLY_CONSISTENT) {
        if (log.isDebugEnabled()) log.debug("Committing transaction during remote prepare: " + tx);

        tx.commit();

        if (log.isDebugEnabled()) log.debug("Committed transaction during remote prepare: " + tx);

        // Don't send response.
        return;
      }

      res = new GridDistributedTxPrepareResponse<K, V>(msg.version());

      Map<K, Collection<GridCacheMvccCandidate<K>>> cands = tx.localCandidates();

      // Add local candidates (completed version must be set below).
      res.candidates(cands);
    } catch (GridException e) {
      if (e instanceof GridCacheTxRollbackException) {
        if (log.isDebugEnabled())
          log.debug("Transaction was rolled back before prepare completed: " + tx);
      } else if (e instanceof GridCacheTxOptimisticException) {
        if (log.isDebugEnabled())
          log.debug("Optimistic failure for remote transaction (will rollback): " + tx);
      } else {
        U.error(log, "Failed to process prepare request: " + msg, e);
      }

      if (tx != null)
        // Automatically rollback remote transactions.
        tx.rollback();

      // Don't send response.
      if (msg.concurrency() == EVENTUALLY_CONSISTENT) return;

      res = new GridDistributedTxPrepareResponse<K, V>(msg.version());

      res.error(e);
    }

    // Add completed versions.
    res.completedVersions(
        ctx.tm().committedVersions(msg.version()), ctx.tm().rolledbackVersions(msg.version()));

    assert msg.concurrency() != EVENTUALLY_CONSISTENT;

    GridNode node = ctx.discovery().node(nodeId);

    if (node != null) {
      try {
        // Reply back to sender.
        ctx.io().send(node, res);
      } catch (GridException e) {
        U.error(
            log,
            "Failed to send tx response to node (did the node leave grid?) [node="
                + node.id()
                + ", msg="
                + res
                + ']',
            e);

        if (tx != null) tx.rollback();
      }
    }
  }