Package org.sonatype.nexus.proxy.storage.remote.httpclient

Source Code of org.sonatype.nexus.proxy.storage.remote.httpclient.HttpClientRemoteStorage

/*
* Sonatype Nexus (TM) Open Source Version
* Copyright (c) 2007-2014 Sonatype, Inc.
* All rights reserved. Includes the third-party code listed at http://links.sonatype.com/products/nexus/oss/attributions.
*
* This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0,
* which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html.
*
* Sonatype Nexus (TM) Professional Version is available from Sonatype, Inc. "Sonatype" and "Sonatype Nexus" are trademarks
* of Sonatype, Inc. Apache Maven is a trademark of the Apache Software Foundation. M2eclipse is a trademark of the
* Eclipse Foundation. All other trademarks are the property of their respective owners.
*/

package org.sonatype.nexus.proxy.storage.remote.httpclient;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.charset.UnsupportedCharsetException;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;

import org.sonatype.nexus.SystemStatus;
import org.sonatype.nexus.httpclient.HttpClientFactory;
import org.sonatype.nexus.httpclient.NexusRedirectStrategy;
import org.sonatype.nexus.httpclient.Page;
import org.sonatype.nexus.httpclient.Page.PageContext;
import org.sonatype.nexus.internal.httpclient.RepositoryPageContext;
import org.sonatype.nexus.mime.MimeSupport;
import org.sonatype.nexus.proxy.ItemNotFoundException;
import org.sonatype.nexus.proxy.RemoteAccessDeniedException;
import org.sonatype.nexus.proxy.RemoteAuthenticationNeededException;
import org.sonatype.nexus.proxy.RemoteStorageException;
import org.sonatype.nexus.proxy.RemoteStorageTransportOverloadedException;
import org.sonatype.nexus.proxy.ResourceStoreRequest;
import org.sonatype.nexus.proxy.item.AbstractStorageItem;
import org.sonatype.nexus.proxy.item.ContentLocator;
import org.sonatype.nexus.proxy.item.DefaultStorageFileItem;
import org.sonatype.nexus.proxy.item.PreparedContentLocator;
import org.sonatype.nexus.proxy.item.RepositoryItemUid;
import org.sonatype.nexus.proxy.item.StorageFileItem;
import org.sonatype.nexus.proxy.item.StorageItem;
import org.sonatype.nexus.proxy.repository.ProxyRepository;
import org.sonatype.nexus.proxy.storage.UnsupportedStorageOperationException;
import org.sonatype.nexus.proxy.storage.remote.AbstractHTTPRemoteRepositoryStorage;
import org.sonatype.nexus.proxy.storage.remote.DefaultRemoteStorageContext.BooleanFlagHolder;
import org.sonatype.nexus.proxy.storage.remote.RemoteItemNotFoundException;
import org.sonatype.nexus.proxy.storage.remote.RemoteRepositoryStorage;
import org.sonatype.nexus.proxy.storage.remote.RemoteStorageContext;
import org.sonatype.nexus.proxy.storage.remote.http.QueryStringBuilder;

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.SharedMetricRegistries;
import com.codahale.metrics.Timer;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Stopwatch;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.DateUtils;
import org.apache.http.conn.ConnectionPoolTimeoutException;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.util.EntityUtils;
import org.codehaus.plexus.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Apache HTTP client (4) {@link RemoteRepositoryStorage} implementation.
*
* @since 2.0
*/
@Named(HttpClientRemoteStorage.PROVIDER_STRING)
@Singleton
public class HttpClientRemoteStorage
    extends AbstractHTTPRemoteRepositoryStorage
    implements RemoteRepositoryStorage
{

  static final Logger outboundRequestLog = LoggerFactory.getLogger("remote.storage.outbound");

  // ----------------------------------------------------------------------
  // Constants
  // ----------------------------------------------------------------------

  /**
   * ID of this provider.
   */
  public static final String PROVIDER_STRING = "apacheHttpClient4x";

  /**
   * HTTP header key sent back by Nexus in case of a missing artifact.
   */
  public static final String NEXUS_MISSING_ARTIFACT_HEADER = "x-nexus-missing-artifact";

  /**
   * Context key of HTTP client.
   */
  private static final String CTX_KEY_CLIENT = PROVIDER_STRING + ".client";

  /**
   * Context key of a flag present in case that remote server is an Amazon S3.
   */
  private static final String CTX_KEY_S3_FLAG = PROVIDER_STRING + ".remoteIsAmazonS3";

  /**
   * Created items while retrieving, can be read.
   */
  private static final boolean CAN_READ = true;

  /**
   * Created items while retrieving, can be written.
   */
  private static final boolean CAN_WRITE = true;

  private final MetricRegistry metricRegistry;

  private final QueryStringBuilder queryStringBuilder;

  private final HttpClientManager httpClientManager;

  // ----------------------------------------------------------------------
  // Constructors
  // ----------------------------------------------------------------------

  @Inject
  HttpClientRemoteStorage(final Provider<SystemStatus> systemStatusProvider,
                          final MimeSupport mimeSupport,
                          final QueryStringBuilder queryStringBuilder,
                          final HttpClientManager httpClientManager)
  {
    super(systemStatusProvider, mimeSupport);
    this.metricRegistry = SharedMetricRegistries.getOrCreate("nexus");
    this.queryStringBuilder = queryStringBuilder;
    this.httpClientManager = httpClientManager;
  }

  // ----------------------------------------------------------------------
  // Public methods
  // ----------------------------------------------------------------------

  @Override
  public String getProviderId() {
    return PROVIDER_STRING;
  }

  /**
   * Verifies that path carried by {@link ResourceStoreRequest} is a valid HTTP path segment candidate. If not,
   * {@link ItemNotFoundException} is thrown along with proper log message.
   *
   * @since 3.0
   */
  private void validatePath(final ProxyRepository repository, final ResourceStoreRequest request)
      throws ItemNotFoundException
  {
    try {
      URI.create(request.getRequestPath());
    }
    catch (IllegalArgumentException e) {
      log.warn("Remote HTTP request with malformed path attempted: repository {}, path {}", repository,
          request.getRequestPath());
      throw new ItemNotFoundException(
          ItemNotFoundException
              .reasonFor(request, repository, "Malformed HTTP request path '{}'", request.getRequestPath()),
          e);
    }
  }

  @Override
  public AbstractStorageItem retrieveItem(final ProxyRepository repository, final ResourceStoreRequest request,
                                          final String baseUrl)
      throws ItemNotFoundException, RemoteStorageException
  {
    validatePath(repository, request);
    final URL remoteURL =
        appendQueryString(repository, request, getAbsoluteUrlFromBase(baseUrl, request.getRequestPath()));

    final String url = remoteURL.toExternalForm();
    if (remoteURL.getPath().endsWith("/")) {
      // NEXUS-5125 we do not want to fetch any collection
      // Even though it is unlikely that we actually see a request for a collection here,
      // requests for paths like this over the REST layer will be localOnly not trigger a remote request.
      //
      // The usual case is that there is a request for a directory that is redirected to '/', see below behavior
      // for SC_MOVED_*
      throw new RemoteItemNotFoundException(request, repository, "remoteIsCollection", remoteURL.toString());
    }

    final HttpGet method = new HttpGet(url);

    final HttpResponse httpResponse = executeRequest(repository, request, method, baseUrl, true);

    if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
      InputStream is;
      try {
        is = new Hc4InputStream(repository,
            new InterruptableInputStream(method, httpResponse.getEntity().getContent()));

        String mimeType = null;
        try {
          mimeType = ContentType.getOrDefault(httpResponse.getEntity()).getMimeType();
        }
        catch (ParseException | UnsupportedCharsetException e) {
          // NEXUS-6622: Java/HC4 gave up, let's ask mime support instead then
        }
        if (mimeType == null) {
          mimeType =
              getMimeSupport().guessMimeTypeFromPath(repository.getMimeRulesSource(),
                  request.getRequestPath());
        }

        final long entityLength = httpResponse.getEntity().getContentLength();
        final DefaultStorageFileItem httpItem =
            new DefaultStorageFileItem(repository, request, CAN_READ, CAN_WRITE, new PreparedContentLocator(
                is, mimeType, entityLength != -1 ? entityLength : ContentLocator.UNKNOWN_LENGTH));

        httpItem.setRemoteUrl(remoteURL.toString());
        httpItem.setModified(makeDateFromHeader(httpResponse.getFirstHeader("last-modified")));
        httpItem.setCreated(httpItem.getModified());

        return httpItem;
      }
      catch (IOException ex) {
        release(httpResponse);
        throw new RemoteStorageException("IO Error during response stream handling [repositoryId=\""
            + repository.getId() + "\", requestPath=\"" + request.getRequestPath() + "\", remoteUrl=\""
            + remoteURL.toString() + "\"]!", ex);
      }
      catch (RuntimeException ex) {
        release(httpResponse);
        throw ex;
      }
    }
    else {
      release(httpResponse);
      if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_FOUND) {
        throw new RemoteItemNotFoundException(request, repository, "NotFound", remoteURL.toString());
      }
      else if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY
          || httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_MOVED_PERMANENTLY) {
        // NEXUS-5125 unfollowed redirect means collection (path.endsWith("/"))
        // see also HttpClientUtil#configure
        throw new RemoteItemNotFoundException(request, repository, "redirected", remoteURL.toString());
      }
      else {
        throw new RemoteStorageException("The method execution returned result code "
            + httpResponse.getStatusLine().getStatusCode() + " (expected 200). [repositoryId=\""
            + repository.getId() + "\", requestPath=\"" + request.getRequestPath() + "\", remoteUrl=\""
            + remoteURL.toString() + "\"]");
      }
    }
  }

  @Override
  public void storeItem(final ProxyRepository repository, final StorageItem item)
      throws UnsupportedStorageOperationException, RemoteStorageException
  {
    if (!(item instanceof StorageFileItem)) {
      throw new UnsupportedStorageOperationException("Storing of non-files remotely is not supported!");
    }
    final StorageFileItem fileItem = (StorageFileItem) item;

    final ResourceStoreRequest request = new ResourceStoreRequest(item);

    try {
      validatePath(repository, request);
    }
    catch (ItemNotFoundException e) {
      throw new RemoteStorageException("Invalid path to store", e);
    }

    final URL remoteUrl = appendQueryString(repository, request, getAbsoluteUrlFromBase(repository, request));

    final HttpPut method = new HttpPut(remoteUrl.toExternalForm());

    final InputStreamEntity entity;
    try {
      entity =
          new InputStreamEntity(new InterruptableInputStream(fileItem.getInputStream()), fileItem.getLength());
    }
    catch (IOException e) {
      throw new RemoteStorageException(e.getMessage() + " [repositoryId=\"" + repository.getId()
          + "\", requestPath=\"" + request.getRequestPath() + "\", remoteUrl=\"" + remoteUrl.toString() + "\"]",
          e);
    }

    entity.setContentType(fileItem.getMimeType());
    method.setEntity(entity);

    final HttpResponse httpResponse = executeRequestAndRelease(repository, request, method, repository.getRemoteUrl());
    final int statusCode = httpResponse.getStatusLine().getStatusCode();

    if (statusCode != HttpStatus.SC_OK && statusCode != HttpStatus.SC_CREATED
        && statusCode != HttpStatus.SC_NO_CONTENT && statusCode != HttpStatus.SC_ACCEPTED) {
      throw new RemoteStorageException("Unexpected response code while executing " + method.getMethod()
          + " method [repositoryId=\"" + repository.getId() + "\", requestPath=\"" + request.getRequestPath()
          + "\", remoteUrl=\"" + remoteUrl.toString() + "\"]. Expected: \"any success (2xx)\". Received: "
          + statusCode + " : " + httpResponse.getStatusLine().getReasonPhrase());
    }
  }

  @Override
  public void deleteItem(final ProxyRepository repository, final ResourceStoreRequest request)
      throws ItemNotFoundException, UnsupportedStorageOperationException, RemoteStorageException
  {
    validatePath(repository, request);
    final URL remoteUrl = appendQueryString(repository, request, getAbsoluteUrlFromBase(repository, request));

    final HttpDelete method = new HttpDelete(remoteUrl.toExternalForm());

    final HttpResponse httpResponse = executeRequestAndRelease(repository, request, method, repository.getRemoteUrl());
    final int statusCode = httpResponse.getStatusLine().getStatusCode();

    if (statusCode != HttpStatus.SC_OK && statusCode != HttpStatus.SC_NO_CONTENT
        && statusCode != HttpStatus.SC_ACCEPTED) {
      throw new RemoteStorageException("The response to HTTP " + method.getMethod()
          + " was unexpected HTTP Code " + statusCode + " : " + httpResponse.getStatusLine().getReasonPhrase()
          + " [repositoryId=\"" + repository.getId() + "\", requestPath=\"" + request.getRequestPath()
          + "\", remoteUrl=\"" + remoteUrl.toString() + "\"]");
    }
  }

  @Override
  protected boolean checkRemoteAvailability(final long newerThen, final ProxyRepository repository,
                                            final ResourceStoreRequest request, final boolean isStrict)
      throws RemoteStorageException
  {
    try {
      validatePath(repository, request);
    }
    catch (ItemNotFoundException e) {
      return false;
    }
    final URL remoteUrl = appendQueryString(repository, request, getAbsoluteUrlFromBase(repository, request));

    HttpRequestBase method;
    HttpResponse httpResponse = null;
    int statusCode = HttpStatus.SC_BAD_REQUEST;

    // artifactory hack, it pukes on HEAD so we will try with GET if HEAD fails
    boolean doGet = false;

    {
      method = new HttpHead(remoteUrl.toExternalForm());
      try {
        httpResponse = executeRequestAndRelease(repository, request, method, repository.getRemoteUrl());
        statusCode = httpResponse.getStatusLine().getStatusCode();
      }
      catch (RemoteStorageException e) {
        // If HEAD failed, attempt a GET. Some repos may not support HEAD method
        doGet = true;

        log.debug("HEAD method failed, will attempt GET. Exception: " + e.getMessage(), e);
      }
      finally {
        // HEAD returned error, but not exception, try GET before failing
        if (!doGet && statusCode != HttpStatus.SC_OK) {
          doGet = true;

          log.debug("HEAD method failed, will attempt GET. Status: " + statusCode);
        }
      }
    }

    {
      if (doGet) {
        // create a GET
        method = new HttpGet(remoteUrl.toExternalForm());

        // execute it
        httpResponse = executeRequestAndRelease(repository, request, method, repository.getRemoteUrl());
        statusCode = httpResponse.getStatusLine().getStatusCode();
      }
    }

    // if we are not strict and remote is S3
    if (!isStrict && isRemotePeerAmazonS3Storage(repository)) {
      // if we are relaxed, we will accept any HTTP response code below 500. This means anyway the HTTP
      // transaction succeeded. This method was never really detecting that the remoteUrl really denotes a root of
      // repository (how could we do that?)
      // this "relaxed" check will help us to "pass" S3 remote storage.
      return statusCode >= HttpStatus.SC_OK && statusCode <= HttpStatus.SC_INTERNAL_SERVER_ERROR;
    }
    else {
      // non relaxed check is strict, and will select only the OK response
      if (statusCode == HttpStatus.SC_OK) {
        // we have it OK
        if (newerThen > 0) {
          // we have newer if this below is true
          return makeDateFromHeader(httpResponse.getFirstHeader("last-modified")) > newerThen;
        }
        else {
          // say true as we don't care about actual timestamp
          return true;
        }
      }
      else if ((statusCode >= HttpStatus.SC_MULTIPLE_CHOICES && statusCode < HttpStatus.SC_BAD_REQUEST)
          || statusCode == HttpStatus.SC_NOT_FOUND) {
        if (RepositoryItemUid.PATH_ROOT.equals(request.getRequestPath()) && statusCode == HttpStatus.SC_NOT_FOUND) {
          // NEXUS-5944: Give it a chance: it might be remote Nexus with browsing disabled?
          // to check that, we will check is remote is Nexus by pinging "well know" location
          // if we got it, we will know it's only browsing forbidden on remote
          final RemoteStorageContext ctx = getRemoteStorageContext(repository);
          final HttpClient httpClient = (HttpClient) ctx.getContextObject(CTX_KEY_CLIENT);
          final PageContext pageContext = new RepositoryPageContext(httpClient, repository);
          final ResourceStoreRequest rmRequest = new ResourceStoreRequest("/.meta/repository-metadata.xml");
          final URL nxRepoMetadataUrl = appendQueryString(
              repository, rmRequest,
              getAbsoluteUrlFromBase(repository, rmRequest));
          try {
            final Page page = Page.getPageFor(pageContext, nxRepoMetadataUrl.toExternalForm());
            if (page.getStatusCode() == 200) {
              // this is a Nexus with browsing disabled. say OK
              log.debug(
                  "Original GET request for URL {} failed with 404, but GET request for URL {} succeeded, we assume remote is a Nexus repository having browsing disabled.",
                  remoteUrl, nxRepoMetadataUrl);
              return true;
            }
          }
          catch (IOException e) {
            // just fall trough
          }
        }
        return false;
      }
      else {
        throw new RemoteStorageException("Unexpected response code while executing " + method.getMethod()
            + " method [repositoryId=\"" + repository.getId() + "\", requestPath=\"" + request.getRequestPath()
            + "\", remoteUrl=\"" + remoteUrl.toString() + "\"]. Expected: \"SUCCESS (200)\". Received: "
            + statusCode + " : " + httpResponse.getStatusLine().getReasonPhrase());
      }
    }
  }

  @Override
  protected void updateContext(final ProxyRepository repository, final RemoteStorageContext ctx)
      throws RemoteStorageException
  {
    // reset current http client, if exists
    ctx.removeContextObject(CTX_KEY_CLIENT);
    ctx.removeContextObject(CTX_KEY_S3_FLAG);
    httpClientManager.release(repository, ctx);

    try {
      // and create a new one
      final HttpClient httpClient = httpClientManager.create(repository, ctx);
      ctx.putContextObject(CTX_KEY_CLIENT, httpClient);
      // NEXUS-3338: we don't know after config change is remote S3 (url changed maybe)
      ctx.putContextObject(CTX_KEY_S3_FLAG, new BooleanFlagHolder());
    }
    catch (IllegalStateException e) {
      throw new RemoteStorageException("Could not create HTTPClient4x instance!", e);
    }
  }

  @Override
  protected String getS3FlagKey() {
    return CTX_KEY_S3_FLAG;
  }

  // ----------------------------------------------------------------------
  // Implementation methods
  // ----------------------------------------------------------------------

  /**
   * Executes the HTTP request.
   * <p/>
   * In case of any exception thrown by HttpClient, it will release the connection. In other cases it is the duty of
   * caller to do it, or process the input stream.
   *
   * @param repository  to execute the HTTP method for
   * @param request     resource store request that triggered the HTTP request
   * @param httpRequest HTTP request to be executed
   * @param baseUrl     The BaseURL used to construct final httpRequest
   * @return response of making the request
   * @throws RemoteStorageException If an error occurred during execution of HTTP request
   */
  @VisibleForTesting
  HttpResponse executeRequest(final ProxyRepository repository, final ResourceStoreRequest request,
                              final HttpUriRequest httpRequest, final String baseUrl, final boolean contentRequest)
      throws RemoteStorageException
  {
    final Timer timer = timer(repository, httpRequest, baseUrl);
    final Timer.Context timerContext = timer.time();
    Stopwatch stopwatch = null;
    if (outboundRequestLog.isDebugEnabled()) {
      stopwatch = new Stopwatch().start();
    }
    try {
      return doExecuteRequest(repository, request, httpRequest, contentRequest);
    }
    finally {
      timerContext.stop();
      if (stopwatch != null) {
        outboundRequestLog
            .debug("[{}] {} {} - {}", repository.getId(), httpRequest.getMethod(), httpRequest.getURI(), stopwatch);
      }
    }
  }

  private Timer timer(final ProxyRepository repository, final HttpUriRequest httpRequest, final String baseUrl) {
    return metricRegistry.timer(MetricRegistry.name(HttpClientRemoteStorage.class, baseUrl, httpRequest.getMethod()));
  }

  private HttpResponse doExecuteRequest(final ProxyRepository repository, final ResourceStoreRequest request,
                                        final HttpUriRequest httpRequest, final boolean contentRequest)
      throws RemoteStorageException
  {
    final URI methodUri = httpRequest.getURI();

    if (log.isDebugEnabled()) {
      log.debug("Invoking HTTP {} method against remote location {}", httpRequest.getMethod(), methodUri);
    }

    final RemoteStorageContext ctx = getRemoteStorageContext(repository);

    final HttpClient httpClient = (HttpClient) ctx.getContextObject(CTX_KEY_CLIENT);

    httpRequest.setHeader("Accept", "*/*");
    httpRequest.setHeader("Accept-Language", "en-us");
    httpRequest.setHeader("Accept-Encoding", "gzip,deflate,identity");
    httpRequest.setHeader("Cache-Control", "no-cache");

    HttpResponse httpResponse = null;
    try {
      final BasicHttpContext httpContext = new BasicHttpContext();
      httpContext.setAttribute(HttpClientFactory.HTTP_CTX_KEY_REPOSITORY, repository);
      if (contentRequest) {
        httpContext.setAttribute(NexusRedirectStrategy.CONTENT_RETRIEVAL_MARKER_KEY, Boolean.TRUE);
      }

      httpResponse = httpClient.execute(httpRequest, httpContext);
      final int statusCode = httpResponse.getStatusLine().getStatusCode();

      final Header httpServerHeader = httpResponse.getFirstHeader("server");
      checkForRemotePeerAmazonS3Storage(repository,
          httpServerHeader == null ? null : httpServerHeader.getValue());

      Header proxyReturnedErrorHeader = httpResponse.getFirstHeader(NEXUS_MISSING_ARTIFACT_HEADER);
      boolean proxyReturnedError =
          proxyReturnedErrorHeader != null && Boolean.valueOf(proxyReturnedErrorHeader.getValue());

      if (statusCode == HttpStatus.SC_FORBIDDEN) {
        throw new RemoteAccessDeniedException(repository, methodUri.toASCIIString(),
            httpResponse.getStatusLine().getReasonPhrase());
      }
      else if (statusCode == HttpStatus.SC_UNAUTHORIZED) {
        throw new RemoteAuthenticationNeededException(repository,
            httpResponse.getStatusLine().getReasonPhrase());
      }
      else if (statusCode == HttpStatus.SC_OK && proxyReturnedError) {
        throw new RemoteStorageException(
            "Invalid artifact found, most likely a proxy redirected to an HTML error page.");
      }

      return httpResponse;
    }
    catch (RemoteStorageException ex) {
      release(httpResponse);
      throw ex;
    }
    catch (ClientProtocolException ex) {
      release(httpResponse);
      throw new RemoteStorageException("Protocol error while executing " + httpRequest.getMethod()
          + " method. [repositoryId=\"" + repository.getId() + "\", requestPath=\"" + request.getRequestPath()
          + "\", remoteUrl=\"" + methodUri.toASCIIString() + "\"]", ex);
    }
    catch (ConnectionPoolTimeoutException ex) {
      release(httpResponse);
      throw new RemoteStorageTransportOverloadedException(repository,
          "Connection pool timeout error while executing " + httpRequest.getMethod() + " method [repositoryId=\""
              + repository.getId() + "\", requestPath=\"" + request.getRequestPath() + "\", remoteUrl=\""
              + methodUri.toASCIIString() + "\"]", ex);
    }
    catch (IOException ex) {
      release(httpResponse);
      throw new RemoteStorageException("Transport error while executing " + httpRequest.getMethod()
          + " method [repositoryId=\"" + repository.getId() + "\", requestPath=\"" + request.getRequestPath()
          + "\", remoteUrl=\"" + methodUri.toASCIIString() + "\"]", ex);
    }
  }

  /**
   * Executes the HTTP request and automatically releases any related resources.
   *
   * @param repository  to execute the HTTP method fpr
   * @param request     resource store request that triggered the HTTP request
   * @param httpRequest HTTP request to be executed
   * @return response of making the request
   * @throws RemoteStorageException If an error occurred during execution of HTTP request
   */
  private HttpResponse executeRequestAndRelease(final ProxyRepository repository,
                                                final ResourceStoreRequest request, final HttpUriRequest httpRequest,
                                                final String baseUrl)
      throws RemoteStorageException
  {
    final HttpResponse httpResponse = executeRequest(repository, request, httpRequest, baseUrl, false);
    release(httpResponse);
    return httpResponse;
  }

  /**
   * Make date from header.
   *
   * @param date the date
   * @return the long
   */
  private long makeDateFromHeader(final Header date) {
    long result = System.currentTimeMillis();
    if (date != null) {
      try {
        result = DateUtils.parseDate(date.getValue()).getTime();
      }
      catch (Exception ex) {
        log.warn("Could not parse date '{}', using system current time as item creation time.", date, ex);
      }
    }
    return result;
  }

  /**
   * Appends repository configured additional query string to provided URL.
   *
   * @param repository that may contain additional query string
   * @param request    the current request
   * @param url        the URL of the remote target to append to
   * @return URL with appended query string or original URL if repository does not have an configured query string
   * @throws RemoteStorageException if query string could not be appended (resulted in an Malformed URL exception)
   */
  private URL appendQueryString(final ProxyRepository repository, final ResourceStoreRequest request, final URL url)
      throws RemoteStorageException
  {
    final RemoteStorageContext ctx = getRemoteStorageContext(repository);
    final String queryString = queryStringBuilder.getQueryString(ctx, repository);
    if (StringUtils.isNotBlank(queryString)) {
      try {
        if (StringUtils.isBlank(url.getQuery())) {
          return new URL(url.toExternalForm() + "?" + queryString);
        }
        else {
          return new URL(url.toExternalForm() + "&" + queryString);
        }
      }
      catch (MalformedURLException e) {
        throw new RemoteStorageException("Could not append query string \"" + queryString + "\" to url \""
            + url + "\"", e);
      }
    }
    return url;
  }

  /**
   * Releases connection resources (back to pool). If an exception appears during releasing, exception is just
   * logged.
   *
   * @param httpResponse to be released
   */
  private void release(final HttpResponse httpResponse) {
    if (httpResponse != null) {
      try {
        EntityUtils.consume(httpResponse.getEntity());
      }
      catch (IOException e) {
        log.warn("Failed to consume entity: " + e); // terse
      }
    }
  }

}
TOP

Related Classes of org.sonatype.nexus.proxy.storage.remote.httpclient.HttpClientRemoteStorage

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.