diff --git a/apis/atmos/src/main/java/org/jclouds/atmos/domain/internal/DelegatingMutableContentMetadata.java b/apis/atmos/src/main/java/org/jclouds/atmos/domain/internal/DelegatingMutableContentMetadata.java index 15c19538de..041fc22fd7 100644 --- a/apis/atmos/src/main/java/org/jclouds/atmos/domain/internal/DelegatingMutableContentMetadata.java +++ b/apis/atmos/src/main/java/org/jclouds/atmos/domain/internal/DelegatingMutableContentMetadata.java @@ -19,6 +19,7 @@ package org.jclouds.atmos.domain.internal; import java.net.URI; +import java.util.Date; import org.jclouds.atmos.domain.MutableContentMetadata; import org.jclouds.io.ContentMetadataBuilder; @@ -154,17 +155,12 @@ public class DelegatingMutableContentMetadata implements MutableContentMetadata } @Override - public void setPropertiesFromHttpHeaders(Multimap headers) { - delegate.setPropertiesFromHttpHeaders(headers); - } - - @Override - public void setExpires(String expires) { + public void setExpires(Date expires) { delegate.setExpires(expires); } @Override - public String getExpires() { + public Date getExpires() { return delegate.getExpires(); } diff --git a/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/AtmosBlobRequestSignerTest.java b/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/AtmosBlobRequestSignerTest.java index bb1465f68e..7f262289ab 100644 --- a/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/AtmosBlobRequestSignerTest.java +++ b/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/AtmosBlobRequestSignerTest.java @@ -21,6 +21,9 @@ package org.jclouds.atmos.blobstore; import static org.testng.Assert.assertEquals; import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; import org.jclouds.apis.ApiMetadata; import org.jclouds.atmos.AtmosApiMetadata; @@ -32,6 +35,7 @@ import org.jclouds.blobstore.domain.Blob; import org.jclouds.blobstore.domain.Blob.Factory; import org.jclouds.date.TimeStamp; import org.jclouds.http.HttpRequest; +import org.jclouds.io.ContentMetadata; import org.jclouds.rest.ConfiguresRestClient; import org.jclouds.rest.internal.BaseAsyncClientTest; import org.jclouds.rest.internal.RestAnnotationProcessor; @@ -89,7 +93,7 @@ public class AtmosBlobRequestSignerTest extends BaseAsyncClientTest defaultLocation, @Memoized Supplier> locations, - Factory blobFactory, Provider uriBuilders) { + Factory blobFactory, Provider uriBuilders, + ContentMetadataCodec contentMetadataCodec) { super(context, blobUtils, service, defaultLocation, locations); this.blobFactory = blobFactory; this.dateService = dateService; @@ -146,6 +149,7 @@ public class TransientAsyncBlobStore extends BaseAsyncBlobStore { this.httpGetOptionsConverter = httpGetOptionsConverter; this.ifDirectoryReturnName = ifDirectoryReturnName; this.storageStrategy = new TransientStorageStrategy(defaultLocation); + this.contentMetadataCodec = contentMetadataCodec; } /** @@ -521,7 +525,7 @@ public class TransientAsyncBlobStore extends BaseAsyncBlobStore { } private void copyPayloadHeadersToBlob(Payload payload, Blob blob) { - blob.getAllHeaders().putAll(HttpUtils.getContentHeadersFromMetadata(payload.getContentMetadata())); + blob.getAllHeaders().putAll(contentMetadataCodec.toHeaders(payload.getContentMetadata())); } /** diff --git a/blobstore/src/main/java/org/jclouds/blobstore/TransientBlobRequestSigner.java b/blobstore/src/main/java/org/jclouds/blobstore/TransientBlobRequestSigner.java index 3a6767b997..93424e3997 100644 --- a/blobstore/src/main/java/org/jclouds/blobstore/TransientBlobRequestSigner.java +++ b/blobstore/src/main/java/org/jclouds/blobstore/TransientBlobRequestSigner.java @@ -31,6 +31,7 @@ import org.jclouds.blobstore.options.GetOptions; import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpUtils; import org.jclouds.http.filters.BasicAuthentication; +import org.jclouds.io.ContentMetadataCodec; import org.jclouds.location.Provider; import com.google.common.base.Supplier; @@ -45,12 +46,15 @@ public class TransientBlobRequestSigner implements BlobRequestSigner { private final BasicAuthentication basicAuth; private final BlobToHttpGetOptions blob2HttpGetOptions; private final Supplier endpoint; - + private final ContentMetadataCodec contentMetadataCodec; + @Inject - public TransientBlobRequestSigner(BasicAuthentication basicAuth, BlobToHttpGetOptions blob2HttpGetOptions, @Provider Supplier endpoint) { + public TransientBlobRequestSigner(BasicAuthentication basicAuth, BlobToHttpGetOptions blob2HttpGetOptions, @Provider Supplier endpoint, + ContentMetadataCodec contentMetadataCodec) { this.basicAuth = checkNotNull(basicAuth, "basicAuth"); this.blob2HttpGetOptions = checkNotNull(blob2HttpGetOptions, "blob2HttpGetOptions"); this.endpoint = endpoint; + this.contentMetadataCodec = contentMetadataCodec; } @Override @@ -64,7 +68,7 @@ public class TransientBlobRequestSigner implements BlobRequestSigner { HttpRequest request = HttpRequest.builder().method("PUT").endpoint( URI.create(String.format("%s/%s/%s", endpoint.get(), container, blob.getMetadata().getName()))).payload( blob.getPayload()).headers( - HttpUtils.getContentHeadersFromMetadata(blob.getMetadata().getContentMetadata())).build(); + contentMetadataCodec.toHeaders(blob.getMetadata().getContentMetadata())).build(); return basicAuth.filter(request); } diff --git a/blobstore/src/main/java/org/jclouds/blobstore/domain/BlobBuilder.java b/blobstore/src/main/java/org/jclouds/blobstore/domain/BlobBuilder.java index 7f8777ce6a..49a6a903e4 100644 --- a/blobstore/src/main/java/org/jclouds/blobstore/domain/BlobBuilder.java +++ b/blobstore/src/main/java/org/jclouds/blobstore/domain/BlobBuilder.java @@ -21,6 +21,7 @@ package org.jclouds.blobstore.domain; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.Date; import java.util.Map; import org.jclouds.blobstore.domain.internal.BlobBuilderImpl; @@ -117,7 +118,7 @@ public interface BlobBuilder { PayloadBlobBuilder contentEncoding(String contentEncoding); - PayloadBlobBuilder expires(String expires); + PayloadBlobBuilder expires(Date expires); /** * diff --git a/blobstore/src/main/java/org/jclouds/blobstore/domain/internal/BlobBuilderImpl.java b/blobstore/src/main/java/org/jclouds/blobstore/domain/internal/BlobBuilderImpl.java index bc7c255812..7943b5ef47 100644 --- a/blobstore/src/main/java/org/jclouds/blobstore/domain/internal/BlobBuilderImpl.java +++ b/blobstore/src/main/java/org/jclouds/blobstore/domain/internal/BlobBuilderImpl.java @@ -24,6 +24,7 @@ import static org.jclouds.io.Payloads.newPayload; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.Date; import java.util.Map; import javax.inject.Inject; @@ -222,7 +223,7 @@ public class BlobBuilderImpl implements BlobBuilder { } @Override - public PayloadBlobBuilder expires(String expires) { + public PayloadBlobBuilder expires(Date expires) { payload.getContentMetadata().setExpires(expires); return this; } diff --git a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobStoreIntegrationTest.java b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobStoreIntegrationTest.java index c49dd1b6f8..5af7ef47fb 100644 --- a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobStoreIntegrationTest.java +++ b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobStoreIntegrationTest.java @@ -23,6 +23,7 @@ import static org.jclouds.blobstore.util.BlobStoreUtils.getContentAsStringOrNull import static org.testng.Assert.assertEquals; import java.io.IOException; +import java.util.Date; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -338,6 +339,21 @@ public class BaseBlobStoreIntegrationTest extends BaseViewLiveTest getContentHeadersFromMetadata(ContentMetadata md) { - Builder builder = ImmutableMultimap.builder(); - if (md.getContentType() != null) - builder.put(HttpHeaders.CONTENT_TYPE, md.getContentType()); - if (md.getContentDisposition() != null) - builder.put("Content-Disposition", md.getContentDisposition()); - if (md.getContentEncoding() != null) - builder.put(HttpHeaders.CONTENT_ENCODING, md.getContentEncoding()); - if (md.getContentLanguage() != null) - builder.put(HttpHeaders.CONTENT_LANGUAGE, md.getContentLanguage()); - if (md.getContentLength() != null) - builder.put(HttpHeaders.CONTENT_LENGTH, md.getContentLength() + ""); - if (md.getContentMD5() != null) - builder.put("Content-MD5", CryptoStreams.base64(md.getContentMD5())); - if (md.getExpires() != null) - builder.put(HttpHeaders.EXPIRES, md.getExpires()); - return builder.build(); - } - public static byte[] toByteArrayOrNull(PayloadEnclosing response) { if (response.getPayload() != null) { InputStream input = response.getPayload().getInput(); diff --git a/core/src/main/java/org/jclouds/http/internal/BaseHttpCommandExecutorService.java b/core/src/main/java/org/jclouds/http/internal/BaseHttpCommandExecutorService.java index dd23d06607..0e3b530708 100644 --- a/core/src/main/java/org/jclouds/http/internal/BaseHttpCommandExecutorService.java +++ b/core/src/main/java/org/jclouds/http/internal/BaseHttpCommandExecutorService.java @@ -45,6 +45,7 @@ import org.jclouds.http.HttpUtils; import org.jclouds.http.IOExceptionRetryHandler; import org.jclouds.http.handlers.DelegatingErrorHandler; import org.jclouds.http.handlers.DelegatingRetryHandler; +import org.jclouds.io.ContentMetadataCodec; import org.jclouds.logging.Logger; import org.jclouds.util.Throwables2; @@ -56,7 +57,8 @@ import com.google.common.io.NullOutputStream; */ public abstract class BaseHttpCommandExecutorService implements HttpCommandExecutorService { protected final HttpUtils utils; - + protected final ContentMetadataCodec contentMetadataCodec; + private final DelegatingRetryHandler retryHandler; private final IOExceptionRetryHandler ioRetryHandler; private final DelegatingErrorHandler errorHandler; @@ -71,11 +73,12 @@ public abstract class BaseHttpCommandExecutorService implements HttpCommandEx protected final HttpWire wire; @Inject - protected BaseHttpCommandExecutorService(HttpUtils utils, + protected BaseHttpCommandExecutorService(HttpUtils utils, ContentMetadataCodec contentMetadataCodec, @Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioWorkerExecutor, DelegatingRetryHandler retryHandler, IOExceptionRetryHandler ioRetryHandler, DelegatingErrorHandler errorHandler, HttpWire wire) { this.utils = checkNotNull(utils, "utils"); + this.contentMetadataCodec = checkNotNull(contentMetadataCodec, "contentMetadataCodec"); this.retryHandler = checkNotNull(retryHandler, "retryHandler"); this.ioRetryHandler = checkNotNull(ioRetryHandler, "ioRetryHandler"); this.errorHandler = checkNotNull(errorHandler, "errorHandler"); diff --git a/core/src/main/java/org/jclouds/http/internal/JavaUrlHttpCommandExecutorService.java b/core/src/main/java/org/jclouds/http/internal/JavaUrlHttpCommandExecutorService.java index c720d0505e..87a862419d 100644 --- a/core/src/main/java/org/jclouds/http/internal/JavaUrlHttpCommandExecutorService.java +++ b/core/src/main/java/org/jclouds/http/internal/JavaUrlHttpCommandExecutorService.java @@ -54,7 +54,6 @@ import javax.ws.rs.core.HttpHeaders; import org.jclouds.Constants; import org.jclouds.JcloudsVersion; -import org.jclouds.crypto.CryptoStreams; import org.jclouds.http.HttpCommandExecutorService; import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpResponse; @@ -62,6 +61,7 @@ import org.jclouds.http.HttpUtils; import org.jclouds.http.IOExceptionRetryHandler; import org.jclouds.http.handlers.DelegatingErrorHandler; import org.jclouds.http.handlers.DelegatingRetryHandler; +import org.jclouds.io.ContentMetadataCodec; import org.jclouds.io.MutableContentMetadata; import org.jclouds.io.Payload; import org.jclouds.logging.Logger; @@ -90,13 +90,13 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe private final Field methodField; @Inject - public JavaUrlHttpCommandExecutorService(HttpUtils utils, + public JavaUrlHttpCommandExecutorService(HttpUtils utils, ContentMetadataCodec contentMetadataCodec, @Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioWorkerExecutor, DelegatingRetryHandler retryHandler, IOExceptionRetryHandler ioRetryHandler, DelegatingErrorHandler errorHandler, HttpWire wire, @Named("untrusted") HostnameVerifier verifier, @Named("untrusted") Supplier untrustedSSLContextProvider) throws SecurityException, NoSuchFieldException { - super(utils, ioWorkerExecutor, retryHandler, ioRetryHandler, errorHandler, wire); + super(utils, contentMetadataCodec, ioWorkerExecutor, retryHandler, ioRetryHandler, errorHandler, wire); if (utils.getMaxConnections() > 0) System.setProperty("http.maxConnections", String.valueOf(checkNotNull(utils, "utils").getMaxConnections())); this.untrustedSSLContextProvider = checkNotNull(untrustedSSLContextProvider, "untrustedSSLContextProvider"); @@ -136,7 +136,7 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe ImmutableMultimap headers = headerBuilder.build(); if (in != null) { Payload payload = newInputStreamPayload(in); - payload.getContentMetadata().setPropertiesFromHttpHeaders(headers); + contentMetadataCodec.fromHeaders(payload.getContentMetadata(), headers); builder.payload(payload); } builder.headers(RestAnnotationProcessor.filterOutContentHeaders(headers)); @@ -214,18 +214,9 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe if (request.getPayload() != null) { MutableContentMetadata md = request.getPayload().getContentMetadata(); - if (md.getContentMD5() != null) - connection.setRequestProperty("Content-MD5", CryptoStreams.base64(md.getContentMD5())); - if (md.getContentType() != null) - connection.setRequestProperty(HttpHeaders.CONTENT_TYPE, md.getContentType()); - if (md.getContentDisposition() != null) - connection.setRequestProperty("Content-Disposition", md.getContentDisposition()); - if (md.getContentEncoding() != null) - connection.setRequestProperty("Content-Encoding", md.getContentEncoding()); - if (md.getContentLanguage() != null) - connection.setRequestProperty("Content-Language", md.getContentLanguage()); - if (md.getExpires() != null) - connection.setRequestProperty("Expires", md.getExpires()); + for (Map.Entry entry : contentMetadataCodec.toHeaders(md).entries()) { + connection.setRequestProperty(entry.getKey(), entry.getValue()); + } if (chunked) { connection.setChunkedStreamingMode(8196); } else { diff --git a/core/src/main/java/org/jclouds/io/ContentMetadata.java b/core/src/main/java/org/jclouds/io/ContentMetadata.java index f60caea616..e9b9b25c8a 100644 --- a/core/src/main/java/org/jclouds/io/ContentMetadata.java +++ b/core/src/main/java/org/jclouds/io/ContentMetadata.java @@ -20,7 +20,9 @@ package org.jclouds.io; import static javax.ws.rs.core.HttpHeaders.CONTENT_LENGTH; import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE; +import static javax.ws.rs.core.HttpHeaders.EXPIRES; +import java.util.Date; import java.util.Set; import org.jclouds.javax.annotation.Nullable; @@ -32,8 +34,12 @@ import com.google.common.collect.ImmutableSet; */ public interface ContentMetadata { public static final Set HTTP_HEADERS = ImmutableSet.of(CONTENT_LENGTH, "Content-MD5", CONTENT_TYPE, - "Content-Disposition", "Content-Encoding", "Content-Language"); + "Content-Disposition", "Content-Encoding", "Content-Language", EXPIRES); + // See http://stackoverflow.com/questions/10584647/simpledateformat-parse-is-one-hour-out-using-rfc-1123-gmt-in-summer + // for why not using "zzz" + public final static String RFC1123_DATE_PATTERN = "EEE, dd MMM yyyyy HH:mm:ss Z"; + /** * Returns the total size of the payload, or the chunk that's available. *

@@ -89,11 +95,13 @@ public interface ContentMetadata { /** * Gives the date/time after which the response is considered stale. * + * @throws IllegalStateException If the Expires header is non-null, and not a valid RFC 1123 date + * * @see */ @Nullable - String getExpires(); + Date getExpires(); - ContentMetadataBuilder toBuilder(); + ContentMetadataBuilder toBuilder(); } \ No newline at end of file diff --git a/core/src/main/java/org/jclouds/io/ContentMetadataBuilder.java b/core/src/main/java/org/jclouds/io/ContentMetadataBuilder.java index 647f6deda3..eee3531118 100644 --- a/core/src/main/java/org/jclouds/io/ContentMetadataBuilder.java +++ b/core/src/main/java/org/jclouds/io/ContentMetadataBuilder.java @@ -18,21 +18,14 @@ */ package org.jclouds.io; -import static com.google.common.collect.Iterables.any; -import static javax.ws.rs.core.HttpHeaders.CONTENT_LENGTH; -import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE; - import java.io.Serializable; import java.util.Arrays; -import java.util.Map.Entry; +import java.util.Date; -import org.jclouds.crypto.CryptoStreams; import org.jclouds.io.payloads.BaseImmutableContentMetadata; import org.jclouds.javax.annotation.Nullable; import com.google.common.base.Objects; -import com.google.common.base.Predicate; -import com.google.common.collect.Multimap; /** * @author Adrian Cole @@ -51,34 +44,7 @@ public class ContentMetadataBuilder implements Serializable { protected String contentDisposition; protected String contentLanguage; protected String contentEncoding; - protected String expires; - - public ContentMetadataBuilder fromHttpHeaders(Multimap headers) { - boolean chunked = any(headers.entries(), new Predicate>() { - @Override - public boolean apply(Entry input) { - return "Transfer-Encoding".equalsIgnoreCase(input.getKey()) && "chunked".equalsIgnoreCase(input.getValue()); - } - }); - for (Entry header : headers.entries()) { - if (!chunked && CONTENT_LENGTH.equalsIgnoreCase(header.getKey())) { - contentLength(new Long(header.getValue())); - } else if ("Content-MD5".equalsIgnoreCase(header.getKey())) { - contentMD5(CryptoStreams.base64(header.getValue())); - } else if (CONTENT_TYPE.equalsIgnoreCase(header.getKey())) { - contentType(header.getValue()); - } else if ("Content-Disposition".equalsIgnoreCase(header.getKey())) { - contentDisposition(header.getValue()); - } else if ("Content-Encoding".equalsIgnoreCase(header.getKey())) { - contentEncoding(header.getValue()); - } else if ("Content-Language".equalsIgnoreCase(header.getKey())) { - contentLanguage(header.getValue()); - } else if ("Expires".equalsIgnoreCase(header.getKey())) { - expires(header.getValue()); - } - } - return this; - } + protected Date expires; public ContentMetadataBuilder contentLength(@Nullable Long contentLength) { this.contentLength = contentLength; @@ -116,7 +82,7 @@ public class ContentMetadataBuilder implements Serializable { return this; } - public ContentMetadataBuilder expires(@Nullable String expires) { + public ContentMetadataBuilder expires(@Nullable Date expires) { this.expires = expires; return this; } diff --git a/core/src/main/java/org/jclouds/io/ContentMetadataCodec.java b/core/src/main/java/org/jclouds/io/ContentMetadataCodec.java new file mode 100644 index 0000000000..bd9298922a --- /dev/null +++ b/core/src/main/java/org/jclouds/io/ContentMetadataCodec.java @@ -0,0 +1,118 @@ +package org.jclouds.io; + +import static com.google.common.collect.Iterables.any; +import static javax.ws.rs.core.HttpHeaders.CONTENT_LENGTH; +import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE; +import static javax.ws.rs.core.HttpHeaders.EXPIRES; + +import java.text.ParseException; +import java.util.Date; +import java.util.Map.Entry; + +import javax.annotation.Resource; +import javax.ws.rs.core.HttpHeaders; + +import org.jclouds.crypto.CryptoStreams; +import org.jclouds.date.DateCodec; +import org.jclouds.date.DateCodecs; +import org.jclouds.io.ContentMetadataCodec.DefaultContentMetadataCodec; +import org.jclouds.logging.Logger; + +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableMultimap.Builder; +import com.google.common.collect.Multimap; +import com.google.inject.ImplementedBy; + +@ImplementedBy(DefaultContentMetadataCodec.class) +public interface ContentMetadataCodec { + + /** + * Generates standard HTTP headers for the give metadata. + */ + public Multimap toHeaders(ContentMetadata md); + + /** + * Sets properties related to the http headers listed in {@link ContentMetadata#HTTP_HEADERS} + */ + public void fromHeaders(MutableContentMetadata contentMetadata, Multimap headers); + + /** + * Parses the 'Expires' header. + * If invalid, returns a date in the past (in accordance with HTTP 1.1 client spec). + */ + public Date parseExpires(String expires); + + /** + * Default implementation, in accordance with HTTP 1.1 spec. + * + * @author aled + */ + public static class DefaultContentMetadataCodec implements ContentMetadataCodec { + + @Resource + protected Logger logger = Logger.NULL; + + private final DateCodec httpExpiresDateCodec = DateCodecs.rfc1123(); + + protected DateCodec getExpiresDateCodec() { + return httpExpiresDateCodec; + } + + @Override + public Multimap toHeaders(ContentMetadata md) { + Builder builder = ImmutableMultimap.builder(); + if (md.getContentType() != null) + builder.put(HttpHeaders.CONTENT_TYPE, md.getContentType()); + if (md.getContentDisposition() != null) + builder.put("Content-Disposition", md.getContentDisposition()); + if (md.getContentEncoding() != null) + builder.put(HttpHeaders.CONTENT_ENCODING, md.getContentEncoding()); + if (md.getContentLanguage() != null) + builder.put(HttpHeaders.CONTENT_LANGUAGE, md.getContentLanguage()); + if (md.getContentLength() != null) + builder.put(HttpHeaders.CONTENT_LENGTH, md.getContentLength() + ""); + if (md.getContentMD5() != null) + builder.put("Content-MD5", CryptoStreams.base64(md.getContentMD5())); + if (md.getExpires() != null) + builder.put(HttpHeaders.EXPIRES, getExpiresDateCodec().toString(md.getExpires())); + return builder.build(); + } + + @Override + public void fromHeaders(MutableContentMetadata contentMetadata, Multimap headers) { + boolean chunked = any(headers.entries(), new Predicate>() { + @Override + public boolean apply(Entry input) { + return "Transfer-Encoding".equalsIgnoreCase(input.getKey()) && "chunked".equalsIgnoreCase(input.getValue()); + } + }); + for (Entry header : headers.entries()) { + if (!chunked && CONTENT_LENGTH.equalsIgnoreCase(header.getKey())) { + contentMetadata.setContentLength(new Long(header.getValue())); + } else if ("Content-MD5".equalsIgnoreCase(header.getKey())) { + contentMetadata.setContentMD5(CryptoStreams.base64(header.getValue())); + } else if (CONTENT_TYPE.equalsIgnoreCase(header.getKey())) { + contentMetadata.setContentType(header.getValue()); + } else if ("Content-Disposition".equalsIgnoreCase(header.getKey())) { + contentMetadata.setContentDisposition(header.getValue()); + } else if ("Content-Encoding".equalsIgnoreCase(header.getKey())) { + contentMetadata.setContentEncoding(header.getValue()); + } else if ("Content-Language".equalsIgnoreCase(header.getKey())) { + contentMetadata.setContentLanguage(header.getValue()); + } else if (EXPIRES.equalsIgnoreCase(header.getKey())) { + contentMetadata.setExpires(parseExpires(header.getValue())); + } + } + } + + public Date parseExpires(String expires) { + try { + return (expires != null) ? getExpiresDateCodec().toDate(expires) : null; + } catch (ParseException e) { + logger.warn(e, "Invalid Expires header (%s); should be in RFC-1123 format; treating as already expired", expires); + return new Date(0); + } + } + } +} diff --git a/core/src/main/java/org/jclouds/io/MutableContentMetadata.java b/core/src/main/java/org/jclouds/io/MutableContentMetadata.java index e9e0a36901..5e8d91782e 100644 --- a/core/src/main/java/org/jclouds/io/MutableContentMetadata.java +++ b/core/src/main/java/org/jclouds/io/MutableContentMetadata.java @@ -18,6 +18,8 @@ */ package org.jclouds.io; +import java.util.Date; + import org.jclouds.javax.annotation.Nullable; import com.google.common.collect.Multimap; @@ -26,12 +28,6 @@ import com.google.common.collect.Multimap; * @author Adrian Cole */ public interface MutableContentMetadata extends ContentMetadata { - /** - * sets properties related to the http headers listed in - * {@link ContentMetadata#HTTP_HEADERS} - * - */ - void setPropertiesFromHttpHeaders(Multimap headers); void setContentLength(@Nullable Long contentLength); @@ -66,5 +62,5 @@ public interface MutableContentMetadata extends ContentMetadata { */ void setContentEncoding(@Nullable String contentEncoding); - void setExpires(@Nullable String expires); + void setExpires(@Nullable Date expires); } \ No newline at end of file diff --git a/core/src/main/java/org/jclouds/io/payloads/BaseImmutableContentMetadata.java b/core/src/main/java/org/jclouds/io/payloads/BaseImmutableContentMetadata.java index c781366e1f..a54e966c7f 100644 --- a/core/src/main/java/org/jclouds/io/payloads/BaseImmutableContentMetadata.java +++ b/core/src/main/java/org/jclouds/io/payloads/BaseImmutableContentMetadata.java @@ -19,7 +19,10 @@ package org.jclouds.io.payloads; import java.io.Serializable; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.Date; import org.jclouds.io.ContentMetadata; import org.jclouds.io.ContentMetadataBuilder; @@ -39,10 +42,10 @@ public class BaseImmutableContentMetadata implements ContentMetadata, Serializab protected String contentDisposition; protected String contentLanguage; protected String contentEncoding; - protected String expires; + protected Date expires; public BaseImmutableContentMetadata(String contentType, Long contentLength, byte[] contentMD5, - String contentDisposition, String contentLanguage, String contentEncoding, String expires) { + String contentDisposition, String contentLanguage, String contentEncoding, Date expires) { this.contentType = contentType; this.contentLength = contentLength; this.contentMD5 = contentMD5; @@ -110,8 +113,8 @@ public class BaseImmutableContentMetadata implements ContentMetadata, Serializab * {@inheritDoc} */ @Override - public String getExpires() { - return this.expires; + public Date getExpires() { + return expires; } @Override diff --git a/core/src/main/java/org/jclouds/io/payloads/BaseMutableContentMetadata.java b/core/src/main/java/org/jclouds/io/payloads/BaseMutableContentMetadata.java index 50c32e878a..ac2045e966 100644 --- a/core/src/main/java/org/jclouds/io/payloads/BaseMutableContentMetadata.java +++ b/core/src/main/java/org/jclouds/io/payloads/BaseMutableContentMetadata.java @@ -19,6 +19,7 @@ package org.jclouds.io.payloads; import java.io.Serializable; +import java.util.Date; import org.jclouds.io.ContentMetadata; import org.jclouds.io.ContentMetadataBuilder; @@ -34,11 +35,6 @@ public class BaseMutableContentMetadata extends ContentMetadataBuilder implement /** The serialVersionUID */ private static final long serialVersionUID = 8364286391963469370L; - @Override - public void setPropertiesFromHttpHeaders(Multimap headers) { - fromHttpHeaders(headers); - } - /** * {@inheritDoc} */ @@ -145,7 +141,7 @@ public class BaseMutableContentMetadata extends ContentMetadataBuilder implement * {@inheritDoc} */ @Override - public void setExpires(@Nullable String expires) { + public void setExpires(@Nullable Date expires) { expires(expires); } @@ -153,8 +149,8 @@ public class BaseMutableContentMetadata extends ContentMetadataBuilder implement * {@inheritDoc} */ @Override - public String getExpires() { - return this.expires; + public Date getExpires() { + return expires; } @Override diff --git a/core/src/main/java/org/jclouds/rest/internal/RestAnnotationProcessor.java b/core/src/main/java/org/jclouds/rest/internal/RestAnnotationProcessor.java index 0d4a878f78..30478ebd0a 100644 --- a/core/src/main/java/org/jclouds/rest/internal/RestAnnotationProcessor.java +++ b/core/src/main/java/org/jclouds/rest/internal/RestAnnotationProcessor.java @@ -89,6 +89,7 @@ import org.jclouds.http.options.HttpRequestOptions; import org.jclouds.http.utils.ModifyRequest; import org.jclouds.internal.ClassMethodArgs; import org.jclouds.io.ContentMetadata; +import org.jclouds.io.ContentMetadataCodec; import org.jclouds.io.Payload; import org.jclouds.io.PayloadEnclosing; import org.jclouds.io.Payloads; @@ -255,6 +256,7 @@ public class RestAnnotationProcessor { private final ParseSax.Factory parserFactory; private final HttpUtils utils; + private final ContentMetadataCodec contentMetadataCodec; private final Provider uriBuilderProvider; private final LoadingCache, Boolean> seedAnnotationCache; private final String apiVersion; @@ -321,11 +323,12 @@ public class RestAnnotationProcessor { @Inject public RestAnnotationProcessor(Injector injector, LoadingCache, Boolean> seedAnnotationCache, @ApiVersion String apiVersion, @BuildVersion String buildVersion, ParseSax.Factory parserFactory, - HttpUtils utils, TypeLiteral typeLiteral) throws ExecutionException { + HttpUtils utils, ContentMetadataCodec contentMetadataCodec, TypeLiteral typeLiteral) throws ExecutionException { this.declaring = (Class) typeLiteral.getRawType(); this.injector = injector; this.parserFactory = parserFactory; this.utils = utils; + this.contentMetadataCodec = contentMetadataCodec; this.uriBuilderProvider = injector.getProvider(UriBuilder.class); this.seedAnnotationCache = seedAnnotationCache; seedAnnotationCache.get(declaring); @@ -545,8 +548,9 @@ public class RestAnnotationProcessor { request = decorateRequest(request); } - if (request.getPayload() != null) - request.getPayload().getContentMetadata().setPropertiesFromHttpHeaders(headers); + if (request.getPayload() != null) { + contentMetadataCodec.fromHeaders(request.getPayload().getContentMetadata(), headers); + } utils.checkRequestHasRequiredProperties(request); return request; } catch (ExecutionException e) { diff --git a/core/src/test/java/org/jclouds/http/handlers/BackoffLimitedRetryHandlerTest.java b/core/src/test/java/org/jclouds/http/handlers/BackoffLimitedRetryHandlerTest.java index 2c9ac3d409..7f18803f93 100644 --- a/core/src/test/java/org/jclouds/http/handlers/BackoffLimitedRetryHandlerTest.java +++ b/core/src/test/java/org/jclouds/http/handlers/BackoffLimitedRetryHandlerTest.java @@ -44,7 +44,9 @@ import org.jclouds.http.TransformingHttpCommandImpl; import org.jclouds.http.functions.ReturnStringIf2xx; import org.jclouds.http.internal.HttpWire; import org.jclouds.http.internal.JavaUrlHttpCommandExecutorService; +import org.jclouds.io.ContentMetadataCodec; import org.jclouds.io.Payloads; +import org.jclouds.io.ContentMetadataCodec.DefaultContentMetadataCodec; import org.jclouds.rest.internal.RestAnnotationProcessor; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -105,15 +107,16 @@ public class BackoffLimitedRetryHandlerTest { } }; - private HttpUtils utils; @BeforeTest void setupExecutorService() throws Exception { ExecutorService execService = Executors.newCachedThreadPool(); BackoffLimitedRetryHandler backoff = new BackoffLimitedRetryHandler(); - utils = new HttpUtils(0, 500, 1, 1); + HttpUtils utils = new HttpUtils(0, 500, 1, 1); + ContentMetadataCodec contentMetadataCodec = new DefaultContentMetadataCodec(); RedirectionRetryHandler retry = new RedirectionRetryHandler(uriBuilderProvider, backoff); - JavaUrlHttpCommandExecutorService httpService = new JavaUrlHttpCommandExecutorService(utils, execService, + JavaUrlHttpCommandExecutorService httpService = new JavaUrlHttpCommandExecutorService(utils, + contentMetadataCodec, execService, new DelegatingRetryHandler(backoff, retry), new BackoffLimitedRetryHandler(), new DelegatingErrorHandler(), new HttpWire(), new HostnameVerifier() { diff --git a/core/src/test/java/org/jclouds/http/internal/TrackingJavaUrlHttpCommandExecutorService.java b/core/src/test/java/org/jclouds/http/internal/TrackingJavaUrlHttpCommandExecutorService.java index db0dba6196..44c55b1f07 100644 --- a/core/src/test/java/org/jclouds/http/internal/TrackingJavaUrlHttpCommandExecutorService.java +++ b/core/src/test/java/org/jclouds/http/internal/TrackingJavaUrlHttpCommandExecutorService.java @@ -37,6 +37,7 @@ import org.jclouds.http.HttpUtils; import org.jclouds.http.IOExceptionRetryHandler; import org.jclouds.http.handlers.DelegatingErrorHandler; import org.jclouds.http.handlers.DelegatingRetryHandler; +import org.jclouds.io.ContentMetadataCodec; import org.jclouds.rest.internal.GeneratedHttpRequest; import com.google.common.base.Supplier; @@ -86,13 +87,13 @@ public class TrackingJavaUrlHttpCommandExecutorService extends JavaUrlHttpComman } @Inject - public TrackingJavaUrlHttpCommandExecutorService(HttpUtils utils, + public TrackingJavaUrlHttpCommandExecutorService(HttpUtils utils, ContentMetadataCodec contentMetadataCodec, @Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioWorkerExecutor, DelegatingRetryHandler retryHandler, IOExceptionRetryHandler ioRetryHandler, DelegatingErrorHandler errorHandler, HttpWire wire, @Named("untrusted") HostnameVerifier verifier, @Named("untrusted") Supplier untrustedSSLContextProvider, List commandsInvoked) throws SecurityException, NoSuchFieldException { - super(utils, ioWorkerExecutor, retryHandler, ioRetryHandler, errorHandler, wire, verifier, + super(utils, contentMetadataCodec, ioWorkerExecutor, retryHandler, ioRetryHandler, errorHandler, wire, verifier, untrustedSSLContextProvider); this.commandsInvoked = commandsInvoked; } diff --git a/core/src/test/java/org/jclouds/rest/internal/BaseRestClientExpectTest.java b/core/src/test/java/org/jclouds/rest/internal/BaseRestClientExpectTest.java index d90abf86fa..45f43a2fb4 100644 --- a/core/src/test/java/org/jclouds/rest/internal/BaseRestClientExpectTest.java +++ b/core/src/test/java/org/jclouds/rest/internal/BaseRestClientExpectTest.java @@ -25,9 +25,9 @@ import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Properties; -import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicInteger; @@ -59,9 +59,11 @@ import org.jclouds.http.handlers.DelegatingErrorHandler; import org.jclouds.http.handlers.DelegatingRetryHandler; import org.jclouds.http.internal.BaseHttpCommandExecutorService; import org.jclouds.http.internal.HttpWire; +import org.jclouds.io.ContentMetadataCodec; import org.jclouds.io.CopyInputStreamInputSupplierMap; import org.jclouds.io.Payload; import org.jclouds.io.Payloads; +import org.jclouds.io.ContentMetadataCodec.DefaultContentMetadataCodec; import org.jclouds.logging.config.NullLoggingModule; import org.jclouds.providers.ProviderMetadata; import org.jclouds.rest.RestApiMetadata; @@ -118,6 +120,8 @@ public abstract class BaseRestClientExpectTest { protected String provider = "mock"; + protected ContentMetadataCodec contentMetadataCodec = new DefaultContentMetadataCodec(); + /** * Override this to supply alternative bindings for use in the test. This is commonly used to * override suppliers of dates so that the test results are predicatable. @@ -188,10 +192,11 @@ public abstract class BaseRestClientExpectTest { @Inject public ExpectHttpCommandExecutorService(Function fn, HttpUtils utils, + ContentMetadataCodec contentMetadataCodec, @Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioExecutor, IOExceptionRetryHandler ioRetryHandler, DelegatingRetryHandler retryHandler, DelegatingErrorHandler errorHandler, HttpWire wire) { - super(utils, ioExecutor, retryHandler, ioRetryHandler, errorHandler, wire); + super(utils, contentMetadataCodec, ioExecutor, retryHandler, ioRetryHandler, errorHandler, wire); this.fn = checkNotNull(fn, "fn"); } @@ -471,7 +476,7 @@ public abstract class BaseRestClientExpectTest { builder.append(header.getKey()).append(": ").append(header.getValue()).append('\n'); } if (request.getPayload() != null) { - for (Entry header : HttpUtils.getContentHeadersFromMetadata( + for (Entry header : contentMetadataCodec.toHeaders( request.getPayload().getContentMetadata()).entries()) { builder.append(header.getKey()).append(": ").append(header.getValue()).append('\n'); } diff --git a/core/src/test/java/org/jclouds/rest/internal/BaseRestClientTest.java b/core/src/test/java/org/jclouds/rest/internal/BaseRestClientTest.java index ad6d5c5f1b..4cbca9ffc5 100644 --- a/core/src/test/java/org/jclouds/rest/internal/BaseRestClientTest.java +++ b/core/src/test/java/org/jclouds/rest/internal/BaseRestClientTest.java @@ -28,6 +28,7 @@ import static org.testng.Assert.assertNull; import java.io.IOException; import java.lang.reflect.Method; +import java.util.Date; import java.util.concurrent.ExecutorService; import org.jclouds.Constants; @@ -42,7 +43,6 @@ import org.jclouds.http.functions.ParseSax; import org.jclouds.io.MutableContentMetadata; import org.jclouds.javax.annotation.Nullable; import org.jclouds.rest.functions.MapHttp4xxCodesToExceptions; -import org.jclouds.rest.internal.RestAnnotationProcessor; import org.jclouds.util.Strings2; import org.testng.annotations.Test; @@ -89,7 +89,7 @@ public abstract class BaseRestClientTest { assertPayloadEquals(request, toMatch, contentType, contentMD5, null); } - protected void assertPayloadEquals(HttpRequest request, String toMatch, String contentType, boolean contentMD5, String expires) { + protected void assertPayloadEquals(HttpRequest request, String toMatch, String contentType, boolean contentMD5, Date expires) { assertPayloadEquals(request, toMatch, contentType, null, null, null, contentMD5, expires); } @@ -101,7 +101,7 @@ public abstract class BaseRestClientTest { protected void assertPayloadEquals(HttpRequest request, String toMatch, String contentType, String contentDispositon, String contentEncoding, String contentLanguage, boolean contentMD5, - String expires) { + Date expires) { if (request.getPayload() == null) { assertNull(toMatch); } else { @@ -123,7 +123,7 @@ public abstract class BaseRestClientTest { } protected void assertContentHeadersEqual(HttpRequest request, String contentType, String contentDispositon, - String contentEncoding, String contentLanguage, Long length, byte[] contentMD5, String expires) { + String contentEncoding, String contentLanguage, Long length, byte[] contentMD5, Date expires) { MutableContentMetadata md = request.getPayload().getContentMetadata(); if (request.getFirstHeaderOrNull(TRANSFER_ENCODING) == null) { assertEquals(md.getContentLength(), length); diff --git a/drivers/apachehc/src/main/java/org/jclouds/http/apachehc/ApacheHCHttpCommandExecutorService.java b/drivers/apachehc/src/main/java/org/jclouds/http/apachehc/ApacheHCHttpCommandExecutorService.java index 6d1ba0c7e2..ac1b99a464 100644 --- a/drivers/apachehc/src/main/java/org/jclouds/http/apachehc/ApacheHCHttpCommandExecutorService.java +++ b/drivers/apachehc/src/main/java/org/jclouds/http/apachehc/ApacheHCHttpCommandExecutorService.java @@ -39,6 +39,7 @@ import org.jclouds.http.handlers.DelegatingErrorHandler; import org.jclouds.http.handlers.DelegatingRetryHandler; import org.jclouds.http.internal.BaseHttpCommandExecutorService; import org.jclouds.http.internal.HttpWire; +import org.jclouds.io.ContentMetadataCodec; import org.jclouds.io.Payload; import org.jclouds.io.Payloads; import org.jclouds.rest.internal.RestAnnotationProcessor; @@ -55,19 +56,21 @@ import com.google.inject.Inject; */ public class ApacheHCHttpCommandExecutorService extends BaseHttpCommandExecutorService { private final HttpClient client; + private final ApacheHCUtils apacheHCUtils; @Inject - ApacheHCHttpCommandExecutorService(HttpUtils utils, + ApacheHCHttpCommandExecutorService(HttpUtils utils, ContentMetadataCodec contentMetadataCodec, @Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioWorkerExecutor, DelegatingRetryHandler retryHandler, IOExceptionRetryHandler ioRetryHandler, DelegatingErrorHandler errorHandler, HttpWire wire, HttpClient client) { - super(utils, ioWorkerExecutor, retryHandler, ioRetryHandler, errorHandler, wire); + super(utils, contentMetadataCodec, ioWorkerExecutor, retryHandler, ioRetryHandler, errorHandler, wire); this.client = client; + this.apacheHCUtils = new ApacheHCUtils(contentMetadataCodec); } @Override protected HttpUriRequest convert(HttpRequest request) throws IOException { - HttpUriRequest returnVal = ApacheHCUtils.convertToApacheRequest(request); + HttpUriRequest returnVal = apacheHCUtils.convertToApacheRequest(request); if (request.getPayload() != null && request.getPayload().getContentMetadata().getContentMD5() != null) returnVal.addHeader("Content-MD5", CryptoStreams.md5Base64(request.getPayload())); return returnVal; @@ -93,8 +96,9 @@ public class ApacheHCHttpCommandExecutorService extends BaseHttpCommandExecutorS for (Header header : apacheResponse.getAllHeaders()) { headers.put(header.getName(), header.getValue()); } - if (payload != null) - payload.getContentMetadata().setPropertiesFromHttpHeaders(headers); + if (payload != null) { + contentMetadataCodec.fromHeaders(payload.getContentMetadata(), headers); + } return new HttpResponse(apacheResponse.getStatusLine().getStatusCode(), apacheResponse.getStatusLine() .getReasonPhrase(), payload, RestAnnotationProcessor.filterOutContentHeaders(headers)); } diff --git a/drivers/apachehc/src/main/java/org/jclouds/http/apachehc/ApacheHCUtils.java b/drivers/apachehc/src/main/java/org/jclouds/http/apachehc/ApacheHCUtils.java index 8c0a8375fc..a9d646e756 100644 --- a/drivers/apachehc/src/main/java/org/jclouds/http/apachehc/ApacheHCUtils.java +++ b/drivers/apachehc/src/main/java/org/jclouds/http/apachehc/ApacheHCUtils.java @@ -23,6 +23,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; +import java.util.Map; +import java.util.Set; import javax.inject.Singleton; import javax.ws.rs.HttpMethod; @@ -45,6 +47,8 @@ import org.apache.http.entity.StringEntity; import org.apache.http.params.CoreProtocolPNames; import org.jclouds.JcloudsVersion; import org.jclouds.http.HttpRequest; +import org.jclouds.io.ContentMetadataCodec; +import org.jclouds.io.MutableContentMetadata; import org.jclouds.io.Payload; import org.jclouds.io.payloads.BasePayload; import org.jclouds.io.payloads.ByteArrayPayload; @@ -53,6 +57,7 @@ import org.jclouds.io.payloads.FilePayload; import org.jclouds.io.payloads.StringPayload; import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableSet; /** * @@ -62,8 +67,14 @@ import com.google.common.base.Throwables; public class ApacheHCUtils { //TODO: look up httpclient version public static final String USER_AGENT = String.format("jclouds/%s httpclient/%s", JcloudsVersion.get(), "4.1.1"); + + private final ContentMetadataCodec contentMetadataCodec; - public static HttpUriRequest convertToApacheRequest(HttpRequest request) { + public ApacheHCUtils(ContentMetadataCodec contentMetadataCodec) { + this.contentMetadataCodec = contentMetadataCodec; + } + + public HttpUriRequest convertToApacheRequest(HttpRequest request) { HttpUriRequest apacheRequest; if (request.getMethod().equals(HttpMethod.HEAD)) { apacheRequest = new HttpHead(request.getEndpoint()); @@ -120,7 +131,7 @@ public class ApacheHCUtils { return apacheRequest; } - public static void addEntityForContent(HttpEntityEnclosingRequest apacheRequest, Payload payload) { + public void addEntityForContent(HttpEntityEnclosingRequest apacheRequest, Payload payload) { payload = payload instanceof DelegatingPayload ? DelegatingPayload.class.cast(payload).getDelegate() : payload; if (payload instanceof StringPayload) { StringEntity nStringEntity = null; @@ -142,18 +153,20 @@ public class ApacheHCUtils { InputStream inputStream = payload.getInput(); if (payload.getContentMetadata().getContentLength() == null) throw new IllegalArgumentException("you must specify size when content is an InputStream"); - InputStreamEntity Entity = new InputStreamEntity(inputStream, payload.getContentMetadata().getContentLength()); - Entity.setContentType(payload.getContentMetadata().getContentType()); - apacheRequest.setEntity(Entity); + InputStreamEntity entity = new InputStreamEntity(inputStream, payload.getContentMetadata().getContentLength()); + entity.setContentType(payload.getContentMetadata().getContentType()); + apacheRequest.setEntity(entity); } - if (payload.getContentMetadata().getContentDisposition() != null) - apacheRequest.addHeader("Content-Disposition", payload.getContentMetadata().getContentDisposition()); - if (payload.getContentMetadata().getContentEncoding() != null) - apacheRequest.addHeader("Content-Encoding", payload.getContentMetadata().getContentEncoding()); - if (payload.getContentMetadata().getContentLanguage() != null) - apacheRequest.addHeader("Content-Language", payload.getContentMetadata().getContentLanguage()); - if (payload.getContentMetadata().getExpires() != null) - apacheRequest.addHeader("Expires", payload.getContentMetadata().getExpires()); + + // TODO Reproducing old behaviour exactly; ignoring Content-Type, Content-Length and Content-MD5 + Set desiredHeaders = ImmutableSet.of("Content-Disposition", "Content-Encoding", "Content-Language", "Expires"); + MutableContentMetadata md = payload.getContentMetadata(); + for (Map.Entry entry : contentMetadataCodec.toHeaders(md).entries()) { + if (desiredHeaders.contains(entry.getKey())) { + apacheRequest.addHeader(entry.getKey(), entry.getValue()); + } + } + assert (apacheRequest.getEntity() != null); } diff --git a/drivers/gae/src/main/java/org/jclouds/gae/ConvertToGaeRequest.java b/drivers/gae/src/main/java/org/jclouds/gae/ConvertToGaeRequest.java index b681d76b2d..b6c6c608a3 100644 --- a/drivers/gae/src/main/java/org/jclouds/gae/ConvertToGaeRequest.java +++ b/drivers/gae/src/main/java/org/jclouds/gae/ConvertToGaeRequest.java @@ -34,6 +34,7 @@ import javax.ws.rs.core.HttpHeaders; import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpUtils; +import org.jclouds.io.ContentMetadataCodec; import org.jclouds.io.Payload; import com.google.appengine.api.urlfetch.FetchOptions; @@ -52,14 +53,17 @@ import com.google.common.io.Closeables; @Singleton public class ConvertToGaeRequest implements Function { public static final String USER_AGENT = "jclouds/1.0 urlfetch/1.4.3"; - protected final HttpUtils utils; // http://code.google.com/appengine/docs/java/urlfetch/overview.html public final Set prohibitedHeaders = ImmutableSet.of("Accept-Encoding", "Content-Length", "Host", "Var", "X-Forwarded-For"); + protected final HttpUtils utils; + protected final ContentMetadataCodec contentMetadataCodec; + @Inject - ConvertToGaeRequest(HttpUtils utils) { + ConvertToGaeRequest(HttpUtils utils, ContentMetadataCodec contentMetadataCodec) { this.utils = utils; + this.contentMetadataCodec = contentMetadataCodec; } /** @@ -115,7 +119,7 @@ public class ConvertToGaeRequest implements Function { Closeables.closeQuietly(input); } - for (Entry header : HttpUtils.getContentHeadersFromMetadata( + for (Entry header : contentMetadataCodec.toHeaders( request.getPayload().getContentMetadata()).entries()) { if (!prohibitedHeaders.contains(header.getKey())) gaeRequest.setHeader(new HTTPHeader(header.getKey(), header.getValue())); diff --git a/drivers/gae/src/main/java/org/jclouds/gae/ConvertToJcloudsResponse.java b/drivers/gae/src/main/java/org/jclouds/gae/ConvertToJcloudsResponse.java index f30a3d7181..7ff83988e5 100644 --- a/drivers/gae/src/main/java/org/jclouds/gae/ConvertToJcloudsResponse.java +++ b/drivers/gae/src/main/java/org/jclouds/gae/ConvertToJcloudsResponse.java @@ -21,6 +21,7 @@ package org.jclouds.gae; import javax.inject.Singleton; import org.jclouds.http.HttpResponse; +import org.jclouds.io.ContentMetadataCodec; import org.jclouds.io.Payload; import org.jclouds.io.Payloads; import org.jclouds.rest.internal.RestAnnotationProcessor; @@ -30,6 +31,7 @@ import com.google.appengine.api.urlfetch.HTTPResponse; import com.google.common.base.Function; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; +import com.google.inject.Inject; /** * @@ -38,6 +40,13 @@ import com.google.common.collect.Multimap; @Singleton public class ConvertToJcloudsResponse implements Function { + private final ContentMetadataCodec contentMetadataCodec; + + @Inject + public ConvertToJcloudsResponse(ContentMetadataCodec contentMetadataCodec) { + this.contentMetadataCodec = contentMetadataCodec; + } + @Override public HttpResponse apply(HTTPResponse gaeResponse) { Payload payload = gaeResponse.getContent() != null ? Payloads.newByteArrayPayload(gaeResponse.getContent()) @@ -51,8 +60,9 @@ public class ConvertToJcloudsResponse implements Function>>>>>> Issue-647: added "Expires" header for ContentMetadata +import org.jclouds.blobstore.BlobStoreContext; +import org.jclouds.date.DateCodec; +import org.jclouds.date.DateCodecs; +import org.jclouds.http.HttpCommandExecutorService; +import org.jclouds.http.TransformingHttpCommandExecutorService; +import org.jclouds.http.TransformingHttpCommandExecutorServiceImpl; +import org.jclouds.http.config.SSLModule; +import org.jclouds.http.internal.JavaUrlHttpCommandExecutorService; +import org.jclouds.io.ContentMetadataCodec; +import org.jclouds.io.ContentMetadataCodec.DefaultContentMetadataCodec; import org.jclouds.s3.blobstore.integration.S3ContainerLiveTest; import org.jclouds.util.Strings2; import org.testng.annotations.Test; -<<<<<<< HEAD import com.google.common.base.Strings; -======= import com.google.common.base.Throwables; ->>>>>>> Issue-647: added "Expires" header for ContentMetadata +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.inject.AbstractModule; +import com.google.inject.Module; +import com.google.inject.Scopes; /** * @author Adrian Cole @@ -63,23 +73,57 @@ public class AWSS3ContainerLiveTest extends S3ContainerLiveTest { final String containerName = getScratchContainerName(); BlobStore blobStore = view.getBlobStore(); try { - final String RFC1123_PATTERN = "EEE, dd MMM yyyyy HH:mm:ss z"; final String blobName = "hello"; - final String expires = new SimpleDateFormat(RFC1123_PATTERN).format(new Date(System.currentTimeMillis()+(60*1000))); + final Date expires = new Date( (System.currentTimeMillis() / 1000) * 1000 + 60*1000); blobStore.createContainerInLocation(null, containerName, publicRead()); blobStore.putBlob(containerName, blobStore.blobBuilder(blobName).payload(TEST_STRING).expires(expires).build()); - assertConsistencyAware(new Runnable() { - public void run() { - try { - String actualExpires = view.getBlobStore().getBlob(containerName, blobName).getPayload().getContentMetadata().getExpires(); - assert expires.equals(actualExpires) : "expires="+actualExpires+"; expected="+expires; - } catch (Exception e) { - Throwables.propagate(e); + assertConsistencyAwareBlobExpiryMetadata(containerName, blobName, expires); + + } finally { + recycleContainer(containerName); + } + } + + @Test(groups = { "live" }) + public void testCreateBlobWithMalformedExpiry() throws InterruptedException, MalformedURLException, IOException { + // Create a blob that has a malformed Expires value; requires overriding the ContentMetadataCodec in Guice... + final ContentMetadataCodec contentMetadataCodec = new DefaultContentMetadataCodec() { + @Override + protected DateCodec getExpiresDateCodec() { + return new DateCodec() { + @Override public Date toDate(String date) throws ParseException { + return DateCodecs.rfc1123().toDate(date); } - } - }); + @Override public String toString(Date date) { + return "wrong"; + } + }; + } + }; + + Module customModule = new AbstractModule() { + @Override + protected void configure() { + bind(ContentMetadataCodec.class).toInstance(contentMetadataCodec); + } + }; + + Iterable modules = Iterables.concat(setupModules(), ImmutableList.of(customModule)); + BlobStoreContext naughtyBlobStoreContext = createView(setupProperties(), modules); + BlobStore naughtyBlobStore = naughtyBlobStoreContext.getBlobStore(); + + final String containerName = getScratchContainerName(); + + try { + final String blobName = "hello"; + + naughtyBlobStore.createContainerInLocation(null, containerName, publicRead()); + naughtyBlobStore.putBlob(containerName, naughtyBlobStore.blobBuilder(blobName) + .payload(TEST_STRING).expires(new Date(System.currentTimeMillis() + 60*1000)).build()); + + assertConsistencyAwareBlobExpiryMetadata(containerName, blobName, new Date(0)); } finally { recycleContainer(containerName); diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/domain/internal/BlobPropertiesImpl.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/domain/internal/BlobPropertiesImpl.java index cd770fbba8..4b64ee5788 100644 --- a/providers/azureblob/src/main/java/org/jclouds/azureblob/domain/internal/BlobPropertiesImpl.java +++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/domain/internal/BlobPropertiesImpl.java @@ -55,7 +55,7 @@ public class BlobPropertiesImpl implements Serializable, BlobProperties { public BlobPropertiesImpl(BlobType type, String name, String container, URI url, Date lastModified, String eTag, long size, String contentType, @Nullable byte[] contentMD5, @Nullable String contentMetadata, - @Nullable String contentLanguage, @Nullable String currentExpires, LeaseStatus leaseStatus, + @Nullable String contentLanguage, @Nullable Date currentExpires, LeaseStatus leaseStatus, Map metadata) { this.type = checkNotNull(type, "type"); this.leaseStatus = checkNotNull(leaseStatus, "leaseStatus"); diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/xml/ContainerNameEnumerationResultsHandler.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/xml/ContainerNameEnumerationResultsHandler.java index c297847f35..60816013c3 100644 --- a/providers/azureblob/src/main/java/org/jclouds/azureblob/xml/ContainerNameEnumerationResultsHandler.java +++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/xml/ContainerNameEnumerationResultsHandler.java @@ -35,6 +35,7 @@ import org.jclouds.crypto.CryptoStreams; import org.jclouds.date.DateService; import org.jclouds.http.HttpUtils; import org.jclouds.http.functions.ParseSax; +import org.jclouds.io.ContentMetadataCodec; import org.xml.sax.Attributes; import org.xml.sax.SAXException; @@ -63,6 +64,7 @@ public class ContainerNameEnumerationResultsHandler extends ParseSax.HandlerWith private StringBuilder currentText = new StringBuilder(); private final DateService dateParser; + private final ContentMetadataCodec contentMetadataCodec; private String delimiter; private String currentName; private long currentSize; @@ -70,7 +72,7 @@ public class ContainerNameEnumerationResultsHandler extends ParseSax.HandlerWith private String currentContentEncoding; private String currentContentLanguage; private BlobType currentBlobType; - private String currentExpires; + private Date currentExpires; private boolean inBlob; private boolean inBlobPrefix; private boolean inMetadata; @@ -80,8 +82,9 @@ public class ContainerNameEnumerationResultsHandler extends ParseSax.HandlerWith private LeaseStatus currentLeaseStatus; @Inject - public ContainerNameEnumerationResultsHandler(DateService dateParser) { + public ContainerNameEnumerationResultsHandler(DateService dateParser, ContentMetadataCodec contentMetadataCodec) { this.dateParser = dateParser; + this.contentMetadataCodec = contentMetadataCodec; } public ListBlobsResponse getResult() { @@ -175,9 +178,12 @@ public class ContainerNameEnumerationResultsHandler extends ParseSax.HandlerWith if (currentContentLanguage.equals("")) currentContentLanguage = null; } else if (qName.equals("Expires")) { - currentExpires = currentText.toString().trim(); - if (currentExpires.equals("")) - currentExpires= null; + String trimmedCurrentText = currentText.toString().trim(); + if (trimmedCurrentText.equals("")) { + currentExpires = null; + } else { + currentExpires = contentMetadataCodec.parseExpires(trimmedCurrentText); + } } currentText = new StringBuilder(); } diff --git a/providers/azureblob/src/test/java/org/jclouds/azureblob/blobstore/AzureBlobRequestSignerTest.java b/providers/azureblob/src/test/java/org/jclouds/azureblob/blobstore/AzureBlobRequestSignerTest.java index 4ee8a121b1..947aacf25d 100644 --- a/providers/azureblob/src/test/java/org/jclouds/azureblob/blobstore/AzureBlobRequestSignerTest.java +++ b/providers/azureblob/src/test/java/org/jclouds/azureblob/blobstore/AzureBlobRequestSignerTest.java @@ -21,6 +21,7 @@ package org.jclouds.azureblob.blobstore; import static org.testng.Assert.assertEquals; import java.io.IOException; +import java.util.Date; import org.jclouds.azureblob.AzureBlobAsyncClient; import org.jclouds.azureblob.AzureBlobProviderMetadata; @@ -86,7 +87,7 @@ public class AzureBlobRequestSignerTest extends BaseAsyncClientTest