/**
   * @param nodes Nodes.
   * @param id ID.
   * @throws IgniteCheckedException If failed.
   */
  private void sendAllPartitions(
      Collection<? extends ClusterNode> nodes, GridDhtPartitionExchangeId id)
      throws IgniteCheckedException {
    GridDhtPartitionsFullMessage m =
        new GridDhtPartitionsFullMessage(id, lastVer.get(), id.topologyVersion());

    for (GridCacheContext cacheCtx : cctx.cacheContexts()) {
      if (!cacheCtx.isLocal()) {
        AffinityTopologyVersion startTopVer = cacheCtx.startTopologyVersion();

        boolean ready = startTopVer == null || startTopVer.compareTo(id.topologyVersion()) <= 0;

        if (ready)
          m.addFullPartitionsMap(cacheCtx.cacheId(), cacheCtx.topology().partitionMap(true));
      }
    }

    // It is important that client topologies be added after contexts.
    for (GridClientPartitionTopology top : cctx.exchange().clientTopologies())
      m.addFullPartitionsMap(top.cacheId(), top.partitionMap(true));

    if (log.isDebugEnabled())
      log.debug(
          "Sending full partition map [nodeIds="
              + F.viewReadOnly(nodes, F.node2id())
              + ", exchId="
              + exchId
              + ", msg="
              + m
              + ']');

    cctx.io().safeSend(nodes, m, SYSTEM_POOL, null);
  }
  /**
   * Updates partition map in all caches.
   *
   * @param msg Partitions full messages.
   */
  private void updatePartitionFullMap(GridDhtPartitionsFullMessage msg) {
    for (Map.Entry<Integer, GridDhtPartitionFullMap> entry : msg.partitions().entrySet()) {
      Integer cacheId = entry.getKey();

      GridCacheContext cacheCtx = cctx.cacheContext(cacheId);

      if (cacheCtx != null) cacheCtx.topology().update(exchId, entry.getValue());
      else {
        ClusterNode oldest = CU.oldestAliveCacheServerNode(cctx, AffinityTopologyVersion.NONE);

        if (oldest != null && oldest.isLocal())
          cctx.exchange().clientTopology(cacheId, this).update(exchId, entry.getValue());
      }
    }
  }
  /**
   * @param nodeId Sender node ID.
   * @param msg Full partition info.
   */
  public void onReceive(final UUID nodeId, final GridDhtPartitionsFullMessage msg) {
    assert msg != null;

    if (isDone()) {
      if (log.isDebugEnabled())
        log.debug("Received message for finished future [msg=" + msg + ", fut=" + this + ']');

      return;
    }

    if (log.isDebugEnabled())
      log.debug("Received full partition map from node [nodeId=" + nodeId + ", msg=" + msg + ']');

    assert exchId.topologyVersion().equals(msg.topologyVersion());

    initFut.listen(
        new CI1<IgniteInternalFuture<Boolean>>() {
          @Override
          public void apply(IgniteInternalFuture<Boolean> t) {
            ClusterNode curOldest = oldestNode.get();

            if (!nodeId.equals(curOldest.id())) {
              if (log.isDebugEnabled())
                log.debug(
                    "Received full partition map from unexpected node [oldest="
                        + curOldest.id()
                        + ", unexpectedNodeId="
                        + nodeId
                        + ']');

              ClusterNode snd = cctx.discovery().node(nodeId);

              if (snd == null) {
                if (log.isDebugEnabled())
                  log.debug(
                      "Sender node left grid, will ignore message from unexpected node [nodeId="
                          + nodeId
                          + ", exchId="
                          + msg.exchangeId()
                          + ']');

                return;
              }

              // Will process message later if sender node becomes oldest node.
              if (snd.order() > curOldest.order()) fullMsgs.put(nodeId, msg);

              return;
            }

            assert msg.exchangeId().equals(exchId);

            assert msg.lastVersion() != null;

            cctx.versions().onReceived(nodeId, msg.lastVersion());

            updatePartitionFullMap(msg);

            onDone(exchId.topologyVersion());
          }
        });
  }