/**
   * Compute the final URI from a partial URI in the request. The following steps are performed:
   * <li>if host is missing and there is a load balancer, get the host/port from server chosen from
   *     load balancer
   * <li>if host is missing and there is no load balancer, try to derive host/port from virtual
   *     address set with the client
   * <li>if host is present and the authority part of the URI is a virtual address set for the
   *     client, and there is a load balancer, get the host/port from server chosen from load
   *     balancer
   * <li>if host is present but none of the above applies, interpret the host as the actual physical
   *     address
   * <li>if host is missing but none of the above applies, throws ClientException
   *
   * @param original Original URI passed from caller
   * @return new request with the final URI
   */
  protected S computeFinalUriWithLoadBalancer(S original) throws ClientException {
    URI newURI;
    URI theUrl = original.getUri();

    if (theUrl == null) {
      throw new ClientException(ClientException.ErrorType.GENERAL, "NULL URL passed in");
    }

    String host = theUrl.getHost();
    Pair<String, Integer> schemeAndPort = deriveSchemeAndPortFromPartialUri(original);
    String scheme = schemeAndPort.first();
    int port = schemeAndPort.second();
    // Various Supported Cases
    // The loadbalancer to use and the instances it has is based on how it was registered
    // In each of these cases, the client might come in using Full Url or Partial URL
    ILoadBalancer lb = getLoadBalancer();
    Object loadBalancerKey = original.getLoadBalancerKey();
    if (host == null) {
      // Partial URL Case
      // well we have to just get the right instances from lb - or we fall back
      if (lb != null) {
        Server svc = lb.chooseServer(loadBalancerKey);
        if (svc == null) {
          throw new ClientException(
              ClientException.ErrorType.GENERAL,
              "LoadBalancer returned null Server for :" + clientName);
        }
        host = svc.getHost();
        port = svc.getPort();
        if (host == null) {
          throw new ClientException(
              ClientException.ErrorType.GENERAL, "Invalid Server for :" + svc);
        }
        if (logger.isDebugEnabled()) {
          logger.debug(clientName + " using LB returned Server:" + svc + "for request:" + theUrl);
        }
      } else {
        // No Full URL - and we dont have a LoadBalancer registered to
        // obtain a server
        // if we have a vipAddress that came with the registration, we
        // can use that else we
        // bail out
        if (vipAddresses != null && vipAddresses.contains(",")) {
          throw new ClientException(
              ClientException.ErrorType.GENERAL,
              this.clientName
                  + "Partial URI of ("
                  + theUrl
                  + ") has been sent in to RestClient (with no LB) to be executed."
                  + " Also, there are multiple vipAddresses and hence RestClient cant pick"
                  + "one vipAddress to complete this partial uri");
        } else if (vipAddresses != null) {
          try {
            Pair<String, Integer> hostAndPort = deriveHostAndPortFromVipAddress(vipAddresses);
            host = hostAndPort.first();
            port = hostAndPort.second();
          } catch (URISyntaxException e) {
            throw new ClientException(
                ClientException.ErrorType.GENERAL,
                this.clientName
                    + "Partial URI of ("
                    + theUrl
                    + ") has been sent in to RestClient (with no LB) to be executed."
                    + " Also, the configured/registered vipAddress is unparseable (to determine host and port)");
          }
        } else {
          throw new ClientException(
              ClientException.ErrorType.GENERAL,
              this.clientName
                  + " has no LoadBalancer registered and passed in a partial URL request (with no host:port)."
                  + " Also has no vipAddress registered");
        }
      }
    } else {
      // Full URL Case
      // This could either be a vipAddress or a hostAndPort or a real DNS
      // if vipAddress or hostAndPort, we just have to consult the loadbalancer
      // but if it does not return a server, we should just proceed anyways
      // and assume its a DNS
      // For restClients registered using a vipAddress AND executing a request
      // by passing in the full URL (including host and port), we should only
      // consult lb IFF the URL passed is registered as vipAddress in Discovery
      boolean shouldInterpretAsVip = false;

      if (lb != null) {
        shouldInterpretAsVip = isVipRecognized(original.getUri().getAuthority());
      }
      if (shouldInterpretAsVip) {
        Server svc = lb.chooseServer(loadBalancerKey);
        if (svc != null) {
          host = svc.getHost();
          port = svc.getPort();
          if (host == null) {
            throw new ClientException(
                ClientException.ErrorType.GENERAL, "Invalid Server for :" + svc);
          }
          if (logger.isDebugEnabled()) {
            logger.debug("using LB returned Server:" + svc + "for request:" + theUrl);
          }
        } else {
          // just fall back as real DNS
          if (logger.isDebugEnabled()) {
            logger.debug(
                host + ":" + port + " assumed to be a valid VIP address or exists in the DNS");
          }
        }
      } else {
        // consult LB to obtain vipAddress backed instance given full URL
        // Full URL execute request - where url!=vipAddress
        if (logger.isDebugEnabled()) {
          logger.debug("Using full URL passed in by caller (not using LB/Discovery):" + theUrl);
        }
      }
    }
    // end of creating final URL
    if (host == null) {
      throw new ClientException(
          ClientException.ErrorType.GENERAL, "Request contains no HOST to talk to");
    }
    // just verify that at this point we have a full URL

    try {
      String urlPath = "";
      if (theUrl.getRawPath() != null && theUrl.getRawPath().startsWith("/")) {
        urlPath = theUrl.getRawPath();
      } else {
        urlPath = "/" + theUrl.getRawPath();
      }

      newURI =
          new URI(
              scheme,
              theUrl.getUserInfo(),
              host,
              port,
              urlPath,
              theUrl.getQuery(),
              theUrl.getFragment());
      return (S) original.replaceUri(newURI);
    } catch (URISyntaxException e) {
      throw new ClientException(ClientException.ErrorType.GENERAL, e.getMessage());
    }
  }