org.apache.maven.wagon.providers.webdav.AbstractHttpClientWagon.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.maven.wagon.providers.webdav.AbstractHttpClientWagon.java

Source

package org.apache.maven.wagon.providers.webdav;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpConnectionManager;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.NTCredentials;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.httpclient.util.DateParseException;
import org.apache.commons.httpclient.util.DateUtil;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.wagon.InputData;
import org.apache.maven.wagon.OutputData;
import org.apache.maven.wagon.PathUtils;
import org.apache.maven.wagon.ResourceDoesNotExistException;
import org.apache.maven.wagon.StreamWagon;
import org.apache.maven.wagon.TransferFailedException;
import org.apache.maven.wagon.Wagon;
import org.apache.maven.wagon.authorization.AuthorizationException;
import org.apache.maven.wagon.events.TransferEvent;
import org.apache.maven.wagon.proxy.ProxyInfo;
import org.apache.maven.wagon.repository.Repository;
import org.apache.maven.wagon.resource.Resource;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Properties;
import java.util.TimeZone;
import java.util.zip.GZIPInputStream;

/**
 * @author <a href="michal.maczka@dimatics.com">Michal Maczka</a>
 * @author <a href="mailto:james@atlassian.com">James William Dumay</a>
 */
public abstract class AbstractHttpClientWagon extends StreamWagon {
    private final class RequestEntityImplementation implements RequestEntity {
        private final Resource resource;

        private final Wagon wagon;

        private File source;

        private ByteBuffer byteBuffer;

        private RequestEntityImplementation(final InputStream stream, final Resource resource, final Wagon wagon,
                final File source) throws TransferFailedException {
            if (source != null) {
                this.source = source;
            } else {
                try {
                    byte[] bytes = IOUtils.toByteArray(stream);
                    this.byteBuffer = ByteBuffer.allocate(bytes.length);
                    this.byteBuffer.put(bytes);
                } catch (IOException e) {
                    throw new TransferFailedException(e.getMessage(), e);
                }
            }

            this.resource = resource;
            this.wagon = wagon;
        }

        public long getContentLength() {
            return resource.getContentLength();
        }

        public String getContentType() {
            return null;
        }

        public boolean isRepeatable() {
            return true;
        }

        public void writeRequest(OutputStream output) throws IOException {
            byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];

            TransferEvent transferEvent = new TransferEvent(wagon, resource, TransferEvent.TRANSFER_PROGRESS,
                    TransferEvent.REQUEST_PUT);
            transferEvent.setTimestamp(System.currentTimeMillis());

            InputStream fin = null;
            try {
                fin = this.source != null ? new FileInputStream(source)
                        : new ByteArrayInputStream(this.byteBuffer.array());
                int remaining = Integer.MAX_VALUE;
                while (remaining > 0) {
                    int n = fin.read(buffer, 0, Math.min(buffer.length, remaining));

                    if (n == -1) {
                        break;
                    }

                    fireTransferProgress(transferEvent, buffer, n);

                    output.write(buffer, 0, n);

                    remaining -= n;
                }
            } finally {
                IOUtils.closeQuietly(fin);
            }

