ch.cyberduck.core.azure.AzureSession.java Source code

Java tutorial

Introduction

Here is the source code for ch.cyberduck.core.azure.AzureSession.java

Source

package ch.cyberduck.core.azure;

/*
 * Copyright (c) 2002-2010 David Kocher. All rights reserved.
 *
 * http://cyberduck.ch/
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Bug fixes, suggestions and comments should be sent to:
 * dkocher@cyberduck.ch
 */

import ch.cyberduck.core.*;
import ch.cyberduck.core.cloud.CloudHTTP4Session;
import ch.cyberduck.core.i18n.Locale;

import org.apache.commons.lang.StringUtils;
import org.apache.http.*;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.Element;
import org.soyatec.windows.azure.authenticate.IAccessPolicy;
import org.soyatec.windows.azure.authenticate.SharedKeyCredentials;
import org.soyatec.windows.azure.blob.*;
import org.soyatec.windows.azure.blob.internal.*;
import org.soyatec.windows.azure.constants.HeaderValues;
import org.soyatec.windows.azure.constants.XmlElementNames;
import org.soyatec.windows.azure.error.StorageErrorCode;
import org.soyatec.windows.azure.error.StorageException;
import org.soyatec.windows.azure.error.StorageServerException;
import org.soyatec.windows.azure.internal.AccessPolicy;
import org.soyatec.windows.azure.internal.OutParameter;
import org.soyatec.windows.azure.internal.ResourceUriComponents;
import org.soyatec.windows.azure.internal.SignedIdentifier;
import org.soyatec.windows.azure.internal.constants.*;
import org.soyatec.windows.azure.util.HttpUtilities;
import org.soyatec.windows.azure.util.NameValueCollection;
import org.soyatec.windows.azure.util.TimeSpan;
import org.soyatec.windows.azure.util.xml.XPathQueryHelper;
import org.soyatec.windows.azure.util.xml.XmlUtil;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.Timestamp;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.Callable;

/**
 * @version $Id$
 */
public class AzureSession extends CloudHTTP4Session {
    private static Logger log = Logger.getLogger(AzureSession.class);

    private static class Factory extends SessionFactory {
        @Override
        protected Session create(Host h) {
            return new AzureSession(h);
        }
    }

    public static SessionFactory factory() {
        return new Factory();
    }

    protected AzureSession(Host h) {
        super(h);
    }

    public List<String> getSupportedStorageClasses() {
        return Collections.emptyList();
    }

    private BlobStorageRest client;

    @Override
    protected BlobStorageRest getClient() throws ConnectionCanceledException {
        if (null == client) {
            throw new ConnectionCanceledException();
        }
        return client;
    }

    @Override
    protected void connect() throws IOException {
        if (this.isConnected()) {
            return;
        }
        this.fireConnectionWillOpenEvent();
        // Prompt the login credentials first
        this.login();
        this.fireConnectionDidOpenEvent();
    }

    @Override
    protected void login(LoginController controller, Credentials credentials) throws IOException {
        // http://*.blob.core.windows.net
        try {
            client = new BlobStorageRest(new URI(host.getProtocol().getScheme() + "://" + host.getHostname()),
                    false, credentials.getUsername(), credentials.getPassword());
        } catch (URISyntaxException e) {
            log.error("Failure parsing URI:" + e.getMessage());
            IOException failure = new IOException(e.getMessage());
            failure.initCause(e);
            throw failure;
        }
        client.setTimeout(TimeSpan.fromMilliseconds(this.timeout()));
        try {
            this.getContainers(true);
        } catch (StorageServerException e) {
            if (this.isLoginFailure(e)) {
                this.message(Locale.localizedString("Login failed", "Credentials"));
                controller.fail(host.getProtocol(), credentials);
                this.login();
            } else {
                IOException failure = new IOException(e.getCause().getMessage());
                failure.initCause(e);
                throw failure;
            }
        }
    }

