/**
  * Extracts the headers from the given HttpResponse.
  *
  * @param response the HttpResponse to get the headers from
  * @return A map of the headers found on the HttpResponse
  */
 public static CaseInsensitiveMap<String> extractHeaders(Response response) {
   CaseInsensitiveMap<String> headers = new CaseInsensitiveMap<String>();
   FluentCaseInsensitiveStringsMap headerMap = response.getHeaders();
   for (String headerName : headerMap.keySet()) {
     for (String headerValue : headerMap.get(headerName)) {
       headers.add(headerName, headerValue);
     }
   }
   return headers;
 }
  private FluentCaseInsensitiveStringsMap computerHeaders() {
    FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap();
    for (String s : response.getHeaderNames()) {
      for (String header : response.getHeaders(s)) {
        h.add(s, header);
      }
    }

    if (trailingHeaders != null && trailingHeaders.getHeaderNames().size() > 0) {
      for (final String s : trailingHeaders.getHeaderNames()) {
        for (String header : response.getHeaders(s)) {
          h.add(s, header);
        }
      }
    }

    return h;
  }
  private FluentCaseInsensitiveStringsMap computerHeaders() {
    FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap();

    Header[] uh = method.getResponseHeaders();

    for (Header e : uh) {
      if (e.getName() != null) {
        h.add(e.getName(), e.getValue());
      }
    }

    uh = method.getResponseFooters();
    for (Header e : uh) {
      if (e.getName() != null) {
        h.add(e.getName(), e.getValue());
      }
    }

    return h;
  }
    public void run() {
      download.setState(Transfer.State.ACTIVE);
      final String uri = validateUri(path);
      final TransferResource transferResource =
          new DefaultTransferResource(repository.getUrl(), path, file, download.getTrace());
      final boolean ignoreChecksum = RepositoryPolicy.CHECKSUM_POLICY_IGNORE.equals(checksumPolicy);
      CompletionHandler completionHandler = null;

      final FileLockCompanion fileLockCompanion =
          (file != null)
              ? createOrGetTmpFile(file.getPath(), allowResumable)
              : new FileLockCompanion(null, null);

      try {
        long length = 0;
        if (fileLockCompanion.getFile() != null) {
          fileProcessor.mkdirs(fileLockCompanion.getFile().getParentFile());
        }

        // Position the file to the end in case we are resuming an aborded download.
        final RandomAccessFile resumableFile =
            fileLockCompanion.getFile() == null
                ? null
                : new RandomAccessFile(fileLockCompanion.getFile(), "rw");
        if (resumableFile != null) {
          length = resumableFile.length();
        }

        FluentCaseInsensitiveStringsMap headers = new FluentCaseInsensitiveStringsMap();
        if (!useCache) {
          headers.add("Pragma", "no-cache");
        }
        headers.add("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2");
        headers.replaceAll(AsyncRepositoryConnector.this.headers);

        Request request = null;
        final AtomicInteger maxRequestTry = new AtomicInteger();
        AsyncHttpClient client = httpClient;
        final AtomicBoolean closeOnComplete = new AtomicBoolean(false);

        /**
         * If length > 0, it means we are resuming a interrupted download. If that's the case, we
         * can't re-use the current httpClient because compression is enabled, and supporting
         * compression per request is not supported in ahc and may never has it could have a
         * performance impact.
         */
        if (length > 0) {
          AsyncHttpClientConfig config = createConfig(session, repository, false);
          client = new AsyncHttpClient(new NettyAsyncHttpProvider(config));
          request = client.prepareGet(uri).setRangeOffset(length).setHeaders(headers).build();
          closeOnComplete.set(true);
        } else {
          request = httpClient.prepareGet(uri).setHeaders(headers).build();
        }

        final Request activeRequest = request;
        final AsyncHttpClient activeHttpClient = client;
        completionHandler =
            new CompletionHandler(transferResource, httpClient, logger, RequestType.GET) {
              private final AtomicBoolean seekEndOnFile = new AtomicBoolean(false);

              private final AtomicBoolean handleTmpFile = new AtomicBoolean(true);

              private final AtomicBoolean localException = new AtomicBoolean(false);

              /** {@inheritDoc} */
              @Override
              public STATE onHeadersReceived(final HttpResponseHeaders headers) throws Exception {

                FluentCaseInsensitiveStringsMap h = headers.getHeaders();
                String rangeByteValue = h.getFirstValue("Content-Range");
                // Make sure the server acceptance of the range requests headers
                if (rangeByteValue != null && rangeByteValue.compareToIgnoreCase("none") != 0) {
                  seekEndOnFile.set(true);
                }
                return super.onHeadersReceived(headers);
              }

              @Override
              public void onThrowable(Throwable t) {
                try {
                  logger.debug("onThrowable", t);
                  /**
                   * If an IOException occurs, let's try to resume the request based on how much
                   * bytes has been so far downloaded. Fail after IOException.
                   */
                  if (!disableResumeSupport
                      && !localException.get()
                      && maxRequestTry.get() < maxIOExceptionRetry
                      && IOException.class.isAssignableFrom(t.getClass())) {
                    logger.debug("Trying to recover from an IOException " + activeRequest);
                    maxRequestTry.incrementAndGet();
                    Request newRequest =
                        new RequestBuilder(activeRequest)
                            .setRangeOffset(resumableFile.length())
                            .build();
                    activeHttpClient.executeRequest(newRequest, this);
                    deleteFile.set(false);
                    return;
                  }
                  localException.set(false);

                  if (closeOnComplete.get()) {
                    activeHttpClient.close();
                  }

                  super.onThrowable(t);
                  if (Exception.class.isAssignableFrom(t.getClass())) {
                    exception = Exception.class.cast(t);
                  } else {
                    exception = new Exception(t);
                  }
                  fireTransferFailed();
                } catch (Throwable ex) {
                  logger.debug("Unexpected exception", ex);
                } finally {
                  if (resumableFile != null) {
                    try {
                      resumableFile.close();
                    } catch (IOException ex) {
                    }
                  }
                  deleteFile(fileLockCompanion);

                  latch.countDown();
                  removeListeners();
                }
              }

              private void removeListeners() {
                removeTransferListener(listener);
              }

              public STATE onBodyPartReceived(final HttpResponseBodyPart content) throws Exception {
                if (status() != null
                    && (status().getStatusCode() == 200 || status().getStatusCode() == 206)) {
                  byte[] bytes = content.getBodyPartBytes();
                  try {
                    // If the content-range header was present, save the bytes at the end of the
                    // file
                    // as we are resuming an existing download.
                    if (seekEndOnFile.get()) {
                      resumableFile.seek(fileLockCompanion.getFile().length());

                      // No need to seek again.
                      seekEndOnFile.set(false);
                    }
                    resumableFile.write(bytes);
                  } catch (IOException ex) {
                    logger.debug("onBodyPartReceived", ex);
                    exception = ex;
                    localException.set(true);
                    throw ex;
                  }
                }
                return super.onBodyPartReceived(content);
              }

              @Override
              public Response onCompleted(Response r) throws Exception {
                try {
                  deleteFile.set(true);
                  try {
                    resumableFile.close();
                  } catch (IOException ex) {
                  }

                  final Response response = super.onCompleted(r);

                  handleResponseCode(uri, response.getStatusCode(), response.getStatusText());

                  if (!ignoreChecksum) {
                    activeHttpClient
                        .getConfig()
                        .executorService()
                        .execute(
                            new Runnable() {
                              public void run() {
                                try {
                                  try {
                                    Map<String, Object> checksums =
                                        ChecksumUtils.calc(
                                            fileLockCompanion.getFile(), checksumAlgos.keySet());
                                    if (!verifyChecksum(
                                            file, uri, (String) checksums.get("SHA-1"), ".sha1")
                                        && !verifyChecksum(
                                            file, uri, (String) checksums.get("MD5"), ".md5")) {
                                      throw new ChecksumFailureException(
                                          "Checksum validation failed"
                                              + ", no checksums available from the repository");
                                    }
                                  } catch (ChecksumFailureException e) {
                                    if (RepositoryPolicy.CHECKSUM_POLICY_FAIL.equals(
                                        checksumPolicy)) {
                                      throw e;
                                    }
                                    if (listener != null) {
                                      listener.transferCorrupted(
                                          newEvent(
                                              transferResource,
                                              e,
                                              RequestType.GET,
                                              EventType.CORRUPTED));
                                    }
                                  }
                                } catch (Exception ex) {
                                  exception = ex;
                                } finally {
                                  if (exception == null) {
                                    try {
                                      rename(fileLockCompanion.getFile(), file);
                                      releaseLock(fileLockCompanion);
                                    } catch (IOException e) {
                                      exception = e;
                                    }
                                  } else {
                                    deleteFile(fileLockCompanion);
                                  }

                                  latch.countDown();
                                  if (closeOnComplete.get()) {
                                    activeHttpClient.close();
                                  }
                                }
                              }
                            });
                  } else {

                    rename(fileLockCompanion.getFile(), file);
                    releaseLock(fileLockCompanion);
                    handleTmpFile.set(false);

                    // asyncHttpClient.close may takes time before all connections get closed.
                    // We unlatch first.
                    latch.countDown();
                    if (closeOnComplete.get()) {
                      activeHttpClient.close();
                    }
                  }
                  removeListeners();

                  return response;
                } catch (Exception ex) {
                  exception = ex;
                  localException.set(true);
                  throw ex;
                } finally {
                  try {
                    if (handleTmpFile.get() && fileLockCompanion.getFile() != null) {
                      if (exception != null) {
                        deleteFile(fileLockCompanion);
                      } else if (ignoreChecksum) {
                        rename(fileLockCompanion.getFile(), file);
                        releaseLock(fileLockCompanion);
                      }
                    }
                  } catch (IOException ex) {
                    exception = ex;
                  }
                }
              }
            };

        try {
          if (file == null) {
            if (!resourceExist(uri)) {
              throw new ResourceDoesNotExistException(
                  "Could not find " + uri + " in " + repository.getUrl());
            }
            latch.countDown();
          } else {
            if (listener != null) {
              completionHandler.addTransferListener(listener);
              listener.transferInitiated(
                  newEvent(transferResource, null, RequestType.GET, EventType.INITIATED));
            }

            activeHttpClient.executeRequest(request, completionHandler);
          }
        } catch (Exception ex) {
          try {
            if (resumableFile != null) {
              resumableFile.close();
            }
          } catch (IOException ex2) {
          }
          deleteFile(fileLockCompanion);
          exception = ex;
          latch.countDown();
        }
      } catch (Throwable t) {
        deleteFile(fileLockCompanion);
        try {
          if (Exception.class.isAssignableFrom(t.getClass())) {
            exception = Exception.class.cast(t);
          } else {
            exception = new Exception(t);
          }
          if (listener != null) {
            listener.transferFailed(
                newEvent(transferResource, exception, RequestType.GET, EventType.FAILED));
          }
        } finally {
          latch.countDown();
        }
      }
    }