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/10 01:36:12 UTC

jclouds git commit: Add support for Swift conditional copy

Repository: jclouds
Updated Branches:
  refs/heads/master a697396e8 -> 2bd055011


Add support for Swift conditional copy


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

Branch: refs/heads/master
Commit: 2bd0550110acca8e5b2e9026f0eb6c9c4d4f34b4
Parents: a697396
Author: Andrew Gaul <ga...@apache.org>
Authored: Mon Feb 8 20:53:28 2016 -0800
Committer: Andrew Gaul <ga...@apache.org>
Committed: Tue Feb 9 16:34:48 2016 -0800

----------------------------------------------------------------------
 .../openstack/swift/v1/features/ObjectApi.java  | 96 ++++++++++++++++++++
 .../openstack/swift/v1/options/CopyOptions.java | 48 ++++++++++
 .../swift/v1/features/ObjectApiLiveTest.java    | 60 ++++++++++++
 .../swift/v1/features/ObjectApiMockTest.java    | 26 ++++++
 4 files changed, 230 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/2bd05501/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 335dfa6..f56a8f5 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
@@ -51,6 +51,7 @@ import org.jclouds.openstack.swift.v1.domain.SwiftObject;
 import org.jclouds.openstack.swift.v1.functions.ETagHeader;
 import org.jclouds.openstack.swift.v1.functions.ParseObjectFromResponse;
 import org.jclouds.openstack.swift.v1.functions.ParseObjectListFromResponse;
+import org.jclouds.openstack.swift.v1.options.CopyOptions;
 import org.jclouds.openstack.swift.v1.options.ListContainerOptions;
 import org.jclouds.openstack.swift.v1.options.PutOptions;
 import org.jclouds.rest.annotations.BinderParam;
