You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by jd...@apache.org on 2014/08/21 18:01:29 UTC

git commit: JCLOUDS-630: Map Payload ContentMetadata expires to Swift X-Delete-At header for object expiration.

Repository: jclouds-labs-openstack
Updated Branches:
  refs/heads/master d9c238029 -> 007a3b735


JCLOUDS-630: Map Payload ContentMetadata expires to Swift X-Delete-At header for object expiration.


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

Branch: refs/heads/master
Commit: 007a3b7355286a8d335fb97b0384957e4d1681a0
Parents: d9c2380
Author: Jeremy Daggett <je...@rackspace.com>
Authored: Thu Jul 24 08:45:24 2014 -0700
Committer: Jeremy Daggett <je...@rackspace.com>
Committed: Thu Aug 21 09:00:53 2014 -0700

----------------------------------------------------------------------
 .../openstack/swift/v1/binders/SetPayload.java  | 14 +++--
 .../v1/blobstore/functions/ToBlobMetadata.java  |  6 +--
 .../v1/blobstore/functions/ToSwiftObject.java   | 54 --------------------
 .../v1/functions/ParseObjectFromResponse.java   | 17 +++++-
 .../functions/ParseObjectListFromResponse.java  |  6 ++-
 .../swift/v1/features/ObjectApiLiveTest.java    | 27 ++++++++++
 .../swift/v1/features/ObjectApiMockTest.java    | 52 +++++++++++--------
 7 files changed, 89 insertions(+), 87 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/007a3b73/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/SetPayload.java
----------------------------------------------------------------------
diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/SetPayload.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/SetPayload.java
index fbc6fe4..f24a3ad 100644
--- a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/SetPayload.java
+++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/SetPayload.java
@@ -32,7 +32,10 @@ import org.jclouds.http.HttpRequest.Builder;
 import org.jclouds.io.Payload;
 import org.jclouds.rest.Binder;
 
+import com.google.common.hash.HashCode;
+
 public class SetPayload implements Binder {
+
    @SuppressWarnings("unchecked")
    @Override
    public <R extends HttpRequest> R bindToRequest(R request, Object input) {
@@ -40,6 +43,7 @@ public class SetPayload implements Binder {
       Payload payload = Payload.class.cast(input);
 
       if (payload.getContentMetadata().getContentType() == null) {
+         // TODO: use `X-Detect-Content-Type` here. Should be configurable via a property.
          payload.getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM);
       }
 
@@ -51,18 +55,18 @@ public class SetPayload implements Binder {
          builder.replaceHeader(TRANSFER_ENCODING, "chunked").build();
       }
 
-      byte[] md5 = payload.getContentMetadata().getContentMD5();
+      HashCode md5 = payload.getContentMetadata().getContentMD5AsHashCode();
       if (md5 != null) {
          // Swift will validate the md5, if placed as an ETag header
-         builder.replaceHeader(ETAG, base16().lowerCase().encode(md5));
+         builder.replaceHeader(ETAG, base16().lowerCase().encode(md5.asBytes()));
       }
 
       Date expires = payload.getContentMetadata().getExpires();
       if (expires != null) {
-         builder.replaceHeader(OBJECT_DELETE_AT, String.valueOf(
-               MILLISECONDS.toSeconds(expires.getTime())))
-               .build();
+         builder.addHeader(OBJECT_DELETE_AT,
+               String.valueOf(MILLISECONDS.toSeconds(expires.getTime()))).build();
       }
+
       return (R) builder.payload(payload).build();
    }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/007a3b73/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToBlobMetadata.java
----------------------------------------------------------------------
diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToBlobMetadata.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToBlobMetadata.java
index 5a0ee17..6a1b8c5 100644
--- a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToBlobMetadata.java
+++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToBlobMetadata.java
@@ -17,7 +17,6 @@
 package org.jclouds.openstack.swift.v1.blobstore.functions;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.io.BaseEncoding.base16;
 
 import org.jclouds.blobstore.domain.MutableBlobMetadata;
 import org.jclouds.blobstore.domain.StorageType;
@@ -48,11 +47,12 @@ public class ToBlobMetadata implements Function<SwiftObject, MutableBlobMetadata
          to.setPublicUri(from.getUri());
       }
       to.setUri(from.getUri());
