org.dasein.cloud.azure.storage.BlobStore.java Source code

Java tutorial

Introduction

Here is the source code for org.dasein.cloud.azure.storage.BlobStore.java

Source

/**
 * Copyright (C) 2013-2015 Dell, Inc
 *
 * ====================================================================
 * Licensed 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.
 * ====================================================================
 */

package org.dasein.cloud.azure.storage;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
import java.util.TimeZone;
import java.util.TreeMap;

import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import org.dasein.cloud.*;
import org.dasein.cloud.azure.Azure;
import org.dasein.cloud.azure.AzureConfigException;
import org.dasein.cloud.azure.AzureStorageMethod;
import org.dasein.cloud.identity.ServiceAction;
import org.dasein.cloud.storage.AbstractBlobStoreSupport;
import org.dasein.cloud.storage.Blob;
import org.dasein.cloud.storage.BlobStoreCapabilities;
import org.dasein.cloud.storage.FileTransfer;
import org.dasein.cloud.util.NamingConstraints;
import org.dasein.util.CalendarWrapper;
import org.dasein.util.Jiterator;
import org.dasein.util.JiteratorPopulator;
import org.dasein.util.PopulatorThread;
import org.dasein.util.uom.storage.*;
import org.dasein.util.uom.storage.Byte;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public class BlobStore extends AbstractBlobStoreSupport<Azure> {
    static private final Logger logger = Azure.getLogger(BlobStore.class);

    static public final int MAX_BUCKETS = 100;
    static public final int MAX_OBJECTS = -1;
    static public final Storage<org.dasein.util.uom.storage.Byte> MAX_OBJECT_SIZE = new Storage<org.dasein.util.uom.storage.Byte>(
            5000000000L, Storage.BYTE);

    private Azure provider = null;

    public BlobStore(Azure provider) {
        super(provider);
        this.provider = provider;
    }

    @Override
    public boolean allowsNestedBuckets() throws CloudException, InternalException {
        return false;
    }

    @Override
    public boolean allowsRootObjects() throws CloudException, InternalException {
        return false;
    }

    @Override
    public boolean allowsPublicSharing() throws CloudException, InternalException {
        return false;
    }

    /*
    @Nonnull
    @Override
    public AzureBlobStoreCapabilities getCapabilities() throws CloudException, InternalException {
    if( capabilities == null ) {
        capabilities = new S3Capabilities(getProvider());
    }
    return capabilities;
    }
     */

    //private transient volatile S3Capabilities capabilities;
    private transient volatile AzureBlobStoreCapabilities capabilities;

    @Override
    public @Nonnull BlobStoreCapabilities getCapabilities() throws CloudException, InternalException {
        if (capabilities == null) {
            capabilities = new AzureBlobStoreCapabilities(getProvider());
        }
        return capabilities;
    }

    private void commitBlocks(@Nonnull String bucket, @Nonnull String object, @Nonnull Collection<String> blockIds)
            throws InternalException, CloudException {
        String resource = bucket + "/" + object;

        TreeMap<String, String> headers = new TreeMap<String, String>();
        TreeMap<String, String> queries = new TreeMap<String, String>();

        queries.put("comp", "blocklist");

        //Create post body
        Document doc = AzureStorageMethod.createDoc();
        Element blockList = doc.createElement("BlockList");

        for (String id : blockIds) {
            Element uncommitted = doc.createElement("Uncommitted");
            uncommitted.setTextContent(id);
            blockList.appendChild(uncommitted);
        }
        doc.appendChild(blockList);

        AzureStorageMethod method = new AzureStorageMethod(provider);

        method.invoke(AzureStorageMethod.Storage_OPERATION_PUT, resource, queries,
                AzureStorageMethod.convertDomToString(doc), headers, true);
    }

    public void copyFile(@Nullable String sourceBucket, @Nonnull String sourceObject, @Nullable String targetBucket,
            @Nonnull String targetObject) throws InternalException, CloudException {
        logger.debug("ENTER - " + BlobStore.class.getName() + ".copyFile(" + sourceBucket + "," + sourceObject + ","
                + targetBucket + "," + targetObject + ")");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                throw new AzureConfigException("No context was set for this request");
            }
            String regionId = ctx.getRegionId();

            if (regionId == null) {
                throw new AzureConfigException("No region ID was specified for this request");
            }
            HashMap<String, String> headers = new HashMap<String, String>();

            String storageService = provider.getStorageService();
            if (storageService == null || storageService.isEmpty()) {
                throw new CloudException(
                        "Unable to find storage service in the current region: " + ctx.getRegionId());
            }

            headers.put("x-ms-copy-source", "/" + storageService + "/" + sourceBucket + "/" + sourceObject);
            TreeMap<String, String> queryParams = new TreeMap<String, String>();
            AzureStorageMethod method = new AzureStorageMethod(provider);

            method.invoke(AzureStorageMethod.Storage_OPERATION_PUT, targetBucket + "/" + targetObject, queryParams,
                    null, headers, true);

            long timeout = System.currentTimeMillis() + (CalendarWrapper.MINUTE * 30L);

            while (timeout > System.currentTimeMillis()) {
                try {
                    Blob blob = getObject(targetBucket, targetObject);

                    if (blob != null) {
                        return;
                    }
                } catch (Throwable ignore) {
                    // ignore
                }
                try {
                    Thread.sleep(60000L);
                } catch (InterruptedException ignore) {
                }
            }
        } finally {
            logger.debug("EXIT - " + BlobStore.class.getName() + ".copyFile()");
        }
    }

    @Override
    public @Nonnull Blob createBucket(@Nonnull String bucketName, boolean findFreeName)
            throws InternalException, CloudException {
        logger.debug("ENTER - " + BlobStore.class.getName() + ".createBucket(" + bucketName + "," + findFreeName);
        if (bucketName.contains("/")) {
            throw new OperationNotSupportedException("Nested buckets not supported");
        }

        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                throw new AzureConfigException("No context was set for this request");
            }
            String regionId = ctx.getRegionId();

            if (regionId == null) {
                throw new AzureConfigException("No region ID was specified for this request");
            }
            TreeMap<String, String> queries = new TreeMap<String, String>();
            AzureStorageMethod method = new AzureStorageMethod(provider);

            queries.put("restype", "container");

            if (findFreeName) {
                String name = bucketName;
                int idx = 1;

                while (exists(name)) {
                    name = bucketName + "-" + (idx++);
                }
                bucketName = name;
            }
            method.invoke(AzureStorageMethod.Storage_OPERATION_PUT, bucketName, queries, null, null, true);
            Blob bucket = getBucket(bucketName);

            if (bucket == null) {
                logger.error("Unable to find newly created bucket: " + bucket);
                throw new CloudException("Unable to find newly created bucket: " + bucket);
            }
            return bucket;
        } finally {
            logger.debug("exit - createRootContainer(String)");
        }
    }

    @Override
    public boolean exists(@Nonnull String bucketName) throws InternalException, CloudException {
        TreeMap<String, String> queries = new TreeMap<String, String>();
        AzureStorageMethod method = new AzureStorageMethod(provider);

        queries.put("comp", "list");

        Document doc = method.getAsDoc(AzureStorageMethod.Storage_OPERATION_GET, "", queries, null, null, true);
        NodeList matches = doc.getElementsByTagName("Container");

        if (matches != null) {
            for (int i = 0; i < matches.getLength(); i++) {
                Node bucket = matches.item(i);

                if (bucket.hasChildNodes()) {
                    NodeList attributes = bucket.getChildNodes();

                    for (int j = 0; j < attributes.getLength(); j++) {
                        Node attr = attributes.item(j);

                        if (attr.getNodeName().equalsIgnoreCase("name") && attr.hasChildNodes()
                                && attr.getFirstChild().getNodeValue().trim().equals(bucketName)) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    private @Nonnull Collection<String> getBlocks(@Nonnull String bucket, @Nonnull String object,
            @Nonnull String blocklistType, @Nonnull String blockTypeTag) throws InternalException, CloudException {
        TreeMap<String, String> queries = new TreeMap<String, String>();
        ArrayList<String> idList = new ArrayList<String>();
        String resource = bucket + "/" + object;

        queries.put("comp", "blocklist");
        // committed, uncommitted, or all ; default committed
        queries.put("blocklisttype", blocklistType);

        try {

            AzureStorageMethod method = new AzureStorageMethod(provider);

            Document doc = method.getAsDoc(AzureStorageMethod.Storage_OPERATION_GET, resource, queries, null, null,
                    true);

            NodeList matches = doc.getElementsByTagName(blockTypeTag);

            if (matches != null) {
                Node block = matches.item(0);
                NodeList blockAttributes = block.getChildNodes();
                for (int i = 0; i < blockAttributes.getLength(); i++) {
                    Node node = blockAttributes.item(i);
                    if (node.getNodeType() == Node.TEXT_NODE)
                        continue;
                    if (!node.getNodeName().equals("Block"))
                        continue;
                    NodeList attributes = node.getChildNodes();
                    for (int j = 0; j < attributes.getLength(); j++) {
                        Node attribute = attributes.item(j);
                        if (attribute.getNodeName().equalsIgnoreCase("Name")) {
                            idList.add(attribute.getFirstChild().getNodeValue());
                        }
                    }
                }
            }
        } catch (AzureConfigException e) {
            e.printStackTrace();
        } catch (InternalException e) {
            e.printStackTrace();
        }
        return idList;
    }

    @Override
    public Blob getBucket(@Nonnull String bucketName) throws InternalException, CloudException {
        for (Blob blob : list(null)) {
            if (blob.isContainer()) {
                String name = blob.getBucketName();

                if (name != null && name.equals(bucketName)) {
                    return blob;
                }
            }
        }
        return null;
    }

    @Override
    public Blob getObject(@Nullable String bucketName, @Nonnull String objectName)
            throws InternalException, CloudException {
        if (bucketName == null) {
            return null;
        }
        for (Blob blob : list(bucketName)) {
            String name = blob.getObjectName();

            if (name != null && name.equals(objectName)) {
                return blob;
            }
        }
        return null;
    }

    @Nullable
    @Override
    public String getSignedObjectUrl(@Nonnull String bucket, @Nonnull String object,
            @Nonnull String expiresEpochInSeconds) throws InternalException, CloudException {
        return null; //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public @Nullable Storage<org.dasein.util.uom.storage.Byte> getObjectSize(@Nullable String bucket,
            @Nullable String object) throws InternalException, CloudException {
        String resource = bucket + "/" + object;

        AzureStorageMethod method = new AzureStorageMethod(provider);
        String blobProperty = "Content-Length";

        String result = method.getBlobProperty(AzureStorageMethod.Storage_OPERATION_GET, resource,
                new HashMap<String, String>(), null, null, true, blobProperty);

        if (result != null) {
            return new Storage<org.dasein.util.uom.storage.Byte>(Long.valueOf(result), Storage.BYTE);
        }
        return null;
    }

    @Override
    public int getMaxBuckets() throws CloudException, InternalException {
        return MAX_BUCKETS;
    }

    @Override
    protected void get(@Nullable String bucket, @Nonnull String object, @Nonnull File toFile,
            @Nullable FileTransfer transfer) throws InternalException, CloudException {
        if (logger.isTraceEnabled()) {
            logger.trace("ENTER - " + BlobStore.class.getName() + ".get(" + bucket + "," + object + "," + toFile
                    + "," + transfer + ")");
        }
        try {
            if (bucket == null) {
                throw new CloudException("No bucket was specified");
            }
            StringBuilder resource = new StringBuilder();

            resource.append(bucket);
            resource.append("/");
            resource.append(object);

            AzureStorageMethod method = new AzureStorageMethod(provider);

            InputStream input = method.getAsStream(AzureStorageMethod.Storage_OPERATION_GET, resource.toString(),
                    new HashMap<String, String>(), null, null, true);

            if (input == null) {
                throw new CloudException("No such file: " + bucket + "/" + object);
            }
            try {
                copy(input, new FileOutputStream(toFile), transfer);
            } catch (FileNotFoundException e) {
                logger.error("Could not find target file to fetch to " + toFile + ": " + e.getMessage());
                throw new InternalException(e);
            } catch (IOException e) {
                logger.error("Could not fetch file to " + toFile + ": " + e.getMessage());
                throw new CloudException(e);
            }
        } finally {
            if (logger.isTraceEnabled()) {
                logger.trace("EXIT - " + BlobStore.class.getName() + ".get()");
            }
        }
    }

    @Override
    public Storage<org.dasein.util.uom.storage.Byte> getMaxObjectSize() {
        return MAX_OBJECT_SIZE;
    }

    @Override
    public int getMaxObjectsPerBucket() throws CloudException, InternalException {
        return MAX_OBJECTS;
    }

    @Override
    public @Nonnull String getProviderTermForBucket(@Nonnull Locale locale) {
        return "bucket";
    }

    @Override
    public @Nonnull String getProviderTermForObject(@Nonnull Locale locale) {
        return "object";
    }

    @Override
    public boolean isPublic(@Nullable String bucket, @Nullable String object)
            throws CloudException, InternalException {
        AzureStorageMethod method = new AzureStorageMethod(provider);
        TreeMap<String, String> queries = new TreeMap<String, String>();
        String resource;

        if (object != null) {
            if (bucket == null) {
                return false;
            }
            resource = bucket + "/" + object;
        } else if (bucket == null) {
            return false;
        } else {
            queries.put("restype", "container");
            resource = bucket;
        }

        InputStream input = method.getAsStream(AzureStorageMethod.Storage_OPERATION_GET, resource, queries, null,
                null, false);

        return (input != null);
    }

    @Override
    public boolean isSubscribed() throws CloudException, InternalException {
        if (logger.isTraceEnabled()) {
            logger.trace("ENTER - " + BlobStore.class.getName() + ".isSubscribed()");
        }
        try {
            return (provider.getStorageService() != null);
        } finally {
            if (logger.isTraceEnabled()) {
                logger.trace("EXIT - " + BlobStore.class.getName() + ".isSubscribed()");
            }
        }
    }

    @Override
    public @Nonnull Collection<Blob> list(final @Nullable String bucket) throws CloudException, InternalException {
        final ProviderContext ctx = provider.getContext();
        PopulatorThread<Blob> populator;

        if (ctx == null) {
            throw new CloudException("No context was specified for this request");
        }
        final String regionId = ctx.getRegionId();

        if (regionId == null) {
            throw new CloudException("No region ID was specified");
        }
        provider.hold();
        populator = new PopulatorThread<Blob>(new JiteratorPopulator<Blob>() {
            public void populate(@Nonnull Jiterator<Blob> iterator) throws CloudException, InternalException {
                try {
                    list(regionId, bucket, iterator);
                } finally {
                    provider.release();
                }
            }
        });
        populator.populate();
        return populator.getResult();
    }

    private void list(@Nonnull String regionId, @Nullable String bucket, @Nonnull Jiterator<Blob> iterator)
            throws CloudException, InternalException {
        if (bucket == null) {
            loadBuckets(regionId, iterator);
        } else {
            loadObjects(regionId, bucket, iterator);
        }
    }

    private void loadBuckets(@Nonnull String regionId, @Nonnull Jiterator<Blob> iterator)
            throws CloudException, InternalException {
        if (logger.isTraceEnabled()) {
            logger.trace("ENTER - " + BlobStore.class.getName() + ".listBuckets()");
        }
        try {
            TreeMap<String, String> queries = new TreeMap<String, String>();
            AzureStorageMethod method = new AzureStorageMethod(provider);

            queries.put("comp", "list");

            Document doc = method.getAsDoc(AzureStorageMethod.Storage_OPERATION_GET, "", queries, null, null, true);
            NodeList matches = doc.getElementsByTagName("Container");

            if (matches != null) {
                for (int i = 0; i < matches.getLength(); i++) {
                    Blob bucket = toBlob(regionId, matches.item(i), "/", true);

                    if (bucket != null) {

                        iterator.push(bucket);
                    }
                }
            }
        } finally {
            if (logger.isTraceEnabled()) {
                logger.trace("EXIT - " + BlobStore.class.getName() + ".listBuckets()");
            }
        }
    }

    private void loadObjects(@Nonnull String regionId, @Nonnull String bucket, @Nonnull Jiterator<Blob> iterator)
            throws CloudException, InternalException {
        TreeMap<String, String> queries = new TreeMap<String, String>();
        AzureStorageMethod method = new AzureStorageMethod(provider);

        queries.put("restype", "container");
        queries.put("comp", "list");

        Document doc = method.getAsDoc(AzureStorageMethod.Storage_OPERATION_GET, bucket, queries, null, null, true);
        if (doc == null)
            return;

        NodeList matches = doc.getElementsByTagName("Blob");

        if (matches != null) {
            for (int i = 0; i < matches.getLength(); i++) {
                Blob file = toBlob(regionId, matches.item(i), bucket, false);

                if (file != null) {
                    iterator.push(file);
                }
            }
        }
    }

    @Override
    public void makePublic(@Nonnull String bucket) throws InternalException, CloudException {
        makePublic(bucket, null);
    }

    @Override
    public void makePublic(@Nullable String bucket, @Nullable String object)
            throws InternalException, CloudException {
        if (bucket == null && object == null) {
            throw new CloudException("No such object: null/null");
        }
        TreeMap<String, String> queries = new TreeMap<String, String>();
        TreeMap<String, String> headers = new TreeMap<String, String>();
        String resource = (object == null ? bucket : (bucket + "/" + object));
        AzureStorageMethod method = new AzureStorageMethod(provider);

        queries.put("restype", "container");
        queries.put("comp", "acl");

        headers.put("x-ms-blob-public-access", "container");

        method.invoke(AzureStorageMethod.Storage_OPERATION_PUT, resource, queries, null, headers, true);
    }

    @Override
    public @Nonnull String[] mapServiceAction(@Nonnull ServiceAction action) {
        return new String[0];
    }

    @Override
    public void move(@Nullable String sourceBucket, @Nullable String object, @Nullable String targetBucket)
            throws InternalException, CloudException {
        if (sourceBucket == null) {
            throw new CloudException("No source bucket was specified");
        }
        if (targetBucket == null) {
            throw new CloudException("No target bucket was specified");
        }
        if (object == null) {
            throw new CloudException("No source object was specified");
        }
        copy(sourceBucket, object, targetBucket, object);
        removeObject(sourceBucket, object);
    }

    @Override
    protected void put(@Nullable String bucket, @Nonnull String object, @Nonnull File file)
            throws CloudException, InternalException {
        if (bucket == null) {
            throw new CloudException("No bucket was specified");
        }

        try {
            InputStream input;

            try {
                input = new FileInputStream(file);
            } catch (IOException e) {
                logger.error("Error reading input file " + file + ": " + e.getMessage());
                throw new InternalException(e);
            }

            int fileSize = input.available();

            if (fileSize > (63 * 1024 * 1024)) {
                putBlocks(bucket, object, input);
            } else {
                TreeMap<String, String> queries = new TreeMap<String, String>();
                TreeMap<String, String> headers = new TreeMap<String, String>();
                AzureStorageMethod method = new AzureStorageMethod(provider);
                String resource = bucket + "/" + object;

                queries.put("timeout", "600");

                headers.put("x-ms-blob-type", "BlockBlob");
                headers.put("content-type", "application/octet-stream");
                method.putWithFile(AzureStorageMethod.Storage_OPERATION_PUT, resource, queries, file, headers,
                        true);
            }
        } catch (IOException e) {
            logger.error("Error uploading file " + file + ": " + e.getMessage());
            throw new CloudException(e);
        }

    }

    @Override
    protected void put(@Nullable String bucket, @Nonnull String object, @Nonnull String content)
            throws CloudException, InternalException {
        TreeMap<String, String> headers = new TreeMap<String, String>();

        headers.put("x-ms-blob-type", "BlockBlob");
        headers.put("content-type", "application/octet-stream");

        AzureStorageMethod method = new AzureStorageMethod(provider);

        method.invoke(AzureStorageMethod.Storage_OPERATION_PUT, bucket + "/" + object,
                new HashMap<String, String>(), content, headers, true);
    }

    private void putBlocks(@Nonnull String bucket, @Nonnull String object, @Nonnull InputStream input)
            throws InternalException, CloudException {
        if (logger.isTraceEnabled()) {
            logger.trace("ENTER - " + BlobStore.class.getName() + ".putBlocks(" + bucket + "," + object
                    + ",<<INPUT STREAM>>)");
        }
        try {
            int basicId = 1000;
            int blockSize = 4 * 1024 * 1024;
            byte[] bytes = new byte[blockSize];
            int read;

            try {
                try {
                    while ((read = input.read(bytes)) != -1) {
                        String blockId = Base64.encodeBase64String(String.valueOf(basicId).getBytes());

                        if (read < blockSize) {
                            byte[] subArray = Arrays.copyOfRange(bytes, 0, read);

                            putBlocks(bucket, object, subArray, blockId);
                        } else {
                            putBlocks(bucket, object, bytes, blockId);
                        }
                        basicId++;
                    }
                } catch (IOException e) {
                    throw new CloudException(e);
                }
            } finally {
                try {
                    input.close();
                } catch (Throwable ignore) {
                }

                ArrayList<String> blockIds = (ArrayList<String>) getBlocks(object, bucket, "all",
                        "UncommittedBlocks");

                commitBlocks(object, bucket, blockIds);
            }
        } finally {
            if (logger.isTraceEnabled()) {
                logger.trace("EXIT - " + BlobStore.class.getName() + ".putBlocks()");
            }
        }
    }

    private void putBlocks(@Nonnull String bucket, @Nonnull String object, @Nonnull byte[] content,
            @Nonnull String blockId) throws InternalException, CloudException {
        TreeMap<String, String> queries = new TreeMap<String, String>();
        TreeMap<String, String> headers = new TreeMap<String, String>();
        AzureStorageMethod method = new AzureStorageMethod(provider);
        String resource = bucket + "/" + object;

        queries.put("blockid", blockId);
        queries.put("comp", "block");

        headers.put("x-ms-blob-type", "BlockBlob");
        headers.put("content-type", "text/plain");

        method.putWithBytes(AzureStorageMethod.Storage_OPERATION_PUT, resource, queries, content, headers, true);
    }

    @Override
    public void removeBucket(@Nonnull String bucket) throws CloudException, InternalException {
        TreeMap<String, String> queries = new TreeMap<String, String>();
        AzureStorageMethod method = new AzureStorageMethod(provider);

        queries.put("restype", "container");
        method.invoke(AzureStorageMethod.Storage_OPERATION_DELETE, bucket, queries, null, null, true);
    }

    @Override
    public void removeObject(@Nullable String bucket, @Nonnull String name)
            throws CloudException, InternalException {
        if (bucket == null) {
            throw new CloudException("No bucket was specified for this request");
        }
        AzureStorageMethod method = new AzureStorageMethod(provider);
        String resource = bucket + "/" + name;

        method.invoke(AzureStorageMethod.Storage_OPERATION_DELETE, resource, new HashMap<String, String>(), null,
                null, true);
    }

    @Override
    public @Nonnull String renameBucket(@Nonnull String oldName, @Nonnull String newName, boolean findFreeName)
            throws CloudException, InternalException {
        Blob bucket = createBucket(newName, findFreeName);

        for (Blob file : list(oldName)) {
            int retries = 10;

            while (true) {
                retries--;
                try {
                    move(oldName, file.getObjectName(), bucket.getBucketName());
                    break;
                } catch (CloudException e) {
                    if (retries < 1) {
                        throw e;
                    }
                }
                try {
                    Thread.sleep(retries * 10000L);
                } catch (InterruptedException ignore) {
                }
            }
        }
        boolean ok = true;
        for (Blob file : list(oldName)) {
            if (file != null) {
                ok = false;
            }
        }
        if (ok) {
            removeBucket(oldName);
        }
        return newName;
    }

    @Override
    public void renameObject(@Nullable String bucket, @Nonnull String object, @Nonnull String newName)
            throws CloudException, InternalException {
        if (bucket == null) {
            throw new CloudException("No bucket was specified");
        }
        copy(bucket, object, bucket, newName);
        removeObject(bucket, object);
    }

    @Override
    public @Nonnull Blob upload(@Nonnull File source, @Nullable String bucket, @Nonnull String fileName)
            throws CloudException, InternalException {
        if (bucket == null) {
            throw new OperationNotSupportedException("Root objects not supported in cloud");
        }
        if (!exists(bucket)) {
            createBucket(bucket, false);
        }
        put(bucket, fileName, source);
        return getObject(bucket, fileName);
    }

    @Override
    public @Nonnull NamingConstraints getBucketNameRules() throws CloudException, InternalException {
        return NamingConstraints.getAlphaNumeric(1, 255).constrainedBy(new char[] { '-', '.' }).limitedToLatin1()
                .lowerCaseOnly();
    }

    @Override
    public @Nonnull NamingConstraints getObjectNameRules() throws CloudException, InternalException {
        return NamingConstraints.getAlphaNumeric(1, 255).constrainedBy(new char[] { '-', '.', ',', '#', '+' })
                .limitedToLatin1().lowerCaseOnly();
    }

    private @Nullable Blob toBlob(@Nonnull String regionId, @Nullable Node node, @Nonnull String bucket,
            boolean isContainer) {
        if (node == null) {
            return null;
        }
        NodeList attributes = node.getChildNodes();
        String object = null, location = null;
        long size = -1L, creationDate = 0L;

        for (int i = 0; i < attributes.getLength(); i++) {
            Node attribute = attributes.item(i);

            if (attribute.getNodeType() == Node.TEXT_NODE) {
                continue;
            }
            String aname = attribute.getNodeName().toLowerCase();
            String value;

            if (attribute.getChildNodes().getLength() > 0) {
                value = attribute.getFirstChild().getNodeValue();
            } else {
                continue;
            }
            if (aname.equalsIgnoreCase("Name")) {
                object = value;
            } else if (aname.equalsIgnoreCase("Url")) {
                location = value;
            } else if (aname.equalsIgnoreCase("Properties")) {
                NodeList propertyAttributes = attribute.getChildNodes();

                for (int j = 0; j < propertyAttributes.getLength(); j++) {
                    Node property = propertyAttributes.item(j);
                    String propertyName = property.getNodeName();
                    String propertyValue;

                    if (property.getChildNodes().getLength() > 0) {
                        propertyValue = property.getFirstChild().getNodeValue();
                    } else {
                        continue;
                    }

                    if (propertyName.equalsIgnoreCase("Content-Length")) {
                        size = Long.valueOf(propertyValue);
                    } else if (propertyName.equalsIgnoreCase("Last-Modified")) {
                        String RFC1123_PATTERN = "EEE, dd MMM yyyy HH:mm:ss z";
                        DateFormat rfc1123Format = new SimpleDateFormat(RFC1123_PATTERN);

                        rfc1123Format.setTimeZone(TimeZone.getTimeZone("GMT"));
                        try {
                            creationDate = rfc1123Format.parse(propertyValue).getTime();
                        } catch (ParseException e) {
                            logger.warn("Invalid date: " + propertyValue);
                        }
                    }
                }
            }
        }
        if (isContainer) {
            return Blob.getInstance(regionId, location, object, creationDate);
        } else {
            return Blob.getInstance(regionId, location, bucket, object, creationDate,
                    new Storage<Byte>(size, Storage.BYTE));
        }
    }
}