public BaseClientResult<ResultType> fetch(Config<ResultType> config)
      throws IOException, ParseException, InterruptedException {

    PartialBaseClientResult<ResultType> partial_result = partialFetch(config);
    try {
      return completeFetch(partial_result);
    } finally {
      closeQuietly(partial_result.getConnection());
    }
  }
  private void setMoreResults(URLConnection conn, PartialBaseClientResult<ResultType> result) {

    String more = conn.getHeaderField(X_MORE_RESULTS);

    if (more == null) result.setHasMoreResultsHeader(false);
    else {
      result.setHasMoreResultsHeader(true);

      if ("true".equals(more)) result.setHasMoreResults(true);
      else result.setHasMoreResults(false);
    }
  }
  /**
   * Fetch the API with the given FeedConfig
   *
   * @throws IOException if there's an error with network transport.
   * @throws ParseException if there's a problem parsing the resulting XML.
   */
  public BaseClientResult<ResultType> completeFetch(
      PartialBaseClientResult<ResultType> partial_result)
      throws IOException, ParseException, InterruptedException {

    Config<ResultType> config = partial_result.getConfig();
    int request_limit = partial_result.getRequestLimit();
    String resource = partial_result.getLastRequestURL();
    boolean has_results_header = partial_result.getHasMoreResultsHeader();
    boolean has_more_results = partial_result.getHasMoreResults();
    String next_request = partial_result.getNextRequestURL();

    BaseClientResult<ResultType> result = new BaseClientResult<ResultType>(config);

    result.setLastRequestURL(resource);
    result.setRequestLimit(request_limit);
    result.setHasMoreResultsHeadder(has_results_header);
    result.setHasMoreResults(has_more_results);
    result.setNextRequestURL(next_request);

    try {

      long before = System.currentTimeMillis();

      long call_before = System.currentTimeMillis();

      URLConnection conn = partial_result.getConnection();

      // TODO: clean up the naming here.  getLocalInputStream actually
      // reads everything into a byte array in memory.
      InputStream localInputStream = getLocalInputStream(conn.getInputStream(), result);

      result.setLocalInputStream(localInputStream);

      if (GZIP_ENCODING.equals(conn.getContentEncoding())) result.setIsCompressed(true);

      InputStream is = result.getInputStream();

      long call_after = System.currentTimeMillis();

      result.setCallDuration(call_after - call_before);

      if (!config.getDisableParse()) {

        if (config.getFormat() == Format.PROTOSTREAM)
          result.setResults(protobufParse(doProtoStreamFetch(localInputStream, config), config));
        else if (config.getFormat() == Format.PROTOBUF)
          result.setResults(protobufParse(doProtobufFetch(localInputStream, config), config));
        else {

          Document doc = doXmlFetch(is, config);

          if (doc != null) {
            result.setResults(xmlParse(doc, config));
          }
        }
      }

      long after = System.currentTimeMillis();

      result.setParseDuration(after - before);

    } catch (Exception e) {
      throw new ParseException(e, "Unable to handle request: " + resource);
    }

    if (!result.getHasMoreResultsHeadder())
      result.setHasMoreResults(result.getResults().size() == request_limit);

    return result;
  }
  /**
   * Fetch the API with the given FeedConfig
   *
   * @throws IOException if there's an error with network transport.
   * @throws ParseException if there's a problem parsing the resulting XML.
   */
  private PartialBaseClientResult<ResultType> startFetch(
      Config<ResultType> config, int request_limit) throws IOException, InterruptedException {

    PartialBaseClientResult<ResultType> result = new PartialBaseClientResult<ResultType>(config);

    if (config.getVendor() == null) throw new RuntimeException("Vendor not specified");

    String resource = config.getNextRequestURL();

    // enforce max limit so that we don't generate runtime exceptions.
    if (request_limit > config.getMaxLimit()) request_limit = config.getMaxLimit();

    if (resource == null) {

      resource = config.getFirstRequestURL();

      // if the API has NEVER been used before then generate the first
      // request URL from the config parameters.
      if (resource == null) resource = config.generateFirstRequestURL(request_limit);
    }

    // apply the request_limit to the current URL.  This needs to be done so
    // that we can change the limit at runtime.  When I originally designed
    // the client I didn't want to support introspecting and mutating the URL
    // on the client but with the optimial limit performance optimization
    // this is impossible.
    resource = setParam(resource, "limit", request_limit);

    // add a connection number to the vendor code
    resource = addConnectionNumber(resource);

    // store the last requested URL so we can expose this to the caller for
    // debug purposes.

    result.setLastRequestURL(resource);
    result.setRequestLimit(request_limit);

    URLConnection conn = getConnection(resource);

    /*
     * If this is an http connection and there is an error code,
     * return throw an error message containing the response
     * message.
     */
    if (conn instanceof HttpURLConnection) {
      HttpURLConnection httpConn = (HttpURLConnection) conn;
      int responseCode = httpConn.getResponseCode();

      if (responseCode >= 400) {
        StringBuilder message = new StringBuilder("");
        InputStream errorStream = httpConn.getErrorStream();
        if (errorStream == null)
          throw new IOException(String.format("Response code %d received", responseCode));

        BufferedReader reader =
            new BufferedReader(new InputStreamReader(new GZIPInputStream(errorStream)));
        String line;
        while ((line = reader.readLine()) != null) message.append(line);

        throw new IOException(message.toString());
      }
    }

    result.setConnection(conn);

    setMoreResults(conn, result);

    result.setNextRequestURL(conn.getHeaderField("X-Next-Request-URL"));

    return result;
  }