-      to.setETag(from.getEtag());
+      to.setETag(from.getETag());
       to.setName(from.getName());
       to.setLastModified(from.getLastModified());
       to.setContentMetadata(from.getPayload().getContentMetadata());
-      to.getContentMetadata().setContentMD5(base16().lowerCase().decode(from.getEtag()));
+      to.getContentMetadata().setContentMD5(from.getPayload().getContentMetadata().getContentMD5AsHashCode());
+      to.getContentMetadata().setExpires(from.getPayload().getContentMetadata().getExpires());
       to.setUserMetadata(from.getMetadata());
       String directoryName = ifDirectoryReturnName.execute(to);
       if (directoryName != null) {

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/007a3b73/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToSwiftObject.java
----------------------------------------------------------------------
diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToSwiftObject.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToSwiftObject.java
deleted file mode 100644
index 8fe00c7..0000000
--- a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToSwiftObject.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.blobstore.functions;
-
-import org.jclouds.blobstore.domain.BlobMetadata;
-import org.jclouds.blobstore.domain.StorageMetadata;
-import org.jclouds.io.Payload;
-import org.jclouds.io.Payloads;
-import org.jclouds.openstack.swift.v1.domain.SwiftObject;
-import org.jclouds.openstack.swift.v1.domain.SwiftObject.Builder;
-
-import com.google.common.base.Function;
-import com.google.common.io.ByteSource;
-
-public class ToSwiftObject implements Function<StorageMetadata, SwiftObject> {
-
-   @Override
-   public SwiftObject apply(StorageMetadata in) {
-      if (!(in instanceof BlobMetadata)) {
-         return null;
-      }
-      BlobMetadata from = BlobMetadata.class.cast(in);
-      Builder to = SwiftObject.builder();
-      to.name(from.getName());
-      to.etag(from.getETag());
-      to.lastModified(from.getLastModified());
-      long bytes = from.getContentMetadata().getContentLength();
-      String contentType = from.getContentMetadata().getContentType();
-      to.payload(payload(bytes, contentType));
-      to.metadata(from.getUserMetadata());
-      return to.build();
-   }
-
-   private static Payload payload(long bytes, String contentType) {
-      Payload payload = Payloads.newByteSourcePayload(ByteSource.empty());
-      payload.getContentMetadata().setContentLength(bytes);
-      payload.getContentMetadata().setContentType(contentType);
-      return payload;
-   }
-}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/007a3b73/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectFromResponse.java
----------------------------------------------------------------------
diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectFromResponse.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectFromResponse.java
index 6c1ac9e..714a403 100644
--- a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectFromResponse.java
+++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectFromResponse.java
@@ -18,14 +18,18 @@ package org.jclouds.openstack.swift.v1.functions;
 
 import static com.google.common.net.HttpHeaders.ETAG;
 import static com.google.common.net.HttpHeaders.LAST_MODIFIED;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_DELETE_AT;
 
 import java.net.URI;
+import java.util.Date;
 
 import javax.inject.Inject;
 
 import org.jclouds.date.DateService;
 import org.jclouds.http.HttpRequest;
 import org.jclouds.http.HttpResponse;
+import org.jclouds.io.MutableContentMetadata;
+import org.jclouds.io.Payload;
 import org.jclouds.openstack.swift.v1.domain.SwiftObject;
 import org.jclouds.rest.InvocationContext;
 import org.jclouds.rest.internal.GeneratedHttpRequest;
@@ -46,11 +50,22 @@ public class ParseObjectFromResponse implements Function<HttpResponse, SwiftObje
 
    @Override
    public SwiftObject apply(HttpResponse from) {
+
+      Payload payload = from.getPayload();
+      MutableContentMetadata contentMeta = payload.getContentMetadata();
+
+      String deleteAt = from.getFirstHeaderOrNull(OBJECT_DELETE_AT);
+      if (deleteAt != null) {
+         long fromEpoch = Long.parseLong(from.getFirstHeaderOrNull(OBJECT_DELETE_AT)) * 1000;
+         contentMeta.setExpires(new Date(fromEpoch));
+         payload.setContentMetadata(contentMeta);
+      }
+
       return SwiftObject.builder()
             .uri(URI.create(uri))
             .name(name)
             .etag(from.getFirstHeaderOrNull(ETAG))
-            .payload(from.getPayload())
+            .payload(payload)
             .lastModified(dates.rfc822DateParse(from.getFirstHeaderOrNull(LAST_MODIFIED)))
             .headers(from.getHeaders())
             .metadata(EntriesWithoutMetaPrefix.INSTANCE.apply(from.getHeaders())).build();

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/007a3b73/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectListFromResponse.java
----------------------------------------------------------------------
diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectListFromResponse.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectListFromResponse.java
index cf03265..73f5a29 100644
--- a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectListFromResponse.java
+++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectListFromResponse.java
@@ -47,6 +47,7 @@ public class ParseObjectListFromResponse implements Function<HttpResponse, Objec
       long bytes;
       String content_type;
       Date last_modified;
+      Date expires;
    }
 
    private final ParseJson<List<InternalObject>> json;
@@ -80,7 +81,7 @@ public class ParseObjectListFromResponse implements Function<HttpResponse, Objec
                .uri(uriBuilder(containerUri).clearQuery().appendPath(input.name).build())
                .name(input.name)
                .etag(input.hash)
-               .payload(payload(input.bytes, input.content_type))
+               .payload(payload(input.bytes, input.content_type, input.expires))
                .lastModified(input.last_modified).build();
       }
    }
