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

Java tutorial

Introduction

Here is the source code for ch.cyberduck.core.azure.AzurePath.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.CloudPath;
import ch.cyberduck.core.i18n.Locale;
import ch.cyberduck.core.io.BandwidthThrottle;
import ch.cyberduck.ui.DateFormatterFactory;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.Header;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HTTP;
import org.apache.log4j.Logger;
import org.soyatec.windows.azure.blob.*;
import org.soyatec.windows.azure.blob.internal.BlobContents;
import org.soyatec.windows.azure.blob.internal.BlobProperties;
import org.soyatec.windows.azure.blob.internal.ContainerAccessControl;
import org.soyatec.windows.azure.error.StorageException;
import org.soyatec.windows.azure.internal.SignedIdentifier;
import org.soyatec.windows.azure.util.NameValueCollection;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.*;

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

    private static class Factory extends PathFactory<AzureSession> {
        @Override
        protected Path create(AzureSession session, String path, int type) {
            return new AzurePath(session, path, type);
        }

        @Override
        protected Path create(AzureSession session, String parent, String name, int type) {
            return new AzurePath(session, parent, name, type);
        }

        @Override
        protected Path create(AzureSession session, String parent, Local file) {
            return new AzurePath(session, parent, file);
        }

        @Override
        protected <T> Path create(AzureSession session, T dict) {
            return new AzurePath(session, dict);
        }
    }

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

    private final AzureSession session;

    protected AzurePath(AzureSession s, String parent, String name, int type) {
        super(parent, name, type);
        this.session = s;
    }

    protected AzurePath(AzureSession s, String path, int type) {
        super(path, type);
        this.session = s;
    }

    protected AzurePath(AzureSession s, String parent, Local file) {
        super(parent, file);
        this.session = s;
    }

    protected <T> AzurePath(AzureSession s, T dict) {
        super(dict);
        this.session = s;
    }

    @Override
    public AzureSession getSession() {
        return session;
    }

    @Override
    public void readMetadata() {
        try {
            this.getSession().check();
            this.getSession().message(MessageFormat
                    .format(Locale.localizedString("Reading metadata of {0}", "Status"), this.getName()));

            IBlobContainer container = this.getSession().getContainer(this.getContainerName());
            if (this.isContainer()) {
                final IContainerProperties properties = container.getContainerProperties();
                if (null == properties.getMetadata()) {
                    log.warn("No container properties for " + this.getAbsolute());
                    return;
                }
                Map<String, String> metadata = new HashMap<String, String>();
                for (Object key : properties.getMetadata().keySet()) {
                    metadata.put(key.toString(), properties.getMetadata().getSingleValue(key.toString()));
                }
                this.attributes().setMetadata(metadata);
            } else if (this.attributes().isFile()) {
                Map<String, String> metadata = new HashMap<String, String>();
                final IBlobProperties properties = container.getBlobProperties(this.getKey());
                if (null == properties.getMetadata()) {
                    log.warn("No blob metadata for " + this.getAbsolute());
                } else {
                    for (Object key : properties.getMetadata().keySet()) {
                        metadata.put(key.toString(), properties.getMetadata().getSingleValue(key.toString()));
                    }
                }
                this.attributes().setMetadata(metadata);
            }
        } catch (StorageException e) {
            this.error("Cannot read file attributes", e);
        } catch (IOException e) {
            this.error("Cannot read file attributes", e);
        }
    }

    @Override
    public void writeMetadata(Map<String, String> meta) {
        try {
            this.getSession().check();
            this.getSession().message(MessageFormat
                    .format(Locale.localizedString("Writing metadata of {0}", "Status"), this.getName()));

            final NameValueCollection collection = new NameValueCollection();
            collection.putAll(meta);

            IBlobContainer container = this.getSession().getContainer(this.getContainerName());
            if (this.isContainer()) {
                container.setContainerMetadata(collection);
            } else if (this.attributes().isFile()) {
                BlobProperties properties = new BlobProperties(this.getKey());
                properties.setMetadata(collection);
                container.updateBlobMetadata(properties);
            }
        } catch (StorageException e) {
            this.error("Cannot write file attributes", e);
        } catch (IOException e) {
            this.error("Cannot write file attributes", e);
        } finally {
            this.attributes().clear(false, false, false, true);
        }
    }

    /**
     * Only supported for containers.
     */
    @Override
    public void readAcl() {
        if (this.isContainer()) {
            try {
                IBlobContainer container = this.getSession().getContainer(this.getContainerName());
                final ContainerAccessControl acl = container.getContainerAccessControl();
                this.attributes().setAcl(this.convert(acl));
            } catch (StorageException e) {
                this.error("Cannot read file attributes", e);
            } catch (IOException e) {
                this.error("Cannot read file attributes", e);
            }
        }
    }

    public static final Acl.UserAndRole PUBLIC_ACL = new Acl.UserAndRole(
            new Acl.CanonicalUser("anonymous", Locale.localizedString("Anonymous", "Azure"), false) {
                @Override
                public String getPlaceholder() {
                    return this.getDisplayName();
                }
            }, new Acl.Role("public"));

    public static final Acl.UserAndRole PRIVATE_ACL = new Acl.UserAndRole(
            new Acl.CanonicalUser("anonymous", Locale.localizedString("Anonymous", "Azure"), false) {
                @Override
                public String getPlaceholder() {
                    return this.getDisplayName();
                }
            }, new Acl.Role("private"));

    protected Acl convert(final ContainerAccessControl list) {
        Acl acl = new Acl();
        acl.addAll(list.isPublic() ? PUBLIC_ACL : PRIVATE_ACL);
        //        for(ISignedIdentifier identifier : list.getSigendIdentifiers()) {
        //            acl.addAll(new Acl.UserAndRole(new Acl.CanonicalUser(identifier.getId(), false),
        //                    new Acl.Role(SharedAccessPermissions.toString(identifier.getPolicy().getPermission()))));
        //        }
        return acl;
    }

    /**
     * Only supported for containers.
     * <p/>
     * Specifying a Container-Level Access Policy.
     * Establishing a container-level access policy serves to group Shared Access Signatures and to provide
     * additional restrictions for signatures that are bound by the policy. You can use a container-level
     * access policy to change the start time, expiry time, or permissions for a signature, or to
     * revoke it, after it has been issued.
     *
     * @param acl       The permissions to apply
     * @param recursive Include subdirectories and files
     */
    @Override
    public void writeAcl(Acl acl, boolean recursive) {
        if (this.isContainer()) {
            try {
                IBlobContainer container = this.getSession().getContainer(this.getContainerName());
                final ContainerAccessControl list;
                if (acl.asList().contains(PUBLIC_ACL)) {
                    list = new ContainerAccessControl(true);
                } else {
                    list = new ContainerAccessControl(false);
                }
                for (Acl.UserAndRole userAndRole : acl.asList()) {
                    if (!userAndRole.isValid()) {
                        continue;
                    }
                    list.addSignedIdentifier(new SignedIdentifier(userAndRole.getUser().getIdentifier(),
                            SharedAccessPermissions.valueOf(userAndRole.getRole().getName()), null, null));
                }
                container.setContainerAccessControl(list);
            } catch (StorageException e) {
                this.error("Cannot change permissions", e);
            } catch (IOException e) {
                this.error("Cannot change permissions", e);
            } finally {
                this.attributes().clear(false, false, true, false);
            }
        }
    }

    @Override
    public boolean exists() {
        if (this.isRoot()) {
            return true;
        }
        try {
            final IBlobContainer container = this.getSession().getClient()
                    .getBlobContainer(this.getContainerName());
            if (this.isContainer()) {
                return container.isContainerExist();
            } else {
                return container.isBlobExist(this.getKey());
            }
        } catch (IOException e) {
            return false;
        } catch (StorageException e) {
            return false;
        } catch (IllegalArgumentException e) {
            log.error(e.getMessage());
            return false;
        }
    }

    @Override
    public AttributedList<Path> list(final AttributedList<Path> children) {
        if (this.attributes().isDirectory()) {
            try {
                this.getSession().check();
                this.getSession().message(MessageFormat
                        .format(Locale.localizedString("Listing directory {0}", "Status"), this.getName()));

                if (this.isRoot()) {
                    // List all containers
                    for (IBlobContainer container : this.getSession().getContainers(true)) {
                        Path p = PathFactory.createPath(this.getSession(), this.getAbsolute(),
                                container.getContainerName(), Path.VOLUME_TYPE | Path.DIRECTORY_TYPE);
                        p.attributes().setOwner(container.getAccountName());
                        p.attributes().setModificationDate(container.getLastModifiedTime().getTime());
                        children.add(p);
                    }
                } else {
                    IBlobContainer container = this.getSession().getContainer(this.getContainerName());
                    final Iterator<IBlobProperties> blobs = container.listBlobs(this.getKey(), true);
                    while (blobs.hasNext()) {
                        final IBlobProperties object = blobs.next();
                        final Path file = PathFactory.createPath(this.getSession(), this.getContainerName(),
                                object.getName(),
                                "application/directory".equals(object.getContentType()) ? Path.DIRECTORY_TYPE
                                        : Path.FILE_TYPE);
                        if (file.getParent().equals(this)) {
                            file.setParent(this);
                            file.attributes().setSize(object.getContentLength());
                            file.attributes().setChecksum(object.getETag());
                            file.attributes().setModificationDate(object.getLastModifiedTime().getTime());
                            file.attributes().setOwner(this.attributes().getOwner());
                            if (file.attributes().isDirectory()) {
                                file.attributes().setPlaceholder(true);
                            }
                            children.add(file);
                        }
                    }
                }
                this.getSession().setWorkdir(this);
            } catch (StorageException e) {
                log.warn("Listing directory failed:" + e.getMessage());
                children.attributes().setReadable(false);
                if (this.cache().isEmpty()) {
                    this.error(e.getMessage(), e);
                }
            } catch (IOException e) {
                log.warn("Listing directory failed:" + e.getMessage());
                children.attributes().setReadable(false);
                if (this.cache().isEmpty()) {
                    this.error(e.getMessage(), e);
                }
            }
        }
        return children;
    }

    @Override
    public void readSize() {
        if (this.attributes().isFile()) {
            try {
                this.getSession().check();
                this.getSession().message(MessageFormat
                        .format(Locale.localizedString("Getting size of {0}", "Status"), this.getName()));

                IBlobContainer container = this.getSession().getContainer(this.getContainerName());
                attributes().setSize(container.getBlobProperties(this.getKey()).getContentLength());
            } catch (StorageException e) {
                this.error("Cannot read file attributes", e);
            } catch (IOException e) {
                this.error("Cannot read file attributes", e);
            }
        }
    }

    @Override
    public void readTimestamp() {
        try {
            this.getSession().check();
            this.getSession().message(MessageFormat
                    .format(Locale.localizedString("Getting timestamp of {0}", "Status"), this.getName()));

            IBlobContainer container = this.getSession().getContainer(this.getContainerName());
            if (this.isContainer()) {
                attributes().setModificationDate(container.getLastModifiedTime().getTime());
            } else if (this.attributes().isFile()) {
                attributes().setModificationDate(
                        container.getBlobProperties(this.getKey()).getLastModifiedTime().getTime());
            }
        } catch (StorageException e) {
            this.error("Cannot read file attributes", e);
        } catch (IOException e) {
            this.error("Cannot read file attributes", e);
        }
    }

    @Override
    public void readChecksum() {
        if (attributes().isFile()) {
            try {
                this.getSession().check();
                this.getSession().message(MessageFormat
                        .format(Locale.localizedString("Compute MD5 hash of {0}", "Status"), this.getName()));

                IBlobContainer container = this.getSession().getContainer(this.getContainerName());
                attributes().setChecksum(container.getBlobProperties(this.getKey()).getETag());
            } catch (StorageException e) {
                this.error("Cannot read file attributes", e);
            } catch (IOException e) {
                this.error("Cannot read file attributes", e);
            }
        }
    }

    @Override
    protected void download(BandwidthThrottle throttle, StreamListener listener, boolean check) {
        if (attributes().isFile()) {
            OutputStream out = null;
            InputStream in = null;
            try {
                if (check) {
                    this.getSession().check();
                }

                AzureSession.AzureContainer container = this.getSession().getContainer(this.getContainerName());
                in = container.getBlob(this.getKey());
                if (null == in) {
                    throw new IOException("Unable opening data stream");
                }
                final Status status = this.status();
                status.setResume(false);
                out = this.getLocal().getOutputStream(status.isResume());
                this.download(in, out, throttle, listener);
            } catch (StorageException e) {
                this.error("Download failed", e);
            } catch (IOException e) {
                this.error("Download failed", e);
            } finally {
                IOUtils.closeQuietly(in);
                IOUtils.closeQuietly(out);
            }
        }
    }

    @Override
    protected void upload(final BandwidthThrottle throttle, final StreamListener listener, final boolean check) {
        if (attributes().isFile()) {
            try {
                if (check) {
                    this.getSession().check();
                }

                final Status status = this.status();
                status.setResume(false);
                final InputStream in = this.getLocal().getInputStream();
                try {
                    AzureSession.AzureContainer container = this.getSession().getContainer(this.getContainerName());
                    final BlobProperties properties = new BlobProperties(this.getKey());
                    properties.setContentType(this.getLocal().getMimeType());
                    NameValueCollection metadata = new NameValueCollection();
                    // Default metadata for new files
                    for (String m : Preferences.instance().getList("azure.metadata.default")) {
                        if (StringUtils.isBlank(m)) {
                            log.warn("Invalid header " + m);
                            continue;
                        }
                        if (!m.contains("=")) {
                            log.warn("Invalid header " + m);
                            continue;
                        }
                        int split = m.indexOf('=');
                        String name = m.substring(0, split);
                        if (StringUtils.isBlank(name)) {
                            log.warn("Missing key in " + m);
                            continue;
                        }
                        String value = m.substring(split + 1);
                        if (StringUtils.isEmpty(value)) {
                            log.warn("Missing value in " + m);
                            continue;
                        }
                        metadata.put(name, value);
                    }
                    properties.setMetadata(metadata);
                    boolean blob = container.createBlob(properties, new AbstractHttpEntity() {
                        private boolean consumed = false;

                        public boolean isRepeatable() {
                            return false;
                        }

                        @Override
                        public Header getContentType() {
                            return new BasicHeader(HTTP.CONTENT_TYPE, getLocal().getMimeType());
                        }

                        public long getContentLength() {
                            return getLocal().attributes().getSize() - status.getCurrent();
                        }

                        public InputStream getContent() throws IOException, IllegalStateException {
                            return getLocal().getInputStream();
                        }

                        public void writeTo(OutputStream out) throws IOException {
                            upload(out, in, throttle, listener);
                            consumed = true;
                        }

                        public boolean isStreaming() {
                            return !consumed;
                        }

                        @Override
                        public void consumeContent() throws IOException {
                            this.consumed = true;
                            // If the input stream is from a connection, closing it will read to
                            // the end of the content. Otherwise, we don't care what it does.
                            getLocal().getInputStream().close();
                        }
                    });
                    if (!blob) {
                        this.status().setComplete(false);
                    }
                } finally {
                    IOUtils.closeQuietly(in);
                }
            } catch (StorageException e) {
                this.error("Upload failed", e);
            } catch (IOException e) {
                this.error("Upload failed", e);
            }
        }
    }

    @Override
    public void mkdir() {
        if (this.attributes().isDirectory()) {
            try {
                this.getSession().check();
                this.getSession().message(MessageFormat
                        .format(Locale.localizedString("Making directory {0}", "Status"), this.getName()));

                if (this.isContainer()) {
                    // Create container at top level
                    this.getSession().getClient().createContainer(this.getContainerName(),
                            new NameValueCollection(), IContainerAccessControl.Private);
                    this.getSession().getContainers(true);
                } else {
                    BlobProperties properties = new BlobProperties(this.getKey());
                    properties.setContentType("application/directory");
                    // Create virtual directory
                    this.getSession().getContainer(this.getContainerName()).createBlob(properties,
                            new BlobContents(new byte[] {}), true);
                }
                this.cache().put(this.getReference(), AttributedList.<Path>emptyList());
                // The directory listing is no more current
                this.getParent().invalidate();
            } catch (StorageException e) {
                this.error("Cannot create folder {0}", e);
            } catch (IOException e) {
                this.error("Cannot create folder {0}", e);
            }
        }
    }

    @Override
    public void delete() {
        try {
            this.getSession().check();
            if (!this.isContainer()) {
                this.getSession().message(
                        MessageFormat.format(Locale.localizedString("Deleting {0}", "Status"), this.getName()));

                IBlobContainer container = this.getSession().getContainer(this.getContainerName());
                container.deleteBlob(this.getKey());
            } else if (attributes().isDirectory()) {
                for (AbstractPath i : this.children()) {
                    if (!this.getSession().isConnected()) {
                        break;
                    }
                    i.delete();
                }
                if (this.isContainer()) {
                    this.getSession().message(
                            MessageFormat.format(Locale.localizedString("Deleting {0}", "Status"), this.getName()));

                    this.getSession().getClient().deleteContainer(this.getContainerName());
                }
            }
            // The directory listing is no more current
            this.getParent().invalidate();
        } catch (IOException e) {
            this.error("Cannot delete {0}", e);
        }
    }

    @Override
    public void readUnixPermission() {
        try {
            if (this.isContainer()) {
                IBlobContainer container = this.getSession().getContainer(this.getContainerName());
                ContainerAccessControl acl = container.getContainerAccessControl();
                Permission permission = new Permission();
                permission.getOtherPermissions()[Permission.READ] = acl.isPublic();
                this.attributes().setPermission(permission);
            }
        } catch (StorageException e) {
            this.error("Cannot change permissions", e);
        } catch (IOException e) {
            this.error("Cannot change permissions", e);
        }
    }

    @Override
    public void writeUnixPermission(Permission perm, boolean recursive) {
        try {
            if (this.isContainer()) {
                IBlobContainer container = this.getSession().getContainer(this.getContainerName());
                final ContainerAccessControl acl;
                if (perm.getOtherPermissions()[Permission.READ]) {
                    acl = new ContainerAccessControl(true);
                } else {
                    acl = new ContainerAccessControl(false);
                }
                container.setContainerAccessControl(acl);
            }
        } catch (StorageException e) {
            this.error("Cannot change permissions", e);
        } catch (IOException e) {
            this.error("Cannot change permissions", e);
        }
    }

    @Override
    public void writeTimestamp(long created, long modified, long accessed) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void rename(AbstractPath renamed) {
        this.copy(renamed);
        // The directory listing of the target is no more current
        renamed.getParent().invalidate();
        this.delete();
        // The directory listing of the source is no more current
        this.getParent().invalidate();
    }

    @Override
    public void copy(AbstractPath copy) {
        if (((Path) copy).getSession().equals(this.getSession())) {
            // Copy on same server
            if (attributes().isFile()) {
                final NameValueCollection metadata = new NameValueCollection();
                if (this.attributes().getMetadata().isEmpty()) {
                    this.readMetadata();
                }
                metadata.putAll(this.attributes().getMetadata());
                try {
                    this.getSession().getContainer(this.getContainerName()).copyBlob(
                            ((AzurePath) copy).getContainerName(), ((AzurePath) copy).getKey(), this.getKey(),
                            metadata, null);
                } catch (IOException e) {
                    this.error("Cannot copy {0}");
                }
            } else if (this.attributes().isDirectory()) {
                for (AbstractPath i : this.children()) {
                    if (!this.getSession().isConnected()) {
                        break;
                    }
                    i.copy(PathFactory.createPath(this.getSession(), copy.getAbsolute(), i.getName(),
                            i.attributes().getType()));
                }
            }
            // The directory listing is no more current
            copy.getParent().invalidate();
        } else {
            // Copy to different host
            super.copy(copy);
        }
    }

    @Override
    public String toURL() {
        try {
            IBlobContainer container = this.getSession().getContainer(this.getContainerName());
            if (this.isContainer()) {
                return container.getContainerUri().toString();
            }
            //return container.getBlobProperties(this.getKey()).getUri().toString();
            StringBuilder url = new StringBuilder();
            url.append(this.getSession().getHost().getProtocol().getScheme()).append("://");
            url.append(this.getSession().getHostnameForContainer(this.getContainerName()));
            url.append(this.getAbsolute());
        } catch (IOException e) {
            log.warn(e.getMessage());
        }
        return super.toURL();
    }

    public DescriptiveUrl toSignedUrl() {
        ResourceType type;
        if (this.isContainer()) {
            type = ResourceType.Container;
        } else {
            type = ResourceType.Blob;
        }
        Date now = new Date();
        Calendar expiry = Calendar.getInstance();
        expiry.setTime(now);
        // If a signed identifier is not specified as part of the Shared Access Signature, the maximum
        // permissible interval over which the signature is valid is one hour. This limit ensures
        // that a signature that is not bound to a container-level access policy is valid for a
        // short duration.
        expiry.add(Calendar.HOUR, 1);
        try {
            final String signedidentifier = null; // Optional. A unique value that correlates to an access policy
            // specified at the container level. The signed identifier may have a maximum size of 64 characters.
            final ISharedAccessUrl shared = this.getSession().getClient().createSharedAccessUrl(
                    this.getContainerName(), this.getKey(), type, SharedAccessPermissions.RL, null, // If the signature does not provide a value for the signedstart field, the
                    // start time is assumed to be the time when the request reaches the Blob service.
                    new DateTime(expiry.getTime()), signedidentifier);
            return new DescriptiveUrl(shared.getRestUrl(),
                    MessageFormat.format(Locale.localizedString("Expires on {0}", "S3"),
                            DateFormatterFactory.instance().getLongFormat(expiry.getTimeInMillis())));
        } catch (ConnectionCanceledException e) {
            log.warn(e.getMessage());
        }
        return new DescriptiveUrl(null, null);
    }
}