ch.cyberduck.core.cf.CFPath.java Source code

Java tutorial

Introduction

Here is the source code for ch.cyberduck.core.cf.CFPath.java

Source

package ch.cyberduck.core.cf;

/*
 *  Copyright (c) 2008 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.AbstractPath;
import ch.cyberduck.core.AttributedList;
import ch.cyberduck.core.ConnectionCanceledException;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.Preferences;
import ch.cyberduck.core.StreamListener;
import ch.cyberduck.core.cdn.Distribution;
import ch.cyberduck.core.cloud.CloudPath;
import ch.cyberduck.core.http.DelayedHttpEntityCallable;
import ch.cyberduck.core.http.ResponseOutputStream;
import ch.cyberduck.core.i18n.Locale;
import ch.cyberduck.core.io.BandwidthThrottle;
import ch.cyberduck.core.local.Local;
import ch.cyberduck.core.transfer.TransferStatus;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpException;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.log4j.Logger;
import org.jets3t.service.utils.ServiceUtils;
import org.w3c.util.DateParser;
import org.w3c.util.InvalidDateException;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.rackspacecloud.client.cloudfiles.FilesContainerInfo;
import com.rackspacecloud.client.cloudfiles.FilesContainerMetaData;
import com.rackspacecloud.client.cloudfiles.FilesNotFoundException;
import com.rackspacecloud.client.cloudfiles.FilesObject;
import com.rackspacecloud.client.cloudfiles.FilesObjectMetaData;

/**
 * Rackspace Cloud Files Implementation
 *
 * @version $Id: CFPath.java 10889 2013-04-19 15:42:22Z dkocher $
 */
public class CFPath extends CloudPath {
    private static Logger log = Logger.getLogger(CFPath.class);

    private final CFSession session;

    public CFPath(CFSession s, String parent, String name, int type) {
        super(parent, name, type);
        this.session = s;
    }

    public CFPath(CFSession s, String path, int type) {
        super(path, type);
        this.session = s;
    }

    public CFPath(CFSession s, String parent, Local file) {
        super(parent, file);
        this.session = s;
    }

    public <T> CFPath(CFSession s, T dict) {
        super(dict);
        this.session = s;
    }

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

    @Override
    public boolean exists() {
        if (super.exists()) {
            return true;
        }
        if (this.isContainer()) {
            try {
                return this.getSession().getClient().containerExists(this.getName());
            } catch (HttpException e) {
                log.warn(String.format("Container %s does not exist", this.getName()));
                return false;
            } catch (ConnectionCanceledException e) {
                log.warn(e.getMessage());
            } catch (IOException e) {
                log.warn(e.getMessage());
            }
        }
        return super.exists();
    }

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