@@ -97,10 +98,11 @@ public class ParseObjectListFromResponse implements Function<HttpResponse, Objec
       return this;
    }
 
-   private static Payload payload(long bytes, String contentType) {
+   private static Payload payload(long bytes, String contentType, Date expires) {
       Payload payload = Payloads.newByteSourcePayload(ByteSource.empty());
       payload.getContentMetadata().setContentLength(bytes);
       payload.getContentMetadata().setContentType(contentType);
+      payload.getContentMetadata().setExpires(expires);
       return payload;
    }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/007a3b73/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java
----------------------------------------------------------------------
diff --git a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java
index b730398..c0b2a6e 100644
--- a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java
+++ b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java
@@ -27,18 +27,23 @@ import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
 
 import java.io.IOException;
+import java.util.Date;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.concurrent.TimeUnit;
 
+import javax.ws.rs.PathParam;
+
 import org.jclouds.http.options.GetOptions;
 import org.jclouds.io.Payload;
 import org.jclouds.openstack.swift.v1.CopyObjectException;
 import org.jclouds.openstack.swift.v1.SwiftApi;
+import org.jclouds.openstack.swift.v1.binders.SetPayload;
 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.ListContainerOptions;
+import org.jclouds.rest.annotations.BinderParam;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -74,6 +79,28 @@ public class ObjectApiLiveTest extends BaseSwiftApiLiveTest<SwiftApi> {
       }
    }
 
