Repository: jclouds-labs-openstack Updated Branches: refs/heads/1.8.x f21914db4 -> 756d68651
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/756d6865 Tree: http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/tree/756d6865 Diff: http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/diff/756d6865 Branch: refs/heads/1.8.x Commit: 756d686519a6f4480e785b25733c39578254832f Parents: f21914d Author: Jeremy Daggett <[email protected]> Authored: Thu Jul 24 08:45:24 2014 -0700 Committer: Jeremy Daggett <[email protected]> Committed: Thu Sep 11 11:29:56 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/756d6865/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/756d6865/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/756d6865/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/756d6865/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/756d6865/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/756d6865/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/756d6865/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; } }