    /**
     * Check for Invalid Access ID or Invalid Secret Key
     *
     * @param e
     * @return True if the error code of the S3 exception is a login failure
     */
    protected boolean isLoginFailure(StorageServerException e) {
        Throwable cause = e.getCause();
        if (cause instanceof StorageServerException) {
            if (403 == ((StorageServerException) cause).getStatusCode()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void close() {
        try {
            if (this.isConnected()) {
                this.fireConnectionWillCloseEvent();
                super.close();
            }
        } finally {
            // No logout required
            client = null;
            this.fireConnectionDidCloseEvent();
        }
    }

    /**
     * Caching the uses's buckets
     */
    private Map<String, AzureContainer> containers = new HashMap<String, AzureContainer>();

    /**
     * Extending REST container to support IO Streams for GET and PUT.
     */
    protected class AzureContainer extends BlobContainerRest {
        public AzureContainer(String containerName, Timestamp lastModified) throws ConnectionCanceledException {
            super(getClient().getBaseUri(), getClient().isUsePathStyleUris(), getClient().getAccountName(),
                    containerName, getClient().getBase64Key(), lastModified, getClient().getTimeout(),
                    getClient().getRetryPolicy());
        }

        /**
         * Create a new blob or overwrite an existing blob.
         *
         * @throws StorageException
         */
        public boolean createBlob(BlobProperties blobProperties, HttpEntity entity) throws StorageException {
            try {
                return putBlobImpl(blobProperties, entity);
            } catch (Exception e) {
                throw HttpUtilities.translateWebException(e);
            }
        }

        private boolean putBlobImpl(final BlobProperties blobProperties, final HttpEntity entity) throws Exception {
            boolean retval;
            IRetryPolicy policy = RetryPolicies.noRetry();
            retval = (Boolean) policy.execute(new Callable<Boolean>() {
                public Boolean call() throws Exception {
                    return uploadData(blobProperties, entity);
                }

            });
            return retval;
        }

        private boolean uploadData(BlobProperties blobProperties, HttpEntity data) throws Exception {
            boolean retval;
            ResourceUriComponents uriComponents = new ResourceUriComponents(getAccountName(), getContainerName(),
                    blobProperties.getName());
            URI blobUri = HttpUtilities.createRequestUri(getBaseUri(), this.isUsePathStyleUris(), getAccountName(),
                    getContainerName(), blobProperties.getName(), getTimeout(), new NameValueCollection(),
                    uriComponents);

            HttpRequest request = createHttpRequestForPutBlob(blobUri, HttpMethod.Put, blobProperties);

            SharedKeyCredentials credentials = getClient().getCredentials();
            credentials.signRequest(request, uriComponents);
            ((HttpEntityEnclosingRequest) request).setEntity(data);
            HttpResponse execute = http().execute((HttpUriRequest) request);
            HttpWebResponse response = new HttpWebResponse(execute);
            if (response.getStatusCode() == HttpStatus.SC_CREATED) {
                retval = true;
            } else {
                retval = false;
                HttpUtilities.processUnexpectedStatusCode(response);
            }
            HttpEntity entity = execute.getEntity();
            if (null != entity) {
                // Release all allocated resources
                EntityUtils.consume(entity);
            }
            blobProperties.setLastModifiedTime(response.getLastModified());
            blobProperties.setETag(response.getHeader(HeaderNames.ETag));
            return retval;
        }

        private HttpRequest createHttpRequestForPutBlob(URI blobUri, String httpMethod,
                IBlobProperties blobProperties) {
            HttpRequest request = HttpUtilities.createHttpRequestWithCommonHeaders(blobUri, httpMethod,
                    getTimeout());
            if (blobProperties.getContentEncoding() != null) {
                request.addHeader(HeaderNames.ContentEncoding, blobProperties.getContentEncoding());
            }
            if (blobProperties.getContentLanguage() != null) {
                request.addHeader(HeaderNames.ContentLanguage, blobProperties.getContentLanguage());
            }
            if (blobProperties.getContentType() != null) {
                request.addHeader(HeaderNames.ContentType, blobProperties.getContentType());
            }
            if (blobProperties.getMetadata() != null && blobProperties.getMetadata().size() > 0) {
                HttpUtilities.addMetadataHeaders(request, blobProperties.getMetadata());
            }
            return request;
        }

        /**
         * @param blobName
         * @return
         * @throws StorageException
         */
        public InputStream getBlob(final String blobName) throws StorageException {
            try {
                HttpWebResponse response = getBlobImpl(HttpMethod.Get, blobName, new OutParameter<Boolean>(false));
                return response.getStream();
            } catch (Exception e) {
                throw HttpUtilities.translateWebException(e);
            }
        }

        private HttpWebResponse getBlobImpl(final String httpMethod, final String blobName,
                OutParameter<Boolean> modified) {
            final HttpWebResponse[] httpWebResponses = new HttpWebResponse[1];
            final OutParameter<Boolean> localModified = new OutParameter<Boolean>(true);
            // Reset the stop flag
            stopFetchProgress(Boolean.FALSE);

            modified.setValue(localModified.getValue());

            IRetryPolicy rp = RetryPolicies.noRetry();
            final long originalPosition = 0;
            rp.execute(new Callable<Object>() {
                public Object call() throws Exception {
                    HttpWebResponse response = downloadData(httpMethod, blobName, 0, 0, new NameValueCollection(),
                            localModified);
                    httpWebResponses[0] = response;
                    return response;
                }
            });
            modified.setValue(localModified.getValue());
            return httpWebResponses[0];
        }

        private HttpWebResponse downloadData(String httpMethod, String blobName, long offset, long length,
                NameValueCollection nvc, OutParameter<Boolean> localModified)
                throws StorageException, ConnectionCanceledException {
            ResourceUriComponents uriComponents = new ResourceUriComponents(getAccountName(), getContainerName(),
                    blobName);
            URI blobUri = HttpUtilities.createRequestUri(getBaseUri(), isUsePathStyleUris(), getAccountName(),
                    getContainerName(), blobName, getTimeout(), nvc, uriComponents);
            HttpRequest request = createHttpRequestForGetBlob(blobUri, httpMethod);

            if (offset != 0 || length != 0) {
                // Use the blob storage custom header for range since the standard
                // HttpWebRequest.
                // AddRange accepts only 32 bit integers and so does not work for
                // large blobs.
                String rangeHeaderValue = MessageFormat.format(HeaderValues.RangeHeaderFormat, offset,
                        offset + length - 1);
                request.addHeader(HeaderNames.StorageRange, rangeHeaderValue);
            }

            final SharedKeyCredentials credentials = getClient().getCredentials();
            credentials.signRequest(request, uriComponents);
            BlobProperties blobProperties;

            try {
                HttpWebResponse response = new HttpWebResponse(http().execute((HttpUriRequest) request));
                if (response.getStatusCode() == HttpStatus.SC_OK
                        || response.getStatusCode() == HttpStatus.SC_PARTIAL_CONTENT) {

                    return response;
                } else {
                    HttpUtilities.processUnexpectedStatusCode(response);
                    return null;
                }
            } catch (Exception we) {
                throw HttpUtilities.translateWebException(we);
            }
        }

        private HttpRequest createHttpRequestForGetBlob(URI blobUri, String httpMethod) {
            return HttpUtilities.createHttpRequestWithCommonHeaders(blobUri, httpMethod, getTimeout());
        }

        @Override
        public ContainerAccessControl getContainerAccessControl() throws StorageException {
            ContainerAccessControl accessControl;
            try {
                accessControl = (ContainerAccessControl) getRetryPolicy()
                        .execute(new Callable<ContainerAccessControl>() {
                            public ContainerAccessControl call() throws Exception {
                                NameValueCollection queryParams = new NameValueCollection();
                                queryParams.put(QueryParams.QueryParamComp, CompConstants.Acl);
                                // New version container ACL
                                queryParams.put(QueryParams.QueryRestType, CompConstants.Container);

                                ResourceUriComponents uriComponents = new ResourceUriComponents(getAccountName(),
                                        getContainerName(), null);
                                URI uri = HttpUtilities.createRequestUri(getBaseUri(), isUsePathStyleUris(),
                                        getAccountName(), getContainerName(), null, getTimeout(), queryParams,
                                        uriComponents);
                                HttpRequest request = HttpUtilities.createHttpRequestWithCommonHeaders(uri,
                                        HttpMethod.Get, getTimeout());
                                request.addHeader(HeaderNames.ApiVersion, XmsVersion.VERSION_2009_07_17);

                                getClient().getCredentials().signRequest(request, uriComponents);
                                HttpWebResponse response = new HttpWebResponse(
                                        http().execute((HttpUriRequest) request));
                                if (response.getStatusCode() == HttpStatus.SC_OK) {
                                    String acl = response.getHeader(HeaderNames.PublicAccess);
                                    boolean publicAcl = false;
                                    if (acl != null) {
                                        publicAcl = Boolean.parseBoolean(acl);
                                        List<SignedIdentifier> identifiers = getSignedIdentifiersFromResponse(
                                                response);
                                        ContainerAccessControl aclEntity;
                                        if (identifiers != null && identifiers.size() > 0) {
                                            aclEntity = new ContainerAccessControl(publicAcl);
                                            aclEntity.setSigendIdentifiers(identifiers);
                                        } else {
                                            aclEntity = publicAcl ? IContainerAccessControl.Public
                                                    : IContainerAccessControl.Private;
                                        }
                                        return aclEntity;
                                    } else {
                                        throw new StorageServerException(StorageErrorCode.ServiceBadResponse,
                                                "The server did not respond with expected container access control header",
                                                response.getStatusCode(), null);
                                    }
                                } else {
                                    HttpUtilities.processUnexpectedStatusCode(response);
                                    return null;
                                }
                            }

                        });
            } catch (Exception e) {
                throw HttpUtilities.translateWebException(e);

            }
            return accessControl;
        }

        @SuppressWarnings("unchecked")
        private List<SignedIdentifier> getSignedIdentifiersFromResponse(HttpWebResponse response) {
            InputStream stream = response.getStream();
            if (stream == null) {
                return Collections.EMPTY_LIST;
            }
            try {
                Document doc = XmlUtil.load(stream, "Container access control parsed error.");
                List selectNodes = doc.selectNodes(XPathQueryHelper.SignedIdentifierListQuery);
                List<SignedIdentifier> result = new ArrayList<SignedIdentifier>();
                if (selectNodes.size() > 0) {
                    for (Object selectNode : selectNodes) {
                        Element element = (Element) selectNode;
                        SignedIdentifier identifier = new SignedIdentifier();
                        identifier.setId(XPathQueryHelper.loadSingleChildStringValue(element,
                                XmlElementNames.ContainerSignedIdentifierId, true));
                        IAccessPolicy policy = new AccessPolicy();
                        Element accesPlocy = (Element) element
                                .selectSingleNode(XmlElementNames.ContainerAccessPolicyName);
                        if (accesPlocy != null && accesPlocy.hasContent()) {
                            String start = XPathQueryHelper.loadSingleChildStringValue(accesPlocy,
                                    XmlElementNames.ContainerAccessPolicyStart, true);
                            if (StringUtils.isNotEmpty(start)) {
                                policy.setStart(new DateTime(start));
                            }
                            String end = XPathQueryHelper.loadSingleChildStringValue(accesPlocy,
                                    XmlElementNames.ContainerAccessPolicyExpiry, true);
                            if (StringUtils.isNotEmpty(end)) {
                                policy.setExpiry(new DateTime(end));
                            }
                            policy.setPermission(
                                    SharedAccessPermissions.valueOf(XPathQueryHelper.loadSingleChildStringValue(
                                            accesPlocy, XmlElementNames.ContainerAccessPolicyPermission, true)));
                            identifier.setPolicy(policy);
                        }
                        result.add(identifier);
                    }
                }
                return result;
            } catch (Exception e) {
                // For dev local storage, Container access control may have no
                // detail.
                org.soyatec.windows.azure.util.Logger.error("Parse container accesss control error", e);
                return Collections.EMPTY_LIST;
            }
        }
    }

    /**
     * @param reload
     * @return
     */
    protected List<AzureContainer> getContainers(boolean reload) throws IOException, StorageServerException {
        if (containers.isEmpty() || reload) {
            containers.clear();
            for (final IBlobContainer container : this.getClient().listBlobContainers()) {
                containers.put(container.getContainerName(),
                        new AzureContainer(container.getContainerName(), container.getLastModifiedTime()));
            }
        }
        return new ArrayList<AzureContainer>(containers.values());
    }

    /**
     * @param bucketname
     * @return
     * @throws IOException
     */
    protected AzureContainer getContainer(final String bucketname) throws IOException {
        try {
            for (AzureContainer container : this.getContainers(false)) {
                if (container.getContainerName().equals(bucketname)) {
                    return container;
                }
            }
        } catch (StorageServerException e) {
            this.error("Cannot read file attributes", e);
        }
        log.warn("Bucket not found with name:" + bucketname);
        return new AzureContainer("$root", null);
    }

    @Override
    public boolean isAclSupported() {
        return false;
    }

    @Override
    public List<Acl.User> getAvailableAclUsers() {
        return Arrays.asList(AzurePath.PUBLIC_ACL.getUser());
        //        List<Acl.User> l = new ArrayList<Acl.User>();
        //        l.add(new Acl.CanonicalUser(""));
        //        l.add(AzurePath.PUBLIC_ACL.getUser());
        //        return l;
    }

    @Override
    public Acl getPrivateAcl(String container) {
        return new Acl();
    }

    @Override
    public Acl getPublicAcl(String container, boolean readable, boolean writable) {
        Acl acl = new Acl();
        if (readable) {
            acl.addAll(AzurePath.PUBLIC_ACL.getUser(), AzurePath.PUBLIC_ACL.getRole());
        }
        return acl;
    }

    /**
     * Valid permissions values are read (r), write (w), delete (d) and list (l).
     *
     * @return
     */
    @Override
    public List<Acl.Role> getAvailableAclRoles(List<Path> files) {
        return Arrays.asList(AzurePath.PUBLIC_ACL.getRole(), AzurePath.PRIVATE_ACL.getRole());
        //        return Arrays.asList(new Acl.Role(SharedAccessPermissions.toString(SharedAccessPermissions.RL)),
        //                new Acl.Role(SharedAccessPermissions.toString(SharedAccessPermissions.RW)),
        //                new Acl.Role(SharedAccessPermissions.toString(SharedAccessPermissions.RWL)),
        //                new Acl.Role(SharedAccessPermissions.toString(SharedAccessPermissions.RWDL)));
    }

    @Override
    public boolean isDownloadResumable() {
        return false;
    }

    @Override
    public boolean isUploadResumable() {
        return false;
    }

    /**
     * Creating files is only possible inside a container.
     *
     * @param workdir The workdir to create query
     * @return Fals if directory is root.
     */
    @Override
    public boolean isCreateFileSupported(Path workdir) {
        return !workdir.isRoot();
    }

    @Override
    public boolean isCDNSupported() {
        return false;
    }

    @Override
    public boolean isChecksumSupported() {
        return true;
    }
}