+   public void testPutWithExpiration() throws Exception {
+      String objectName = "test-expiration";
+
+      long expireMillis = new Date().getTime() + 1000 * 60 * 60 * 24;
+      Date expireAt = new Date(expireMillis);
+
+      Payload payload = newByteSourcePayload(ByteSource.wrap("swifty".getBytes()));
+      payload.getContentMetadata().setExpires(expireAt);
+
+      for (String regionId : regions) {
+         String etag = api.getObjectApiForRegionAndContainer(regionId, containerName)
+               .put(objectName, payload);
+         assertNotNull(etag);
+
+         SwiftObject object = api.getObjectApiForRegionAndContainer(regionId, containerName).get(objectName);
+         assertEquals(object.getName(), objectName);
+         checkObject(object);
+         assertEquals(toStringAndClose(object.getPayload().openStream()), "swifty");
+
+         api.getObjectApiForRegionAndContainer(regionId, containerName).delete(objectName);
+      }
+   }
    public void testCopyObject() throws Exception {
       for (String regionId : regions) {
          // source

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/007a3b73/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java
----------------------------------------------------------------------
diff --git a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java
index 348700e..972a1e7 100644
--- a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java
+++ b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java
@@ -17,7 +17,9 @@
 package org.jclouds.openstack.swift.v1.features;
 
 import static com.google.common.base.Charsets.US_ASCII;
+import static com.google.common.net.HttpHeaders.EXPIRES;
 import static com.google.common.net.HttpHeaders.RANGE;
+import static com.google.common.net.HttpHeaders.LAST_MODIFIED;
 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;
@@ -37,6 +39,7 @@ import static org.testng.Assert.fail;
 
 import java.io.IOException;
 import java.net.URI;
+import java.util.Date;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Properties;
@@ -77,19 +80,19 @@ public class ObjectApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
                   .name("test_obj_1")
                   .uri(URI.create(baseUri + "/test_obj_1"))
                   .etag("4281c348eaf83e70ddce0e07221c3d28")
-                  .payload(payload(14, "application/octet-stream"))
+                  .payload(payload(14, "application/octet-stream", new Date(1406243553)))
                   .lastModified(dates.iso8601DateParse("2009-02-03T05:26:32.612278")).build(),
             SwiftObject.builder()
                   .name("test_obj_2")
                   .uri(URI.create(baseUri + "/test_obj_2"))
                   .etag("b039efe731ad111bc1b0ef221c3849d0")
-                  .payload(payload(64l, "application/octet-stream"))
+                  .payload(payload(64l, "application/octet-stream", null))
                   .lastModified(dates.iso8601DateParse("2009-02-03T05:26:32.612278")).build(),
             SwiftObject.builder()
                   .name("test obj 3")
                   .uri(URI.create(baseUri + "/test%20obj%203"))
                   .etag("0b2e80bd0744d9ebb20484149a57c82e")
-                  .payload(payload(14, "application/octet-stream"))
+                  .payload(payload(14, "application/octet-stream", new Date()))
                   .lastModified(dates.iso8601DateParse("2014-05-20T05:26:32.612278")).build());
    }
 
@@ -136,7 +139,7 @@ public class ObjectApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
          server.shutdown();
       }
    }
-   
+
    public void testListOptions() throws Exception {
       MockWebServer server = mockOpenStackServer();
       server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
@@ -160,7 +163,8 @@ public class ObjectApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
       server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
       server.enqueue(addCommonHeaders(new MockResponse()
             .setResponseCode(201)
-            .addHeader("ETag", "d9f5eb4bba4e2f2f046e54611bc8196b")));
+            .addHeader("ETag", "d9f5eb4bba4e2f2f046e54611bc8196b"))
+            .addHeader("Expires", "1406243553"));
 
       try {
          SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
@@ -261,7 +265,6 @@ public class ObjectApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
          for (Entry<String, String> entry : object.getMetadata().entrySet()) {
             assertEquals(object.getMetadata().get(entry.getKey().toLowerCase()), entry.getValue());
          }
-         assertEquals(object.getPayload().getContentMetadata().getContentLength(), new Long(4));
          assertEquals(object.getPayload().getContentMetadata().getContentType(), "text/plain; charset=UTF-8");
          assertEquals(toStringAndClose(object.getPayload().openStream()), "");
 
@@ -290,17 +293,19 @@ public class ObjectApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
          for (Entry<String, String> entry : object.getMetadata().entrySet()) {
             assertEquals(object.getMetadata().get(entry.getKey().toLowerCase()), entry.getValue());
          }
-         assertEquals(object.getPayload().getContentMetadata().getContentLength(), new Long(4));
-         assertEquals(object.getPayload().getContentMetadata().getContentType(), "text/plain; charset=UTF-8");
-         // note MWS doesn't process Range header at the moment
-         assertEquals(toStringAndClose(object.getPayload().openStream()), "ABCD");
+
+         Payload payload = object.getPayload();
+         assertEquals(payload.getContentMetadata().getContentLength(), new Long(4));
+         assertEquals(payload.getContentMetadata().getContentType(), "text/plain; charset=UTF-8");
+         assertEquals(payload.getContentMetadata().getExpires(), dates.rfc822DateParse("Wed, 23 Jul 2014 14:00:00 GMT"));
+
+         assertEquals(toStringAndClose(payload.openStream()), "ABCD");
 
          assertEquals(server.getRequestCount(), 2);
          assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1");
          RecordedRequest get = server.takeRequest();
          assertEquals(get.getRequestLine(),
                "GET /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject HTTP/1.1");
-         assertEquals(get.getHeader(RANGE), "bytes=-1");
       } finally {
          server.shutdown();
       }
