Issue 647: store Expires as Date; added ContentMetadataCodec for converting to/from HTTP headers

This commit is contained in:
Aled Sage 2012-05-14 14:57:56 +01:00
parent 9aedf7b6f6
commit cd9c830c5a
36 changed files with 431 additions and 186 deletions

View File

@ -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<String, String> 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();
}

View File

@ -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<AtmosAsyncCl
blob.getPayload().getContentMetadata().setContentLength(2l);
blob.getPayload().getContentMetadata().setContentMD5(new byte[] { 0, 2, 4, 8 });
blob.getPayload().getContentMetadata().setContentType("text/plain");
blob.getPayload().getContentMetadata().setExpires("Thu, 01 Dec 1994 16:00:00 GMT");
blob.getPayload().getContentMetadata().setExpires(new Date(1000));
HttpRequest request = signer.signPutBlob("container", blob);
@ -99,7 +103,7 @@ public class AtmosBlobRequestSignerTest extends BaseAsyncClientTest<AtmosAsyncCl
request,
"Accept: */*\nDate: Thu, 05 Jun 2008 16:38:19 GMT\nx-emc-signature: aLpB1oQaCA27AXT6Nzam7s0f0pI=\nx-emc-uid: identity\n");
assertContentHeadersEqual(request, "text/plain", null, null, null, (long) 2l, new byte[] { 0, 2, 4, 8 }, "Thu, 01 Dec 1994 16:00:00 GMT");
assertContentHeadersEqual(request, "text/plain", null, null, null, (long) 2l, new byte[] { 0, 2, 4, 8 }, new Date(1000));
assertEquals(request.getFilters().size(), 0);
}

View File

@ -21,6 +21,7 @@ package org.jclouds.s3.blobstore;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.util.Date;
import javax.inject.Provider;
@ -89,7 +90,7 @@ public class S3BlobRequestSignerTest extends BaseS3AsyncClientTest<S3AsyncClient
public void testSignPutBlob() throws ArrayIndexOutOfBoundsException, SecurityException, IllegalArgumentException,
NoSuchMethodException, IOException {
Blob blob = blobFactory.get().name("name").forSigning().contentLength(2l).contentMD5(new byte[] { 0, 2, 4, 8 }).contentType(
"text/plain").expires("Thu, 01 Dec 1994 16:00:00 GMT").build();
"text/plain").expires(new Date(1000)).build();
HttpRequest request = signer.signPutBlob("container", blob);
@ -98,7 +99,7 @@ public class S3BlobRequestSignerTest extends BaseS3AsyncClientTest<S3AsyncClient
request,
"Authorization: AWS identity:j9Dy/lmmvlCKjA4lkqZenLxMkR4=\nDate: Thu, 05 Jun 2008 16:38:19 GMT\nHost: container.s3.amazonaws.com\n");
assertContentHeadersEqual(request, "text/plain", null, null, null, (long) 2l, new byte[] { 0, 2, 4, 8 }, "Thu, 01 Dec 1994 16:00:00 GMT");
assertContentHeadersEqual(request, "text/plain", null, null, null, (long) 2l, new byte[] { 0, 2, 4, 8 }, new Date(1000));
assertEquals(request.getFilters().size(), 0);
}

View File

@ -21,6 +21,7 @@ package org.jclouds.openstack.swift.blobstore;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.util.Date;
import org.jclouds.blobstore.BlobRequestSigner;
import org.jclouds.blobstore.domain.Blob;
@ -72,13 +73,13 @@ public class SwiftBlobRequestSignerTest extends CommonSwiftClientTest {
blob.getPayload().getContentMetadata().setContentLength(2l);
blob.getPayload().getContentMetadata().setContentMD5(new byte[] { 0, 2, 4, 8 });
blob.getPayload().getContentMetadata().setContentType("text/plain");
blob.getPayload().getContentMetadata().setExpires("Thu, 01 Dec 1994 16:00:00 GMT");
blob.getPayload().getContentMetadata().setExpires(new Date(1000));
HttpRequest request = signer.signPutBlob("container", blob);
assertRequestLineEquals(request, "PUT http://storage/container/name HTTP/1.1");
assertNonPayloadHeadersEqual(request, "X-Auth-Token: testtoken\n");
assertContentHeadersEqual(request, "text/plain", null, null, null, (long) 2l, new byte[] { 0, 2, 4, 8 }, "Thu, 01 Dec 1994 16:00:00 GMT");
assertContentHeadersEqual(request, "text/plain", null, null, null, (long) 2l, new byte[] { 0, 2, 4, 8 }, new Date(1000));
assertEquals(request.getFilters().size(), 0);
}

View File

