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 2016/02/17 01:30:22 UTC

[1/8] jclouds git commit: Only quote ETag if it does not already have quotes

Repository: jclouds
Updated Branches:
  refs/heads/master c8bbb44f3 -> e0a7ea7fd


Only quote ETag if it does not already have quotes


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

Branch: refs/heads/master
Commit: e0a7ea7fdf4554d919b23ce72391a37e41d7cad8
Parents: 7eb46cc
Author: Andrew Gaul <ga...@apache.org>
Authored: Fri Feb 12 22:24:33 2016 -0800
Committer: Andrew Gaul <ga...@apache.org>
Committed: Tue Feb 16 16:29:54 2016 -0800

----------------------------------------------------------------------
 .../java/org/jclouds/s3/options/CopyObjectOptions.java   | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/e0a7ea7f/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 374278d..d9e6064 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
@@ -226,7 +226,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
    public CopyObjectOptions ifSourceETagMatches(String eTag) {
       checkState(getIfNoneMatch() == null, "ifETagDoesntMatch() is not compatible with ifETagMatches()");
       checkState(getIfModifiedSince() == null, "ifModifiedSince() is not compatible with ifETagMatches()");
-      replaceHeader(COPY_SOURCE_IF_MATCH, String.format("\"%1$s\"", checkNotNull(eTag, "eTag")));
+      replaceHeader(COPY_SOURCE_IF_MATCH, maybeQuoteETag(checkNotNull(eTag, "eTag")));
       return this;
    }
 
@@ -243,7 +243,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
       checkState(getIfMatch() == null, "ifETagMatches() is not compatible with ifETagDoesntMatch()");
       Preconditions.checkState(getIfUnmodifiedSince() == null,
                "ifUnmodifiedSince() is not compatible with ifETagDoesntMatch()");
-      replaceHeader(COPY_SOURCE_IF_NO_MATCH, String.format("\"%s\"", checkNotNull(eTag, "ifETagDoesntMatch")));
+      replaceHeader(COPY_SOURCE_IF_NO_MATCH, maybeQuoteETag(checkNotNull(eTag, "ifETagDoesntMatch")));
       return this;
    }
 
@@ -397,4 +397,11 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
          return options.overrideMetadataWith(metadata);
       }
    }
+
+   private static String maybeQuoteETag(String eTag) {
+      if (!eTag.startsWith("\"") && !eTag.endsWith("\"")) {
+         eTag = "\"" + eTag + "\"";
+      }
+      return eTag;
+   }
 }


[7/8] jclouds git commit: JCLOUDS-651: Swift support for conditional copies

Posted by ga...@apache.org.
JCLOUDS-651: Swift support for conditional copies


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

Branch: refs/heads/master
Commit: 7eb46cce366e5bfb6021b03104e9cf64f764c42d
Parents: 6cdb121
Author: Andrew Gaul <ga...@apache.org>
Authored: Fri Feb 12 03:07:51 2016 -0800
Committer: Andrew Gaul <ga...@apache.org>
Committed: Tue Feb 16 16:29:54 2016 -0800

----------------------------------------------------------------------
 .../v1/blobstore/RegionScopedSwiftBlobStore.java   | 17 ++++++++++++++++-
 .../integration/SwiftBlobIntegrationLiveTest.java  | 14 ++++++++++++++
 2 files changed, 30 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/7eb46cce/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 5b331ed..8c4745a 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