            output.flush();
        }
    }

    protected static final int SC_NULL = -1;

    protected static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone("GMT");

    private HttpClient client;

    protected HttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();

    /**
     * @deprecated Use httpConfiguration instead.
     */
    private Properties httpHeaders;

    /**
     * @since 1.0-beta-6
     */
    private HttpConfiguration httpConfiguration;

    private HttpMethod getMethod;

    public void openConnectionInternal() {
        repository.setUrl(getURL(repository));
        client = new HttpClient(connectionManager);

        // WAGON-273: default the cookie-policy to browser compatible
        client.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);

        String username = null;
        String password = null;
        String domain = null;

        if (authenticationInfo != null) {
            username = authenticationInfo.getUserName();

            if (StringUtils.contains(username, "\\")) {
                String[] domainAndUsername = username.split("\\\\");
                domain = domainAndUsername[0];
                username = domainAndUsername[1];
            }

            password = authenticationInfo.getPassword();

        }

        String host = getRepository().getHost();

        if (StringUtils.isNotEmpty(username) && StringUtils.isNotEmpty(password)) {
            Credentials creds;
            if (domain != null) {
                creds = new NTCredentials(username, password, host, domain);
            } else {
                creds = new UsernamePasswordCredentials(username, password);
            }

            int port = getRepository().getPort() > -1 ? getRepository().getPort() : AuthScope.ANY_PORT;

            AuthScope scope = new AuthScope(host, port);
            client.getState().setCredentials(scope, creds);
        }

        HostConfiguration hc = new HostConfiguration();

        ProxyInfo proxyInfo = getProxyInfo(getRepository().getProtocol(), getRepository().getHost());
        if (proxyInfo != null) {
            String proxyUsername = proxyInfo.getUserName();
            String proxyPassword = proxyInfo.getPassword();
            String proxyHost = proxyInfo.getHost();
            int proxyPort = proxyInfo.getPort();
            String proxyNtlmHost = proxyInfo.getNtlmHost();
            String proxyNtlmDomain = proxyInfo.getNtlmDomain();
            if (proxyHost != null) {
                hc.setProxy(proxyHost, proxyPort);

                if (proxyUsername != null && proxyPassword != null) {
                    Credentials creds;
                    if (proxyNtlmHost != null || proxyNtlmDomain != null) {
                        creds = new NTCredentials(proxyUsername, proxyPassword, proxyNtlmHost, proxyNtlmDomain);
                    } else {
                        creds = new UsernamePasswordCredentials(proxyUsername, proxyPassword);
                    }

                    int port = proxyInfo.getPort() > -1 ? proxyInfo.getPort() : AuthScope.ANY_PORT;

                    AuthScope scope = new AuthScope(proxyHost, port);
                    client.getState().setProxyCredentials(scope, creds);
                }
            }
        }

        hc.setHost(host);

        //start a session with the webserver
        client.setHostConfiguration(hc);
    }

    public void closeConnection() {
        if (connectionManager instanceof MultiThreadedHttpConnectionManager) {
            ((MultiThreadedHttpConnectionManager) connectionManager).shutdown();
        }
    }

    public void put(File source, String resourceName)
            throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
        Resource resource = new Resource(resourceName);

        firePutInitiated(resource, source);

        resource.setContentLength(source.length());

        resource.setLastModified(source.lastModified());

        put(null, resource, source);
    }

    public void putFromStream(final InputStream stream, String destination, long contentLength, long lastModified)
            throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
        Resource resource = new Resource(destination);

        firePutInitiated(resource, null);

        resource.setContentLength(contentLength);

        resource.setLastModified(lastModified);

        put(stream, resource, null);
    }

    private void put(final InputStream stream, Resource resource, File source)
            throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException {
        StringBuilder url = new StringBuilder(getRepository().getUrl());
        String[] parts = StringUtils.split(resource.getName(), "/");
        for (String part : parts) {
            // TODO: Fix encoding...
            if (!url.toString().endsWith("/")) {
                url.append('/');
            }
            url.append(URLEncoder.encode(part));
        }
        RequestEntityImplementation requestEntityImplementation = new RequestEntityImplementation(stream, resource,
                this, source);
        put(resource, source, requestEntityImplementation, url.toString());

    }

    private void put(Resource resource, File source, RequestEntityImplementation requestEntityImplementation,
            String url) throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException {

        // preemptive true for put
        client.getParams().setAuthenticationPreemptive(true);

        //Parent directories need to be created before posting
        try {
            mkdirs(PathUtils.dirname(resource.getName()));
        } catch (IOException e) {
            fireTransferError(resource, e, TransferEvent.REQUEST_GET);
        }

        PutMethod putMethod = new PutMethod(url);

        firePutStarted(resource, source);

        try {
            putMethod.setRequestEntity(requestEntityImplementation);

            int statusCode;
            try {
                statusCode = execute(putMethod);

            } catch (IOException e) {
                fireTransferError(resource, e, TransferEvent.REQUEST_PUT);

                throw new TransferFailedException(e.getMessage(), e);
            }

            fireTransferDebug(url + " - Status code: " + statusCode);

            // Check that we didn't run out of retries.
            switch (statusCode) {
            // Success Codes
            case HttpStatus.SC_OK: // 200
            case HttpStatus.SC_CREATED: // 201
            case HttpStatus.SC_ACCEPTED: // 202
            case HttpStatus.SC_NO_CONTENT: // 204
                break;

            // handle all redirect even if http specs says " the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user"
            case HttpStatus.SC_MOVED_PERMANENTLY: // 301
            case HttpStatus.SC_MOVED_TEMPORARILY: // 302
            case HttpStatus.SC_SEE_OTHER: // 303
                String relocatedUrl = calculateRelocatedUrl(putMethod);
                fireTransferDebug("relocate to " + relocatedUrl);
                put(resource, source, requestEntityImplementation, relocatedUrl);
                return;

            case SC_NULL: {
                TransferFailedException e = new TransferFailedException("Failed to transfer file: " + url);
                fireTransferError(resource, e, TransferEvent.REQUEST_PUT);
                throw e;
            }

            case HttpStatus.SC_FORBIDDEN:
                fireSessionConnectionRefused();
                throw new AuthorizationException("Access denied to: " + url);

            case HttpStatus.SC_NOT_FOUND:
                throw new ResourceDoesNotExistException("File: " + url + " does not exist");

                //add more entries here
            default: {
                TransferFailedException e = new TransferFailedException(
                        "Failed to transfer file: " + url + ". Return code is: " + statusCode);
                fireTransferError(resource, e, TransferEvent.REQUEST_PUT);
                throw e;
            }
            }

            firePutCompleted(resource, source);
        } finally {
            putMethod.releaseConnection();
        }
    }

    protected String calculateRelocatedUrl(EntityEnclosingMethod method) {
        Header locationHeader = method.getResponseHeader("Location");
        String locationField = locationHeader.getValue();
        // is it a relative Location or a full ?
        return locationField.startsWith("http") ? locationField : getURL(getRepository()) + '/' + locationField;
    }

    protected void mkdirs(String dirname) throws IOException {
        // do nothing as default.
    }

    public boolean resourceExists(String resourceName) throws TransferFailedException, AuthorizationException {
        StringBuilder url = new StringBuilder(getRepository().getUrl());
        if (!url.toString().endsWith("/")) {
            url.append('/');
        }
        url.append(resourceName);
        HeadMethod headMethod = new HeadMethod(url.toString());

        int statusCode;
        try {
            statusCode = execute(headMethod);
        } catch (IOException e) {
            throw new TransferFailedException(e.getMessage(), e);
        }
        try {
            switch (statusCode) {
            case HttpStatus.SC_OK:
                return true;

            case HttpStatus.SC_NOT_MODIFIED:
                return true;

            case SC_NULL:
                throw new TransferFailedException("Failed to transfer file: " + url);

            case HttpStatus.SC_FORBIDDEN:
                throw new AuthorizationException("Access denied to: " + url);

            case HttpStatus.SC_UNAUTHORIZED:
                throw new AuthorizationException("Not authorized.");

            case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
                throw new AuthorizationException("Not authorized by proxy.");

            case HttpStatus.SC_NOT_FOUND:
                return false;

            //add more entries here
            default:
                throw new TransferFailedException(
                        "Failed to transfer file: " + url + ". Return code is: " + statusCode);
            }
        } finally {
            headMethod.releaseConnection();
        }
    }

    protected int execute(HttpMethod httpMethod) throws IOException {
        int statusCode;

        setParameters(httpMethod);
        setHeaders(httpMethod);

        statusCode = client.executeMethod(httpMethod);
        return statusCode;
    }

    protected void setParameters(HttpMethod method) {
        HttpMethodConfiguration config = httpConfiguration == null ? null
                : httpConfiguration.getMethodConfiguration(method);
        if (config != null) {
            HttpMethodParams params = config.asMethodParams(method.getParams());
            if (params != null) {
                method.setParams(params);
            }
        }

        if (config == null || config.getConnectionTimeout() == HttpMethodConfiguration.DEFAULT_CONNECTION_TIMEOUT) {
            method.getParams().setSoTimeout(getTimeout());
        }
    }

    protected void setHeaders(HttpMethod method) {
        HttpMethodConfiguration config = httpConfiguration == null ? null
                : httpConfiguration.getMethodConfiguration(method);
        if (config == null || config.isUseDefaultHeaders()) {
            // TODO: merge with the other headers and have some better defaults, unify with lightweight headers
            method.addRequestHeader("Cache-control", "no-cache");
            method.addRequestHeader("Cache-store", "no-store");
            method.addRequestHeader("Pragma", "no-cache");
            method.addRequestHeader("Expires", "0");
            method.addRequestHeader("Accept-Encoding", "gzip");
        }

        if (httpHeaders != null) {
            for (Object header : httpHeaders.keySet()) {
                method.addRequestHeader((String) header, httpHeaders.getProperty((String) header));
            }
        }

        Header[] headers = config == null ? null : config.asRequestHeaders();
        if (headers != null) {
            for (Header header : headers) {
                method.addRequestHeader(header);
            }
        }
    }

    /**
     * getUrl
     * Implementors can override this to remove unwanted parts of the url such as role-hints
     *
     * @param repository
     * @return
     */
    protected String getURL(Repository repository) {
        return repository.getUrl();
    }

    protected HttpClient getClient() {
        return client;
    }

    public void setConnectionManager(HttpConnectionManager connectionManager) {
        this.connectionManager = connectionManager;
    }

    public Properties getHttpHeaders() {
        return httpHeaders;
    }

    public void setHttpHeaders(Properties httpHeaders) {
        this.httpHeaders = httpHeaders;
    }

    public HttpConfiguration getHttpConfiguration() {
        return httpConfiguration;
    }

    public void setHttpConfiguration(HttpConfiguration httpConfiguration) {
        this.httpConfiguration = httpConfiguration;
    }

    public void fillInputData(InputData inputData)
            throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
        Resource resource = inputData.getResource();

        StringBuilder url = new StringBuilder(getRepository().getUrl());
        if (!url.toString().endsWith("/")) {
            url.append('/');
        }
        url.append(resource.getName());

        getMethod = new GetMethod(url.toString());

        long timestamp = resource.getLastModified();
        if (timestamp > 0) {
            SimpleDateFormat fmt = new SimpleDateFormat("EEE, dd-MMM-yy HH:mm:ss zzz", Locale.US);
            fmt.setTimeZone(GMT_TIME_ZONE);
            Header hdr = new Header("If-Modified-Since", fmt.format(new Date(timestamp)));
            fireTransferDebug("sending ==> " + hdr + "(" + timestamp + ")");
            getMethod.addRequestHeader(hdr);
        }

        int statusCode;
        try {
            statusCode = execute(getMethod);
        } catch (IOException e) {
            fireTransferError(resource, e, TransferEvent.REQUEST_GET);

            throw new TransferFailedException(e.getMessage(), e);
        }

        fireTransferDebug(url + " - Status code: " + statusCode);

        // TODO [BP]: according to httpclient docs, really should swallow the output on error. verify if that is
        // required
        switch (statusCode) {
        case HttpStatus.SC_OK:
            break;

        case HttpStatus.SC_NOT_MODIFIED:
            // return, leaving last modified set to original value so getIfNewer should return unmodified
            return;

        case SC_NULL: {
            TransferFailedException e = new TransferFailedException("Failed to transfer file: " + url);
            fireTransferError(resource, e, TransferEvent.REQUEST_GET);
            throw e;
        }

        case HttpStatus.SC_FORBIDDEN:
            fireSessionConnectionRefused();
            throw new AuthorizationException("Access denied to: " + url);

        case HttpStatus.SC_UNAUTHORIZED:
            fireSessionConnectionRefused();
            throw new AuthorizationException("Not authorized.");

        case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
            fireSessionConnectionRefused();
            throw new AuthorizationException("Not authorized by proxy.");

        case HttpStatus.SC_NOT_FOUND:
            throw new ResourceDoesNotExistException("File: " + url + " does not exist");

            // add more entries here
        default: {
            cleanupGetTransfer(resource);
            TransferFailedException e = new TransferFailedException(
                    "Failed to transfer file: " + url + ". Return code is: " + statusCode);
            fireTransferError(resource, e, TransferEvent.REQUEST_GET);
            throw e;
        }
        }

        InputStream is = null;

        Header contentLengthHeader = getMethod.getResponseHeader("Content-Length");

        if (contentLengthHeader != null) {
            try {
                long contentLength = Integer.valueOf(contentLengthHeader.getValue()).intValue();

                resource.setContentLength(contentLength);
            } catch (NumberFormatException e) {
                fireTransferDebug(
                        "error parsing content length header '" + contentLengthHeader.getValue() + "' " + e);
            }
        }

        Header lastModifiedHeader = getMethod.getResponseHeader("Last-Modified");

        long lastModified = 0;

        if (lastModifiedHeader != null) {
            try {
                lastModified = DateUtil.parseDate(lastModifiedHeader.getValue()).getTime();

                resource.setLastModified(lastModified);
            } catch (DateParseException e) {
                fireTransferDebug("Unable to parse last modified header");
            }

            fireTransferDebug("last-modified = " + lastModifiedHeader.getValue() + " (" + lastModified + ")");
        }

        Header contentEncoding = getMethod.getResponseHeader("Content-Encoding");
        boolean isGZipped = contentEncoding != null && "gzip".equalsIgnoreCase(contentEncoding.getValue());

        try {
            is = getMethod.getResponseBodyAsStream();
            if (isGZipped) {
                is = new GZIPInputStream(is);
            }
        } catch (IOException e) {
            fireTransferError(resource, e, TransferEvent.REQUEST_GET);

            String msg = "Error occurred while retrieving from remote repository:" + getRepository() + ": "
                    + e.getMessage();

            throw new TransferFailedException(msg, e);
        }

        inputData.setInputStream(is);
    }

    protected void cleanupGetTransfer(Resource resource) {
        if (getMethod != null) {
            getMethod.releaseConnection();
        }
    }

    @Override
    public void putFromStream(InputStream stream, String destination)
            throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
        putFromStream(stream, destination, -1, -1);
    }

    @Override
    protected void putFromStream(InputStream stream, Resource resource)
            throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException {
        putFromStream(stream, resource.getName(), -1, -1);
    }

    @Override
    public void fillOutputData(OutputData outputData) throws TransferFailedException {
        // no needed in this implementation but throw an Exception if used
        throw new IllegalStateException("this wagon http client must not use fillOutputData");
    }
}