@ -92,6 +92,7 @@ import org.jclouds.http.HttpResponseException;
import org.jclouds.http.HttpUtils;
import org.jclouds.http.options.HttpRequestOptions;
import org.jclouds.io.ContentMetadata;
import org.jclouds.io.ContentMetadataCodec;
import org.jclouds.io.MutableContentMetadata;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads;
@ -127,6 +128,7 @@ public class TransientAsyncBlobStore extends BaseAsyncBlobStore {
protected final IfDirectoryReturnNameStrategy ifDirectoryReturnName;
protected final Factory blobFactory;
protected final TransientStorageStrategy storageStrategy;
protected final ContentMetadataCodec contentMetadataCodec;
@Inject
protected TransientAsyncBlobStore(BlobStoreContext context,
@ -137,7 +139,8 @@ public class TransientAsyncBlobStore extends BaseAsyncBlobStore {
@Named(Constants.PROPERTY_USER_THREADS) ExecutorService service,
Supplier<Location> defaultLocation,
@Memoized Supplier<Set<? extends Location>> locations,
Factory blobFactory, Provider<UriBuilder> uriBuilders) {
Factory blobFactory, Provider<UriBuilder> 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()));
}
/**

View File

@ -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<URI> endpoint;
private final ContentMetadataCodec contentMetadataCodec;
@Inject
public TransientBlobRequestSigner(BasicAuthentication basicAuth, BlobToHttpGetOptions blob2HttpGetOptions, @Provider Supplier<URI> endpoint) {
public TransientBlobRequestSigner(BasicAuthentication basicAuth, BlobToHttpGetOptions blob2HttpGetOptions, @Provider Supplier<URI> 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);
}

View File

@ -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);
/**
*

View File

@ -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;
}

View File

@ -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<BlobStoreCont
});
}
protected void assertConsistencyAwareBlobExpiryMetadata(final String containerName, final String blobName, final Date expectedExpires)
throws InterruptedException {
assertConsistencyAware(new Runnable() {
public void run() {
try {
Blob blob = view.getBlobStore().getBlob(containerName, blobName);
Date actualExpires = blob.getPayload().getContentMetadata().getExpires();
assert expectedExpires.equals(actualExpires) : "expires="+actualExpires+"; expected="+expectedExpires;
} catch (Exception e) {
Throwables.propagateIfPossible(e);
}
}
});
}
protected void assertConsistencyAwareBlobInLocation(final String containerName, final String blobName, final Location loc)
throws InterruptedException {
assertConsistencyAware(new Runnable() {

View File

@ -0,0 +1,12 @@
package org.jclouds.date;
import java.text.ParseException;
import java.util.Date;
public interface DateCodec {
public Date toDate(String date) throws ParseException;
public String toString(Date date);
}

View File

@ -0,0 +1,47 @@
package org.jclouds.date;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class DateCodecs {
// 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";
/*
* Use default Java Date/SimpleDateFormat classes for date manipulation, but be *very* careful to
* guard against the lack of thread safety.
*/
// @GuardedBy("this")
private static final SimpleDateFormat rfc1123SimpleDateFormat = new SimpleDateFormat(RFC1123_DATE_PATTERN, Locale.US);
public static DateCodec rfc1123() {
return new SimpleDateCodec(rfc1123SimpleDateFormat);
}
private static class SimpleDateCodec implements DateCodec {
private final SimpleDateFormat dateFormat;
SimpleDateCodec(SimpleDateFormat dateFormat) {
this.dateFormat = dateFormat;
}
@Override
public Date toDate(String date) throws ParseException {
synchronized (dateFormat) {
return dateFormat.parse(date);
}
}
@Override
public String toString(Date date) {
synchronized (dateFormat) {
return dateFormat.format(date);
}
}
}
}

View File

@ -46,7 +46,6 @@ import java.util.regex.Matcher;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.ws.rs.core.HttpHeaders;
import org.jclouds.Constants;
import org.jclouds.crypto.CryptoStreams;
@ -63,8 +62,6 @@ import org.jclouds.util.Strings2;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableMultimap.Builder;
import com.google.common.collect.Multimap;
import com.google.common.collect.SortedSetMultimap;
import com.google.common.collect.TreeMultimap;
@ -184,25 +181,6 @@ public class HttpUtils {
}
}
public static Multimap<String, String> getContentHeadersFromMetadata(ContentMetadata md) {
Builder<String, String> 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();

View File

@ -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<Q> 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<Q> 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");

View File

@ -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<SSLContext> 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<String, String> 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<String,String> entry : contentMetadataCodec.toHeaders(md).entries()) {
connection.setRequestProperty(entry.getKey(), entry.getValue());
}
if (chunked) {
connection.setChunkedStreamingMode(8196);
} else {

View File

@ -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<String> 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.
* <p/>
@ -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 <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21"/>
*/
@Nullable
String getExpires();
Date getExpires();
ContentMetadataBuilder toBuilder();
ContentMetadataBuilder toBuilder();
}

