You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by ga...@apache.org on 2015/04/09 08:21:31 UTC

[01/16] jclouds git commit: JCLOUDS-651: Add Azure server-side copyBlob

Repository: jclouds
Updated Branches:
  refs/heads/1.9.x 1742110e8 -> f37956511


JCLOUDS-651: Add Azure server-side copyBlob


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/afbf61c6
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/afbf61c6
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/afbf61c6

Branch: refs/heads/1.9.x
Commit: afbf61c6573a8b896ebbecae9bfa5e4299074353
Parents: 76f261e
Author: Andrew Gaul <ga...@apache.org>
Authored: Tue Mar 31 11:36:26 2015 -0700
Committer: Andrew Gaul <ga...@apache.org>
Committed: Wed Apr 8 23:13:51 2015 -0700

----------------------------------------------------------------------
 .../azureblob/blobstore/AzureBlobStore.java     | 29 ++++++++++++++++++++
 1 file changed, 29 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/afbf61c6/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobStore.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobStore.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobStore.java
index 9144700..ccd31b5 100644
--- a/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobStore.java
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobStore.java
@@ -19,7 +19,9 @@ package org.jclouds.azureblob.blobstore;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.jclouds.azure.storage.options.ListOptions.Builder.includeMetadata;
 
+import java.net.URI;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import javax.inject.Inject;
@@ -38,6 +40,7 @@ import org.jclouds.azureblob.blobstore.strategy.MultipartUploadStrategy;
 import org.jclouds.azureblob.domain.ContainerProperties;
 import org.jclouds.azureblob.domain.ListBlobBlocksResponse;
 import org.jclouds.azureblob.domain.PublicAccess;
+import org.jclouds.azureblob.options.CopyBlobOptions;
 import org.jclouds.azureblob.options.ListBlobsOptions;
 import org.jclouds.blobstore.BlobStoreContext;
 import org.jclouds.blobstore.domain.Blob;
@@ -49,6 +52,7 @@ import org.jclouds.blobstore.domain.StorageMetadata;
 import org.jclouds.blobstore.domain.internal.PageSetImpl;
 import org.jclouds.blobstore.functions.BlobToHttpGetOptions;
 import org.jclouds.blobstore.internal.BaseBlobStore;
+import org.jclouds.blobstore.options.CopyOptions;
 import org.jclouds.blobstore.options.CreateContainerOptions;
 import org.jclouds.blobstore.options.ListContainerOptions;
 import org.jclouds.blobstore.options.PutOptions;
@@ -58,6 +62,7 @@ import org.jclouds.domain.Location;
 import org.jclouds.http.options.GetOptions;
 
 import com.google.common.base.Function;
+import com.google.common.base.Optional;
 import com.google.common.base.Supplier;
 import com.google.common.collect.Iterables;
 import org.jclouds.io.Payload;
@@ -215,6 +220,30 @@ public class AzureBlobStore extends BaseBlobStore {
       return putBlob(container, blob);
    }
 
+   @Override
+   public String copyBlob(String fromContainer, String fromName, String toContainer, String toName,
+         CopyOptions options) {
+      CopyBlobOptions.Builder azureOptions = CopyBlobOptions.builder();
+
+      Optional<Map<String, String>> userMetadata = options.getUserMetadata();
+      if (userMetadata.isPresent()) {
+         azureOptions.overrideUserMetadata(userMetadata.get());
+      }
+
+      URI source = context.getSigner().signGetBlob(fromContainer, fromName).getEndpoint();
+      sync.copyBlob(source, toContainer, toName, azureOptions.build());
+
+      String eTag = sync.getBlobProperties(toContainer, toName).getETag();
+
+      // TODO: Azure does not allow updating system metadata during copy - call SetBlobProperties (not yet implemented)
+      // TODO: content disposition
+      // TODO: content encoding
+      // TODO: content language
+      // TODO: content type
+
+      return eTag;
+   }
+
    /**
     * This implementation invokes {@link AzureBlobClient#deleteObject}
     * 


[11/16] jclouds git commit: JCLOUDS-651: Fix filesystem integration tests in OSX

Posted by ga...@apache.org.
JCLOUDS-651: Fix filesystem integration tests in OSX


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/4e37ffb6
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/4e37ffb6
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/4e37ffb6

Branch: refs/heads/1.9.x
Commit: 4e37ffb6b6502e5ccd6df516380168155cc7d8e8
Parents: 211b073
Author: Ignasi Barrera <na...@apache.org>
Authored: Tue Apr 7 10:51:33 2015 +0200
Committer: Andrew Gaul <ga...@apache.org>
Committed: Wed Apr 8 23:13:53 2015 -0700

----------------------------------------------------------------------
 .../blobstore/integration/internal/BaseBlobIntegrationTest.java  | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/4e37ffb6/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
----------------------------------------------------------------------
diff --git a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
index 1be2597..12e83c3 100644
--- a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
+++ b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
@@ -760,7 +760,7 @@ public class BaseBlobIntegrationTest extends BaseBlobStoreIntegrationTest {
             Closeables2.closeQuietly(is);
          }
          checkContentMetadata(toBlob);
-         assertThat(toBlob.getMetadata().getUserMetadata()).isEqualTo(userMetadata);
+         checkUserMetadata(toBlob.getMetadata().getUserMetadata(), userMetadata);
       } finally {
          returnContainer(toContainer);
          returnContainer(fromContainer);
@@ -799,7 +799,7 @@ public class BaseBlobIntegrationTest extends BaseBlobStoreIntegrationTest {
          // TODO: S3 overrideMetadataWith also overrides system metadata
          // TODO: Swift does not preserve system metadata
          //checkContentMetadata(toBlob);
-         assertThat(toBlob.getMetadata().getUserMetadata()).isEqualTo(userMetadata);
+         checkUserMetadata(toBlob.getMetadata().getUserMetadata(), userMetadata);
       } finally {
          returnContainer(toContainer);
          returnContainer(fromContainer);


[02/16] jclouds git commit: JCLOUDS-651: Add copyBlob to portable abstraction

Posted by ga...@apache.org.
JCLOUDS-651: Add copyBlob to portable abstraction

Some providers optimize this operation with a server-side copy.  Add a
fallback implementation which does a client-side copy.


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/76f261e8
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/76f261e8
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/76f261e8

Branch: refs/heads/1.9.x
Commit: 76f261e818c91da4229b93fd3dfa70c1c5ff8db5
Parents: 5487400
Author: Andrew Gaul <ga...@apache.org>
Authored: Fri Sep 5 17:32:08 2014 -0700
Committer: Andrew Gaul <ga...@apache.org>
Committed: Wed Apr 8 23:13:51 2015 -0700

----------------------------------------------------------------------
 .../java/org/jclouds/blobstore/BlobStore.java   | 13 ++++
 .../blobstore/config/LocalBlobStore.java        | 41 +++++++++++
 .../blobstore/internal/BaseBlobStore.java       | 44 +++++++++++
 .../jclouds/blobstore/options/CopyOptions.java  | 57 +++++++++++++++
 .../internal/BaseBlobIntegrationTest.java       | 77 ++++++++++++++++++++
 5 files changed, 232 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/76f261e8/blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java b/blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java
index bebdb48..f4b26d2 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java
@@ -27,6 +27,7 @@ import org.jclouds.blobstore.domain.BlobMetadata;
 import org.jclouds.blobstore.domain.ContainerAccess;
 import org.jclouds.blobstore.domain.PageSet;
 import org.jclouds.blobstore.domain.StorageMetadata;
+import org.jclouds.blobstore.options.CopyOptions;
 import org.jclouds.blobstore.options.CreateContainerOptions;
 import org.jclouds.blobstore.options.GetOptions;
 import org.jclouds.blobstore.options.ListContainerOptions;
@@ -230,6 +231,18 @@ public interface BlobStore {
    String putBlob(String container, Blob blob, PutOptions options);
 
    /**
+    * Copy blob from one container to another.  Some providers implement this
+    * more efficiently than corresponding getBlob and putBlob operations.
+    *
+    * Note: options are currently ignored
+    *
+    * @return ETag of new blob
+    * @throws ContainerNotFoundException if either container does not exist
+    */
+   String copyBlob(String fromContainer, String fromName, String toContainer, String toName,
+         CopyOptions options);
+
+   /**
     * Retrieves the metadata of a {@code Blob} at location {@code container/name}
     * 
     * @param container

http://git-wip-us.apache.org/repos/asf/jclouds/blob/76f261e8/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java b/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
index 36e883d..2e1b636 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
@@ -30,7 +30,9 @@ import static org.jclouds.blobstore.options.ListContainerOptions.Builder.recursi
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.Date;
+import java.util.Map;
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.regex.Pattern;
@@ -57,6 +59,7 @@ import org.jclouds.blobstore.domain.StorageType;
 import org.jclouds.blobstore.domain.internal.MutableStorageMetadataImpl;
 import org.jclouds.blobstore.domain.internal.PageSetImpl;
 import org.jclouds.blobstore.domain.internal.StorageMetadataImpl;
+import org.jclouds.blobstore.options.CopyOptions;
 import org.jclouds.blobstore.options.CreateContainerOptions;
 import org.jclouds.blobstore.options.GetOptions;
 import org.jclouds.blobstore.options.ListContainerOptions;
@@ -75,11 +78,13 @@ import org.jclouds.io.ContentMetadata;
 import org.jclouds.io.ContentMetadataCodec;
 import org.jclouds.io.Payload;
 import org.jclouds.logging.Logger;
+import org.jclouds.util.Closeables2;
 
 import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.base.Supplier;
+import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 
@@ -493,6 +498,42 @@ public final class LocalBlobStore implements BlobStore {
       }
    }
 
+   @Override
+   public String copyBlob(String fromContainer, String fromName, String toContainer, String toName,
+         CopyOptions options) {
+      Blob blob = getBlob(fromContainer, fromName);
+      if (blob == null) {
+         throw new KeyNotFoundException(fromContainer, fromName, "while copying");
+      }
+
+      InputStream is = null;
+      try {
+         is = blob.getPayload().openStream();
+         ContentMetadata metadata = blob.getMetadata().getContentMetadata();
+         BlobBuilder.PayloadBlobBuilder builder = blobBuilder(toName)
+               .payload(is)
+               .contentDisposition(metadata.getContentDisposition())
+               .contentEncoding(metadata.getContentEncoding())
+               .contentLanguage(metadata.getContentLanguage())
+               .contentType(metadata.getContentType());
+         Long contentLength = metadata.getContentLength();
+         if (contentLength != null) {
+            builder.contentLength(contentLength);
+         }
+         Optional<Map<String, String>> userMetadata = options.getUserMetadata();
+         if (userMetadata.isPresent()) {
+            builder.userMetadata(userMetadata.get());
+         } else {
+            builder.userMetadata(blob.getMetadata().getUserMetadata());
+         }
+         return putBlob(toContainer, builder.build());
+      } catch (IOException ioe) {
+         throw Throwables.propagate(ioe);
+      } finally {
+         Closeables2.closeQuietly(is);
+      }
+   }
+
    private void copyPayloadHeadersToBlob(Payload payload, Blob blob) {
       blob.getAllHeaders().putAll(contentMetadataCodec.toHeaders(payload.getContentMetadata()));
    }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/76f261e8/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseBlobStore.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseBlobStore.java b/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseBlobStore.java
index 73a01b3..1204e0d 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseBlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseBlobStore.java
@@ -21,6 +21,9 @@ import static com.google.common.base.Preconditions.checkState;
 import static org.jclouds.blobstore.options.ListContainerOptions.Builder.recursive;
 import static org.jclouds.util.Predicates2.retry;
 
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.Map;
 import java.util.Set;
 
 import javax.inject.Inject;
@@ -28,17 +31,23 @@ import javax.inject.Inject;
 import org.jclouds.blobstore.BlobStore;
 import org.jclouds.blobstore.BlobStoreContext;
 import org.jclouds.blobstore.ContainerNotFoundException;
+import org.jclouds.blobstore.KeyNotFoundException;
 import org.jclouds.blobstore.domain.Blob;
 import org.jclouds.blobstore.domain.BlobBuilder;
 import org.jclouds.blobstore.domain.PageSet;
 import org.jclouds.blobstore.domain.StorageMetadata;
+import org.jclouds.blobstore.options.CopyOptions;
 import org.jclouds.blobstore.options.ListContainerOptions;
 import org.jclouds.blobstore.util.BlobUtils;
 import org.jclouds.collect.Memoized;
 import org.jclouds.domain.Location;
+import org.jclouds.io.ContentMetadata;
+import org.jclouds.util.Closeables2;
 
+import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.base.Supplier;
+import com.google.common.base.Throwables;
 
 public abstract class BaseBlobStore implements BlobStore {
 
@@ -231,4 +240,39 @@ public abstract class BaseBlobStore implements BlobStore {
     */
    protected abstract boolean deleteAndVerifyContainerGone(String container);
 
