org.sonatype.nexus.proxy.storage.remote.commonshttpclient.CommonsHttpClientRemoteStorage.java Source code

Java tutorial

Introduction

Here is the source code for org.sonatype.nexus.proxy.storage.remote.commonshttpclient.CommonsHttpClientRemoteStorage.java

Source

/*
 * Sonatype Nexus (TM) Open Source Version
 * Copyright (c) 2007-2012 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.commonshttpclient;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.zip.GZIPInputStream;

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

import org.apache.commons.httpclient.CustomMultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.util.DateParseException;
import org.apache.commons.httpclient.util.DateUtil;
import org.codehaus.plexus.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonatype.nexus.ApplicationStatusSource;
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.ResourceStoreRequest;
import org.sonatype.nexus.proxy.item.AbstractStorageItem;
import org.sonatype.nexus.proxy.item.DefaultStorageFileItem;
import org.sonatype.nexus.proxy.item.PreparedContentLocator;
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.utils.UserAgentBuilder;

import com.google.common.base.Stopwatch;

/**
 * The Class CommonsHttpClientRemoteStorage.
 * 
 * @author cstamas
 *
 * @deprecated Use httpclient4 components instead
 */
@Deprecated
@Named(CommonsHttpClientRemoteStorage.PROVIDER_STRING)
@Singleton
public class CommonsHttpClientRemoteStorage extends AbstractHTTPRemoteRepositoryStorage
        implements RemoteRepositoryStorage {
    private static final Logger timingLog = LoggerFactory.getLogger("remote.storage.timing");

    public static final String PROVIDER_STRING = "apacheHttpClient3x";

    public static final String CTX_KEY = PROVIDER_STRING;

    public static final String CTX_KEY_CLIENT = CTX_KEY + ".client";

    public static final String CTX_KEY_HTTP_CONFIGURATION = CTX_KEY + ".httpConfiguration";

    public static final String CTX_KEY_S3_FLAG = CTX_KEY + ".remoteIsAmazonS3";

    public static final String NEXUS_MISSING_ARTIFACT_HEADER = "x-nexus-missing-artifact";

    @Inject
    protected CommonsHttpClientRemoteStorage(final UserAgentBuilder userAgentBuilder,
            final ApplicationStatusSource applicationStatusSource, final MimeSupport mimeSupport) {
        super(userAgentBuilder, applicationStatusSource, mimeSupport);
    }

    // ===============================================================================
    // RemoteStorage iface

    public String getProviderId() {
        return PROVIDER_STRING;
    }

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

        HttpMethod method;

        method = new GetMethod(remoteURL.toString());

        int response = executeMethod(repository, request, method, remoteURL);

        if (response == HttpStatus.SC_OK) {
            if (method.getPath().endsWith("/")) {
                // this is a collection and not a file!
                // httpClient will follow redirections, and the getPath()
                // _should_
                // give us URL with ending "/"
                method.releaseConnection();

                throw new RemoteItemNotFoundException(request, repository, "remoteIsCollection",
                        remoteURL.toString());
            }

            GetMethod get = (GetMethod) method;

            InputStream is;

            try {
                is = get.getResponseBodyAsStream();
                if (get.getResponseHeader("Content-Encoding") != null
                        && "gzip".equals(get.getResponseHeader("Content-Encoding").getValue())) {
                    is = new GZIPInputStream(is);
                }

                String mimeType;

                if (method.getResponseHeader("content-type") != null) {
                    mimeType = method.getResponseHeader("content-type").getValue();
                } else {
                    mimeType = getMimeSupport().guessMimeTypeFromPath(repository.getMimeRulesSource(),
                            request.getRequestPath());
                }

                DefaultStorageFileItem httpItem = new DefaultStorageFileItem(repository, request, true, true,
                        new PreparedContentLocator(new HttpClientInputStream(get, is), mimeType));

                if (get.getResponseContentLength() != -1) {
                    // FILE
                    httpItem.setLength(get.getResponseContentLength());
                }

                httpItem.setRemoteUrl(remoteURL.toString());

                httpItem.setModified(makeDateFromHeader(method.getResponseHeader("last-modified")));

                httpItem.setCreated(httpItem.getModified());

                httpItem.getItemContext().putAll(request.getRequestContext());

                return httpItem;
            } catch (IOException ex) {
                method.releaseConnection();

                throw new RemoteStorageException("IO Error during response stream handling [repositoryId=\""
                        + repository.getId() + "\", requestPath=\"" + request.getRequestPath() + "\", remoteUrl=\""
                        + remoteURL.toString() + "\"]!", ex);
            } catch (RuntimeException ex) {
                method.releaseConnection();

                throw ex;
            }
        } else {
            method.releaseConnection();

            if (response == HttpStatus.SC_NOT_FOUND) {
                throw new RemoteItemNotFoundException(request, repository, "NotFound", remoteURL.toString());
            } else {
                throw new RemoteStorageException("The method execution returned result code " + response
                        + " (expected 200). [repositoryId=\"" + repository.getId() + "\", requestPath=\""
                        + request.getRequestPath() + "\", remoteUrl=\"" + remoteURL.toString() + "\"]");
            }
        }
    }

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

        StorageFileItem fItem = (StorageFileItem) item;

        ResourceStoreRequest request = new ResourceStoreRequest(item);

        URL remoteURL = getAbsoluteUrlFromBase(repository, request);

        PutMethod method = new PutMethod(remoteURL.toString());

        try {
            method.setRequestEntity(
                    new InputStreamRequestEntity(fItem.getInputStream(), fItem.getLength(), fItem.getMimeType()));

            int response = executeMethod(repository, request, method, remoteURL);

            if (response != HttpStatus.SC_OK && response != HttpStatus.SC_CREATED
                    && response != HttpStatus.SC_NO_CONTENT && response != HttpStatus.SC_ACCEPTED) {
                throw new RemoteStorageException("Unexpected response code while executing " + method.getName()
                        + " method [repositoryId=\"" + repository.getId() + "\", requestPath=\""
                        + request.getRequestPath() + "\", remoteUrl=\"" + remoteURL.toString()
                        + "\"]. Expected: \"any success (2xx)\". Received: " + response + " : "
                        + HttpStatus.getStatusText(response));
            }
        } catch (IOException e) {
            throw new RemoteStorageException(
                    e.getMessage() + " [repositoryId=\"" + repository.getId() + "\", requestPath=\""
                            + request.getRequestPath() + "\", remoteUrl=\"" + remoteURL.toString() + "\"]",
                    e);
        } finally {
            method.releaseConnection();
        }
    }

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

        DeleteMethod method = new DeleteMethod(remoteURL.toString());

        try {
            int response = executeMethod(repository, request, method, remoteURL);

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

    protected void updateContext(ProxyRepository repository, RemoteStorageContext ctx) {
        HttpClient httpClient = new HttpClient(new CustomMultiThreadedHttpConnectionManager());

        HttpClientProxyUtil.applyProxyToHttpClient(httpClient, ctx, getLogger());

        ctx.putContextObject(CTX_KEY_CLIENT, httpClient);

        ctx.putContextObject(CTX_KEY_HTTP_CONFIGURATION, httpClient.getHostConfiguration());

        // NEXUS-3338: we don't know afer config change is remote S3 (url changed maybe)
        ctx.putContextObject(CTX_KEY_S3_FLAG, new BooleanFlagHolder());
    }

    protected int executeMethod(ProxyRepository repository, ResourceStoreRequest request, HttpMethod method,
            URL remoteUrl) throws RemoteStorageException {
        final Stopwatch stopwatch = timingLog.isDebugEnabled() ? new Stopwatch().start() : null;
        try {
            return doExecuteMethod(repository, request, method, remoteUrl);
        } finally {
            if (stopwatch != null) {
                stopwatch.stop();
                timingLog.debug("[{}] {} {} took {}",
                        new Object[] { repository.getId(), method.getName(), remoteUrl, stopwatch });
            }
        }
    }

    /**
     * Execute method. 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 method the method
     * @return the int
     */
    protected int doExecuteMethod(ProxyRepository repository, ResourceStoreRequest request, HttpMethod method,
            URL remoteUrl) throws RemoteStorageException {
        URI methodURI = null;

        try {
            methodURI = method.getURI();
        } catch (URIException e) {
            getLogger().debug("Could not format debug log message", e);
        }

        if (getLogger().isDebugEnabled()) {
            getLogger().debug("Invoking HTTP " + method.getName() + " method against remote location " + methodURI);
        }

        RemoteStorageContext ctx = getRemoteStorageContext(repository);

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

        HostConfiguration httpConfiguration = (HostConfiguration) ctx.getContextObject(CTX_KEY_HTTP_CONFIGURATION);

        method.setRequestHeader(new Header("user-agent", formatUserAgentString(ctx, repository)));
        method.setRequestHeader(new Header("accept", "*/*"));
        method.setRequestHeader(new Header("accept-language", "en-us"));
        method.setRequestHeader(new Header("accept-encoding", "gzip, identity"));
        method.setRequestHeader(new Header("cache-control", "no-cache"));

        // HTTP keep alive should not be used, except when NTLM is used
        Boolean isNtlmUsed = (Boolean) ctx.getContextObject(HttpClientProxyUtil.NTLM_IS_IN_USE_KEY);

        if (isNtlmUsed == null || !isNtlmUsed) {
            method.setRequestHeader(new Header("Connection", "close"));
            method.setRequestHeader(new Header("Proxy-Connection", "close"));
        }

        method.setFollowRedirects(true);

        if (StringUtils.isNotBlank(ctx.getRemoteConnectionSettings().getQueryString())) {
            method.setQueryString(ctx.getRemoteConnectionSettings().getQueryString());
        }

        int resultCode;

        try {
            resultCode = httpClient.executeMethod(httpConfiguration, method);

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

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

            if (resultCode == HttpStatus.SC_FORBIDDEN) {
                throw new RemoteAccessDeniedException(repository, remoteUrl,
                        HttpStatus.getStatusText(HttpStatus.SC_FORBIDDEN));
            } else if (resultCode == HttpStatus.SC_UNAUTHORIZED) {
                throw new RemoteAuthenticationNeededException(repository,
                        HttpStatus.getStatusText(HttpStatus.SC_UNAUTHORIZED));
            } else if (resultCode == HttpStatus.SC_OK && proxyReturnedError) {
                throw new RemoteStorageException(
                        "Invalid artifact found, most likely a proxy redirected to an HTML error page.");
            }
        } catch (RemoteStorageException e) {
            method.releaseConnection();

            throw e;
        } catch (HttpException ex) {
            method.releaseConnection();

            throw new RemoteStorageException("Protocol error while executing " + method.getName()
                    + " method. [repositoryId=\"" + repository.getId() + "\", requestPath=\""
                    + request.getRequestPath() + "\", remoteUrl=\"" + methodURI + "\"]", ex);
        } catch (IOException ex) {
            method.releaseConnection();

            throw new RemoteStorageException("Transport error while executing " + method.getName()
                    + " method [repositoryId=\"" + repository.getId() + "\", requestPath=\""
                    + request.getRequestPath() + "\", remoteUrl=\"" + methodURI + "\"]", ex);
        }

        return resultCode;
    }

    /**
     * Make date from header.
     * 
     * @param date the date
     * @return the long
     */
    protected long makeDateFromHeader(Header date) {
        long result = System.currentTimeMillis();
        if (date != null) {
            try {
                result = DateUtil.parseDate(date.getValue()).getTime();
            } catch (DateParseException ex) {
                getLogger().warn(
                        "Could not parse date '" + date + "', using system current time as item creation time.",
                        ex);
            } catch (NullPointerException ex) {
                getLogger().warn("Parsed date is null, using system current time as item creation time.");
            }
        }
        return result;
    }

    @Override
    protected boolean checkRemoteAvailability(long newerThen, ProxyRepository repository,
            ResourceStoreRequest request, boolean isStrict) throws RemoteStorageException {
        URL remoteURL = getAbsoluteUrlFromBase(repository, request);

        HttpMethodBase method = new HeadMethod(remoteURL.toString());

        int response = HttpStatus.SC_BAD_REQUEST;

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

        try {
            response = executeMethod(repository, request, method, remoteURL);
        } catch (RemoteStorageException e) {
            // If HEAD failed, attempt a GET. Some repos may not support HEAD method
            doGet = true;

            getLogger().debug("HEAD method failed, will attempt GET.  Exception: " + e.getMessage(), e);
        } finally {
            method.releaseConnection();

            // HEAD returned error, but not exception, try GET before failing
            if (!doGet && response != HttpStatus.SC_OK) {
                doGet = true;

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

        if (doGet) {
            // create a GET
            method = new GetMethod(remoteURL.toString());

            try {
                // execute it
                response = executeMethod(repository, request, method, remoteURL);
            } finally {
                // and release it immediately
                method.releaseConnection();
            }
        }

        // 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 response >= HttpStatus.SC_OK && response <= HttpStatus.SC_INTERNAL_SERVER_ERROR;
        } else {
            // non relaxed check is strict, and will select only the OK response
            if (response == HttpStatus.SC_OK) {
                // we have it
                // we have newer if this below is true
                return makeDateFromHeader(method.getResponseHeader("last-modified")) > newerThen;
            } else if ((response >= HttpStatus.SC_MULTIPLE_CHOICES && response < HttpStatus.SC_BAD_REQUEST)
                    || response == HttpStatus.SC_NOT_FOUND) {
                return false;
            } else {
                throw new RemoteStorageException("Unexpected response code while executing " + method.getName()
                        + " method [repositoryId=\"" + repository.getId() + "\", requestPath=\""
                        + request.getRequestPath() + "\", remoteUrl=\"" + remoteURL.toString()
                        + "\"]. Expected: \"SUCCESS (200)\". Received: " + response + " : "
                        + HttpStatus.getStatusText(response));
            }
        }
    }

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

}