            if (this.isContainer()) {
                attributes().setSize(
                        this.getSession().getClient().getContainerInfo(this.getContainerName()).getTotalSize());
            } else if (this.attributes().isFile()) {
                attributes().setSize(Long.valueOf(this.getSession().getClient()
                        .getObjectMetaData(this.getContainerName(), this.getKey()).getContentLength()));
            }
        } catch (HttpException e) {
            this.error("Cannot read file attributes", e);
        } catch (IOException e) {
            this.error("Cannot read file attributes", e);
        }
    }

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

                final String checksum = this.getSession().getClient()
                        .getObjectMetaData(this.getContainerName(), this.getKey()).getETag();
                attributes().setChecksum(checksum);
                attributes().setETag(checksum);
            } catch (HttpException e) {
                this.error("Cannot read file attributes", e);
            } catch (IOException e) {
                this.error("Cannot read file attributes", e);
            }
        }
    }

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

                try {
                    attributes().setModificationDate(ServiceUtils
                            .parseRfc822Date(this.getSession().getClient()
                                    .getObjectMetaData(this.getContainerName(), this.getKey()).getLastModified())
                            .getTime());
                } catch (ParseException e) {
                    log.error("Failure parsing timestamp", e);
                }
            } catch (HttpException e) {
                this.error("Cannot read file attributes", e);
            } catch (IOException e) {
                this.error("Cannot read file attributes", e);
            }
        }
    }

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

            if (this.isRoot()) {
                final int limit = Preferences.instance().getInteger("cf.list.limit");
                String marker = null;
                List<FilesContainerInfo> list;
                // List all containers
                do {
                    list = this.getSession().getClient().listContainersInfo(limit, marker);
                    for (FilesContainerInfo container : list) {
                        Path p = new CFPath(this.getSession(), this.getAbsolute(), container.getName(),
                                VOLUME_TYPE | DIRECTORY_TYPE);
                        p.attributes().setSize(container.getTotalSize());
                        p.attributes().setOwner(this.getSession().getClient().getUserName());

                        children.add(p);

                        marker = container.getName();
                    }
                } while (list.size() == limit);
            } else {
                final int limit = Preferences.instance().getInteger("cf.list.limit");
                String marker = null;
                List<FilesObject> list;
                do {
                    list = this.getSession().getClient().listObjectsStartingWith(this.getContainerName(),
                            this.isContainer() ? StringUtils.EMPTY : this.getKey() + Path.DELIMITER, null, limit,
                            marker, Path.DELIMITER);
                    for (FilesObject object : list) {
                        final Path file = new CFPath(this.getSession(), this.getContainerName(), object.getName(),
                                "application/directory".equals(object.getMimeType()) ? DIRECTORY_TYPE : FILE_TYPE);
                        file.setParent(this);
                        if (file.attributes().isFile()) {
                            file.attributes().setSize(object.getSize());
                            file.attributes().setChecksum(object.getMd5sum());
                            try {
                                final Date modified = DateParser.parse(object.getLastModified());
                                if (null != modified) {
                                    file.attributes().setModificationDate(modified.getTime());
                                }
                            } catch (InvalidDateException e) {
                                log.warn("Not ISO 8601 format:" + e.getMessage());
                            }
                        }
                        if (file.attributes().isDirectory()) {
                            file.attributes().setPlaceholder(true);
                            if (children.contains(file.getReference())) {
                                continue;
                            }
                        }
                        file.attributes().setOwner(this.attributes().getOwner());

                        children.add(file);

                        marker = object.getName();
                    }
                    if (Preferences.instance().getBoolean("cf.list.cdn.preload")) {
                        for (Distribution.Method method : this.getSession().cdn()
                                .getMethods(this.getContainerName())) {
                            // Cache CDN configuration
                            this.getSession().cdn().read(
                                    this.getSession().cdn().getOrigin(method, this.getContainerName()), method);
                        }
                    }
                } while (list.size() == limit);
            }
        } catch (HttpException e) {
            log.warn("Listing directory failed:" + e.getMessage());
            children.attributes().setReadable(false);
            if (!session.cache().containsKey(this.getReference())) {
                this.error(e.getMessage(), e);
            }
        } catch (IOException e) {
            log.warn("Listing directory failed:" + e.getMessage());
            children.attributes().setReadable(false);
            if (!session.cache().containsKey(this.getReference())) {
                this.error(e.getMessage(), e);
            }
        }
        return children;
    }

    @Override
    public InputStream read(final TransferStatus status) throws IOException {
        try {
            if (status.isResume()) {
                return this.getSession().getClient().getObjectAsRangedStream(this.getContainerName(), this.getKey(),
                        status.getCurrent(), status.getLength());
            }
            return this.getSession().getClient().getObjectAsStream(this.getContainerName(), this.getKey());
        } catch (HttpException e) {
            IOException failure = new IOException(e.getMessage());
            failure.initCause(e);
            throw failure;
        }
    }

    @Override
    public void download(final BandwidthThrottle throttle, final StreamListener listener,
            final TransferStatus status) {
        OutputStream out = null;
        InputStream in = null;
        try {
            in = this.read(status);
            out = this.getLocal().getOutputStream(status.isResume());
            this.download(in, out, throttle, listener, status);
        } catch (IOException e) {
            this.error("Download failed", e);
        } finally {
            IOUtils.closeQuietly(in);
            IOUtils.closeQuietly(out);
        }
    }

    @Override
    public void upload(final BandwidthThrottle throttle, final StreamListener listener,
            final TransferStatus status) {
        try {
            String md5sum = null;
            if (Preferences.instance().getBoolean("cf.upload.metadata.md5")) {
                this.getSession().message(MessageFormat
                        .format(Locale.localizedString("Compute MD5 hash of {0}", "Status"), this.getName()));
                md5sum = this.getLocal().attributes().getChecksum();
            }
            MessageDigest digest = null;
            if (!Preferences.instance().getBoolean("cf.upload.metadata.md5")) {
                try {
                    digest = MessageDigest.getInstance("MD5");
                } catch (NoSuchAlgorithmException e) {
                    log.error("Failure loading MD5 digest", e);
                }
            }
            InputStream in = null;
            ResponseOutputStream<String> out = null;
            try {
                if (null == digest) {
                    log.warn("MD5 calculation disabled");
                    in = this.getLocal().getInputStream();
                } else {
                    in = new DigestInputStream(this.getLocal().getInputStream(), digest);
                }
                out = this.write(status, md5sum);
                this.upload(out, in, throttle, listener, status);
            } finally {
                IOUtils.closeQuietly(in);
                IOUtils.closeQuietly(out);
            }
            if (null != digest && null != out) {
                this.getSession().message(MessageFormat
                        .format(Locale.localizedString("Compute MD5 hash of {0}", "Status"), this.getName()));
                // Obtain locally-calculated MD5 hash.
                String expectedETag = ServiceUtils.toHex(digest.digest());
                // Compare our locally-calculated hash with the ETag returned.
                final String result = out.getResponse();
                if (!expectedETag.equals(result)) {
                    throw new IOException("Mismatch between MD5 hash of uploaded data (" + expectedETag
                            + ") and ETag returned (" + result + ") for object key: " + this.getKey());
                } else {
                    if (log.isDebugEnabled()) {
                        log.debug("Object upload was automatically verified, the calculated MD5 hash "
                                + "value matched the ETag returned: " + this.getKey());
                    }
                }
            }
        } catch (IOException e) {
            this.error("Upload failed", e);
        }
    }

    @Override
    public OutputStream write(final TransferStatus status) throws IOException {
        return this.write(status, null);
    }

    private ResponseOutputStream<String> write(final TransferStatus status, final String md5sum)
            throws IOException {
        final HashMap<String, String> metadata = new HashMap<String, String>();
        // Default metadata for new files
        for (String m : Preferences.instance().getList("cf.metadata.default")) {
            if (StringUtils.isBlank(m)) {
                log.warn(String.format("Invalid header %s", m));
                continue;
            }
            if (!m.contains("=")) {
                log.warn(String.format("Invalid header %s", m));
                continue;
            }
            int split = m.indexOf('=');
            String name = m.substring(0, split);
            if (StringUtils.isBlank(name)) {
                log.warn(String.format("Missing key in %s", m));
                continue;
            }
            String value = m.substring(split + 1);
            if (StringUtils.isEmpty(value)) {
                log.warn(String.format("Missing value in %s", m));
                continue;
            }
            metadata.put(name, value);
        }

        // Submit store call to background thread
        final DelayedHttpEntityCallable<String> command = new DelayedHttpEntityCallable<String>() {
            /**
             *
             * @return The ETag returned by the server for the uploaded object
             */
            @Override
            public String call(AbstractHttpEntity entity) throws IOException {
                try {
                    return CFPath.this.getSession().getClient().storeObjectAs(CFPath.this.getContainerName(),
                            CFPath.this.getKey(), entity, metadata, md5sum);
                } catch (HttpException e) {
                    IOException failure = new IOException(e.getMessage());
                    failure.initCause(e);
                    throw failure;
                }
            }

            @Override
            public long getContentLength() {
                return status.getLength() - status.getCurrent();
            }
        };
        return this.write(command);
    }

    @Override
    public void mkdir() {
        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.getName());
            } else {
                // Create virtual directory
                this.getSession().getClient().createFullPath(this.getContainerName(), this.getKey());
            }
        } catch (HttpException 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();
            this.getSession().message(
                    MessageFormat.format(Locale.localizedString("Deleting {0}", "Status"), this.getName()));

            final String container = this.getContainerName();
            if (attributes().isFile()) {
                this.getSession().getClient().deleteObject(container, this.getKey());
            } else if (attributes().isDirectory()) {
                for (AbstractPath i : this.children()) {
                    if (!this.getSession().isConnected()) {
                        break;
                    }
                    i.delete();
                }
                if (this.isContainer()) {
                    this.getSession().getClient().deleteContainer(container);
                } else {
                    try {
                        this.getSession().getClient().deleteObject(container, this.getKey());
                    } catch (FilesNotFoundException e) {
                        // No real placeholder but just a delimiter returned in the object listing.
                        log.warn(e.getMessage());
                    }
                }
            }
        } catch (HttpException e) {
            this.error("Cannot delete {0}", e);
        } catch (IOException e) {
            this.error("Cannot delete {0}", e);
        }
    }

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

            if (this.attributes().isFile()) {
                final FilesObjectMetaData meta = this.getSession().getClient()
                        .getObjectMetaData(this.getContainerName(), this.getKey());
                this.attributes().setMetadata(meta.getMetaData());
            }
            if (this.attributes().isVolume()) {
                final FilesContainerMetaData meta = this.getSession().getClient()
                        .getContainerMetaData(this.getContainerName());
                this.attributes().setMetadata(meta.getMetaData());
            }
        } catch (HttpException e) {
            this.error("Cannot read file attributes", e);
        } catch (IOException e) {
            this.error("Cannot read file attributes", e);
        }
    }

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

            if (this.attributes().isFile()) {
                this.getSession().getClient().updateObjectMetadata(this.getContainerName(), this.getKey(), meta);
            } else if (this.attributes().isVolume()) {
                this.getSession().getClient().updateContainerMetadata(this.getContainerName(), meta);
            }
        } catch (HttpException 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);
        }
    }

    @Override
    public void rename(final AbstractPath renamed) {
        try {
            this.getSession().check();
            this.getSession().message(MessageFormat.format(Locale.localizedString("Renaming {0} to {1}", "Status"),
                    this.getName(), renamed));

            if (this.attributes().isFile()) {
                this.getSession().getClient().copyObject(this.getContainerName(), this.getKey(),
                        ((CFPath) renamed).getContainerName(), ((CFPath) renamed).getKey());
                this.getSession().getClient().deleteObject(this.getContainerName(), this.getKey());
            } else if (this.attributes().isDirectory()) {
                for (Path i : this.children()) {
                    if (!this.getSession().isConnected()) {
                        break;
                    }
                    i.rename(new CFPath(this.getSession(), renamed.getAbsolute(), i.getName(),
                            i.attributes().getType()));
                }
                try {
                    this.getSession().getClient().deleteObject(this.getContainerName(), this.getKey());
                } catch (FilesNotFoundException e) {
                    // No real placeholder but just a delimiter returned in the object listing.
                    log.warn(e.getMessage());
                }
            }
        } catch (HttpException e) {
            this.error("Cannot rename {0}", e);
        } catch (IOException e) {
            this.error("Cannot rename {0}", e);
        }
    }

    @Override
    public void copy(final AbstractPath copy, final BandwidthThrottle throttle, final StreamListener listener,
            final TransferStatus status) {
        if (((Path) copy).getSession().equals(this.getSession())) {
            // Copy on same server
            try {
                this.getSession().check();
                this.getSession().message(MessageFormat
                        .format(Locale.localizedString("Copying {0} to {1}", "Status"), this.getName(), copy));

                if (this.attributes().isFile()) {
                    this.getSession().getClient().copyObject(this.getContainerName(), this.getKey(),
                            ((CFPath) copy).getContainerName(), ((CFPath) copy).getKey());
                    listener.bytesSent(this.attributes().getSize());
                    status.setComplete();
                }
            } catch (HttpException e) {
                this.error("Cannot copy {0}", e);
            } catch (IOException e) {
                this.error("Cannot copy {0}", e);
            }
        } else {
            // Copy to different host
            super.copy(copy, throttle, listener, status);
        }
    }

    /**
     * @return Publicy accessible URL of given object
     */
    @Override
    public String toHttpURL() {
        CFSession session = this.getSession();
        for (Distribution.Method method : session.cdn().getMethods(this.getContainerName())) {
            if (session.cdn().isCached(method)) {
                final Distribution distribution = session.cdn()
                        .read(session.cdn().getOrigin(method, this.getContainerName()), method);
                return distribution.getURL(this);
            }
        }
        // Storage URL is not accessible
        return null;
    }
}