+   @Override
+   public String copyBlob(String fromContainer, String fromName, String toContainer, String toName,
+         CopyOptions options) {
+      Blob blob = getBlob(fromContainer, fromName);
+      if (blob == null) {
+         throw new KeyNotFoundException(fromContainer, fromName, "while copying");
+      }
+
+      InputStream is = null;
+      try {
+         is = blob.getPayload().openStream();
+         ContentMetadata metadata = blob.getMetadata().getContentMetadata();
+         BlobBuilder.PayloadBlobBuilder builder = blobBuilder(toName)
+               .payload(is)
+               .contentDisposition(metadata.getContentDisposition())
+               .contentEncoding(metadata.getContentEncoding())
+               .contentLanguage(metadata.getContentLanguage())
+               .contentType(metadata.getContentType());
+         Long contentLength = metadata.getContentLength();
+         if (contentLength != null) {
+            builder.contentLength(contentLength);
+         }
+         Optional<Map<String, String>> userMetadata = options.getUserMetadata();
+         if (userMetadata.isPresent()) {
+            builder.userMetadata(userMetadata.get());
+         } else {
+            builder.userMetadata(blob.getMetadata().getUserMetadata());
+         }
+         return putBlob(toContainer, builder.build());
+      } catch (IOException ioe) {
+         throw Throwables.propagate(ioe);
+      } finally {
+         Closeables2.closeQuietly(is);
+      }
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/76f261e8/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java b/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java
new file mode 100644
index 0000000..3b89e98
--- /dev/null
+++ b/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jclouds.blobstore.options;
+
+import java.util.Map;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+
+public final class CopyOptions {
+   public static final CopyOptions NONE = builder().build();
+
+   private final Optional<Map<String, String>> userMetadata;
+
+   private CopyOptions(Builder builder) {
+      this.userMetadata = Optional.fromNullable(builder.userMetadata);
+   }
+
+   public Optional<Map<String, String>> getUserMetadata() {
+      return userMetadata;
+   }
+
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public static class Builder {
+      Map<String, String> userMetadata;
+
+      Builder() {
+      }
+
+      public Builder userMetadata(Map<String, String> userMetadata) {
+         this.userMetadata = ImmutableMap.copyOf(userMetadata);
+         return this;
+      }
+
+      public CopyOptions build() {
+          return new CopyOptions(this);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/76f261e8/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
----------------------------------------------------------------------
diff --git a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
index 81b3af2..b205e84 100644
--- a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
+++ b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
@@ -57,6 +57,7 @@ import org.jclouds.blobstore.domain.BlobMetadata;
 import org.jclouds.blobstore.domain.PageSet;
 import org.jclouds.blobstore.domain.StorageMetadata;
 import org.jclouds.blobstore.domain.StorageType;
+import org.jclouds.blobstore.options.CopyOptions;
 import org.jclouds.blobstore.options.PutOptions;
 import org.jclouds.crypto.Crypto;
 import org.jclouds.encryption.internal.JCECrypto;
@@ -82,6 +83,7 @@ import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.common.hash.HashCode;
+import com.google.common.io.ByteStreams;
 import com.google.common.io.ByteSource;
 import com.google.common.io.Files;
 import com.google.common.util.concurrent.ListenableFuture;
@@ -729,6 +731,81 @@ public class BaseBlobIntegrationTest extends BaseBlobStoreIntegrationTest {
       }
    }
 
+   @Test(groups = { "integration", "live" })
+   public void testCopyBlobCopyMetadata() throws Exception {
+      BlobStore blobStore = view.getBlobStore();
+      String fromName = "source";
+      String toName = "to";
+      ByteSource payload = TestUtils.randomByteSource().slice(0, 1024);
+      Map<String, String> userMetadata = ImmutableMap.of("key1", "value1", "key2", "value2");
+      PayloadBlobBuilder blobBuilder = blobStore
+            .blobBuilder(fromName)
+            .payload(payload)
+            .contentLength(payload.size());
+      addContentMetadata(blobBuilder);
+      blobBuilder.userMetadata(userMetadata);
+      Blob blob = blobBuilder.build();
+
+      String fromContainer = getContainerName();
+      String toContainer = getContainerName();
+      try {
+         blobStore.putBlob(fromContainer, blob);
+         blobStore.copyBlob(fromContainer, fromName, toContainer, toName, CopyOptions.NONE);
+         Blob toBlob = blobStore.getBlob(toContainer, toName);
+         InputStream is = null;
+         try {
+            is = toBlob.getPayload().openStream();
+            assertEquals(ByteStreams.toByteArray(is), payload.read());
+         } finally {
+            Closeables2.closeQuietly(is);
+         }
+         // TODO: Swift does not preserve system metadata
+         //checkContentMetadata(toBlob);
+         assertThat(toBlob.getMetadata().getUserMetadata()).isEqualTo(userMetadata);
+      } finally {
+         returnContainer(toContainer);
+         returnContainer(fromContainer);
+      }
+   }
+
+   @Test(groups = { "integration", "live" })
+   public void testCopyBlobReplaceMetadata() throws Exception {
+      BlobStore blobStore = view.getBlobStore();
+      String fromName = "source";
+      String toName = "to";
+      ByteSource payload = TestUtils.randomByteSource().slice(0, 1024);
+      PayloadBlobBuilder blobBuilder = blobStore
+            .blobBuilder(fromName)
+            .payload(payload)
+            .contentLength(payload.size());
+      addContentMetadata(blobBuilder);
+      Blob blob = blobBuilder.build();
+
+      String fromContainer = getContainerName();
+      String toContainer = getContainerName();
+      try {
+         blobStore.putBlob(fromContainer, blob);
+         Map<String, String> userMetadata = ImmutableMap.of("key1", "value1", "key2", "value2");
+         blobStore.copyBlob(fromContainer, fromName, toContainer, toName,
+               CopyOptions.builder().userMetadata(userMetadata).build());
+         Blob toBlob = blobStore.getBlob(toContainer, toName);
+         InputStream is = null;
+         try {
+            is = toBlob.getPayload().openStream();
+            assertEquals(ByteStreams.toByteArray(is), payload.read());
+         } finally {
+            Closeables2.closeQuietly(is);
+         }
+         // TODO: S3 overrideMetadataWith also overrides system metadata
+         // TODO: Swift does not preserve system metadata
+         //checkContentMetadata(toBlob);
+         assertThat(toBlob.getMetadata().getUserMetadata()).isEqualTo(userMetadata);
+      } finally {
+         returnContainer(toContainer);
+         returnContainer(fromContainer);
+      }
+   }
+
    protected void validateMetadata(BlobMetadata metadata) throws IOException {
       assert metadata.getContentMetadata().getContentType().startsWith("text/plain") : metadata.getContentMetadata()
                .getContentType();


[04/16] jclouds git commit: Return ETag from Azure setBlobMetadata

Posted by ga...@apache.org.
Return ETag from Azure setBlobMetadata

Also fix up live tests.


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/75f6d76f
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/75f6d76f
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/75f6d76f

Branch: refs/heads/1.9.x
Commit: 75f6d76f1d9efa52f999611fc7fa78a35e941496
Parents: 1742110
Author: Andrew Gaul <ga...@apache.org>
Authored: Fri Dec 19 16:06:51 2014 -0800
Committer: Andrew Gaul <ga...@apache.org>
Committed: Wed Apr 8 23:13:51 2015 -0700

----------------------------------------------------------------------
 .../org/jclouds/azureblob/AzureBlobClient.java  |  3 +-
 .../azureblob/AzureBlobClientLiveTest.java      | 53 ++++++++------------
 .../jclouds/azureblob/AzureBlobClientTest.java  |  2 +-
 3 files changed, 24 insertions(+), 34 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/75f6d76f/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java
index 7223888..368dda0 100644
--- a/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java
@@ -415,7 +415,8 @@ public interface AzureBlobClient extends Closeable {
    @PUT
    @Path("{container}/{name}")
    @QueryParams(keys = { "comp" }, values = { "metadata" })
-   void setBlobMetadata(
+   @ResponseParser(ParseETagHeader.class)
+   String setBlobMetadata(
          @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container,
          @PathParam("name") String name, @BinderParam(BindMapToHeadersWithPrefix.class) Map<String, String> metadata);
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/75f6d76f/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java b/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java
index de8d90c..2dccf19 100644
--- a/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java
+++ b/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java
@@ -231,32 +231,26 @@ public class AzureBlobClientLiveTest extends BaseBlobStoreIntegrationTest {
 
       // Test HEAD of object
       BlobProperties metadata = getApi().getBlobProperties(privateContainer, object.getProperties().getName());
-      // TODO assertEquals(metadata.getName(),
-      // object.getProperties().getName());
-      // we can't check this while hacking around lack of content-md5, as GET of
-      // the first byte will
-      // show incorrect length 1, the returned size, as opposed to the real
-      // length. This is an ok
-      // tradeoff, as a container list will contain the correct size of the
-      // objects in an
-      // inexpensive fashion
-      // http://code.google.com/p/jclouds/issues/detail?id=92
-      // assertEquals(metadata.getSize(), data.length());
+      assertEquals(metadata.getName(), object.getProperties().getName());
+      assertEquals(metadata.getContentMetadata().getContentLength(), Long.valueOf(data.length()));
       assertEquals(metadata.getContentMetadata().getContentType(), "text/plain");
-      // Azure doesn't return the Content-MD5 on head request..
-      assertEquals(base16().lowerCase().encode(md5),
+      assertEquals(base16().lowerCase().encode(metadata.getContentMetadata().getContentMD5()),
             base16().lowerCase().encode(object.getProperties().getContentMetadata().getContentMD5()));
       assertEquals(metadata.getETag(), newEtag);
       assertEquals(metadata.getMetadata().entrySet().size(), 1);
       assertEquals(metadata.getMetadata().get("mykey"), "metadata-value");
 
-      // // Test POST to update object's metadata
-      // Multimap<String, String> userMetadata = LinkedHashMultimap.create();
-      // userMetadata.put("New-Metadata-1", "value-1");
-      // userMetadata.put("New-Metadata-2", "value-2");
-      // assertTrue(getApi().setBlobProperties(privateContainer,
-      // object.getProperties().getName(),
-      // userMetadata));
+      // Test POST to update object's metadata
+      Map<String, String> userMetadata = ImmutableMap.<String, String>builder()
+            .put("new_metadata_1", "value-1")
+            .put("new_metadata_2", "value-2")
+            .build();
+      String eTag = getApi().setBlobMetadata(privateContainer, object.getProperties().getName(), userMetadata);
+      assertThat(eTag).isNotNull();
+
+      // Azure ETag are timestamps not content hash
+      String eTag2 = getApi().setBlobMetadata(privateContainer, object.getProperties().getName(), userMetadata);
+      assertThat(eTag2).isNotNull().isNotEqualTo(eTag);
 
       // Test GET of missing object
       assert getApi().getBlob(privateContainer, "non-existent-object") == null;
@@ -264,21 +258,14 @@ public class AzureBlobClientLiveTest extends BaseBlobStoreIntegrationTest {
       // Test GET of object (including updated metadata)
       AzureBlob getBlob = getApi().getBlob(privateContainer, object.getProperties().getName());
       assertEquals(Strings2.toStringAndClose(getBlob.getPayload().openStream()), data);
-      // TODO assertEquals(getBlob.getName(), object.getProperties().getName());
+      assertEquals(getBlob.getProperties().getName(), object.getProperties().getName());
       assertEquals(getBlob.getPayload().getContentMetadata().getContentLength(), Long.valueOf(data.length()));
       assertEquals(getBlob.getProperties().getContentMetadata().getContentType(), "text/plain");
       assertEquals(base16().lowerCase().encode(md5),
             base16().lowerCase().encode(getBlob.getProperties().getContentMetadata().getContentMD5()));
-      assertEquals(newEtag, getBlob.getProperties().getETag());
-      // wait until we can update metadata
-      // assertEquals(getBlob.getProperties().getMetadata().entries().size(),
-      // 2);
-      // assertEquals(
-      // Iterables.getLast(getBlob.getProperties().getMetadata().get("New-Metadata-1")),
-      // "value-1");
-      // assertEquals(
-      // Iterables.getLast(getBlob.getProperties().getMetadata().get("New-Metadata-2")),
-      // "value-2");
+      assertEquals(getBlob.getProperties().getMetadata().size(), 2);
+      assertEquals(getBlob.getProperties().getMetadata().get("new_metadata_1"), "value-1");
+      assertEquals(getBlob.getProperties().getMetadata().get("new_metadata_2"), "value-2");
       assertEquals(metadata.getMetadata().entrySet().size(), 1);
       assertEquals(metadata.getMetadata().get("mykey"), "metadata-value");
 
@@ -290,7 +277,9 @@ public class AzureBlobClientLiveTest extends BaseBlobStoreIntegrationTest {
                   .maxResults(1).includeMetadata());
       assertEquals(response.size(), 1);
       assertEquals(Iterables.getOnlyElement(response).getName(), object.getProperties().getName());
-      assertEquals(Iterables.getOnlyElement(response).getMetadata(), ImmutableMap.of("mykey", "metadata-value"));
+      assertEquals(Iterables.getOnlyElement(response).getMetadata().size(), 2);
+      assertEquals(Iterables.getOnlyElement(response).getMetadata().get("new_metadata_1"), "value-1");
+      assertEquals(Iterables.getOnlyElement(response).getMetadata().get("new_metadata_2"), "value-2");
 
       // Test PUT with invalid ETag (as if object's data was corrupted in
       // transit)

http://git-wip-us.apache.org/repos/asf/jclouds/blob/75f6d76f/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientTest.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientTest.java b/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientTest.java
index 18a266a..5e82b35 100644
--- a/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientTest.java
+++ b/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientTest.java
@@ -299,7 +299,7 @@ public class AzureBlobClientTest extends BaseRestAnnotationProcessingTest<AzureB
                "x-ms-version: 2012-02-12\n");
       assertPayloadEquals(request, null, null, false);
 
-      assertResponseParserClassEquals(method, request, ReleasePayloadAndReturn.class);
+      assertResponseParserClassEquals(method, request, ParseETagHeader.class);
       assertSaxResponseParserClassEquals(method, null);
       assertFallbackClassEquals(method, null);
    }


[14/16] jclouds git commit: JCLOUDS-651: Swift copy object content metadata

Posted by ga...@apache.org.
JCLOUDS-651: Swift copy object content metadata


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/f3795651
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/f3795651
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/f3795651

Branch: refs/heads/1.9.x
Commit: f3795651140449c2ddc2ed2c9c36cfc88339ea38
Parents: a260582
Author: Andrew Gaul <ga...@apache.org>
Authored: Mon Apr 6 17:51:58 2015 -0700
Committer: Andrew Gaul <ga...@apache.org>
Committed: Wed Apr 8 23:13:54 2015 -0700

----------------------------------------------------------------------
 .../blobstore/RegionScopedSwiftBlobStore.java   | 78 +++++++++++++-------
 .../SwiftBlobIntegrationLiveTest.java           |  5 ++
 2 files changed, 55 insertions(+), 28 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/f3795651/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
index 4192a96..7962bbd 100644
--- a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
@@ -237,45 +237,67 @@ public class RegionScopedSwiftBlobStore implements BlobStore {
    public String copyBlob(String fromContainer, String fromName, String toContainer, String toName,
          CopyOptions options) {
       ObjectApi objectApi = api.getObjectApi(regionId, toContainer);
-      SwiftObject metadata = api.getObjectApi(regionId, fromContainer).getWithoutBody(fromName);
 
       Map<String, String> userMetadata;
-      if (options.getUserMetadata().isPresent()) {
-         userMetadata = options.getUserMetadata().get();
+      Map<String, String> systemMetadata = Maps.newHashMap();
+      ContentMetadata contentMetadata = options.getContentMetadata().orNull();
+
+      if (contentMetadata != null ||
+            options.getUserMetadata().isPresent()) {
+         if (contentMetadata != null) {
+            String contentDisposition = contentMetadata.getContentDisposition();
+            if (contentDisposition != null) {
+               systemMetadata.put(HttpHeaders.CONTENT_DISPOSITION, contentDisposition);
+            }
+
+            String contentEncoding = contentMetadata.getContentEncoding();
+            if (contentEncoding != null) {
+               systemMetadata.put(HttpHeaders.CONTENT_ENCODING, contentEncoding);
+            }
+
+            String contentLanguage = contentMetadata.getContentLanguage();
+            if (contentLanguage != null) {
+               systemMetadata.put(HttpHeaders.CONTENT_LANGUAGE, contentLanguage);
+            }
+
+            String contentType = contentMetadata.getContentType();
+            if (contentType != null) {
+               systemMetadata.put(HttpHeaders.CONTENT_TYPE, contentType);
+            }
+         }
+         if (options.getUserMetadata().isPresent()) {
+            userMetadata = options.getUserMetadata().get();
+         } else {
+            userMetadata = Maps.newHashMap();
+         }
       } else {
+         SwiftObject metadata = api.getObjectApi(regionId, fromContainer).getWithoutBody(fromName);
+         contentMetadata = metadata.getPayload().getContentMetadata();
+         String contentDisposition = contentMetadata.getContentDisposition();
+         if (contentDisposition != null) {
+            systemMetadata.put(HttpHeaders.CONTENT_DISPOSITION, contentDisposition);
+         }
+         String contentEncoding = contentMetadata.getContentEncoding();
+         if (contentEncoding != null) {
+            systemMetadata.put(HttpHeaders.CONTENT_ENCODING, contentEncoding);
+         }
+         String contentLanguage = contentMetadata.getContentLanguage();
+         if (contentLanguage != null) {
+            systemMetadata.put(HttpHeaders.CONTENT_LANGUAGE, contentLanguage);
+         }
+         String contentType = contentMetadata.getContentType();
+         if (contentType != null) {
+            systemMetadata.put(HttpHeaders.CONTENT_TYPE, contentType);
+         }
          userMetadata = metadata.getMetadata();
       }
 
-      // copy existing system metadata
-      Map<String, String> systemMetadata = Maps.newHashMap();
-      ContentMetadata contentMetadata = metadata.getPayload().getContentMetadata();
-      String contentDisposition = contentMetadata.getContentDisposition();
-      if (contentDisposition != null) {
-         systemMetadata.put(HttpHeaders.CONTENT_DISPOSITION, contentDisposition);
-      }
-      String contentEncoding = contentMetadata.getContentEncoding();
-      if (contentEncoding != null) {
-         systemMetadata.put(HttpHeaders.CONTENT_ENCODING, contentEncoding);
-      }
-      String contentLanguage = contentMetadata.getContentLanguage();
-      if (contentLanguage != null) {
-         systemMetadata.put(HttpHeaders.CONTENT_LANGUAGE, contentLanguage);
-      }
-      String contentType = contentMetadata.getContentType();
-      if (contentType != null) {
-         systemMetadata.put(HttpHeaders.CONTENT_TYPE, contentType);
-      }
-
       boolean copied = objectApi.copy(toName, fromContainer, fromName, userMetadata, systemMetadata);
       if (!copied) {
          throw new RuntimeException("could not copy blob");
       }
 
-      // TODO: override content disposition
-      // TODO: override content encoding
-      // TODO: override content language
-      // TODO: override content type
-
+      // TODO: Swift copy object *appends* user metadata, does not overwrite
       return objectApi.getWithoutBody(toName).getETag();
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/f3795651/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobIntegrationLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobIntegrationLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobIntegrationLiveTest.java
index f05b185..c3eb7de 100644
--- a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobIntegrationLiveTest.java
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobIntegrationLiveTest.java
@@ -78,4 +78,9 @@ public class SwiftBlobIntegrationLiveTest extends BaseBlobIntegrationTest {
    public void testSetBlobAccess() throws Exception {
       throw new SkipException("unsupported in swift");
    }
+
+   @Override
+   public void testCopyBlobReplaceMetadata() throws Exception {
+      throw new SkipException("Swift only supports appending to user metadata, not replacing it");
+   }
 }


[07/16] jclouds git commit: JCLOUDS-651: Ensure copy replaces user metadata

Posted by ga...@apache.org.
JCLOUDS-651: Ensure copy replaces user metadata

Some object stores have additive semantics for metadata.  As currently
implemented Azure, S3, and Swift consistently replace user metadata
but have different behavior for system metadata, e.g., Content-Type.


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/c87e2052
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/c87e2052
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/c87e2052

Branch: refs/heads/1.9.x
Commit: c87e2052acee357c3174f077e5982a64e55408c8
Parents: 70eac74
Author: Andrew Gaul <ga...@apache.org>
Authored: Fri Apr 3 17:50:33 2015 -0700
Committer: Andrew Gaul <ga...@apache.org>
Committed: Wed Apr 8 23:13:52 2015 -0700

----------------------------------------------------------------------
 .../blobstore/integration/internal/BaseBlobIntegrationTest.java   | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/c87e2052/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
----------------------------------------------------------------------
diff --git a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
index b205e84..0d57fc5 100644
--- a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
+++ b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
@@ -776,6 +776,7 @@ public class BaseBlobIntegrationTest extends BaseBlobStoreIntegrationTest {
       ByteSource payload = TestUtils.randomByteSource().slice(0, 1024);
       PayloadBlobBuilder blobBuilder = blobStore
             .blobBuilder(fromName)
+            .userMetadata(ImmutableMap.of("key1", "value1", "key2", "value2"))
             .payload(payload)
             .contentLength(payload.size());
       addContentMetadata(blobBuilder);
@@ -785,7 +786,7 @@ public class BaseBlobIntegrationTest extends BaseBlobStoreIntegrationTest {
       String toContainer = getContainerName();
       try {
          blobStore.putBlob(fromContainer, blob);
-         Map<String, String> userMetadata = ImmutableMap.of("key1", "value1", "key2", "value2");
+         Map<String, String> userMetadata = ImmutableMap.of("key3", "value3", "key4", "value4");
          blobStore.copyBlob(fromContainer, fromName, toContainer, toName,
                CopyOptions.builder().userMetadata(userMetadata).build());
          Blob toBlob = blobStore.getBlob(toContainer, toName);


[16/16] jclouds git commit: JCLOUDS-651: portable copy object content metadata

Posted by ga...@apache.org.
JCLOUDS-651: portable copy object content metadata


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/0647ba8c
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/0647ba8c
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/0647ba8c

Branch: refs/heads/1.9.x
Commit: 0647ba8c5506a506dde9cadb2db3cb7013afb1be
Parents: 3f2e9f3
Author: Andrew Gaul <ga...@apache.org>
Authored: Mon Apr 6 17:51:17 2015 -0700
Committer: Andrew Gaul <ga...@apache.org>
Committed: Wed Apr 8 23:13:54 2015 -0700

----------------------------------------------------------------------
 .../blobstore/config/LocalBlobStore.java        | 31 ++++++++++++++++----
 .../jclouds/blobstore/options/CopyOptions.java  | 16 ++++++++++
 .../internal/BaseBlobIntegrationTest.java       | 22 ++++++++++----
 3 files changed, 58 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/0647ba8c/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java b/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
index 2e1b636..6da6f1f 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
@@ -511,15 +511,36 @@ public final class LocalBlobStore implements BlobStore {
          is = blob.getPayload().openStream();
          ContentMetadata metadata = blob.getMetadata().getContentMetadata();
          BlobBuilder.PayloadBlobBuilder builder = blobBuilder(toName)
-               .payload(is)
-               .contentDisposition(metadata.getContentDisposition())
-               .contentEncoding(metadata.getContentEncoding())
-               .contentLanguage(metadata.getContentLanguage())
-               .contentType(metadata.getContentType());
+               .payload(is);
          Long contentLength = metadata.getContentLength();
          if (contentLength != null) {
             builder.contentLength(contentLength);
          }
+
+         if (options.getContentMetadata().isPresent()) {
+            ContentMetadata contentMetadata = options.getContentMetadata().get();
+            String contentDisposition = contentMetadata.getContentDisposition();
+            if (contentDisposition != null) {
+               builder.contentDisposition(contentDisposition);
+            }
+            String contentEncoding = contentMetadata.getContentEncoding();
+            if (contentEncoding != null) {
+               builder.contentEncoding(contentEncoding);
+            }
+            String contentLanguage = contentMetadata.getContentLanguage();
+            if (contentLanguage != null) {
+               builder.contentLanguage(contentLanguage);
+            }
+            String contentType = contentMetadata.getContentType();
+            if (contentType != null) {
+               builder.contentType(contentType);
+            }
+         } else {
+            builder.contentDisposition(metadata.getContentDisposition())
+                   .contentEncoding(metadata.getContentEncoding())
+                   .contentLanguage(metadata.getContentLanguage())
+                   .contentType(metadata.getContentType());
+         }
          Optional<Map<String, String>> userMetadata = options.getUserMetadata();
          if (userMetadata.isPresent()) {
             builder.userMetadata(userMetadata.get());

http://git-wip-us.apache.org/repos/asf/jclouds/blob/0647ba8c/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java b/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java
index 8886da7..4084cf9 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java
@@ -17,8 +17,12 @@
 
 package org.jclouds.blobstore.options;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import java.util.Map;
 
+import org.jclouds.io.ContentMetadata;
+
 import com.google.common.annotations.Beta;
 import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableMap;
@@ -27,12 +31,18 @@ import com.google.common.collect.ImmutableMap;
 public final class CopyOptions {
    public static final CopyOptions NONE = builder().build();
 
+   private final Optional<ContentMetadata> contentMetadata;
    private final Optional<Map<String, String>> userMetadata;
 
    private CopyOptions(Builder builder) {
+      this.contentMetadata = Optional.fromNullable(builder.contentMetadata);
       this.userMetadata = Optional.fromNullable(builder.userMetadata);
    }
 
+   public Optional<ContentMetadata> getContentMetadata() {
+      return contentMetadata;
+   }
+
    public Optional<Map<String, String>> getUserMetadata() {
       return userMetadata;
    }
@@ -42,11 +52,17 @@ public final class CopyOptions {
    }
 
    public static class Builder {
+      ContentMetadata contentMetadata;
       Map<String, String> userMetadata;
 
       Builder() {
       }
 
+      public Builder contentMetadata(ContentMetadata contentMetadata) {
+         this.contentMetadata = checkNotNull(contentMetadata, "contentMetadata");
+         return this;
+      }
+
       public Builder userMetadata(Map<String, String> userMetadata) {
          this.userMetadata = ImmutableMap.copyOf(userMetadata);
          return this;

http://git-wip-us.apache.org/repos/asf/jclouds/blob/0647ba8c/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
----------------------------------------------------------------------
diff --git a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
index 12e83c3..23de246 100644
--- a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
+++ b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
@@ -62,6 +62,7 @@ import org.jclouds.blobstore.options.PutOptions;
 import org.jclouds.crypto.Crypto;
 import org.jclouds.encryption.internal.JCECrypto;
 import org.jclouds.http.HttpResponseException;
+import org.jclouds.io.ContentMetadataBuilder;
 import org.jclouds.io.Payload;
 import org.jclouds.io.Payloads;
 import org.jclouds.io.payloads.ByteSourcePayload;
@@ -777,7 +778,11 @@ public class BaseBlobIntegrationTest extends BaseBlobStoreIntegrationTest {
             .blobBuilder(fromName)
             .userMetadata(ImmutableMap.of("key1", "value1", "key2", "value2"))
             .payload(payload)
-            .contentLength(payload.size());
+            .contentLength(payload.size())
+            .contentDisposition("attachment; filename=original.jpg")
+            .contentEncoding("compress")
+            .contentLanguage("fr")
+            .contentType("audio/ogg");
       addContentMetadata(blobBuilder);
       Blob blob = blobBuilder.build();
 
@@ -786,8 +791,15 @@ public class BaseBlobIntegrationTest extends BaseBlobStoreIntegrationTest {
       try {
          blobStore.putBlob(fromContainer, blob);
          Map<String, String> userMetadata = ImmutableMap.of("key3", "value3", "key4", "value4");
-         blobStore.copyBlob(fromContainer, fromName, toContainer, toName,
-               CopyOptions.builder().userMetadata(userMetadata).build());
+         blobStore.copyBlob(fromContainer, fromName, toContainer, toName, CopyOptions.builder()
+               .contentMetadata(ContentMetadataBuilder.create()
+                     .contentType("text/csv")
+                     .contentDisposition("attachment; filename=photo.jpg")
+                     .contentEncoding("gzip")
+                     .contentLanguage("en")
+                     .build())
+               .userMetadata(userMetadata)
+               .build());
          Blob toBlob = blobStore.getBlob(toContainer, toName);
          InputStream is = null;
          try {
@@ -796,9 +808,7 @@ public class BaseBlobIntegrationTest extends BaseBlobStoreIntegrationTest {
          } finally {
             Closeables2.closeQuietly(is);
          }
-         // TODO: S3 overrideMetadataWith also overrides system metadata
-         // TODO: Swift does not preserve system metadata
-         //checkContentMetadata(toBlob);
+         checkContentMetadata(toBlob);
          checkUserMetadata(toBlob.getMetadata().getUserMetadata(), userMetadata);
       } finally {
          returnContainer(toContainer);


[13/16] jclouds git commit: JCLOUDS-651: Azure copy object content metadata

Posted by ga...@apache.org.
JCLOUDS-651: Azure copy object content metadata


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/7c275bde
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/7c275bde
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/7c275bde

Branch: refs/heads/1.9.x
Commit: 7c275bdeddfa066a8a2c8b2646ae99cca91bf89c
Parents: 0647ba8
Author: Andrew Gaul <ga...@apache.org>
Authored: Mon Apr 6 22:06:14 2015 -0700
Committer: Andrew Gaul <ga...@apache.org>
Committed: Wed Apr 8 23:13:54 2015 -0700

----------------------------------------------------------------------
 .../org/jclouds/azureblob/AzureBlobClient.java  | 12 ++++
 .../BindAzureContentMetadataToRequest.java      | 59 ++++++++++++++++++++
 .../azureblob/blobstore/AzureBlobStore.java     | 33 +++++++++--
 3 files changed, 98 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/7c275bde/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java
index ed7c5f8..d34ae26 100644
--- a/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java
@@ -46,6 +46,8 @@ import org.jclouds.azure.storage.reference.AzureStorageHeaders;
 import org.jclouds.azureblob.binders.BindAzureBlobMetadataToRequest;
 import org.jclouds.azureblob.binders.BindAzureBlobMetadataToMultipartRequest;
 import org.jclouds.azureblob.binders.BindAzureBlocksToRequest;
+import org.jclouds.azureblob.binders.BindAzureContentMetadataToRequest;
+import org.jclouds.azureblob.binders.BindAzureCopyOptionsToRequest;
 import org.jclouds.azureblob.binders.BindPublicAccessToRequest;
 import org.jclouds.azureblob.domain.AzureBlob;
 import org.jclouds.azureblob.domain.BlobProperties;
@@ -69,6 +71,7 @@ import org.jclouds.azureblob.xml.ContainerNameEnumerationResultsHandler;
 import org.jclouds.blobstore.binders.BindMapToHeadersWithPrefix;
 import org.jclouds.http.functions.ParseETagHeader;
 import org.jclouds.http.options.GetOptions;
+import org.jclouds.io.ContentMetadata;
 import org.jclouds.io.Payload;
 import org.jclouds.rest.annotations.BinderParam;
 import org.jclouds.rest.annotations.Fallback;
@@ -412,6 +415,15 @@ public interface AzureBlobClient extends Closeable {
          @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container,
          @PathParam("name") String name);
 
+   @Named("SetBlobProperties")
+   @PUT
+   @Path("{container}/{name}")
+   @QueryParams(keys = { "comp" }, values = { "metadata" })
+   @ResponseParser(ParseETagHeader.class)
+   String setBlobProperties(
+         @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container,
+         @PathParam("name") String name,
+         @BinderParam(BindAzureContentMetadataToRequest.class) ContentMetadata contentMetadata);
 
    @Named("SetBlobMetadata")
    @PUT

http://git-wip-us.apache.org/repos/asf/jclouds/blob/7c275bde/providers/azureblob/src/main/java/org/jclouds/azureblob/binders/BindAzureContentMetadataToRequest.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/binders/BindAzureContentMetadataToRequest.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/binders/BindAzureContentMetadataToRequest.java
new file mode 100644
index 0000000..2bf496d
--- /dev/null
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/binders/BindAzureContentMetadataToRequest.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.azureblob.binders;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import javax.inject.Singleton;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.io.ContentMetadata;
+import org.jclouds.rest.Binder;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Multimaps;
+
+@Singleton
+public class BindAzureContentMetadataToRequest implements Binder {
+
+   public BindAzureContentMetadataToRequest() {
+   }
+
+   @SuppressWarnings("unchecked")
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Object input) {
+      checkArgument(checkNotNull(input, "input") instanceof ContentMetadata,
+         "this binder is only valid for ContentMetadata");
+      checkNotNull(request, "request");
+      ContentMetadata contentMetadata = (ContentMetadata) input;
+
+      ImmutableMap.Builder<String, String> headers = ImmutableMap.builder();
+
+      String contentType = contentMetadata.getContentType();
+      if (contentType != null) {
+         headers.put("x-ms-blob-type", contentType);
+      }
+
+      String contentDisposition = contentMetadata.getContentDisposition();
+      if (contentDisposition != null) {
+         headers.put("x-ms-blob-content-disposition", contentDisposition);
+      }
+
+      return (R) request.toBuilder().replaceHeaders(Multimaps.forMap(headers.build())).build();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/7c275bde/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobStore.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobStore.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobStore.java
index ccd31b5..7e8b0e9 100644
--- a/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobStore.java
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobStore.java
@@ -60,11 +60,13 @@ import org.jclouds.blobstore.util.BlobUtils;
 import org.jclouds.collect.Memoized;
 import org.jclouds.domain.Location;
 import org.jclouds.http.options.GetOptions;
+import org.jclouds.io.ContentMetadata;
 
 import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.base.Supplier;
 import com.google.common.collect.Iterables;
+import org.jclouds.io.ContentMetadataBuilder;
 import org.jclouds.io.Payload;
 
 @Singleton
@@ -233,13 +235,32 @@ public class AzureBlobStore extends BaseBlobStore {
       URI source = context.getSigner().signGetBlob(fromContainer, fromName).getEndpoint();
       sync.copyBlob(source, toContainer, toName, azureOptions.build());
 
-      String eTag = sync.getBlobProperties(toContainer, toName).getETag();
+      ContentMetadataBuilder builder = ContentMetadataBuilder.create();
 
-      // TODO: Azure does not allow updating system metadata during copy - call SetBlobProperties (not yet implemented)
-      // TODO: content disposition
-      // TODO: content encoding
-      // TODO: content language
-      // TODO: content type
+      String eTag = null;
+
+      Optional<ContentMetadata> contentMetadata = options.getContentMetadata();
+      if (contentMetadata.isPresent()) {
+         String contentDisposition = contentMetadata.get().getContentDisposition();
+         if (contentDisposition != null) {
+            builder.contentDisposition(contentDisposition);
+         }
+
+         String contentType = contentMetadata.get().getContentType();
+         if (contentType != null) {
+            builder.contentType(contentType);
+         }
+
+         eTag = sync.setBlobProperties(toContainer, toName, builder.build());
+      }
+
+      if (userMetadata.isPresent()) {
+         eTag = sync.setBlobMetadata(toContainer, toName, userMetadata.get());
+      }
+
+      if (eTag == null) {
+         eTag = sync.getBlobProperties(toContainer, toName).getETag();
+      }
 
       return eTag;
    }


[06/16] jclouds git commit: JCLOUDS-651: Add S3 server-side copyBlob

Posted by ga...@apache.org.
JCLOUDS-651: Add S3 server-side copyBlob


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/b4897c66
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/b4897c66
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/b4897c66

Branch: refs/heads/1.9.x
Commit: b4897c668935f60017b6487810162863f1f6177b
Parents: afbf61c
Author: Andrew Gaul <ga...@apache.org>
Authored: Fri Sep 5 19:06:16 2014 -0700
Committer: Andrew Gaul <ga...@apache.org>
Committed: Wed Apr 8 23:13:52 2015 -0700

----------------------------------------------------------------------
 .../org/jclouds/s3/blobstore/S3BlobStore.java   | 22 ++++++++++++++++++++
 1 file changed, 22 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/b4897c66/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobStore.java
----------------------------------------------------------------------
diff --git a/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobStore.java b/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobStore.java
index 2d6b84a..cd38e52 100644
--- a/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobStore.java
+++ b/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobStore.java
@@ -21,6 +21,7 @@ import static com.google.common.base.Preconditions.checkState;
 import static org.jclouds.util.Predicates2.retry;
 
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import javax.inject.Inject;
@@ -37,6 +38,7 @@ import org.jclouds.blobstore.domain.PageSet;
 import org.jclouds.blobstore.domain.StorageMetadata;
 import org.jclouds.blobstore.functions.BlobToHttpGetOptions;
 import org.jclouds.blobstore.internal.BaseBlobStore;
+import org.jclouds.blobstore.options.CopyOptions;
 import org.jclouds.blobstore.options.CreateContainerOptions;
 import org.jclouds.blobstore.options.ListContainerOptions;
 import org.jclouds.blobstore.options.PutOptions;
@@ -57,12 +59,14 @@ import org.jclouds.s3.domain.AccessControlList.GroupGranteeURI;
 import org.jclouds.s3.domain.AccessControlList.Permission;
 import org.jclouds.s3.domain.BucketMetadata;
 import org.jclouds.s3.domain.CannedAccessPolicy;
+import org.jclouds.s3.options.CopyObjectOptions;
 import org.jclouds.s3.options.ListBucketOptions;
 import org.jclouds.s3.options.PutBucketOptions;
 import org.jclouds.s3.options.PutObjectOptions;
 import org.jclouds.s3.util.S3Utils;
 
 import com.google.common.base.Function;
+import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.base.Supplier;
 import com.google.common.cache.CacheLoader;
@@ -273,6 +277,24 @@ public class S3BlobStore extends BaseBlobStore {
       return sync.putObject(container, blob2Object.apply(blob), options);
    }
 
+   @Override
+   public String copyBlob(String fromContainer, String fromName, String toContainer, String toName,
+         CopyOptions options) {
+      CopyObjectOptions s3Options = new CopyObjectOptions();
+
+      // TODO: content disposition
+      // TODO: content encoding
+      // TODO: content language
+      // TODO: content type
+
+      Optional<Map<String, String>> userMetadata = options.getUserMetadata();
+      if (userMetadata.isPresent()) {
+         s3Options.overrideMetadataWith(userMetadata.get());
+      }
+
+      return sync.copyObject(fromContainer, fromName, toContainer, toName, s3Options).getETag();
+   }
+
    /**
     * This implementation invokes {@link S3Client#deleteObject}
     * 


[03/16] jclouds git commit: JCLOUDS-871: Azure Copy Blob support

Posted by ga...@apache.org.
JCLOUDS-871: Azure Copy Blob support

API reference:

http://msdn.microsoft.com/en-us/library/dd894037.aspx


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/54874000
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/54874000
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/54874000

Branch: refs/heads/1.9.x
Commit: 5487400037c901b4ea0bb0244ada9fcf109a1d3d
Parents: 75f6d76
Author: Andrew Gaul <ga...@apache.org>
Authored: Sat Sep 6 18:03:23 2014 -0700
Committer: Andrew Gaul <ga...@apache.org>
Committed: Wed Apr 8 23:13:51 2015 -0700

----------------------------------------------------------------------
 .../storage/reference/AzureStorageHeaders.java  |   7 +
 .../org/jclouds/azureblob/AzureBlobClient.java  |  13 ++
 .../binders/BindAzureCopyOptionsToRequest.java  |  70 ++++++++
 .../azureblob/options/CopyBlobOptions.java      | 109 +++++++++++++
 .../azureblob/AzureBlobClientLiveTest.java      | 160 +++++++++++++++++++
 .../jclouds/azureblob/AzureBlobClientTest.java  |  99 ++++++++++++
 6 files changed, 458 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/54874000/providers/azureblob/src/main/java/org/jclouds/azure/storage/reference/AzureStorageHeaders.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/main/java/org/jclouds/azure/storage/reference/AzureStorageHeaders.java b/providers/azureblob/src/main/java/org/jclouds/azure/storage/reference/AzureStorageHeaders.java
index 0c60e51..b57caa9 100644
--- a/providers/azureblob/src/main/java/org/jclouds/azure/storage/reference/AzureStorageHeaders.java
+++ b/providers/azureblob/src/main/java/org/jclouds/azure/storage/reference/AzureStorageHeaders.java
@@ -24,6 +24,13 @@ package org.jclouds.azure.storage.reference;
 public final class AzureStorageHeaders {
 
    public static final String USER_METADATA_PREFIX = "x-ms-meta-";
+
+   public static final String COPY_SOURCE = "x-ms-copy-source";
+   public static final String COPY_SOURCE_IF_MODIFIED_SINCE = "x-ms-source-if-modified-since";
+   public static final String COPY_SOURCE_IF_UNMODIFIED_SINCE = "x-ms-source-if-unmodified-since";
+   public static final String COPY_SOURCE_IF_MATCH = "x-ms-source-if-match";
+   public static final String COPY_SOURCE_IF_NONE_MATCH = "x-ms-source-if-none-match";
+
    public static final String REQUEST_ID = "x-ms-request-id";
    public static final String VERSION = "x-ms-version";
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/54874000/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java
index 368dda0..ed7c5f8 100644
--- a/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java
@@ -26,6 +26,7 @@ import static org.jclouds.blobstore.BlobStoreFallbacks.NullOnContainerNotFound;
 import static org.jclouds.blobstore.BlobStoreFallbacks.NullOnKeyNotFound;
 
 import java.io.Closeable;
+import java.net.URI;
 import java.util.List;
 import java.util.Map;
 
@@ -57,6 +58,7 @@ import org.jclouds.azureblob.functions.ParseBlobFromHeadersAndHttpContent;
 import org.jclouds.azureblob.functions.ParseBlobPropertiesFromHeaders;
 import org.jclouds.azureblob.functions.ParseContainerPropertiesFromHeaders;
 import org.jclouds.azureblob.functions.ParsePublicAccessHeader;
+import org.jclouds.azureblob.options.CopyBlobOptions;
 import org.jclouds.azureblob.options.CreateContainerOptions;
 import org.jclouds.azureblob.options.ListBlobsOptions;
 import org.jclouds.azureblob.predicates.validators.BlockIdValidator;
@@ -442,4 +444,15 @@ public interface AzureBlobClient extends Closeable {
          @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container,
          @PathParam("name") String name);
 
+   /**
+    * @throws ContainerNotFoundException if the container is not present.
+    */
+   @Named("CopyBlob")
+   @PUT
+   @Path("{toContainer}/{toName}")
+   @Headers(keys = AzureStorageHeaders.COPY_SOURCE, values = "{copySource}")
+   void copyBlob(
+         @PathParam("copySource") URI copySource,
+         @PathParam("toContainer") @ParamValidators(ContainerNameValidator.class) String toContainer, @PathParam("toName") String toName,
+         @BinderParam(BindAzureCopyOptionsToRequest.class) CopyBlobOptions options);
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/54874000/providers/azureblob/src/main/java/org/jclouds/azureblob/binders/BindAzureCopyOptionsToRequest.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/binders/BindAzureCopyOptionsToRequest.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/binders/BindAzureCopyOptionsToRequest.java
new file mode 100644
index 0000000..218fe16
--- /dev/null
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/binders/BindAzureCopyOptionsToRequest.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jclouds.azureblob.binders;
+
+import java.util.Date;
+import java.util.Map;
+
+import org.jclouds.azure.storage.reference.AzureStorageHeaders;
+import org.jclouds.azureblob.options.CopyBlobOptions;
+import org.jclouds.date.DateService;
+import org.jclouds.date.internal.SimpleDateFormatDateService;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.rest.Binder;
+
+import com.google.common.base.Optional;
+
+/** Binds options to a copyBlob request. */
+public class BindAzureCopyOptionsToRequest implements Binder {
+   private static final DateService dateService = new SimpleDateFormatDateService();
+
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Object input) {
+      HttpRequest.Builder builder = request.toBuilder();
+      CopyBlobOptions options = (CopyBlobOptions) input;
+
+      Optional<Map<String, String>> userMetadata = options.getUserMetadata();
+      if (userMetadata.isPresent()) {
+         for (Map.Entry<String, String> entry : userMetadata.get().entrySet()) {
+            builder.addHeader(AzureStorageHeaders.USER_METADATA_PREFIX + entry.getKey(), entry.getValue());
+         }
+      }
+
+      Optional<Date> ifModifiedSince = options.getIfModifiedSince();
+      if (ifModifiedSince.isPresent()) {
+         builder.addHeader(AzureStorageHeaders.COPY_SOURCE_IF_MODIFIED_SINCE, dateService.rfc822DateFormat(ifModifiedSince.get()));
+      }
+
+      Optional<Date> ifUnmodifiedSince = options.getIfUnmodifiedSince();
+      if (ifUnmodifiedSince.isPresent()) {
+         builder.addHeader(AzureStorageHeaders.COPY_SOURCE_IF_UNMODIFIED_SINCE, dateService.rfc822DateFormat(ifUnmodifiedSince.get()));
+      }
+
+      Optional<String> ifMatch = options.getIfMatch();
+      if (ifMatch.isPresent()) {
+         builder.addHeader(AzureStorageHeaders.COPY_SOURCE_IF_MATCH, ifMatch.get());
+      }
+
+      Optional<String> ifNoneMatch = options.getIfNoneMatch();
+      if (ifNoneMatch.isPresent()) {
+         builder.addHeader(AzureStorageHeaders.COPY_SOURCE_IF_NONE_MATCH, ifNoneMatch.get());
+      }
+
+      return (R) builder.build();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/54874000/providers/azureblob/src/main/java/org/jclouds/azureblob/options/CopyBlobOptions.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/options/CopyBlobOptions.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/options/CopyBlobOptions.java
new file mode 100644
index 0000000..9baf624
--- /dev/null
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/options/CopyBlobOptions.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jclouds.azureblob.options;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Date;
+import java.util.Map;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+
+public final class CopyBlobOptions {
+   public static final CopyBlobOptions NONE = CopyBlobOptions.builder().build();
+
+   private final Optional<Map<String, String>> userMetadata;
+   private final Optional<Date> ifModifiedSince;
+   private final Optional<Date> ifUnmodifiedSince;
+   private final Optional<String> ifMatch;
+   private final Optional<String> ifNoneMatch;
+
+   private CopyBlobOptions(Map<String, String> userMetadata, Date ifModifiedSince, Date ifUnmodifiedSince,
+         String ifMatch, String ifNoneMatch) {
+      this.userMetadata = Optional.fromNullable(userMetadata);
+      this.ifModifiedSince = Optional.fromNullable(ifModifiedSince);
+      this.ifUnmodifiedSince = Optional.fromNullable(ifUnmodifiedSince);
+      this.ifMatch = Optional.fromNullable(ifMatch);
+      this.ifNoneMatch = Optional.fromNullable(ifNoneMatch);
+   }
+
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public Optional<Map<String, String>> getUserMetadata() {
+      return userMetadata;
+   }
+
+   public Optional<Date> getIfModifiedSince() {
+      return Optional.fromNullable(ifModifiedSince.isPresent() ? (Date) ifModifiedSince.get().clone() : null);
+   }
+
+   public Optional<Date> getIfUnmodifiedSince() {
+      return Optional.fromNullable(ifUnmodifiedSince.isPresent() ? (Date) ifUnmodifiedSince.get().clone() : null);
+   }
+
+   public Optional<String> getIfMatch() {
+      return ifMatch;
+   }
+
+   public Optional<String> getIfNoneMatch() {
+      return ifNoneMatch;
+   }
+
+   public static class Builder {
+      private Map<String, String> userMetadata;
+      private Date ifModifiedSince;
+      private Date ifUnmodifiedSince;
+      private String ifMatch;
+      private String ifNoneMatch;
+
+      Builder() {
+      }
+
+      public Builder overrideUserMetadata(Map<String, String> userMetadata) {
+         this.userMetadata = ImmutableMap.copyOf(checkNotNull(userMetadata, "userMetadata"));
+         return this;
+      }
+
+      public Builder ifModifiedSince(Date ifModifiedSince) {
+         this.ifModifiedSince = (Date) checkNotNull(ifModifiedSince, "ifModifiedSince").clone();
+         return this;
+      }
+
+      public Builder ifUnmodifiedSince(Date ifUnmodifiedSince) {
+         this.ifUnmodifiedSince = (Date) checkNotNull(ifUnmodifiedSince, "ifUnmodifiedSince").clone();
+         return this;
+      }
+
+      public Builder ifMatch(String ifMatch) {
+         this.ifMatch = checkNotNull(ifMatch, "ifMatch");
+         return this;
+      }
+
+      public Builder ifNoneMatch(String ifNoneMatch) {
+         this.ifNoneMatch = checkNotNull(ifNoneMatch, "ifNoneMatch");
+         return this;
+      }
+
+      public CopyBlobOptions build() {
+         return new CopyBlobOptions(userMetadata, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/54874000/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java b/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java
index 2dccf19..db3ef9d 100644
--- a/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java
+++ b/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java
@@ -18,6 +18,7 @@ package org.jclouds.azureblob;
 
 import static com.google.common.io.BaseEncoding.base16;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown;
 import static org.jclouds.azure.storage.options.ListOptions.Builder.includeMetadata;
 import static org.jclouds.azureblob.options.CreateContainerOptions.Builder.withMetadata;
 import static org.jclouds.azureblob.options.CreateContainerOptions.Builder.withPublicAccess;
@@ -29,6 +30,8 @@ import java.lang.reflect.UndeclaredThrowableException;
 import java.net.URI;
 import java.security.SecureRandom;
 import java.util.Arrays;
+import java.util.Date;
+import java.util.Map;
 import java.util.Set;
 
 import org.jclouds.azure.storage.AzureStorageResponseException;
@@ -40,14 +43,17 @@ import org.jclouds.azureblob.domain.ContainerProperties;
 import org.jclouds.azureblob.domain.ListBlobBlocksResponse;
 import org.jclouds.azureblob.domain.ListBlobsResponse;
 import org.jclouds.azureblob.domain.PublicAccess;
+import org.jclouds.azureblob.options.CopyBlobOptions;
 import org.jclouds.azureblob.options.ListBlobsOptions;
 import org.jclouds.blobstore.ContainerNotFoundException;
 import org.jclouds.blobstore.integration.internal.BaseBlobStoreIntegrationTest;
 import org.jclouds.http.HttpResponseException;
 import org.jclouds.http.options.GetOptions;
+import org.jclouds.io.ByteStreams2;
 import org.jclouds.io.Payloads;
 import org.jclouds.util.Strings2;
 import org.jclouds.util.Throwables2;
+import org.jclouds.utils.TestUtils;
 import org.testng.annotations.Test;
 
 import com.google.common.base.Charsets;
@@ -56,6 +62,7 @@ import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.Iterables;
 import com.google.common.hash.Hashing;
 import com.google.common.io.BaseEncoding;
+import com.google.common.io.ByteSource;
 
 @Test(groups = "live", singleThreaded = true)
 public class AzureBlobClientLiveTest extends BaseBlobStoreIntegrationTest {
@@ -384,4 +391,157 @@ public class AzureBlobClientLiveTest extends BaseBlobStoreIntegrationTest {
       client.setPublicAccessForContainer(blockContainer, access);
       assertThat(client.getPublicAccessForContainer(blockContainer)).isEqualTo(access);
    }
+
+   @Test(timeOut = 5 * 60 * 1000, dependsOnMethods = { "testCreateContainer" })
+   public void testCopyBlob() throws Exception {
+      ByteSource byteSource = TestUtils.randomByteSource().slice(0, 1024);
+
+      // create blob
+      AzureBlob object = getApi().newBlob();
+      object.getProperties().setName("from");
+      object.setPayload(byteSource.read());
+      getApi().putBlob(privateContainer, object);
+
+      // copy blob
+      URI copySource = view.getSigner().signGetBlob(privateContainer, "from").getEndpoint();
+      getApi().copyBlob(copySource, privateContainer, "to", CopyBlobOptions.NONE);
+
+      // ensure copied blob matches original
+      AzureBlob getBlob = getApi().getBlob(privateContainer, "to");
+      assertEquals(ByteStreams2.toByteArrayAndClose(getBlob.getPayload().openStream()), byteSource.read());
+      assertThat(getBlob.getProperties().getMetadata().isEmpty());
+   }
+
+   @Test(timeOut = 5 * 60 * 1000, dependsOnMethods = { "testCreateContainer" })
+   public void testCopyBlobReplaceMetadata() throws Exception {
+      ByteSource byteSource = TestUtils.randomByteSource().slice(0, 1024);
+
+      // create blob
+      AzureBlob object = getApi().newBlob();
+      object.getProperties().setName("from");
+      object.setPayload(byteSource.read());
+      getApi().putBlob(privateContainer, object);
+
+      // copy blob
+      URI copySource = view.getSigner().signGetBlob(privateContainer, "from").getEndpoint();
+      Map<String, String> newMetadata = ImmutableMap.of("foo", "bar");
+      getApi().copyBlob(copySource, privateContainer, "to", CopyBlobOptions.builder().overrideUserMetadata(newMetadata).build());
+
+      // ensure copied blob matches original
+      AzureBlob getBlob = getApi().getBlob(privateContainer, "to");
+      assertEquals(ByteStreams2.toByteArrayAndClose(getBlob.getPayload().openStream()), byteSource.read());
+      assertThat(getBlob.getProperties().getMetadata()).isEqualTo(newMetadata);
+   }
+
+   @Test(timeOut = 5 * 60 * 1000, dependsOnMethods = { "testCreateContainer" })
+   public void testCopyBlobIfModifiedSince() throws Exception {
+      ByteSource byteSource = TestUtils.randomByteSource().slice(0, 1024);
+
+      // create blob
+      AzureBlob object = getApi().newBlob();
+      object.getProperties().setName("from");
+      object.setPayload(byteSource.read());
+      String eTag = getApi().putBlob(privateContainer, object);
+
+      long now = System.currentTimeMillis();
+      Date before = new Date(now - 1000 * 1000);
+      Date after = new Date(now + 1000 * 1000);
+      URI copySource = view.getSigner().signGetBlob(privateContainer, "from").getEndpoint();
+
+      // failure case
+      try {
+         getApi().copyBlob(copySource, privateContainer, "to-if-modified-since", CopyBlobOptions.builder().ifModifiedSince(after).build());
+         failBecauseExceptionWasNotThrown(AzureStorageResponseException.class);
+      } catch (AzureStorageResponseException asre) {
+         assertThat(asre.getResponse().getStatusCode()).as("status code").isEqualTo(412);
+      }
+
+      // success case
+      getApi().copyBlob(copySource, privateContainer, "to-if-modified-since", CopyBlobOptions.builder().ifModifiedSince(before).build());
+      AzureBlob getBlob = getApi().getBlob(privateContainer, "to-if-modified-since");
+      assertEquals(ByteStreams2.toByteArrayAndClose(getBlob.getPayload().openStream()), byteSource.read());
+   }
+
+   @Test(timeOut = 5 * 60 * 1000, dependsOnMethods = { "testCreateContainer" })
+   public void testCopyBlobIfUnmodifiedSince() throws Exception {
+      ByteSource byteSource = TestUtils.randomByteSource().slice(0, 1024);
+
+      // create blob
+      AzureBlob object = getApi().newBlob();
+      object.getProperties().setName("from");
+      object.setPayload(byteSource.read());
+      String eTag = getApi().putBlob(privateContainer, object);
+
+      long now = System.currentTimeMillis();
+      Date before = new Date(now - 1000 * 1000);
+      Date after = new Date(now + 1000 * 1000);
+      URI copySource = view.getSigner().signGetBlob(privateContainer, "from").getEndpoint();
+
+      // failure case
+      try {
+         getApi().copyBlob(copySource, privateContainer, "to-if-unmodifed-since", CopyBlobOptions.builder().ifUnmodifiedSince(before).build());
+         failBecauseExceptionWasNotThrown(AzureStorageResponseException.class);
+      } catch (AzureStorageResponseException asre) {
+         assertThat(asre.getResponse().getStatusCode()).as("status code").isEqualTo(412);
+      }
+
+      // success case
+      getApi().copyBlob(copySource, privateContainer, "to-if-unmodifed-since", CopyBlobOptions.builder().ifUnmodifiedSince(after).build());
+      AzureBlob getBlob = getApi().getBlob(privateContainer, "to-if-unmodifed-since");
+      assertEquals(ByteStreams2.toByteArrayAndClose(getBlob.getPayload().openStream()), byteSource.read());
+   }
+
+   @Test(timeOut = 5 * 60 * 1000, dependsOnMethods = { "testCreateContainer" })
+   public void testCopyBlobIfMatch() throws Exception {
+      ByteSource byteSource = TestUtils.randomByteSource().slice(0, 1024);
+
+      // create blob
+      AzureBlob object = getApi().newBlob();
+      object.getProperties().setName("from");
+      object.setPayload(byteSource.read());
+      String eTag = getApi().putBlob(privateContainer, object);
+      String fakeETag = "0x8CEB669D794AFE2";
+
+      URI copySource = view.getSigner().signGetBlob(privateContainer, "from").getEndpoint();
+
+      // failure case
+      try {
+         getApi().copyBlob(copySource, privateContainer, "to-if-match", CopyBlobOptions.builder().ifMatch(fakeETag).build());
+         failBecauseExceptionWasNotThrown(AzureStorageResponseException.class);
+      } catch (AzureStorageResponseException asre) {
+         assertThat(asre.getResponse().getStatusCode()).as("status code").isEqualTo(412);
+      }
+
+      // success case
+      getApi().copyBlob(copySource, privateContainer, "to-if-match", CopyBlobOptions.builder().ifMatch(eTag).build());
+      AzureBlob getBlob = getApi().getBlob(privateContainer, "to-if-match");
+      assertEquals(ByteStreams2.toByteArrayAndClose(getBlob.getPayload().openStream()), byteSource.read());
+   }
+
+   @Test(timeOut = 5 * 60 * 1000, dependsOnMethods = { "testCreateContainer" })
+   public void testCopyBlobIfNoneMatch() throws Exception {
+      ByteSource byteSource = TestUtils.randomByteSource().slice(0, 1024);
+
+      // create blob
+      AzureBlob object = getApi().newBlob();
+      object.getProperties().setName("from");
+      object.setPayload(byteSource.read());
+      String eTag = getApi().putBlob(privateContainer, object);
+      String fakeETag = "0x8CEB669D794AFE2";
+
+      URI copySource = view.getSigner().signGetBlob(privateContainer, "from").getEndpoint();
+
+      // failure case
+      try {
+         getApi().copyBlob(copySource, privateContainer, "to-if-none-match", CopyBlobOptions.builder().ifNoneMatch(eTag).build());
+         failBecauseExceptionWasNotThrown(AzureStorageResponseException.class);
+      } catch (AzureStorageResponseException asre) {
+         assertThat(asre.getResponse().getStatusCode()).as("status code").isEqualTo(412);
+      }
+
+      // success case
+      getApi().copyBlob(copySource, privateContainer, "to-if-none-match", CopyBlobOptions.builder().ifNoneMatch(fakeETag).build());
+      AzureBlob getBlob = getApi().getBlob(privateContainer, "to-if-none-match");
+      assertEquals(ByteStreams2.toByteArrayAndClose(getBlob.getPayload().openStream()), byteSource.read());
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/54874000/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientTest.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientTest.java b/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientTest.java
index 5e82b35..af6ea34 100644
--- a/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientTest.java
+++ b/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientTest.java
@@ -22,6 +22,8 @@ import static org.jclouds.reflect.Reflection2.method;
 import static org.testng.Assert.assertEquals;
 
 import java.io.IOException;
+import java.net.URI;
+import java.util.Date;
 import java.util.Map;
 
 import org.jclouds.Fallbacks.TrueOnNotFoundOr404;
@@ -33,6 +35,7 @@ import org.jclouds.azureblob.domain.PublicAccess;
 import org.jclouds.azureblob.functions.ParseBlobFromHeadersAndHttpContent;
 import org.jclouds.azureblob.functions.ParseContainerPropertiesFromHeaders;
 import org.jclouds.azureblob.functions.ParsePublicAccessHeader;
+import org.jclouds.azureblob.options.CopyBlobOptions;
 import org.jclouds.azureblob.options.CreateContainerOptions;
 import org.jclouds.azureblob.options.ListBlobsOptions;
 import org.jclouds.azureblob.xml.AccountNameEnumerationResultsHandler;
@@ -304,6 +307,102 @@ public class AzureBlobClientTest extends BaseRestAnnotationProcessingTest<AzureB
       assertFallbackClassEquals(method, null);
    }
 
+   public void testCopyBlob() throws Exception {
+      Invokable<?, ?> method = method(AzureBlobClient.class, "copyBlob", URI.class, String.class, String.class, CopyBlobOptions.class);
+      GeneratedHttpRequest request = processor.createRequest(method, ImmutableList.<Object> of(
+               URI.create("https://identity.blob.core.windows.net/fromcontainer/fromblob"), "tocontainer", "toblob", CopyBlobOptions.NONE));
+
+      assertRequestLineEquals(request,
+               "PUT https://identity.blob.core.windows.net/tocontainer/toblob HTTP/1.1");
+      checkFilters(request);
+      assertNonPayloadHeadersEqual(request,
+               "x-ms-copy-source: https://identity.blob.core.windows.net/fromcontainer/fromblob\n" +
+               "x-ms-version: 2013-08-15\n");
+      assertPayloadEquals(request, null, null, false);
+   }
+
+   public void testCopyBlobOverwriteUserMetadata() throws Exception {
+      CopyBlobOptions options = CopyBlobOptions.builder().overrideUserMetadata(ImmutableMap.of("foo", "bar")).build();
+      Invokable<?, ?> method = method(AzureBlobClient.class, "copyBlob", URI.class, String.class, String.class, CopyBlobOptions.class);
+      GeneratedHttpRequest request = processor.createRequest(method, ImmutableList.<Object> of(
+               URI.create("https://identity.blob.core.windows.net/fromcontainer/fromblob"), "tocontainer", "toblob", options));
+
+      assertRequestLineEquals(request,
+               "PUT https://identity.blob.core.windows.net/tocontainer/toblob HTTP/1.1");
+      checkFilters(request);
+      assertNonPayloadHeadersEqual(request,
+               "x-ms-copy-source: https://identity.blob.core.windows.net/fromcontainer/fromblob\n" +
+               "x-ms-meta-foo: bar\n" +
+               "x-ms-version: 2013-08-15\n");
+      assertPayloadEquals(request, null, null, false);
+   }
+
+   public void testCopyBlobIfModifiedSince() throws Exception {
+      CopyBlobOptions options = CopyBlobOptions.builder().ifModifiedSince(new Date(1000)).build();
+      Invokable<?, ?> method = method(AzureBlobClient.class, "copyBlob", URI.class, String.class, String.class, CopyBlobOptions.class);
+      GeneratedHttpRequest request = processor.createRequest(method, ImmutableList.<Object> of(
+               URI.create("https://identity.blob.core.windows.net/fromcontainer/fromblob"), "tocontainer", "toblob", options));
+
+      assertRequestLineEquals(request,
+               "PUT https://identity.blob.core.windows.net/tocontainer/toblob HTTP/1.1");
+      checkFilters(request);
+      assertNonPayloadHeadersEqual(request,
+               "x-ms-copy-source: https://identity.blob.core.windows.net/fromcontainer/fromblob\n" +
+               "x-ms-source-if-modified-since: Thu, 01 Jan 1970 00:00:01 GMT\n" +
+               "x-ms-version: 2013-08-15\n");
+      assertPayloadEquals(request, null, null, false);
+   }
+
+   public void testCopyBlobIfUnmodifiedSince() throws Exception {
+      CopyBlobOptions options = CopyBlobOptions.builder().ifUnmodifiedSince(new Date(1000)).build();
+      Invokable<?, ?> method = method(AzureBlobClient.class, "copyBlob", URI.class, String.class, String.class, CopyBlobOptions.class);
+      GeneratedHttpRequest request = processor.createRequest(method, ImmutableList.<Object> of(
+               URI.create("https://identity.blob.core.windows.net/fromcontainer/fromblob"), "tocontainer", "toblob", options));
+
+      assertRequestLineEquals(request,
+               "PUT https://identity.blob.core.windows.net/tocontainer/toblob HTTP/1.1");
+      checkFilters(request);
+      assertNonPayloadHeadersEqual(request,
+               "x-ms-copy-source: https://identity.blob.core.windows.net/fromcontainer/fromblob\n" +
+               "x-ms-source-if-unmodified-since: Thu, 01 Jan 1970 00:00:01 GMT\n" +
+               "x-ms-version: 2013-08-15\n");
+      assertPayloadEquals(request, null, null, false);
+   }
+
+   public void testCopyBlobIfMatch() throws Exception {
+      String eTag = "0x8CEB669D794AFE2";
+      CopyBlobOptions options = CopyBlobOptions.builder().ifMatch(eTag).build();
+      Invokable<?, ?> method = method(AzureBlobClient.class, "copyBlob", URI.class, String.class, String.class, CopyBlobOptions.class);
+      GeneratedHttpRequest request = processor.createRequest(method, ImmutableList.<Object> of(
+               URI.create("https://identity.blob.core.windows.net/fromcontainer/fromblob"), "tocontainer", "toblob", options));
+
+      assertRequestLineEquals(request,
+               "PUT https://identity.blob.core.windows.net/tocontainer/toblob HTTP/1.1");
+      checkFilters(request);
+      assertNonPayloadHeadersEqual(request,
+               "x-ms-copy-source: https://identity.blob.core.windows.net/fromcontainer/fromblob\n" +
+               "x-ms-source-if-match: " + eTag + "\n" +
+               "x-ms-version: 2013-08-15\n");
+      assertPayloadEquals(request, null, null, false);
+   }
+
+   public void testCopyBlobIfNoneMatch() throws Exception {
+      String eTag = "0x8CEB669D794AFE2";
+      CopyBlobOptions options = CopyBlobOptions.builder().ifNoneMatch(eTag).build();
+      Invokable<?, ?> method = method(AzureBlobClient.class, "copyBlob", URI.class, String.class, String.class, CopyBlobOptions.class);
+      GeneratedHttpRequest request = processor.createRequest(method, ImmutableList.<Object> of(
+               URI.create("https://identity.blob.core.windows.net/fromcontainer/fromblob"), "tocontainer", "toblob", options));
+
+      assertRequestLineEquals(request,
+               "PUT https://identity.blob.core.windows.net/tocontainer/toblob HTTP/1.1");
+      checkFilters(request);
+      assertNonPayloadHeadersEqual(request,
+               "x-ms-copy-source: https://identity.blob.core.windows.net/fromcontainer/fromblob\n" +
+               "x-ms-source-if-none-match: " + eTag + "\n" +
+               "x-ms-version: 2013-08-15\n");
+      assertPayloadEquals(request, null, null, false);
+   }
+
    @Override
    protected void checkFilters(HttpRequest request) {
       assertEquals(request.getFilters().size(), 1);


[12/16] jclouds git commit: JCLOUDS-651: Add @Beta annotations to copy methods

Posted by ga...@apache.org.
JCLOUDS-651: Add @Beta annotations to copy methods


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/3f2e9f37
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/3f2e9f37
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/3f2e9f37

Branch: refs/heads/1.9.x
Commit: 3f2e9f37fd6fad5aa5862467422b0599369bd927
Parents: 4e37ffb
Author: Andrew Gaul <ga...@apache.org>
Authored: Wed Apr 8 16:21:06 2015 -0700
Committer: Andrew Gaul <ga...@apache.org>
Committed: Wed Apr 8 23:13:53 2015 -0700

----------------------------------------------------------------------
 blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java       | 1 +
 .../src/main/java/org/jclouds/blobstore/options/CopyOptions.java   | 2 ++
 2 files changed, 3 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/3f2e9f37/blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java b/blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java
index f4b26d2..cf81c02 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java
@@ -239,6 +239,7 @@ public interface BlobStore {
     * @return ETag of new blob
     * @throws ContainerNotFoundException if either container does not exist
     */
+   @Beta
    String copyBlob(String fromContainer, String fromName, String toContainer, String toName,
          CopyOptions options);
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/3f2e9f37/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java b/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java
index 3b89e98..8886da7 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java
@@ -19,9 +19,11 @@ package org.jclouds.blobstore.options;
 
 import java.util.Map;
 
+import com.google.common.annotations.Beta;
 import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableMap;
 
+@Beta
 public final class CopyOptions {
    public static final CopyOptions NONE = builder().build();
 


[05/16] jclouds git commit: JCLOUDS-651: Copy Swift system metadata

Posted by ga...@apache.org.
JCLOUDS-651: Copy Swift system metadata


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/211b0739
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/211b0739
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/211b0739

Branch: refs/heads/1.9.x
Commit: 211b07390d2550a1576584ddf8063d50cd94b4d2
Parents: 1e8495e
Author: Andrew Gaul <ga...@apache.org>
Authored: Thu Apr 2 17:16:36 2015 -0700
Committer: Andrew Gaul <ga...@apache.org>
Committed: Wed Apr 8 23:13:52 2015 -0700

----------------------------------------------------------------------
 .../blobstore/RegionScopedSwiftBlobStore.java   | 49 ++++++++++++++------
 .../internal/BaseBlobIntegrationTest.java       |  3 +-
 2 files changed, 37 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/211b0739/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
index 4516264..4192a96 100644
--- a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
@@ -53,6 +53,7 @@ import org.jclouds.blobstore.options.PutOptions;
 import org.jclouds.blobstore.strategy.ClearListStrategy;
 import org.jclouds.collect.Memoized;
 import org.jclouds.domain.Location;
+import org.jclouds.io.ContentMetadata;
 import org.jclouds.io.Payload;
 import org.jclouds.io.payloads.ByteSourcePayload;
 import org.jclouds.openstack.swift.v1.SwiftApi;
@@ -78,7 +79,9 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
 import com.google.common.io.ByteSource;
+import com.google.common.net.HttpHeaders;
 import com.google.inject.AbstractModule;
 import com.google.inject.Injector;
 import com.google.inject.assistedinject.Assisted;
@@ -234,25 +237,45 @@ public class RegionScopedSwiftBlobStore implements BlobStore {
    public String copyBlob(String fromContainer, String fromName, String toContainer, String toName,
          CopyOptions options) {
       ObjectApi objectApi = api.getObjectApi(regionId, toContainer);
+      SwiftObject metadata = api.getObjectApi(regionId, fromContainer).getWithoutBody(fromName);
 
-      boolean copied = objectApi.copy(toName, fromContainer, fromName);
-      if (!copied) {
-         throw new RuntimeException("could not copy blob");
+      Map<String, String> userMetadata;
+      if (options.getUserMetadata().isPresent()) {
+         userMetadata = options.getUserMetadata().get();
+      } else {
+         userMetadata = metadata.getMetadata();
       }
 
-      // TODO: content disposition
-      // TODO: content encoding
-      // TODO: content language
-      // TODO: content type
+      // copy existing system metadata
+      Map<String, String> systemMetadata = Maps.newHashMap();
+      ContentMetadata contentMetadata = metadata.getPayload().getContentMetadata();
+      String contentDisposition = contentMetadata.getContentDisposition();
+      if (contentDisposition != null) {
+         systemMetadata.put(HttpHeaders.CONTENT_DISPOSITION, contentDisposition);
+      }
+      String contentEncoding = contentMetadata.getContentEncoding();
+      if (contentEncoding != null) {
+         systemMetadata.put(HttpHeaders.CONTENT_ENCODING, contentEncoding);
+      }
+      String contentLanguage = contentMetadata.getContentLanguage();
+      if (contentLanguage != null) {
+         systemMetadata.put(HttpHeaders.CONTENT_LANGUAGE, contentLanguage);
+      }
+      String contentType = contentMetadata.getContentType();
+      if (contentType != null) {
+         systemMetadata.put(HttpHeaders.CONTENT_TYPE, contentType);
+      }
 
-      Optional<Map<String, String>> userMetadata = options.getUserMetadata();
-      if (userMetadata.isPresent()) {
-         boolean updated = objectApi.updateMetadata(toName, userMetadata.get());
-         if (!updated) {
-            throw new RuntimeException("could not copy blob");
-         }
+      boolean copied = objectApi.copy(toName, fromContainer, fromName, userMetadata, systemMetadata);
+      if (!copied) {
+         throw new RuntimeException("could not copy blob");
       }
 
+      // TODO: override content disposition
+      // TODO: override content encoding
+      // TODO: override content language
+      // TODO: override content type
+
       return objectApi.getWithoutBody(toName).getETag();
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/211b0739/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
----------------------------------------------------------------------
diff --git a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
index 0d57fc5..1be2597 100644
--- a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
+++ b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
@@ -759,8 +759,7 @@ public class BaseBlobIntegrationTest extends BaseBlobStoreIntegrationTest {
          } finally {
             Closeables2.closeQuietly(is);
          }
-         // TODO: Swift does not preserve system metadata
-         //checkContentMetadata(toBlob);
+         checkContentMetadata(toBlob);
          assertThat(toBlob.getMetadata().getUserMetadata()).isEqualTo(userMetadata);
       } finally {
          returnContainer(toContainer);


[08/16] jclouds git commit: JCLOUDS-651: Add Swift server-side copyBlob

Posted by ga...@apache.org.
JCLOUDS-651: Add Swift server-side copyBlob

This has some limitations as discussed in JCLOUDS-872.


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/70eac741
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/70eac741
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/70eac741

Branch: refs/heads/1.9.x
Commit: 70eac7414033c5cffb19ec33a2695624ae9657aa
Parents: b4897c6
Author: Andrew Gaul <ga...@apache.org>
Authored: Sun Mar 29 21:51:03 2015 -0700
Committer: Andrew Gaul <ga...@apache.org>
Committed: Wed Apr 8 23:13:52 2015 -0700

----------------------------------------------------------------------
 .../blobstore/RegionScopedSwiftBlobStore.java   | 28 ++++++++++++++++++++
 1 file changed, 28 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/70eac741/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
index d535569..4516264 100644
--- a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
@@ -25,6 +25,7 @@ import static org.jclouds.location.predicates.LocationPredicates.idEquals;
 import static org.jclouds.openstack.swift.v1.options.PutOptions.Builder.metadata;
 
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import javax.inject.Inject;
@@ -44,6 +45,7 @@ import org.jclouds.blobstore.domain.internal.BlobBuilderImpl;
 import org.jclouds.blobstore.domain.internal.BlobImpl;
 import org.jclouds.blobstore.domain.internal.PageSetImpl;
 import org.jclouds.blobstore.functions.BlobToHttpGetOptions;
+import org.jclouds.blobstore.options.CopyOptions;
 import org.jclouds.blobstore.options.CreateContainerOptions;
 import org.jclouds.blobstore.options.GetOptions;
 import org.jclouds.blobstore.options.ListContainerOptions;
@@ -229,6 +231,32 @@ public class RegionScopedSwiftBlobStore implements BlobStore {
    }
 
    @Override
+   public String copyBlob(String fromContainer, String fromName, String toContainer, String toName,
+         CopyOptions options) {
+      ObjectApi objectApi = api.getObjectApi(regionId, toContainer);
+
+      boolean copied = objectApi.copy(toName, fromContainer, fromName);
+      if (!copied) {
+         throw new RuntimeException("could not copy blob");
+      }
+
+      // TODO: content disposition
+      // TODO: content encoding
+      // TODO: content language
+      // TODO: content type
+
+      Optional<Map<String, String>> userMetadata = options.getUserMetadata();
+      if (userMetadata.isPresent()) {
+         boolean updated = objectApi.updateMetadata(toName, userMetadata.get());
+         if (!updated) {
+            throw new RuntimeException("could not copy blob");
+         }
+      }
+
+      return objectApi.getWithoutBody(toName).getETag();
+   }
+
+   @Override
    public BlobMetadata blobMetadata(String container, String name) {
       SwiftObject object = api.getObjectApi(regionId, container).get(name);
       if (object == null) {


[10/16] jclouds git commit: Allows copying an object and modifying metadata (user and object)

Posted by ga...@apache.org.
Allows copying an object and modifying metadata (user and object)


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/b5491c48
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/b5491c48
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/b5491c48

Branch: refs/heads/1.9.x
Commit: b5491c48483861bf6ca3be007b6d7a44fd11b4f4
Parents: c87e205
Author: Zack Shoylev <za...@rackspace.com>
Authored: Tue Mar 31 17:30:56 2015 -0500
Committer: Andrew Gaul <ga...@apache.org>
Committed: Wed Apr 8 23:13:52 2015 -0700

----------------------------------------------------------------------
 .../swift/v1/binders/BindMetadataToHeaders.java |  6 +++
 .../openstack/swift/v1/features/ObjectApi.java  | 33 ++++++++++++
 .../swift/v1/features/ObjectApiMockTest.java    | 56 ++++++++++++++++++--
 3 files changed, 91 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/b5491c48/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java
index 9451934..d27e19d 100644
--- a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java
@@ -85,6 +85,12 @@ public class BindMetadataToHeaders implements Binder {
       }
    }
 
+   public static class BindHeaderMetadataToHeaders extends BindMetadataToHeaders {
+      BindHeaderMetadataToHeaders() {
+         super("");
+      }
+   }
+
    public static class BindRemoveObjectMetadataToHeaders extends BindMetadataToHeaders.ForRemoval {
       BindRemoveObjectMetadataToHeaders() {
          super(OBJECT_METADATA_PREFIX);

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b5491c48/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java
index 4d4d603..7d89ba3 100644
--- a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java
@@ -41,6 +41,7 @@ import org.jclouds.http.options.GetOptions;
 import org.jclouds.io.Payload;
 import org.jclouds.javax.annotation.Nullable;
 import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest;
+import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindHeaderMetadataToHeaders;
 import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindObjectMetadataToHeaders;
 import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindRemoveObjectMetadataToHeaders;
 import org.jclouds.openstack.swift.v1.binders.SetPayload;
@@ -262,4 +263,36 @@ public interface ObjectApi {
                 @PathParam("sourceContainer") String sourceContainer,
                 @PathParam("sourceObject") String sourceObject);
 
+   /**
+    * Copies an object from one container to another.
+    *
+    * <h3>NOTE</h3>
+    * This is a server side copy.
+    *
+    * @param destinationObject
+    *           the destination object name.
+    * @param sourceContainer
+    *           the source container name.
+    * @param sourceObject
+    *           the source object name.
+    * @param userMetadata
+    *           Freeform metadata for the object, automatically prefixed/escaped
+    * @param objectMetadata
+    *           Unprefixed/unescaped metadata, such as Content-Disposition
+    *
+    * @return {@code true} if the object was successfully copied, {@code false} if not.
+    *
+    * @throws org.jclouds.openstack.swift.v1.CopyObjectException if the source or destination container do not exist.
+    */
+   @Named("object:copy")
+   @PUT
+   @Path("/{destinationObject}")
+   @Headers(keys = OBJECT_COPY_FROM, values = "/{sourceContainer}/{sourceObject}")
+   @Fallback(FalseOnContainerNotFound.class)
+   boolean copy(@PathParam("destinationObject") String destinationObject,
+         @PathParam("sourceContainer") String sourceContainer,
+         @PathParam("sourceObject") String sourceObject,
+         @BinderParam(BindObjectMetadataToHeaders.class) Map<String, String> userMetadata,
+         @BinderParam(BindHeaderMetadataToHeaders.class) Map<String, String> objectMetadata);
+
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b5491c48/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java
index 87166df..81a856e 100644
--- a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java
@@ -39,6 +39,7 @@ import static org.testng.Assert.fail;
 import java.io.IOException;
 import java.net.URI;
 import java.util.Date;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Properties;
@@ -193,7 +194,8 @@ public class ObjectApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
    public void testCreateWithSpacesAndSpecialCharacters() throws Exception {
       MockWebServer server = mockOpenStackServer();
       server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
-      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201).addHeader("ETag", "d9f5eb4bba4e2f2f046e54611bc8196b")));
+      server.enqueue(addCommonHeaders(
+            new MockResponse().setResponseCode(201).addHeader("ETag", "d9f5eb4bba4e2f2f046e54611bc8196b")));
 
       final String containerName = "container # ! special";
       final String objectName = "object # ! special";
@@ -279,7 +281,8 @@ public class ObjectApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
 
          assertEquals(server.getRequestCount(), 2);
          assertAuthentication(server);
-         assertRequest(server.takeRequest(), "HEAD", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject");
+         assertRequest(server.takeRequest(), "HEAD",
+               "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject");
       } finally {
          server.shutdown();
       }
@@ -339,7 +342,7 @@ public class ObjectApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
 
          fail("testReplaceTimeout test should have failed with an HttpResponseException.");
       } finally {
-         try { 
+         try {
             server.shutdown();
          } catch (IOException e) {
             // MockWebServer 2.1.0 introduces an active wait for its executor termination.
@@ -469,7 +472,7 @@ public class ObjectApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
       try {
          SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
          assertTrue(api.getObjectApi("DFW", "foo")
-            .copy("bar.txt", "bar", "foo.txt"));
+               .copy("bar.txt", "bar", "foo.txt"));
 
          assertEquals(server.getRequestCount(), 2);
          assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1");
@@ -498,6 +501,51 @@ public class ObjectApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
       }
    }
 
+   public void testCopyObjectWithMetadata() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201)
+            .addHeader(SwiftHeaders.OBJECT_COPY_FROM, "/bar/foo.txt")));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertTrue(api.getObjectApi("DFW", "foo")
+               .copy("bar.txt", "bar", "foo.txt", ImmutableMap.of("someUserHeader", "someUserMetadataValue"),
+                     ImmutableMap.of("Content-Disposition", "attachment; filename=\"fname.ext\"")));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1");
+
+         RecordedRequest copyRequest = server.takeRequest();
+         assertEquals(copyRequest.getRequestLine(),
+               "PUT /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/foo/bar.txt HTTP/1.1");
+
+         List<String> requestHeaders = copyRequest.getHeaders();
+         assertTrue(requestHeaders.contains("X-Object-Meta-someuserheader: someUserMetadataValue"));
+         assertTrue(requestHeaders.contains("content-disposition: attachment; filename=\"fname.ext\""));
+         assertTrue(requestHeaders.contains(SwiftHeaders.OBJECT_COPY_FROM + ": /bar/foo.txt"));
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   @Test(expectedExceptions = CopyObjectException.class)
+   public void testCopyObjectWithMetadataFail() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)
+            .addHeader(SwiftHeaders.OBJECT_COPY_FROM, "/bar/foo.txt")));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertTrue(api.getObjectApi("DFW", "foo")
+               .copy("bar.txt", "bar", "foo.txt", ImmutableMap.of("someUserHeader", "someUserMetadataValue"),
+                     ImmutableMap.of("Content-Disposition", "attachment; filename=\"fname.ext\"")));
+      } finally {
+         server.shutdown();
+      }
+   }
+
    private static final Map<String, String> metadata = ImmutableMap.of("ApiName", "swift", "ApiVersion", "v1.1");
 
    static MockResponse objectResponse() {


[15/16] jclouds git commit: JCLOUDS-651: S3 copy object content metadata

Posted by ga...@apache.org.
JCLOUDS-651: S3 copy object content metadata


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/a2605829
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/a2605829
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/a2605829

Branch: refs/heads/1.9.x
Commit: a260582949d382af9501e33d4896e025e63c8f79
Parents: 7c275bd
Author: Andrew Gaul <ga...@apache.org>
Authored: Mon Apr 6 17:51:51 2015 -0700
Committer: Andrew Gaul <ga...@apache.org>
Committed: Wed Apr 8 23:13:54 2015 -0700

----------------------------------------------------------------------
 .../org/jclouds/s3/blobstore/S3BlobStore.java   | 27 ++++++--
 .../jclouds/s3/options/CopyObjectOptions.java   | 67 +++++++++++++++++++-
 2 files changed, 89 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/a2605829/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobStore.java
----------------------------------------------------------------------
diff --git a/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobStore.java b/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobStore.java
index cd38e52..c93f7e2 100644
--- a/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobStore.java
+++ b/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobStore.java
@@ -47,6 +47,7 @@ import org.jclouds.blobstore.util.BlobUtils;
 import org.jclouds.collect.Memoized;
 import org.jclouds.domain.Location;
 import org.jclouds.http.options.GetOptions;
+import org.jclouds.io.ContentMetadata;
 import org.jclouds.s3.S3Client;
 import org.jclouds.s3.blobstore.functions.BlobToObject;
 import org.jclouds.s3.blobstore.functions.BucketToResourceList;
@@ -282,10 +283,28 @@ public class S3BlobStore extends BaseBlobStore {
          CopyOptions options) {
       CopyObjectOptions s3Options = new CopyObjectOptions();
 
-      // TODO: content disposition
-      // TODO: content encoding
-      // TODO: content language
-      // TODO: content type
+      Optional<ContentMetadata> contentMetadata = options.getContentMetadata();
+      if (contentMetadata.isPresent()) {
+         String contentDisposition = contentMetadata.get().getContentDisposition();
+         if (contentDisposition != null) {
+            s3Options.contentDisposition(contentDisposition);
+         }
+
+         String contentEncoding = contentMetadata.get().getContentEncoding();
+         if (contentEncoding != null) {
+            s3Options.contentEncoding(contentEncoding);
+         }
+
+         String contentLanguage = contentMetadata.get().getContentLanguage();
+         if (contentLanguage != null) {
+            s3Options.contentLanguage(contentLanguage);
+         }
+
+         String contentType = contentMetadata.get().getContentType();
+         if (contentType != null) {
+            s3Options.contentType(contentType);
+         }
+      }
 
       Optional<Map<String, String>> userMetadata = options.getUserMetadata();
       if (userMetadata.isPresent()) {

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a2605829/apis/s3/src/main/java/org/jclouds/s3/options/CopyObjectOptions.java
----------------------------------------------------------------------
diff --git a/apis/s3/src/main/java/org/jclouds/s3/options/CopyObjectOptions.java b/apis/s3/src/main/java/org/jclouds/s3/options/CopyObjectOptions.java
index 25f833c..2370bf5 100644
--- a/apis/s3/src/main/java/org/jclouds/s3/options/CopyObjectOptions.java
+++ b/apis/s3/src/main/java/org/jclouds/s3/options/CopyObjectOptions.java
@@ -43,6 +43,7 @@ import org.jclouds.s3.domain.CannedAccessPolicy;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.Multimap;
+import com.google.common.net.HttpHeaders;
 
 /**
  * Contains options supported in the REST API for the COPY object operation.
@@ -71,6 +72,10 @@ import com.google.common.collect.Multimap;
 public class CopyObjectOptions extends BaseHttpRequestOptions {
    private static final DateService dateService = new SimpleDateFormatDateService();
    public static final CopyObjectOptions NONE = new CopyObjectOptions();
+   private String contentDisposition;
+   private String contentEncoding;
+   private String contentLanguage;
+   private String contentType;
    private Map<String, String> metadata;
    private CannedAccessPolicy acl = CannedAccessPolicy.PRIVATE;
 
@@ -249,16 +254,56 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
       for (Entry<String, String> entry : headers.entries()) {
          returnVal.put(entry.getKey().replace(DEFAULT_AMAZON_HEADERTAG, headerTag), entry.getValue());
       }
+      boolean replace = false;
+      if (contentDisposition != null) {
+         returnVal.put(HttpHeaders.CONTENT_DISPOSITION, contentDisposition);
+         replace = true;
+      }
+      if (contentEncoding != null) {
+         returnVal.put(HttpHeaders.CONTENT_ENCODING, contentEncoding);
+         replace = true;
+      }
+      if (contentLanguage != null) {
+         returnVal.put(HttpHeaders.CONTENT_LANGUAGE, contentLanguage);
+         replace = true;
+      }
+      if (contentType != null) {
+         returnVal.put(HttpHeaders.CONTENT_TYPE, contentType);
+         replace = true;
+      }
       if (metadata != null) {
-         returnVal.put(METADATA_DIRECTIVE.replace(DEFAULT_AMAZON_HEADERTAG, headerTag), "REPLACE");
          for (Map.Entry<String, String> entry : metadata.entrySet()) {
             String key = entry.getKey();
             returnVal.put(key.startsWith(metadataPrefix) ? key : metadataPrefix + key, entry.getValue());
          }
+         replace = true;
+      }
+      if (replace) {
+         returnVal.put(METADATA_DIRECTIVE.replace(DEFAULT_AMAZON_HEADERTAG, headerTag), "REPLACE");
       }
       return returnVal.build();
    }
 
+   public CopyObjectOptions contentDisposition(String contentDisposition) {
+      this.contentDisposition = checkNotNull(contentDisposition, "contentDisposition");
+      return this;
+   }
+
+   public CopyObjectOptions contentEncoding(String contentEncoding) {
+      this.contentEncoding = checkNotNull(contentEncoding, "contentEncoding");
+      return this;
+   }
+
+   public CopyObjectOptions contentLanguage(String contentLanguage) {
+      this.contentLanguage = checkNotNull(contentLanguage, "contentLanguage");
+      return this;
+   }
+
+   public CopyObjectOptions contentType(String contentType) {
+      this.contentType = checkNotNull(contentType, "contentType");
+      return this;
+   }
+
    /**
     * Use the provided metadata instead of what is on the source object.
     */
@@ -309,6 +354,26 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
          return options.ifSourceETagDoesntMatch(eTag);
       }
 
+      public static CopyObjectOptions contentDisposition(String contentDisposition) {
+         CopyObjectOptions options = new CopyObjectOptions();
+         return options.contentDisposition(contentDisposition);
+      }
+
+      public static CopyObjectOptions contentEncoding(String contentEncoding) {
+         CopyObjectOptions options = new CopyObjectOptions();
+         return options.contentEncoding(contentEncoding);
+      }
+
+      public static CopyObjectOptions contentLanguage(String contentLanguage) {
+         CopyObjectOptions options = new CopyObjectOptions();
+         return options.contentLanguage(contentLanguage);
+      }
+
+      public static CopyObjectOptions contentType(String contentType) {
+         CopyObjectOptions options = new CopyObjectOptions();
+         return options.contentType(contentType);
+      }
+
       /**
        * @see #overrideMetadataWith(Multimap)
        */


[09/16] jclouds git commit: Adds live test.

Posted by ga...@apache.org.
Adds live test.


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/1e8495e9
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/1e8495e9
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/1e8495e9

Branch: refs/heads/1.9.x
Commit: 1e8495e905998d8c29d9e50a79ea784cb4317b62
Parents: b5491c4
Author: Zack Shoylev <za...@rackspace.com>
Authored: Thu Apr 2 15:55:01 2015 -0500
Committer: Andrew Gaul <ga...@apache.org>
Committed: Wed Apr 8 23:13:52 2015 -0700

----------------------------------------------------------------------
 .../swift/v1/binders/BindMetadataToHeaders.java |  4 +-
 .../openstack/swift/v1/features/ObjectApi.java  | 24 +++++-
 .../swift/v1/features/ObjectApiLiveTest.java    | 81 ++++++++++++++++++++
 3 files changed, 105 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/1e8495e9/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java
index d27e19d..59ff29a 100644
--- a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java
@@ -85,8 +85,8 @@ public class BindMetadataToHeaders implements Binder {
       }
    }
 
-   public static class BindHeaderMetadataToHeaders extends BindMetadataToHeaders {
-      BindHeaderMetadataToHeaders() {
+   public static class BindRawMetadataToHeaders extends BindMetadataToHeaders {
+      BindRawMetadataToHeaders() {
          super("");
       }
    }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1e8495e9/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java
index 7d89ba3..841fd9d 100644
--- a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java
@@ -41,8 +41,8 @@ import org.jclouds.http.options.GetOptions;
 import org.jclouds.io.Payload;
 import org.jclouds.javax.annotation.Nullable;
 import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest;
-import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindHeaderMetadataToHeaders;
 import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindObjectMetadataToHeaders;
+import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindRawMetadataToHeaders;
 import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindRemoveObjectMetadataToHeaders;
 import org.jclouds.openstack.swift.v1.binders.SetPayload;
 import org.jclouds.openstack.swift.v1.domain.ObjectList;
@@ -208,6 +208,26 @@ public interface ObjectApi {
          @BinderParam(BindObjectMetadataToHeaders.class) Map<String, String> metadata);
 
    /**
+    * Creates or updates the metadata for a {@link SwiftObject} without escaping the key.
+    * This will also update metadata such as content-disposition.
+    *
+    * @param objectName
+    *           corresponds to {@link SwiftObject#getName()}.
+    * @param metadata
+    *           the metadata to create or update.
+    *
+    * @return {@code true} if the metadata was successfully created or updated,
+    *         {@code false} if not.
+    */
+   @Named("object:updateMetadata")
+   @POST
+   @Path("/{objectName}")
+   @Produces("")
+   @Fallback(FalseOnNotFoundOr404.class)
+   boolean updateRawMetadata(@PathParam("objectName") String objectName,
+         @BinderParam(BindRawMetadataToHeaders.class) Map<String, String> metadata);
+
+   /**
     * Deletes the metadata from a {@link SwiftObject}.
     *
     * @param objectName
@@ -293,6 +313,6 @@ public interface ObjectApi {
          @PathParam("sourceContainer") String sourceContainer,
          @PathParam("sourceObject") String sourceObject,
          @BinderParam(BindObjectMetadataToHeaders.class) Map<String, String> userMetadata,
-         @BinderParam(BindHeaderMetadataToHeaders.class) Map<String, String> objectMetadata);
+         @BinderParam(BindRawMetadataToHeaders.class) Map<String, String> objectMetadata);
 
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1e8495e9/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java
index 5a472ff..12d769d 100644
--- a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java
@@ -45,6 +45,7 @@ import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Multimap;
 import com.google.common.io.ByteSource;
 
 /**
@@ -96,6 +97,7 @@ public class ObjectApiLiveTest extends BaseSwiftApiLiveTest<SwiftApi> {
          api.getObjectApi(regionId, containerName).delete(objectName);
       }
    }
+
    public void testCopyObject() throws Exception {
       for (String regionId : regions) {
          // source
@@ -153,6 +155,85 @@ public class ObjectApiLiveTest extends BaseSwiftApiLiveTest<SwiftApi> {
       }
    }
 
+   public void testCopyObjectWithMetadata() throws Exception {
+      for (String regionId : regions) {
+         // source
+         String sourceContainer = "src" + containerName;
+         String sourceObjectName = "original.txt";
+         String badSource = "badSource";
+
+         // destination
+         String destinationContainer = "dest" + containerName;
+         String destinationObject = "copy.txt";
+         String destinationPath = "/" + destinationContainer + "/" + destinationObject;
+
+         ContainerApi containerApi = api.getContainerApi(regionId);
+
+         // create source and destination dirs
+         containerApi.create(sourceContainer);
+         containerApi.create(destinationContainer);
+
+         // get the api for this region and container
+         ObjectApi srcApi = api.getObjectApi(regionId, sourceContainer);
+         ObjectApi destApi = api.getObjectApi(regionId, destinationContainer);
+
+         // Create source object
+         assertNotNull(srcApi.put(sourceObjectName, PAYLOAD));
+         SwiftObject sourceObject = srcApi.get(sourceObjectName);
+         checkObject(sourceObject);
+
+         srcApi.updateMetadata(sourceObjectName,
+               ImmutableMap.of("userProvidedMetadataKey", "userProvidedMetadataValue"));
+
+         // Create the destination object
+         assertNotNull(destApi.put(destinationObject, PAYLOAD));
+         SwiftObject object = destApi.get(destinationObject);
+         checkObject(object);
+
+         // check the copy operation
+         assertTrue(destApi.copy(destinationObject, sourceContainer, sourceObjectName,
+               ImmutableMap.<String, String>of("additionalUserMetakey", "additionalUserMetavalue"),
+               ImmutableMap.of("Content-Disposition", "attachment; filename=\"updatedname.txt\"")));
+         assertNotNull(destApi.get(destinationObject));
+
+         // now get a real SwiftObject
+         SwiftObject destSwiftObject = destApi.get(destinationObject);
+         assertEquals(toStringAndClose(destSwiftObject.getPayload().openStream()), "swifty");
+
+         /**
+          * Make sure all src metadata is in dest
+          * Make sure the new content disposition is in dest
+          */
+         Multimap<String, String> srcHeaders = null;
+         Multimap<String, String> destHeaders = null;
+         srcHeaders = srcApi.get(sourceObjectName).getHeaders();
+         destHeaders = destApi.get(destinationObject).getHeaders();
+         for (Entry<String, String> header : srcHeaders.entries()) {
+            if (header.getKey().equals("Date"))continue;
+            if (header.getKey().equals("Last-Modified"))continue;
+            if (header.getKey().equals("X-Trans-Id"))continue;
+            if (header.getKey().equals("X-Timestamp"))continue;
+            assertTrue(destHeaders.containsEntry(header.getKey(), header.getValue()), "Could not find: " + header);
+         }
+         assertEquals(destApi.get(destinationObject).getPayload().getContentMetadata().getContentDisposition(), "attachment; filename=\"updatedname.txt\"");
+
+         // test exception thrown on bad source name
+         try {
+            destApi.copy(destinationObject, badSource, sourceObjectName);
+            fail("Expected CopyObjectException");
+         } catch (CopyObjectException e) {
+            assertEquals(e.getSourcePath(), "/" + badSource + "/" + sourceObjectName);
+            assertEquals(e.getDestinationPath(), destinationPath);
+         }
+
+         deleteAllObjectsInContainer(regionId, sourceContainer);
+         containerApi.deleteIfEmpty(sourceContainer);
+
+         deleteAllObjectsInContainer(regionId, destinationContainer);
+         containerApi.deleteIfEmpty(destinationContainer);
+      }
+   }
+
    public void testList() throws Exception {
       for (String regionId : regions) {
          ObjectApi objectApi = api.getObjectApi(regionId, containerName);