@@ -264,8 +265,10 @@ public interface ObjectApi {
     * @param sourceObject
     *           the source object name.
     *
+    * @deprecated call copy(String, String, String, CopyOptions) instead
     * @throws KeyNotFoundException if the source or destination container do not exist.
     */
+   @Deprecated
    @Named("object:copy")
    @PUT
    @Path("/{destinationObject}")
@@ -275,6 +278,32 @@ public interface ObjectApi {
                 @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 options
+    *           conditional copy
+    *
+    * @throws KeyNotFoundException if the source or destination container do not exist.
+    */
+   @Named("object:copy")
+   @PUT
+   @Path("/{destinationObject}")
+   @Headers(keys = OBJECT_COPY_FROM, values = "/{sourceContainer}/{sourceObject}")
+   void copy(@PathParam("destinationObject") String destinationObject,
+                @PathParam("sourceContainer") String sourceContainer,
+                @PathParam("sourceObject") String sourceObject,
+                CopyOptions options);
+
+   /**
     * Copies an object from one container to another, replacing metadata.
     *
     * <h3>NOTE</h3>
@@ -291,8 +320,10 @@ public interface ObjectApi {
     * @param objectMetadata
     *           Unprefixed/unescaped metadata, such as Content-Disposition
     *
+    * @deprecated call copy(String, String, String, Map, Map, CopyOptions) instead
     * @throws KeyNotFoundException if the source or destination container do not exist.
     */
+   @Deprecated
    @Named("object:copy")
    @PUT
    @Path("/{destinationObject}")
@@ -303,6 +334,37 @@ public interface ObjectApi {
          @BinderParam(BindObjectMetadataToHeaders.class) Map<String, String> userMetadata,
          @BinderParam(BindToHeaders.class) Map<String, String> objectMetadata);
 
+   /**
+    * Copies an object from one container to another, replacing metadata.
+    *
+    * <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
+    * @param options
+    *           conditional copy
+    *
+    * @throws KeyNotFoundException if the source or destination container do not exist.
+    */
+   @Named("object:copy")
+   @PUT
+   @Path("/{destinationObject}")
+   @Headers(keys = {OBJECT_COPY_FROM, OBJECT_COPY_FRESH_METADATA}, values = {"/{sourceContainer}/{sourceObject}", "True"})
+   void copy(@PathParam("destinationObject") String destinationObject,
+         @PathParam("sourceContainer") String sourceContainer,
+         @PathParam("sourceObject") String sourceObject,
+         @BinderParam(BindObjectMetadataToHeaders.class) Map<String, String> userMetadata,
+         @BinderParam(BindToHeaders.class) Map<String, String> objectMetadata,
+         CopyOptions options);
 
    /**
     * Copies an object from one container to another, appending metadata.
@@ -321,8 +383,10 @@ public interface ObjectApi {
     * @param objectMetadata
     *           Unprefixed/unescaped metadata, such as Content-Disposition
     *
+    * @deprecated call copyAppendMetadata(String, String, String, Map, Map, CopyOptions) instead
     * @throws KeyNotFoundException if the source or destination container do not exist.
     */
+   @Deprecated
    @Named("object:copy")
    @PUT
    @Path("/{destinationObject}")
@@ -332,4 +396,36 @@ public interface ObjectApi {
          @PathParam("sourceObject") String sourceObject,
          @BinderParam(BindObjectMetadataToHeaders.class) Map<String, String> userMetadata,
          @BinderParam(BindToHeaders.class) Map<String, String> objectMetadata);
+
+   /**
+    * Copies an object from one container to another, appending metadata.
+    *
+    * <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
+    * @param options
+    *           conditional copy
+    *
+    * @throws KeyNotFoundException if the source or destination container do not exist.
+    */
+   @Named("object:copy")
+   @PUT
+   @Path("/{destinationObject}")
+   @Headers(keys = OBJECT_COPY_FROM, values = "/{sourceContainer}/{sourceObject}")
+   void copyAppendMetadata(@PathParam("destinationObject") String destinationObject,
+         @PathParam("sourceContainer") String sourceContainer,
+         @PathParam("sourceObject") String sourceObject,
+         @BinderParam(BindObjectMetadataToHeaders.class) Map<String, String> userMetadata,
+         @BinderParam(BindToHeaders.class) Map<String, String> objectMetadata,
+         CopyOptions options);
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/2bd05501/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/CopyOptions.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/CopyOptions.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/CopyOptions.java
new file mode 100644
index 0000000..f7c35b78
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/CopyOptions.java
@@ -0,0 +1,48 @@
+/*
+ * 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.openstack.swift.v1.options;
+
+import java.util.Date;
+
+import org.jclouds.date.DateService;
+import org.jclouds.date.internal.SimpleDateFormatDateService;
+import org.jclouds.http.options.BaseHttpRequestOptions;
+
+import com.google.common.net.HttpHeaders;
+
+public final class CopyOptions extends BaseHttpRequestOptions {
+   public static final CopyOptions NONE = new CopyOptions();
+
+   private static final DateService dateService = new SimpleDateFormatDateService();
+
+   public CopyOptions ifMatch(String ifMatch) {
+      this.headers.put(HttpHeaders.IF_MATCH, ifMatch);
+      return this;
+   }
+
+   // Swift only supports If-None-Match: * which is not useful for copy
+
+   public CopyOptions ifModifiedSince(Date ifModifiedSince) {
+      this.headers.put(HttpHeaders.IF_MODIFIED_SINCE, dateService.rfc822DateFormat(ifModifiedSince));
+      return this;
+   }
+
+   public CopyOptions ifUnmodifiedSince(Date ifUnmodifiedSince) {
+      this.headers.put(HttpHeaders.IF_UNMODIFIED_SINCE, dateService.rfc822DateFormat(ifUnmodifiedSince));
+      return this;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/2bd05501/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 dc5b4c1..33d9ac7 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
@@ -33,13 +33,16 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.concurrent.TimeUnit;
 
+import org.assertj.core.api.Fail;
 import org.jclouds.blobstore.KeyNotFoundException;
+import org.jclouds.http.HttpResponseException;
 import org.jclouds.http.options.GetOptions;
 import org.jclouds.io.Payload;
 import org.jclouds.openstack.swift.v1.SwiftApi;
 import org.jclouds.openstack.swift.v1.domain.ObjectList;
 import org.jclouds.openstack.swift.v1.domain.SwiftObject;
 import org.jclouds.openstack.swift.v1.internal.BaseSwiftApiLiveTest;
+import org.jclouds.openstack.swift.v1.options.CopyOptions;
 import org.jclouds.openstack.swift.v1.options.ListContainerOptions;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
@@ -245,6 +248,63 @@ public class ObjectApiLiveTest extends BaseSwiftApiLiveTest<SwiftApi> {
       }
    }
 
+   public void testCopyObjectConditional() 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);
+
+         destApi.copy(destinationObject, sourceContainer, sourceObjectName, new CopyOptions().ifMatch(sourceObject.getETag()));
+         try {
+            destApi.copy(destinationObject, sourceContainer, sourceObjectName, new CopyOptions().ifMatch("fake-etag"));
+            Fail.failBecauseExceptionWasNotThrown(HttpResponseException.class);
+         } catch (HttpResponseException hre) {
+            assertThat(hre.getResponse().getStatusCode()).isEqualTo(412);
+         }
+
+         long now = System.currentTimeMillis();
+         Date before = new Date(now - 1000 * 1000);
+         Date after = new Date(now + 1000 * 1000);
+
+         destApi.copy(destinationObject, sourceContainer, sourceObjectName, new CopyOptions().ifModifiedSince(before));
+         try {
+            destApi.copy(destinationObject, sourceContainer, sourceObjectName, new CopyOptions().ifModifiedSince(after));
+            Fail.failBecauseExceptionWasNotThrown(HttpResponseException.class);
+         } catch (HttpResponseException hre) {
+            assertThat(hre.getResponse().getStatusCode()).isEqualTo(304);
+         }
+
+         try {
+            destApi.copy(destinationObject, sourceContainer, sourceObjectName, new CopyOptions().ifUnmodifiedSince(before));
+            Fail.failBecauseExceptionWasNotThrown(HttpResponseException.class);
+         } catch (HttpResponseException hre) {
+            assertThat(hre.getResponse().getStatusCode()).isEqualTo(412);
+         }
+         destApi.copy(destinationObject, sourceContainer, sourceObjectName, new CopyOptions().ifUnmodifiedSince(after));
+      }
+   }
+
    public void testList() throws Exception {
       for (String regionId : regions) {
          ObjectApi objectApi = api.getObjectApi(regionId, containerName);

http://git-wip-us.apache.org/repos/asf/jclouds/blob/2bd05501/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 77305eb..096a0e1 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
@@ -19,6 +19,7 @@ package org.jclouds.openstack.swift.v1.features;
 import static com.google.common.base.Charsets.US_ASCII;
 import static com.google.common.io.BaseEncoding.base16;
 import static com.google.common.net.HttpHeaders.EXPIRES;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.jclouds.Constants.PROPERTY_MAX_RETRIES;
 import static org.jclouds.Constants.PROPERTY_RETRY_DELAY_START;
 import static org.jclouds.Constants.PROPERTY_SO_TIMEOUT;
@@ -52,6 +53,7 @@ import org.jclouds.io.payloads.ByteSourcePayload;
 import org.jclouds.openstack.swift.v1.SwiftApi;
 import org.jclouds.openstack.swift.v1.domain.ObjectList;
 import org.jclouds.openstack.swift.v1.domain.SwiftObject;
+import org.jclouds.openstack.swift.v1.options.CopyOptions;
 import org.jclouds.openstack.swift.v1.options.ListContainerOptions;
 import org.jclouds.openstack.swift.v1.reference.SwiftHeaders;
 import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest;
@@ -484,6 +486,30 @@ public class ObjectApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
       }
    }
 
+   public void testCopyObjectConditional() 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");
+         api.getObjectApi("DFW", "foo").copy("bar.txt", "bar", "foo.txt", new CopyOptions().ifMatch("fakeetag"));
+
+         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();
+         assertThat(requestHeaders).contains("If-Match: fakeetag");
+         assertThat(requestHeaders).contains(SwiftHeaders.OBJECT_COPY_FROM + ": /bar/foo.txt");
+      } finally {
+         server.shutdown();
+      }
+   }
+
    @Test(expectedExceptions = KeyNotFoundException.class)
    public void testCopyObjectFail() throws InterruptedException, IOException {
       MockWebServer server = mockOpenStackServer();