@@ -253,6 +253,21 @@ public class RegionScopedSwiftBlobStore implements BlobStore {
          CopyOptions options) {
       ObjectApi objectApi = api.getObjectApi(regionId, toContainer);
 
+      org.jclouds.openstack.swift.v1.options.CopyOptions swiftOptions = new org.jclouds.openstack.swift.v1.options.CopyOptions();
+
+      if (options.ifMatch() != null) {
+         swiftOptions.ifMatch(options.ifMatch());
+      }
+      if (options.ifNoneMatch() != null) {
+         throw new UnsupportedOperationException("Swift does not support ifNoneMatch");
+      }
+      if (options.ifModifiedSince() != null) {
+         swiftOptions.ifModifiedSince(options.ifModifiedSince());
+      }
+      if (options.ifUnmodifiedSince() != null) {
+         swiftOptions.ifUnmodifiedSince(options.ifUnmodifiedSince());
+      }
+
       Map<String, String> systemMetadata = Maps.newHashMap();
       ContentMetadata contentMetadata = options.contentMetadata();
       Map<String, String> userMetadata = options.userMetadata();
@@ -307,7 +322,7 @@ public class RegionScopedSwiftBlobStore implements BlobStore {
          userMetadata = metadata.getMetadata();
       }
 
-      objectApi.copy(toName, fromContainer, fromName, userMetadata, systemMetadata);
+      objectApi.copy(toName, fromContainer, fromName, userMetadata, systemMetadata, swiftOptions);
 
       // 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/7eb46cce/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 6440788..441b6a2 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
@@ -101,6 +101,20 @@ public class SwiftBlobIntegrationLiveTest extends BaseBlobIntegrationTest {
    }
 
    @Override
+   @Test(expectedExceptions = UnsupportedOperationException.class)
+   public void testCopyIfNoneMatch() throws Exception {
+      super.testCopyIfNoneMatch();
+   }
+
+   @Override
+   @Test(expectedExceptions = UnsupportedOperationException.class)
+   public void testCopyIfNoneMatchNegative() throws Exception {
+      super.testCopyIfNoneMatchNegative();
+   }
+
+   // TODO: testCopyIfModifiedSinceNegative throws HTTP 304 not 412 error
+
+   @Override
    protected long getMinimumMultipartBlobSize() {
       return 1;
    }


[4/8] jclouds git commit: JCLOUDS-651: Local blobstore support for conditional copies

Posted by ga...@apache.org.
JCLOUDS-651: Local blobstore support for conditional copies


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

Branch: refs/heads/master
Commit: 66609e6d70e94bd508a9a84f15c95c768cd13469
Parents: 8945258
Author: Andrew Gaul <ga...@apache.org>
Authored: Fri Feb 12 13:52:15 2016 -0800
Committer: Andrew Gaul <ga...@apache.org>
Committed: Tue Feb 16 16:29:54 2016 -0800

----------------------------------------------------------------------
 .../blobstore/config/LocalBlobStore.java        | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/66609e6d/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 42e6f39..5d806aa 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
@@ -530,6 +530,26 @@ public final class LocalBlobStore implements BlobStore {
          throw new KeyNotFoundException(fromContainer, fromName, "while copying");
       }
 
+      String eTag = maybeQuoteETag(blob.getMetadata().getETag());
+      if (eTag != null) {
+         if (options.ifMatch() != null && !options.ifMatch().equals(eTag)) {
+            throw returnResponseException(412);
+         }
+         if (options.ifNoneMatch() != null && options.ifNoneMatch().equals(eTag)) {
+            throw returnResponseException(412);
+         }
+      }
+
+      Date lastModified = blob.getMetadata().getLastModified();
+      if (lastModified != null) {
+         if (options.ifModifiedSince() != null && lastModified.compareTo(options.ifModifiedSince()) <= 0) {
+            throw returnResponseException(412);
+         }
+         if (options.ifUnmodifiedSince() != null && lastModified.compareTo(options.ifUnmodifiedSince()) >= 0) {
+            throw returnResponseException(412);
+         }
+      }
+
       InputStream is = null;
       try {
          is = blob.getPayload().openStream();


[2/8] jclouds git commit: JCLOUDS-651: S3 support for conditional copies

Posted by ga...@apache.org.
JCLOUDS-651: S3 support for conditional copies


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

Branch: refs/heads/master
Commit: 6cdb1216a7bc2ca341f3774e4eff3e594c49b31b
Parents: 467f348
Author: Andrew Gaul <ga...@apache.org>
Authored: Thu Feb 11 21:31:55 2016 -0800
Committer: Andrew Gaul <ga...@apache.org>
Committed: Tue Feb 16 16:29:54 2016 -0800

----------------------------------------------------------------------
 .../main/java/org/jclouds/s3/blobstore/S3BlobStore.java | 12 ++++++++++++
 .../integration/AWSS3BlobIntegrationLiveTest.java       |  6 ++++++
 2 files changed, 18 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/6cdb1216/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 e01c498..ef3729f 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
@@ -270,6 +270,18 @@ public class S3BlobStore extends BaseBlobStore {
    public String copyBlob(String fromContainer, String fromName, String toContainer, String toName,
          CopyOptions options) {
       CopyObjectOptions s3Options = new CopyObjectOptions();
+      if (options.ifMatch() != null) {
+         s3Options.ifSourceETagMatches(options.ifMatch());
+      }
+      if (options.ifNoneMatch() != null) {
+         s3Options.ifSourceETagDoesntMatch(options.ifNoneMatch());
+      }
+      if (options.ifModifiedSince() != null) {
+         s3Options.ifSourceModifiedSince(options.ifModifiedSince());
+      }
+      if (options.ifUnmodifiedSince() != null) {
+         s3Options.ifSourceUnmodifiedSince(options.ifUnmodifiedSince());
+      }
 
       ContentMetadata contentMetadata = options.contentMetadata();
       if (contentMetadata != null) {

http://git-wip-us.apache.org/repos/asf/jclouds/blob/6cdb1216/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/integration/AWSS3BlobIntegrationLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/integration/AWSS3BlobIntegrationLiveTest.java b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/integration/AWSS3BlobIntegrationLiveTest.java
index cba1f72..ddbe114 100644
--- a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/integration/AWSS3BlobIntegrationLiveTest.java
+++ b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/integration/AWSS3BlobIntegrationLiveTest.java
@@ -18,10 +18,16 @@ package org.jclouds.aws.s3.blobstore.integration;
 
 import org.jclouds.s3.blobstore.integration.S3BlobIntegrationLiveTest;
 import org.testng.annotations.Test;
+import org.testng.SkipException;
 
 @Test(groups = "live", testName = "AWSS3BlobIntegrationLiveTest")
 public class AWSS3BlobIntegrationLiveTest extends S3BlobIntegrationLiveTest {
    public AWSS3BlobIntegrationLiveTest() {
       provider = "aws-s3";
    }
+
+   @Override
+   public void testCopyIfModifiedSinceNegative() throws Exception {
+      throw new SkipException("S3 supports copyIfModifiedSince but test uses time in the future which Amazon does not support");
+   }
 }


[8/8] jclouds git commit: JCLOUDS-651: Azure support for conditional copies

Posted by ga...@apache.org.
JCLOUDS-651: Azure support for conditional copies


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

Branch: refs/heads/master
Commit: 467f3483bc29685945b284c906a01e1d44769ec3
Parents: cc8af83
Author: Andrew Gaul <ga...@apache.org>
Authored: Fri Feb 12 02:45:28 2016 -0800
Committer: Andrew Gaul <ga...@apache.org>
Committed: Tue Feb 16 16:29:54 2016 -0800

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


http://git-wip-us.apache.org/repos/asf/jclouds/blob/467f3483/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 90e7d99..d839024 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
@@ -236,6 +236,19 @@ public class AzureBlobStore extends BaseBlobStore {
          CopyOptions options) {
       CopyBlobOptions.Builder azureOptions = CopyBlobOptions.builder();
 
+      if (options.ifMatch() != null) {
+         azureOptions.ifMatch(options.ifMatch());
+      }
+      if (options.ifNoneMatch() != null) {
+         azureOptions.ifNoneMatch(options.ifNoneMatch());
+      }
+      if (options.ifModifiedSince() != null) {
+         azureOptions.ifModifiedSince(options.ifModifiedSince());
+      }
+      if (options.ifUnmodifiedSince() != null) {
+         azureOptions.ifUnmodifiedSince(options.ifUnmodifiedSince());
+      }
+
       Map<String, String> userMetadata = options.userMetadata();
       if (userMetadata != null) {
          azureOptions.overrideUserMetadata(userMetadata);


[3/8] jclouds git commit: JCLOUDS-651: Portable support for conditional copies

Posted by ga...@apache.org.
JCLOUDS-651: Portable support for conditional copies


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

Branch: refs/heads/master
Commit: 8945258d79a5fc9b3a5aeb35703213ca06c74d0e
Parents: 293d3f8
Author: Andrew Gaul <ga...@apache.org>
Authored: Thu Feb 11 21:11:24 2016 -0800
Committer: Andrew Gaul <ga...@apache.org>
Committed: Tue Feb 16 16:29:54 2016 -0800

----------------------------------------------------------------------
 .../blobstore/internal/BaseBlobStore.java       |  39 +++
 .../jclouds/blobstore/options/CopyOptions.java  |  27 ++-
 .../internal/BaseBlobIntegrationTest.java       | 236 +++++++++++++++++++
 3 files changed, 301 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/8945258d/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 820721e..c6d21ff 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseBlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseBlobStore.java
@@ -23,6 +23,7 @@ import static org.jclouds.util.Predicates2.retry;
 
 import java.io.InputStream;
 import java.io.IOException;
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -46,6 +47,10 @@ import org.jclouds.blobstore.strategy.internal.MultipartUploadSlicingAlgorithm;
 import org.jclouds.blobstore.util.BlobUtils;
 import org.jclouds.collect.Memoized;
 import org.jclouds.domain.Location;
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.HttpResponseException;
 import org.jclouds.io.ContentMetadata;
 import org.jclouds.io.Payload;
 import org.jclouds.io.PayloadSlicer;
@@ -258,6 +263,26 @@ public abstract class BaseBlobStore implements BlobStore {
          throw new KeyNotFoundException(fromContainer, fromName, "while copying");
       }
 
+      String eTag = maybeQuoteETag(blob.getMetadata().getETag());
+      if (eTag != null) {
+         if (options.ifMatch() != null && !options.ifMatch().equals(eTag)) {
+            throw returnResponseException(412);
+         }
+         if (options.ifNoneMatch() != null && options.ifNoneMatch().equals(eTag)) {
+            throw returnResponseException(412);
+         }
+      }
+
+      Date lastModified = blob.getMetadata().getLastModified();
+      if (lastModified != null) {
+         if (options.ifModifiedSince() != null && lastModified.compareTo(options.ifModifiedSince()) <= 0) {
+            throw returnResponseException(412);
+         }
+         if (options.ifUnmodifiedSince() != null && lastModified.compareTo(options.ifUnmodifiedSince()) >= 0) {
+            throw returnResponseException(412);
+         }
+      }
+
       InputStream is = null;
       try {
          is = blob.getPayload().openStream();
@@ -311,4 +336,18 @@ public abstract class BaseBlobStore implements BlobStore {
       }
       return completeMultipartUpload(mpu, parts);
    }
+
+   private static HttpResponseException returnResponseException(int code) {
+      HttpResponse response = HttpResponse.builder().statusCode(code).build();
+      // TODO: bogus endpoint
+      return new HttpResponseException(new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://stub")
+            .build()), response);
+   }
+
+   private static String maybeQuoteETag(String eTag) {
+      if (!eTag.startsWith("\"") && !eTag.endsWith("\"")) {
+         eTag = "\"" + eTag + "\"";
+      }
+      return eTag;
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/8945258d/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 bb7985c..4e11c3c 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java
@@ -17,6 +17,7 @@
 
 package org.jclouds.blobstore.options;
 
+import java.util.Date;
 import java.util.Map;
 
 import org.jclouds.io.ContentMetadata;
@@ -24,6 +25,7 @@ import org.jclouds.javax.annotation.Nullable;
 
 import com.google.auto.value.AutoValue;
 import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableMap;
 
 @AutoValue
 @Beta
@@ -39,11 +41,34 @@ public abstract class CopyOptions {
    @Nullable
    public abstract Map<String, String> userMetadata();
 
+   @Nullable
+   public abstract Date ifModifiedSince();
+   @Nullable
+   public abstract Date ifUnmodifiedSince();
+   @Nullable
+   public abstract String ifMatch();
+   @Nullable
+   public abstract String ifNoneMatch();
+
    @AutoValue.Builder
    public abstract static class Builder {
       public abstract Builder contentMetadata(ContentMetadata contentMetadata);
       public abstract Builder userMetadata(Map<String, String> userMetadata);
 
-      public abstract CopyOptions build();
+      public abstract Builder ifModifiedSince(Date ifModifiedSince);
+      public abstract Builder ifUnmodifiedSince(Date ifUnmodifiedSince);
+      public abstract Builder ifMatch(String ifMatch);
+      public abstract Builder ifNoneMatch(String ifNoneMatch);
+
+      abstract Map<String, String> userMetadata();
+      abstract CopyOptions autoBuild();
+
+      public CopyOptions build() {
+         Map<String, String> userMetadata = userMetadata();
+         if (userMetadata != null) {
+            userMetadata(ImmutableMap.copyOf(userMetadata));
+         }
+         return autoBuild();
+      }
    }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/8945258d/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 7740dbc..369f98e 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
@@ -42,11 +42,13 @@ import java.util.Map;
 import java.util.Random;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.ws.rs.core.MediaType;
 
+import org.assertj.core.api.Fail;
 import org.jclouds.blobstore.BlobStore;
 import org.jclouds.blobstore.ContainerNotFoundException;
 import org.jclouds.blobstore.KeyNotFoundException;
@@ -948,6 +950,240 @@ public class BaseBlobIntegrationTest extends BaseBlobStoreIntegrationTest {
    }
 
    @Test(groups = { "integration", "live" })
+   public void testCopyIfMatch() throws Exception {
+      BlobStore blobStore = view.getBlobStore();
+      String fromName = "source";
+      String toName = "to";
+      ByteSource payload = TestUtils.randomByteSource().slice(0, 1024);
+      Blob blob = blobStore
+            .blobBuilder(fromName)
+            .payload(payload)
+            .contentLength(payload.size())
+            .build();
+      String fromContainer = getContainerName();
+      String toContainer = getContainerName();
+      try {
+         String eTag = blobStore.putBlob(fromContainer, blob);
+         blobStore.copyBlob(fromContainer, fromName, toContainer, toName, CopyOptions.builder().ifMatch(eTag).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);
+         }
+      } finally {
+         returnContainer(toContainer);
+         returnContainer(fromContainer);
+      }
+   }
+
+   @Test(groups = { "integration", "live" })
+   public void testCopyIfMatchNegative() throws Exception {
+      BlobStore blobStore = view.getBlobStore();
+      String fromName = "source";
+      String toName = "to";
+      ByteSource payload = TestUtils.randomByteSource().slice(0, 1024);
+      Blob blob = blobStore
+            .blobBuilder(fromName)
+            .payload(payload)
+            .contentLength(payload.size())
+            .build();
+      String fromContainer = getContainerName();
+      String toContainer = getContainerName();
+      try {
+         blobStore.putBlob(fromContainer, blob);
+         try {
+            blobStore.copyBlob(fromContainer, fromName, toContainer, toName, CopyOptions.builder().ifMatch("fake-etag").build());
+            Fail.failBecauseExceptionWasNotThrown(HttpResponseException.class);
+         } catch (HttpResponseException hre) {
+            assertThat(hre.getResponse().getStatusCode()).isEqualTo(412);
+         }
+      } finally {
+         returnContainer(toContainer);
+         returnContainer(fromContainer);
+      }
+   }
+
+   @Test(groups = { "integration", "live" })
+   public void testCopyIfNoneMatch() throws Exception {
+      BlobStore blobStore = view.getBlobStore();
+      String fromName = "source";
+      String toName = "to";
+      ByteSource payload = TestUtils.randomByteSource().slice(0, 1024);
+      Blob blob = blobStore
+            .blobBuilder(fromName)
+            .payload(payload)
+            .contentLength(payload.size())
+            .build();
+      String fromContainer = getContainerName();
+      String toContainer = getContainerName();
+      try {
+         blobStore.putBlob(fromContainer, blob);
+         blobStore.copyBlob(fromContainer, fromName, toContainer, toName, CopyOptions.builder().ifNoneMatch("fake-etag").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);
+         }
+      } finally {
+         returnContainer(toContainer);
+         returnContainer(fromContainer);
+      }
+   }
+
+   @Test(groups = { "integration", "live" })
+   public void testCopyIfNoneMatchNegative() throws Exception {
+      BlobStore blobStore = view.getBlobStore();
+      String fromName = "source";
+      String toName = "to";
+      ByteSource payload = TestUtils.randomByteSource().slice(0, 1024);
+      Blob blob = blobStore
+            .blobBuilder(fromName)
+            .payload(payload)
+            .contentLength(payload.size())
+            .build();
+      String fromContainer = getContainerName();
+      String toContainer = getContainerName();
+      try {
+         String eTag = blobStore.putBlob(fromContainer, blob);
+         try {
+            blobStore.copyBlob(fromContainer, fromName, toContainer, toName, CopyOptions.builder().ifNoneMatch(eTag).build());
+            Fail.failBecauseExceptionWasNotThrown(HttpResponseException.class);
+         } catch (HttpResponseException hre) {
+            assertThat(hre.getResponse().getStatusCode()).isEqualTo(412);
+         }
+      } finally {
+         returnContainer(toContainer);
+         returnContainer(fromContainer);
+      }
+   }
+
+   @Test(groups = { "integration", "live" })
+   public void testCopyIfModifiedSince() throws Exception {
+      BlobStore blobStore = view.getBlobStore();
+      String fromName = "source";
+      String toName = "to";
+      ByteSource payload = TestUtils.randomByteSource().slice(0, 1024);
+      Blob blob = blobStore
+            .blobBuilder(fromName)
+            .payload(payload)
+            .contentLength(payload.size())
+            .build();
+      String fromContainer = getContainerName();
+      String toContainer = getContainerName();
+      try {
+         blobStore.putBlob(fromContainer, blob);
+         Date before = new Date(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1));
+         blobStore.copyBlob(fromContainer, fromName, toContainer, toName, CopyOptions.builder().ifModifiedSince(before).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);
+         }
+      } finally {
+         returnContainer(toContainer);
+         returnContainer(fromContainer);
+      }
+   }
+
+   @Test(groups = { "integration", "live" })
+   public void testCopyIfModifiedSinceNegative() throws Exception {
+      BlobStore blobStore = view.getBlobStore();
+      String fromName = "source";
+      String toName = "to";
+      ByteSource payload = TestUtils.randomByteSource().slice(0, 1024);
+      Blob blob = blobStore
+            .blobBuilder(fromName)
+            .payload(payload)
+            .contentLength(payload.size())
+            .build();
+      String fromContainer = getContainerName();
+      String toContainer = getContainerName();
+      try {
+         blobStore.putBlob(fromContainer, blob);
+         // TODO: some problem with S3 and times in the future?
+         Date after = new Date(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1));
+         try {
+            blobStore.copyBlob(fromContainer, fromName, toContainer, toName, CopyOptions.builder().ifModifiedSince(after).build());
+            Fail.failBecauseExceptionWasNotThrown(HttpResponseException.class);
+         } catch (HttpResponseException hre) {
+            // most object stores return 412 but swift returns 304
+            assertThat(hre.getResponse().getStatusCode()).isIn(304, 412);
+         }
+      } finally {
+         returnContainer(toContainer);
+         returnContainer(fromContainer);
+      }
+   }
+
+   @Test(groups = { "integration", "live" })
+   public void testCopyIfUnmodifiedSince() throws Exception {
+      BlobStore blobStore = view.getBlobStore();
+      String fromName = "source";
+      String toName = "to";
+      ByteSource payload = TestUtils.randomByteSource().slice(0, 1024);
+      Blob blob = blobStore
+            .blobBuilder(fromName)
+            .payload(payload)
+            .contentLength(payload.size())
+            .build();
+      String fromContainer = getContainerName();
+      String toContainer = getContainerName();
+      try {
+         blobStore.putBlob(fromContainer, blob);
+         Date after = new Date(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1));
+         blobStore.copyBlob(fromContainer, fromName, toContainer, toName, CopyOptions.builder().ifUnmodifiedSince(after).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);
+         }
+      } finally {
+         returnContainer(toContainer);
+         returnContainer(fromContainer);
+      }
+   }
+
+   @Test(groups = { "integration", "live" })
+   public void testCopyIfUnmodifiedSinceNegative() throws Exception {
+      BlobStore blobStore = view.getBlobStore();
+      String fromName = "source";
+      String toName = "to";
+      ByteSource payload = TestUtils.randomByteSource().slice(0, 1024);
+      Blob blob = blobStore
+            .blobBuilder(fromName)
+            .payload(payload)
+            .contentLength(payload.size())
+            .build();
+      String fromContainer = getContainerName();
+      String toContainer = getContainerName();
+      try {
+         blobStore.putBlob(fromContainer, blob);
+         Date before = new Date(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1));
+         try {
+            blobStore.copyBlob(fromContainer, fromName, toContainer, toName, CopyOptions.builder().ifUnmodifiedSince(before).build());
+            Fail.failBecauseExceptionWasNotThrown(HttpResponseException.class);
+         } catch (HttpResponseException hre) {
+            assertThat(hre.getResponse().getStatusCode()).isEqualTo(412);
+         }
+      } finally {
+         returnContainer(toContainer);
+         returnContainer(fromContainer);
+      }
+   }
+
+   @Test(groups = { "integration", "live" })
    public void testMultipartUploadNoPartsAbort() throws Exception {
       BlobStore blobStore = view.getBlobStore();
       String container = getContainerName();


[6/8] jclouds git commit: JCLOUDS-651: Atmos support for conditional copies

Posted by ga...@apache.org.
JCLOUDS-651: Atmos support for conditional copies


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

Branch: refs/heads/master
Commit: cc8af838e8427ec3d59afd091642877911dc7be0
Parents: 66609e6
Author: Andrew Gaul <ga...@apache.org>
Authored: Fri Feb 12 20:43:27 2016 -0800
Committer: Andrew Gaul <ga...@apache.org>
Committed: Tue Feb 16 16:29:54 2016 -0800

----------------------------------------------------------------------
 .../jclouds/atmos/blobstore/AtmosBlobStore.java | 13 +++++++++++
 .../integration/AtmosIntegrationLiveTest.java   | 24 ++++++++++++++++++++
 2 files changed, 37 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/cc8af838/apis/atmos/src/main/java/org/jclouds/atmos/blobstore/AtmosBlobStore.java
----------------------------------------------------------------------
diff --git a/apis/atmos/src/main/java/org/jclouds/atmos/blobstore/AtmosBlobStore.java b/apis/atmos/src/main/java/org/jclouds/atmos/blobstore/AtmosBlobStore.java
index 6516198..a263af9 100644
--- a/apis/atmos/src/main/java/org/jclouds/atmos/blobstore/AtmosBlobStore.java
+++ b/apis/atmos/src/main/java/org/jclouds/atmos/blobstore/AtmosBlobStore.java
@@ -45,6 +45,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.PutOptions;
 import org.jclouds.blobstore.strategy.internal.FetchBlobMetadata;
@@ -334,4 +335,16 @@ public class AtmosBlobStore extends BaseBlobStore {
    public int getMaximumNumberOfParts() {
       throw new UnsupportedOperationException("Atmos does not support multipart uploads");
    }
+
+   @Override
+   public String copyBlob(String fromContainer, String fromName, String toContainer, String toName,
+         CopyOptions options) {
+      if (options.ifMatch() != null) {
+         throw new UnsupportedOperationException("Atmos does not support ifMatch");
+      }
+      if (options.ifNoneMatch() != null) {
+         throw new UnsupportedOperationException("Atmos does not support ifNoneMatch");
+      }
+      return super.copyBlob(fromContainer, fromName, toContainer, toName, options);
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/cc8af838/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/integration/AtmosIntegrationLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/integration/AtmosIntegrationLiveTest.java b/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/integration/AtmosIntegrationLiveTest.java
index f534787..640524f 100644
--- a/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/integration/AtmosIntegrationLiveTest.java
+++ b/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/integration/AtmosIntegrationLiveTest.java
@@ -164,4 +164,28 @@ public class AtmosIntegrationLiveTest extends BaseBlobIntegrationTest {
    public void testPutBlobAccessMultipart() throws Exception {
       super.testPutBlobAccessMultipart();
    }
+
+   @Override
+   @Test(groups = { "integration", "live" }, expectedExceptions = UnsupportedOperationException.class)
+   public void testCopyIfMatch() throws Exception {
+      super.testCopyIfMatch();
+   }
+
+   @Override
+   @Test(groups = { "integration", "live" }, expectedExceptions = UnsupportedOperationException.class)
+   public void testCopyIfMatchNegative() throws Exception {
+      super.testCopyIfMatchNegative();
+   }
+
+   @Override
+   @Test(groups = { "integration", "live" }, expectedExceptions = UnsupportedOperationException.class)
+   public void testCopyIfNoneMatch() throws Exception {
+      super.testCopyIfNoneMatch();
+   }
+
+   @Override
+   @Test(groups = { "integration", "live" }, expectedExceptions = UnsupportedOperationException.class)
+   public void testCopyIfNoneMatchNegative() throws Exception {
+      super.testCopyIfNoneMatchNegative();
+   }
 }


[5/8] jclouds git commit: Convert CopyOptions into an AutoValue

Posted by ga...@apache.org.
Convert CopyOptions into an AutoValue

This commit requires an interface change since AutoValue lacks support
for Optional and uses Nullable annotations instead.


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

Branch: refs/heads/master
Commit: 293d3f864e778913bb29ce577699bbeabfbd71af
Parents: c8bbb44
Author: Andrew Gaul <ga...@apache.org>
Authored: Tue Feb 9 20:37:57 2016 -0800
Committer: Andrew Gaul <ga...@apache.org>
Committed: Tue Feb 16 16:29:54 2016 -0800

----------------------------------------------------------------------
 .../blobstore/RegionScopedSwiftBlobStore.java   | 11 ++--
 .../org/jclouds/s3/blobstore/S3BlobStore.java   | 21 ++++----
 .../blobstore/config/LocalBlobStore.java        | 11 ++--
 .../blobstore/internal/BaseBlobStore.java       | 11 ++--
 .../jclouds/blobstore/options/CopyOptions.java  | 54 +++++---------------
 .../azureblob/blobstore/AzureBlobStore.java     | 21 ++++----
 6 files changed, 49 insertions(+), 80 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/293d3f86/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 a0d40bc..5b331ed 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
@@ -253,12 +253,11 @@ public class RegionScopedSwiftBlobStore implements BlobStore {
          CopyOptions options) {
       ObjectApi objectApi = api.getObjectApi(regionId, toContainer);
 
-      Map<String, String> userMetadata;
       Map<String, String> systemMetadata = Maps.newHashMap();
-      ContentMetadata contentMetadata = options.getContentMetadata().orNull();
+      ContentMetadata contentMetadata = options.contentMetadata();
+      Map<String, String> userMetadata = options.userMetadata();
 
-      if (contentMetadata != null ||
-            options.getUserMetadata().isPresent()) {
+      if (contentMetadata != null || userMetadata != null) {
          if (contentMetadata != null) {
             String contentDisposition = contentMetadata.getContentDisposition();
             if (contentDisposition != null) {
@@ -280,9 +279,7 @@ public class RegionScopedSwiftBlobStore implements BlobStore {
                systemMetadata.put(HttpHeaders.CONTENT_TYPE, contentType);
             }
          }
-         if (options.getUserMetadata().isPresent()) {
-            userMetadata = options.getUserMetadata().get();
-         } else {
+         if (userMetadata == null) {
             userMetadata = Maps.newHashMap();
          }
       } else {

http://git-wip-us.apache.org/repos/asf/jclouds/blob/293d3f86/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 7803108..e01c498 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
@@ -71,7 +71,6 @@ 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.collect.ImmutableList;
@@ -272,37 +271,37 @@ public class S3BlobStore extends BaseBlobStore {
          CopyOptions options) {
       CopyObjectOptions s3Options = new CopyObjectOptions();
 
-      Optional<ContentMetadata> contentMetadata = options.getContentMetadata();
-      if (contentMetadata.isPresent()) {
-         String cacheControl = contentMetadata.get().getCacheControl();
+      ContentMetadata contentMetadata = options.contentMetadata();
+      if (contentMetadata != null) {
+         String cacheControl = contentMetadata.getCacheControl();
          if (cacheControl != null) {
             s3Options.cacheControl(cacheControl);
          }
 
-         String contentDisposition = contentMetadata.get().getContentDisposition();
+         String contentDisposition = contentMetadata.getContentDisposition();
          if (contentDisposition != null) {
             s3Options.contentDisposition(contentDisposition);
          }
 
-         String contentEncoding = contentMetadata.get().getContentEncoding();
+         String contentEncoding = contentMetadata.getContentEncoding();
          if (contentEncoding != null) {
             s3Options.contentEncoding(contentEncoding);
          }
 
-         String contentLanguage = contentMetadata.get().getContentLanguage();
+         String contentLanguage = contentMetadata.getContentLanguage();
          if (contentLanguage != null) {
             s3Options.contentLanguage(contentLanguage);
          }
 
-         String contentType = contentMetadata.get().getContentType();
+         String contentType = contentMetadata.getContentType();
          if (contentType != null) {
             s3Options.contentType(contentType);
          }
       }
 
-      Optional<Map<String, String>> userMetadata = options.getUserMetadata();
-      if (userMetadata.isPresent()) {
-         s3Options.overrideMetadataWith(userMetadata.get());
+      Map<String, String> userMetadata = options.userMetadata();
+      if (userMetadata != null) {
+         s3Options.overrideMetadataWith(userMetadata);
       }
 
       return sync.copyObject(fromContainer, fromName, toContainer, toName, s3Options).getETag();

http://git-wip-us.apache.org/repos/asf/jclouds/blob/293d3f86/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 a5f3406..42e6f39 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
@@ -541,8 +541,8 @@ public final class LocalBlobStore implements BlobStore {
             builder.contentLength(contentLength);
          }
 
-         if (options.getContentMetadata().isPresent()) {
-            ContentMetadata contentMetadata = options.getContentMetadata().get();
+         ContentMetadata contentMetadata = options.contentMetadata();
+         if (contentMetadata != null) {
             String cacheControl = contentMetadata.getCacheControl();
             if (cacheControl != null) {
                builder.cacheControl(cacheControl);
@@ -570,9 +570,10 @@ public final class LocalBlobStore implements BlobStore {
                    .contentLanguage(metadata.getContentLanguage())
                    .contentType(metadata.getContentType());
          }
-         Optional<Map<String, String>> userMetadata = options.getUserMetadata();
-         if (userMetadata.isPresent()) {
-            builder.userMetadata(userMetadata.get());
+
+         Map<String, String> userMetadata = options.userMetadata();
+         if (userMetadata != null) {
+            builder.userMetadata(userMetadata);
          } else {
             builder.userMetadata(blob.getMetadata().getUserMetadata());
          }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/293d3f86/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 3c14f5f..820721e 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseBlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseBlobStore.java
@@ -52,7 +52,6 @@ import org.jclouds.io.PayloadSlicer;
 import org.jclouds.util.Closeables2;
 
 import com.google.common.annotations.Beta;
-import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.base.Supplier;
 import com.google.common.base.Throwables;
@@ -270,8 +269,8 @@ public abstract class BaseBlobStore implements BlobStore {
          }
 
          ContentMetadata metadata;
-         if (options.getContentMetadata().isPresent()) {
-            metadata = options.getContentMetadata().get();
+         if (options.contentMetadata() != null) {
+            metadata = options.contentMetadata();
          } else {
             metadata = blob.getMetadata().getContentMetadata();
          }
@@ -281,9 +280,9 @@ public abstract class BaseBlobStore implements BlobStore {
                .contentLanguage(metadata.getContentLanguage())
                .contentType(metadata.getContentType());
 
-         Optional<Map<String, String>> userMetadata = options.getUserMetadata();
-         if (userMetadata.isPresent()) {
-            builder.userMetadata(userMetadata.get());
+         Map<String, String> userMetadata = options.userMetadata();
+         if (userMetadata != null) {
+            builder.userMetadata(userMetadata);
          } else {
             builder.userMetadata(blob.getMetadata().getUserMetadata());
          }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/293d3f86/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 4084cf9..bb7985c 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java
@@ -17,59 +17,33 @@
 
 package org.jclouds.blobstore.options;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-
 import java.util.Map;
 
 import org.jclouds.io.ContentMetadata;
+import org.jclouds.javax.annotation.Nullable;
 
+import com.google.auto.value.AutoValue;
 import com.google.common.annotations.Beta;
-import com.google.common.base.Optional;
-import com.google.common.collect.ImmutableMap;
 
+@AutoValue
 @Beta
-public final class CopyOptions {
+public abstract 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;
-   }
-
    public static Builder builder() {
-      return new Builder();
+      return new AutoValue_CopyOptions.Builder();
    }
 
-   public static class Builder {
-      ContentMetadata contentMetadata;
-      Map<String, String> userMetadata;
-
-      Builder() {
-      }
-
-      public Builder contentMetadata(ContentMetadata contentMetadata) {
-         this.contentMetadata = checkNotNull(contentMetadata, "contentMetadata");
-         return this;
-      }
+   @Nullable
+   public abstract ContentMetadata contentMetadata();
+   @Nullable
+   public abstract Map<String, String> userMetadata();
 
-      public Builder userMetadata(Map<String, String> userMetadata) {
-         this.userMetadata = ImmutableMap.copyOf(userMetadata);
-         return this;
-      }
+   @AutoValue.Builder
+   public abstract static class Builder {
+      public abstract Builder contentMetadata(ContentMetadata contentMetadata);
+      public abstract Builder userMetadata(Map<String, String> userMetadata);
 
-      public CopyOptions build() {
-          return new CopyOptions(this);
-      }
+      public abstract CopyOptions build();
    }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/293d3f86/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 eed4ada..90e7d99 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
@@ -69,7 +69,6 @@ import org.jclouds.io.MutableContentMetadata;
 import org.jclouds.io.PayloadSlicer;
 
 import com.google.common.base.Function;
-import com.google.common.base.Optional;
 import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
@@ -237,39 +236,39 @@ public class AzureBlobStore extends BaseBlobStore {
          CopyOptions options) {
       CopyBlobOptions.Builder azureOptions = CopyBlobOptions.builder();
 
-      Optional<Map<String, String>> userMetadata = options.getUserMetadata();
-      if (userMetadata.isPresent()) {
-         azureOptions.overrideUserMetadata(userMetadata.get());
+      Map<String, String> userMetadata = options.userMetadata();
+      if (userMetadata != null) {
+         azureOptions.overrideUserMetadata(userMetadata);
       }
 
       URI source = context.getSigner().signGetBlob(fromContainer, fromName).getEndpoint();
       String eTag = sync.copyBlob(source, toContainer, toName, azureOptions.build());
 
-      Optional<ContentMetadata> contentMetadata = options.getContentMetadata();
-      if (contentMetadata.isPresent()) {
+      ContentMetadata contentMetadata = options.contentMetadata();
+      if (contentMetadata != null) {
          ContentMetadataBuilder builder = ContentMetadataBuilder.create();
 
-         String cacheControl = contentMetadata.get().getCacheControl();
+         String cacheControl = contentMetadata.getCacheControl();
          if (cacheControl != null) {
             builder.cacheControl(cacheControl);
          }
 
-         String contentDisposition = contentMetadata.get().getContentDisposition();
+         String contentDisposition = contentMetadata.getContentDisposition();
          if (contentDisposition != null) {
             builder.contentDisposition(contentDisposition);
          }
 
-         String contentEncoding = contentMetadata.get().getContentEncoding();
+         String contentEncoding = contentMetadata.getContentEncoding();
          if (contentEncoding != null) {
             builder.contentEncoding(contentEncoding);
          }
 
-         String contentLanguage = contentMetadata.get().getContentLanguage();
+         String contentLanguage = contentMetadata.getContentLanguage();
          if (contentLanguage != null) {
             builder.contentLanguage(contentLanguage);
          }
 
-         String contentType = contentMetadata.get().getContentType();
+         String contentType = contentMetadata.getContentType();
          if (contentType != null) {
             builder.contentType(contentType);
          }