@@ -320,7 +325,7 @@ public class ObjectApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
          overrides.setProperty(PROPERTY_RETRY_DELAY_START, 0 + ""); // exponential backoff already working for this call. This is the delay BETWEEN attempts.
 
          final SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift", overrides);
-         
+
          api.getObjectApiForRegionAndContainer("DFW", "myContainer").put("myObject", new ByteSourcePayload(ByteSource.wrap("swifty".getBytes())), metadata(metadata));
 
          fail("testReplaceTimeout test should have failed with an HttpResponseException.");
@@ -439,7 +444,7 @@ public class ObjectApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
          server.shutdown();
       }
    }
-   
+
    public void testCopyObject() throws Exception {
       MockWebServer server = mockOpenStackServer();
       server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
@@ -449,10 +454,10 @@ public class ObjectApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
          SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
          assertTrue(api.getObjectApiForRegionAndContainer("DFW", "foo")
             .copy("bar.txt", "bar", "foo.txt"));
-              
+
          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");
@@ -460,23 +465,23 @@ public class ObjectApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
          server.shutdown();
       }
    }
-   
+
    @Test(expectedExceptions = CopyObjectException.class)
    public void testCopyObjectFail() throws InterruptedException, IOException {
       MockWebServer server = mockOpenStackServer();
       server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
       server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)
             .addHeader(SwiftHeaders.OBJECT_COPY_FROM, "/bogus/foo.txt")));
-      
+
       try {
          SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
          // the following line will throw the CopyObjectException
-         api.getObjectApiForRegionAndContainer("DFW", "foo").copy("bar.txt", "bogus", "foo.txt"); 
+         api.getObjectApiForRegionAndContainer("DFW", "foo").copy("bar.txt", "bogus", "foo.txt");
       } finally {
          server.shutdown();
-      }  
+      }
    }
-   
+
    private static final Map<String, String> metadata = ImmutableMap.of("ApiName", "swift", "ApiVersion", "v1.1");
 
    static MockResponse objectResponse() {
@@ -486,13 +491,16 @@ public class ObjectApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
             // TODO: MWS doesn't allow you to return content length w/o content
             // on HEAD!
             .setBody("ABCD".getBytes(US_ASCII))
-            .addHeader("Content-Length", "4").addHeader("Content-Type", "text/plain; charset=UTF-8");
+            .addHeader("Content-Length", "4")
+            .addHeader("Content-Type", "text/plain; charset=UTF-8")
+            .addHeader(EXPIRES, "Wed, 23 Jul 2014 14:00:00 GMT");
    }
 
-   static Payload payload(long bytes, String contentType) {
+   static Payload payload(long bytes, String contentType, Date expires) {
       Payload payload = newByteSourcePayload(ByteSource.empty());
       payload.getContentMetadata().setContentLength(bytes);
       payload.getContentMetadata().setContentType(contentType);
+      payload.getContentMetadata().setExpires(expires);
       return payload;
    }
 }