/** {@inheritDoc} */
  @Override
  protected GridClientConfiguration clientConfiguration() throws GridClientException {
    GridClientConfiguration cfg = super.clientConfiguration();

    cfg.setServers(Collections.<String>emptySet());
    cfg.setRouters(Collections.singleton(HOST + ":" + ROUTER_PORT));

    return cfg;
  }
  /** {@inheritDoc} */
  @SuppressWarnings("BusyWait")
  @Override
  public void init(Collection<InetSocketAddress> srvs)
      throws GridClientException, InterruptedException {
    init0();

    GridClientException firstEx = null;

    for (int i = 0; i < INIT_RETRY_CNT; i++) {
      Collection<InetSocketAddress> srvsCp = new ArrayList<>(srvs);

      while (!srvsCp.isEmpty()) {
        GridClientConnection conn = null;

        try {
          conn = connect(null, srvsCp);

          conn.topology(cfg.isAutoFetchAttributes(), cfg.isAutoFetchMetrics(), null).get();

          return;
        } catch (GridServerUnreachableException e) {
          // No connection could be opened to any of initial addresses - exit to retry loop.
          assert conn == null
              : "GridClientConnectionResetException was thrown from GridClientConnection#topology";

          if (firstEx == null) firstEx = e;

          break;
        } catch (GridClientConnectionResetException e) {
          // Connection was established but topology update failed -
          // trying other initial addresses if any.
          assert conn != null : "GridClientConnectionResetException was thrown from connect()";

          if (firstEx == null) firstEx = e;

          if (!srvsCp.remove(conn.serverAddress()))
            // We have misbehaving collection or equals - just exit to avoid infinite loop.
            break;
        }
      }

      Thread.sleep(INIT_RETRY_INTERVAL);
    }

    for (GridClientConnection c : conns.values()) {
      conns.remove(c.serverAddress(), c);

      c.close(FAILED, false);
    }

    throw firstEx;
  }
  /**
   * Returns connection to one of the given addresses.
   *
   * @param nodeId {@code UUID} of node for mapping with connection. {@code null} if no need of
   *     mapping.
   * @param srvs Collection of addresses to connect to.
   * @return Connection to use for operations, targeted for the given node.
   * @throws GridServerUnreachableException If connection can't be established.
   * @throws GridClientClosedException If connections manager has been closed already.
   * @throws InterruptedException If connection was interrupted.
   */
  public GridClientConnection connection(@Nullable UUID nodeId, Collection<InetSocketAddress> srvs)
      throws GridServerUnreachableException, GridClientClosedException, InterruptedException {
    if (srvs == null || srvs.isEmpty())
      throw new GridServerUnreachableException(
          "Failed to establish connection to the grid" + " (address list is empty).");

    checkClosed();

    // Search for existent connection.
    for (InetSocketAddress endPoint : srvs) {
      assert endPoint != null;

      GridClientConnection conn = conns.get(endPoint);

      if (conn == null) continue;

      // Ignore closed connections.
      if (conn.closeIfIdle(cfg.getMaxConnectionIdleTime())) {
        closeIdle();

        continue;
      }

      if (nodeId != null) nodeConns.put(nodeId, conn);

      return conn;
    }

    return connect(nodeId, srvs);
  }
  /**
   * Gets active communication facade.
   *
   * @param node Remote node to which connection should be established.
   * @throws GridServerUnreachableException If none of the servers can be reached after the
   *     exception.
   * @throws GridClientClosedException If client was closed manually.
   * @throws InterruptedException If connection was interrupted.
   */
  @Override
  public GridClientConnection connection(GridClientNode node)
      throws GridClientClosedException, GridServerUnreachableException, InterruptedException {
    assert node != null;

    // Use router's connections if defined.
    if (!routers.isEmpty()) return connection(null, routers);

    GridClientConnection conn = nodeConns.get(node.nodeId());

    if (conn != null) {
      // Ignore closed connections.
      if (conn.closeIfIdle(cfg.getMaxConnectionIdleTime())) closeIdle();
      else return conn;
    }

    // Use node's connection, if node is available over rest.
    Collection<InetSocketAddress> endpoints = node.availableAddresses(cfg.getProtocol(), true);

    List<InetSocketAddress> resolvedEndpoints = new ArrayList<>(endpoints.size());

    for (InetSocketAddress endpoint : endpoints)
      if (!endpoint.isUnresolved()) resolvedEndpoints.add(endpoint);

    if (resolvedEndpoints.isEmpty()) {
      throw new GridServerUnreachableException(
          "No available endpoints to connect " + "(is rest enabled for this node?): " + node);
    }

    boolean sameHost =
        node.attributes().isEmpty()
            || F.containsAny(U.allLocalMACs(), node.attribute(ATTR_MACS).toString().split(", "));

    Collection<InetSocketAddress> srvs = new LinkedHashSet<>();

    if (sameHost) {
      Collections.sort(resolvedEndpoints, U.inetAddressesComparator(true));

      srvs.addAll(resolvedEndpoints);
    } else {
      for (InetSocketAddress endpoint : resolvedEndpoints)
        if (!endpoint.getAddress().isLoopbackAddress()) srvs.add(endpoint);
    }

    return connection(node.nodeId(), srvs);
  }
  /**
   * Close all connections idling for more then {@link
   * GridClientConfiguration#getMaxConnectionIdleTime()} milliseconds.
   */
  @SuppressWarnings("ForLoopReplaceableByForEach")
  private void closeIdle() {
    for (Iterator<Map.Entry<UUID, GridClientConnection>> it = nodeConns.entrySet().iterator();
        it.hasNext(); ) {
      Map.Entry<UUID, GridClientConnection> entry = it.next();

      GridClientConnection conn = entry.getValue();

      if (conn.closeIfIdle(cfg.getMaxConnectionIdleTime())) {
        conns.remove(conn.serverAddress(), conn);

        nodeConns.remove(entry.getKey(), conn);
      }
    }

    for (GridClientConnection conn : conns.values())
      if (conn.closeIfIdle(cfg.getMaxConnectionIdleTime()))
        conns.remove(conn.serverAddress(), conn);
  }
  /**
   * Create new connection to specified server.
   *
   * @param nodeId {@code UUID} of node for mapping with connection. {@code null} if no need of
   *     mapping.
   * @param addr Remote socket to connect.
   * @return Established connection.
   * @throws IOException If connection failed.
   * @throws GridClientException If protocol error happened.
   * @throws InterruptedException If thread was interrupted before connection was established.
   */
  protected GridClientConnection connect(@Nullable UUID nodeId, InetSocketAddress addr)
      throws IOException, GridClientException, InterruptedException {
    endpointStripedLock.lock(addr);

    try {
      GridClientConnection old = conns.get(addr);

      if (old != null) {
        if (old.isClosed()) {
          conns.remove(addr, old);

          if (nodeId != null) nodeConns.remove(nodeId, old);
        } else {
          if (nodeId != null) nodeConns.put(nodeId, old);

          return old;
        }
      }

      SecurityCredentials cred = null;

      try {
        if (cfg.getSecurityCredentialsProvider() != null)
          cred = cfg.getSecurityCredentialsProvider().credentials();
      } catch (IgniteCheckedException e) {
        throw new GridClientException("Failed to obtain client credentials.", e);
      }

      GridClientConnection conn;

      if (cfg.getProtocol() == GridClientProtocol.TCP) {
        conn =
            new GridClientNioTcpConnection(
                srv,
                clientId,
                addr,
                sslCtx,
                pingExecutor,
                cfg.getConnectTimeout(),
                cfg.getPingInterval(),
                cfg.getPingTimeout(),
                cfg.isTcpNoDelay(),
                cfg.getMarshaller(),
                marshId,
                top,
                cred,
                keepPortablesThreadLocal());
      } else
        throw new GridServerUnreachableException(
            "Failed to create client (protocol is not supported): " + cfg.getProtocol());

      old = conns.putIfAbsent(addr, conn);

      assert old == null;

      if (nodeId != null) nodeConns.put(nodeId, conn);

      return conn;
    } finally {
      endpointStripedLock.unlock(addr);
    }
  }
  /**
   * @param clientId Client ID.
   * @param sslCtx SSL context to enable secured connection or {@code null} to use unsecured one.
   * @param cfg Client configuration.
   * @param routers Routers or empty collection to use endpoints from topology info.
   * @param top Topology.
   * @param marshId Marshaller ID.
   * @throws GridClientException In case of error.
   */
  @SuppressWarnings("unchecked")
  protected GridClientConnectionManagerAdapter(
      UUID clientId,
      SSLContext sslCtx,
      GridClientConfiguration cfg,
      Collection<InetSocketAddress> routers,
      GridClientTopology top,
      @Nullable Byte marshId,
      boolean routerClient)
      throws GridClientException {
    assert clientId != null : "clientId != null";
    assert cfg != null : "cfg != null";
    assert routers != null : "routers != null";
    assert top != null : "top != null";

    this.clientId = clientId;
    this.sslCtx = sslCtx;
    this.cfg = cfg;
    this.routers = new ArrayList<>(routers);
    this.top = top;

    log = Logger.getLogger(getClass().getName());

    executor =
        cfg.getExecutorService() != null
            ? cfg.getExecutorService()
            : Executors.newCachedThreadPool(new GridClientThreadFactory("exec", true));

    pingExecutor =
        cfg.getProtocol() == GridClientProtocol.TCP
            ? Executors.newScheduledThreadPool(
                Runtime.getRuntime().availableProcessors(),
                new GridClientThreadFactory("exec", true))
            : null;

    this.marshId = marshId;

    if (marshId == null && cfg.getMarshaller() == null)
      throw new GridClientException("Failed to start client (marshaller is not configured).");

    if (cfg.getProtocol() == GridClientProtocol.TCP) {
      try {
        IgniteLogger gridLog = new JavaLogger(false);

        GridNioFilter[] filters;

        GridNioFilter codecFilter =
            new GridNioCodecFilter(new GridTcpRestParser(routerClient), gridLog, false);

        if (sslCtx != null) {
          GridNioSslFilter sslFilter =
              new GridNioSslFilter(sslCtx, true, ByteOrder.nativeOrder(), gridLog);

          sslFilter.directMode(false);
          sslFilter.clientMode(true);

          filters = new GridNioFilter[] {codecFilter, sslFilter};
        } else filters = new GridNioFilter[] {codecFilter};

        srv =
            GridNioServer.builder()
                .address(U.getLocalHost())
                .port(-1)
                .listener(new NioListener(log))
                .filters(filters)
                .logger(gridLog)
                .selectorCount(Runtime.getRuntime().availableProcessors())
                .sendQueueLimit(1024)
                .byteOrder(ByteOrder.nativeOrder())
                .tcpNoDelay(cfg.isTcpNoDelay())
                .directBuffer(true)
                .directMode(false)
                .socketReceiveBufferSize(0)
                .socketSendBufferSize(0)
                .idleTimeout(Long.MAX_VALUE)
                .gridName(routerClient ? "routerClient" : "gridClient")
                .daemon(cfg.isDaemon())
                .build();

        srv.start();
      } catch (IOException | IgniteCheckedException e) {
        throw new GridClientException("Failed to start connection server.", e);
      }
    }
  }
  /**
   * Copy constructor.
   *
   * @param cfg Configuration to be copied.
   */
  public GridClientConfiguration(GridClientConfiguration cfg) {
    // Preserve alphabetical order for maintenance;
    autoFetchAttrs = cfg.isAutoFetchAttributes();
    autoFetchMetrics = cfg.isAutoFetchMetrics();
    balancer = cfg.getBalancer();
    connectTimeout = cfg.getConnectTimeout();
    credProvider = cfg.getSecurityCredentialsProvider();
    enableAttrsCache = cfg.isEnableAttributesCache();
    enableMetricsCache = cfg.isEnableMetricsCache();
    executor = cfg.getExecutorService();
    marshaller = cfg.getMarshaller();
    maxConnIdleTime = cfg.getMaxConnectionIdleTime();
    pingInterval = cfg.getPingInterval();
    pingTimeout = cfg.getPingTimeout();
    proto = cfg.getProtocol();
    routers = cfg.getRouters();
    srvs = cfg.getServers();
    sslCtxFactory = cfg.getSslContextFactory();
    tcpNoDelay = cfg.isTcpNoDelay();
    topRefreshFreq = cfg.getTopologyRefreshFrequency();
    daemon = cfg.isDaemon();
    marshaller = cfg.getMarshaller();

    setDataConfigurations(cfg.getDataConfigurations());
  }