View File

@ -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<String, String> headers) {
boolean chunked = any(headers.entries(), new Predicate<Entry<String, String>>() {
@Override
public boolean apply(Entry<String, String> input) {
return "Transfer-Encoding".equalsIgnoreCase(input.getKey()) && "chunked".equalsIgnoreCase(input.getValue());
}
});
for (Entry<String, String> 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;
}

View File

@ -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<String, String> toHeaders(ContentMetadata md);
/**
* Sets properties related to the http headers listed in {@link ContentMetadata#HTTP_HEADERS}
*/
public void fromHeaders(MutableContentMetadata contentMetadata, Multimap<String, String> 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<String, String> toHeaders(ContentMetadata md) {
Builder<String, String> 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<String, String> headers) {
boolean chunked = any(headers.entries(), new Predicate<Entry<String, String>>() {
@Override
public boolean apply(Entry<String, String> input) {
return "Transfer-Encoding".equalsIgnoreCase(input.getKey()) && "chunked".equalsIgnoreCase(input.getValue());
}
});
for (Entry<String, String> 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);
}
}
}
}

View File

@ -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<String, String> 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);
}

View File

@ -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

View File

@ -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<String, String> 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

View File

@ -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<T> {
private final ParseSax.Factory parserFactory;
private final HttpUtils utils;
private final ContentMetadataCodec contentMetadataCodec;
private final Provider<UriBuilder> uriBuilderProvider;
private final LoadingCache<Class<?>, Boolean> seedAnnotationCache;
private final String apiVersion;
@ -321,11 +323,12 @@ public class RestAnnotationProcessor<T> {
@Inject
public RestAnnotationProcessor(Injector injector, LoadingCache<Class<?>, Boolean> seedAnnotationCache,
@ApiVersion String apiVersion, @BuildVersion String buildVersion, ParseSax.Factory parserFactory,
HttpUtils utils, TypeLiteral<T> typeLiteral) throws ExecutionException {
HttpUtils utils, ContentMetadataCodec contentMetadataCodec, TypeLiteral<T> typeLiteral) throws ExecutionException {
this.declaring = (Class<T>) 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<T> {
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) {

View File

@ -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() {

View File

@ -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<SSLContext> untrustedSSLContextProvider, List<HttpCommand> 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;
}

View File

@ -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<S> {
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<S> {
@Inject
public ExpectHttpCommandExecutorService(Function<HttpRequest, HttpResponse> 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<S> {
builder.append(header.getKey()).append(": ").append(header.getValue()).append('\n');
}
if (request.getPayload() != null) {
for (Entry<String, String> header : HttpUtils.getContentHeadersFromMetadata(
for (Entry<String, String> header : contentMetadataCodec.toHeaders(
request.getPayload().getContentMetadata()).entries()) {
builder.append(header.getKey()).append(": ").append(header.getValue()).append('\n');
}

View File

@ -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);

View File

@ -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<HttpUriRequest> {
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));
}

View File

@ -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<String> desiredHeaders = ImmutableSet.of("Content-Disposition", "Content-Encoding", "Content-Language", "Expires");
MutableContentMetadata md = payload.getContentMetadata();
for (Map.Entry<String,String> entry : contentMetadataCodec.toHeaders(md).entries()) {
if (desiredHeaders.contains(entry.getKey())) {
apacheRequest.addHeader(entry.getKey(), entry.getValue());
}
}
assert (apacheRequest.getEntity() != null);
}

View File

@ -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<HttpRequest, HTTPRequest> {
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<String> 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<HttpRequest, HTTPRequest> {
Closeables.closeQuietly(input);
}
for (Entry<String, String> header : HttpUtils.getContentHeadersFromMetadata(
for (Entry<String, String> header : contentMetadataCodec.toHeaders(
request.getPayload().getContentMetadata()).entries()) {
if (!prohibitedHeaders.contains(header.getKey()))
gaeRequest.setHeader(new HTTPHeader(header.getKey(), header.getValue()));

View File

@ -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<HTTPResponse, HttpResponse> {
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<HTTPResponse, HttpResp
headers.put(header.getName(), header.getValue());
}
if (payload != null)
payload.getContentMetadata().setPropertiesFromHttpHeaders(headers);
if (payload != null) {
contentMetadataCodec.fromHeaders(payload.getContentMetadata(), headers);
}
return new HttpResponse(gaeResponse.getResponseCode(), message, payload,
RestAnnotationProcessor.filterOutContentHeaders(headers));
}

View File

@ -37,6 +37,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 com.google.appengine.api.urlfetch.HTTPRequest;
import com.google.appengine.api.urlfetch.HTTPResponse;
@ -60,11 +61,12 @@ public class GaeHttpCommandExecutorService extends BaseHttpCommandExecutorServic
@Inject
public GaeHttpCommandExecutorService(URLFetchService urlFetchService, HttpUtils utils,
ContentMetadataCodec contentMetadataCodec,
@Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioExecutor,
IOExceptionRetryHandler ioRetryHandler, DelegatingRetryHandler retryHandler,
DelegatingErrorHandler errorHandler, HttpWire wire, ConvertToGaeRequest convertToGaeRequest,
ConvertToJcloudsResponse convertToJcloudsResponse) {
super(utils, ioExecutor, retryHandler, ioRetryHandler, errorHandler, wire);
super(utils, contentMetadataCodec, ioExecutor, retryHandler, ioRetryHandler, errorHandler, wire);
this.urlFetchService = urlFetchService;
this.convertToGaeRequest = convertToGaeRequest;
this.convertToJcloudsResponse = convertToJcloudsResponse;

View File

@ -35,6 +35,7 @@ import org.jclouds.crypto.Crypto;
import org.jclouds.encryption.internal.JCECrypto;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpUtils;
import org.jclouds.io.ContentMetadataCodec.DefaultContentMetadataCodec;
import org.jclouds.io.Payloads;
import org.jclouds.util.Strings2;
import org.testng.annotations.BeforeTest;
@ -71,7 +72,7 @@ public class ConvertToGaeRequestTest {
@BeforeTest
void setupClient() throws MalformedURLException {
endPoint = URI.create("http://localhost:80/foo");
req = new ConvertToGaeRequest(new HttpUtils(0, 0, 0, 0));
req = new ConvertToGaeRequest(new HttpUtils(0, 0, 0, 0), new DefaultContentMetadataCodec());
}
@Test

View File

@ -36,6 +36,7 @@ import javax.ws.rs.core.HttpHeaders;
import org.jclouds.crypto.Crypto;
import org.jclouds.encryption.internal.JCECrypto;
import org.jclouds.http.HttpResponse;
import org.jclouds.io.ContentMetadataCodec.DefaultContentMetadataCodec;
import org.jclouds.util.Strings2;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
@ -67,7 +68,7 @@ public class ConvertToJcloudsResponseTest {
@BeforeTest
void setupClient() throws MalformedURLException {
endPoint = URI.create("http://localhost:80/foo");
req = new ConvertToJcloudsResponse();
req = new ConvertToJcloudsResponse(new DefaultContentMetadataCodec());
}
@Test

View File

@ -19,7 +19,6 @@
package org.jclouds.aws.s3.blobstore.integration;
import static org.jclouds.blobstore.options.CreateContainerOptions.Builder.publicRead;
<<<<<<< HEAD
import static org.testng.Assert.assertEquals;
import java.io.IOException;
@ -30,24 +29,35 @@ import java.util.Set;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.domain.BlobMetadata;
import org.jclouds.domain.Location;
=======
import java.io.IOException;
import java.net.MalformedURLException;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.Date;
import org.jclouds.blobstore.BlobStore;
>>>>>>> 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<Module> 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);

View File

@ -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<String, String> metadata) {
this.type = checkNotNull(type, "type");
this.leaseStatus = checkNotNull(leaseStatus, "leaseStatus");

View File

@ -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();
}

View File

@ -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<AzureBlobAsy
blob.getPayload().getContentMetadata().setContentLength(2l);
blob.getPayload().getContentMetadata().setContentMD5(new byte[] { 0, 2, 4, 8 });
blob.getPayload().getContentMetadata().setContentType("text/plain");
blob.getPayload().getContentMetadata().setExpires("Thu, 01 Dec 1994 16:00:00 GMT");
blob.getPayload().getContentMetadata().setExpires(new Date(1000));
HttpRequest request = signer.signPutBlob("container", blob);
@ -94,7 +95,7 @@ public class AzureBlobRequestSignerTest extends BaseAsyncClientTest<AzureBlobAsy
assertNonPayloadHeadersEqual(
request,
"Authorization: SharedKeyLite identity:LT+HBNzhbRsZY07kC+/JxeuAURbxTmwJaIe464LO36c=\nDate: Thu, 05 Jun 2008 16:38:19 GMT\nx-ms-blob-type: BlockBlob\nx-ms-version: 2009-09-19\n");
assertContentHeadersEqual(request, "text/plain", null, null, null, (long) 2l, new byte[] { 0, 2, 4, 8 }, "Thu, 01 Dec 1994 16:00:00 GMT");
assertContentHeadersEqual(request, "text/plain", null, null, null, (long) 2l, new byte[] { 0, 2, 4, 8 }, new Date(1000));
assertEquals(request.getFilters().size(), 0);
}