Merge pull request #628 from aledsage/Issue-647-AddExpiresHeader

Issue-647: adding "Expires" header for ContentMetadata
This commit is contained in:
Adrian Cole 2012-05-17 08:57:51 -07:00
commit 2a9f48cfba
47 changed files with 643 additions and 179 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,8 +155,13 @@ public class DelegatingMutableContentMetadata implements MutableContentMetadata
}
@Override
public void setPropertiesFromHttpHeaders(Multimap<String, String> headers) {
delegate.setPropertiesFromHttpHeaders(headers);
public void setExpires(Date expires) {
delegate.setExpires(expires);
}
@Override
public Date getExpires() {
return delegate.getExpires();
}
@Override

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,6 +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(new Date(1000));
HttpRequest request = signer.signPutBlob("container", blob);
@ -98,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 });
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

@ -95,6 +95,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.Payload;
import org.jclouds.io.Payloads;
import org.jclouds.io.payloads.BaseMutableContentMetadata;
@ -124,6 +125,7 @@ public class FilesystemAsyncBlobStore extends BaseAsyncBlobStore {
protected final DateService dateService;
protected final Crypto crypto;
protected final HttpGetOptionsListToGetOptions httpGetOptionsConverter;
protected final ContentMetadataCodec contentMetadataCodec;
protected final IfDirectoryReturnNameStrategy ifDirectoryReturnName;
protected final Factory blobFactory;
protected final FilesystemStorageStrategy storageStrategy;
@ -132,6 +134,7 @@ public class FilesystemAsyncBlobStore extends BaseAsyncBlobStore {
protected FilesystemAsyncBlobStore(BlobStoreContext context,
DateService dateService, Crypto crypto,
HttpGetOptionsListToGetOptions httpGetOptionsConverter,
ContentMetadataCodec contentMetadataCodec,
IfDirectoryReturnNameStrategy ifDirectoryReturnName,
BlobUtils blobUtils,
@Named(Constants.PROPERTY_USER_THREADS) ExecutorService service,
@ -143,6 +146,7 @@ public class FilesystemAsyncBlobStore extends BaseAsyncBlobStore {
this.dateService = dateService;
this.crypto = crypto;
this.httpGetOptionsConverter = httpGetOptionsConverter;
this.contentMetadataCodec = contentMetadataCodec;
this.ifDirectoryReturnName = ifDirectoryReturnName;
this.storageStrategy = checkNotNull(storageStrategy, "Storage strategy");
}
@ -475,7 +479,7 @@ public class FilesystemAsyncBlobStore 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

@ -114,6 +114,21 @@ public class NovaEC2ParserModule extends AbstractModule {
if (Objects.equal("-", toParse)) return null;
return delegate.iso8601SecondsDateParse(toParse);
}
@Override
public String rfc1123DateFormat(Date date) {
return delegate.rfc1123DateFormat(date);
}
@Override
public String rfc1123DateFormat() {
return delegate.rfc1123DateFormat();
}
@Override
public Date rfc1123DateParse(String toParse) {
return delegate.rfc1123DateParse(toParse);
}
}
}

View File

@ -59,7 +59,7 @@ public class BucketListObjectMetadata implements Serializable, ObjectMetadata {
this.lastModified = lastModified;
this.eTag = eTag;
this.owner = owner;
this.contentMetadata = new BaseImmutableContentMetadata(null, contentLength, md5, null, null, null);
this.contentMetadata = new BaseImmutableContentMetadata(null, contentLength, md5, null, null, null, null);
this.storageClass = storageClass;
}

View File

@ -47,7 +47,7 @@ public class CopyObjectResult implements Serializable, ObjectMetadata {
public CopyObjectResult(Date lastModified, String eTag) {
this.lastModified = lastModified;
this.eTag = eTag;
this.contentMetadata = new BaseImmutableContentMetadata(null, null, null, null, null, null);
this.contentMetadata = new BaseImmutableContentMetadata(null, null, null, null, null, null, null);
}
/**

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").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 });
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,12 +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(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 });
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,6 +118,8 @@ public interface BlobBuilder {
PayloadBlobBuilder contentEncoding(String contentEncoding);
PayloadBlobBuilder expires(Date expires);
/**
*
* @see Payloads#calculateMD5

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;
@ -221,6 +222,12 @@ public class BlobBuilderImpl implements BlobBuilder {
return this;
}
@Override
public PayloadBlobBuilder expires(Date expires) {
payload.getContentMetadata().setExpires(expires);
return this;
}
@Override
public PayloadBlobBuilder forSigning() {
return builder.forSigning();

View File

@ -86,7 +86,7 @@ public class TransientBlobRequestSignerTest extends BaseAsyncClientTest<Transien
assertNonPayloadHeadersEqual(
request,
"Authorization: Basic aWRlbnRpdHk6Y3JlZGVudGlhbA==\nContent-Length: 2\nContent-MD5: AAIECA==\nContent-Type: text/plain\n");
assertContentHeadersEqual(request, "text/plain", null, null, null, (long) 2l, new byte[] { 0, 2, 4, 8 });
assertContentHeadersEqual(request, "text/plain", null, null, null, (long) 2l, new byte[] { 0, 2, 4, 8 }, null);
assertEquals(request.getFilters().size(), 0);
}
@ -94,9 +94,9 @@ public class TransientBlobRequestSignerTest extends BaseAsyncClientTest<Transien
public void testSignPutBlobWithGenerate() throws ArrayIndexOutOfBoundsException, SecurityException,
IllegalArgumentException, NoSuchMethodException, IOException {
Blob blob = blobFactory.get().name(blobName).payload("foo").calculateMD5().contentType("text/plain").build();
assertEquals(blob.getPayload().getContentMetadata().getContentMD5(), new byte[] { -84, -67, 24, -37, 76, -62, -8,
92, -19, -17, 101, 79, -52, -60, -92, -40 });
byte[] md5 = new byte[] { -84, -67, 24, -37, 76, -62, -8, 92, -19, -17, 101, 79, -52, -60, -92, -40 };
assertEquals(blob.getPayload().getContentMetadata().getContentMD5(), md5);
HttpRequest request = signer.signPutBlob(containerName, blob);
@ -104,8 +104,7 @@ public class TransientBlobRequestSignerTest extends BaseAsyncClientTest<Transien
assertNonPayloadHeadersEqual(
request,
"Authorization: Basic aWRlbnRpdHk6Y3JlZGVudGlhbA==\nContent-Length: 3\nContent-MD5: rL0Y20zC+Fzt72VPzMSk2A==\nContent-Type: text/plain\n");
assertContentHeadersEqual(request, "text/plain", null, null, null, (long) 3l, new byte[] { -84, -67, 24, -37, 76,
-62, -8, 92, -19, -17, 101, 79, -52, -60, -92, -40 });
assertContentHeadersEqual(request, "text/plain", null, null, null, (long) 3l, md5, null);
assertEquals(request.getFilters().size(), 0);
}

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,17 @@
package org.jclouds.date;
import org.jclouds.date.internal.SimpleDateCodecFactory;
import com.google.inject.ImplementedBy;
/**
* Codecs for converting from Date->String and vice versa.
*
* @author aled
*/
@ImplementedBy(SimpleDateCodecFactory.class)
public interface DateCodecFactory {
public DateCodec rfc1123();
}

View File

@ -60,4 +60,10 @@ public interface DateService {
Date iso8601SecondsDateParse(String toParse);
String rfc1123DateFormat(Date date);
String rfc1123DateFormat();
Date rfc1123DateParse(String toParse);
}

View File

@ -0,0 +1,41 @@
package org.jclouds.date.internal;
import static com.google.common.base.Preconditions.checkNotNull;
import java.text.ParseException;
import java.util.Date;
import org.jclouds.date.DateCodec;
import org.jclouds.date.DateCodecFactory;
import org.jclouds.date.DateService;
import com.google.inject.Inject;
public class SimpleDateCodecFactory implements DateCodecFactory {
private final DateService dateService;
private volatile DateCodec rfc1123Codec;
@Inject
public SimpleDateCodecFactory(final DateService dateService) {
this.dateService = checkNotNull(dateService, "dateService");
}
public DateCodec rfc1123() {
if (rfc1123Codec == null) {
rfc1123Codec = new DateCodec() {
@Override
public Date toDate(String date) throws ParseException {
return dateService.rfc1123DateParse(date);
}
@Override
public String toString(Date date) {
return dateService.rfc1123DateFormat(date);
}
};
}
return rfc1123Codec;
}
}

View File

@ -51,6 +51,11 @@ public class SimpleDateFormatDateService implements DateService {
// @GuardedBy("this")
private static final SimpleDateFormat rfc822SimpleDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
// See http://stackoverflow.com/questions/10584647/simpledateformat-parse-is-one-hour-out-using-rfc-1123-gmt-in-summer
// for why not using "zzz"
// @GuardedBy("this")
private static final SimpleDateFormat rfc1123SimpleDateFormat = new SimpleDateFormat("EEE, dd MMM yyyyy HH:mm:ss Z", Locale.US);
// @GuardedBy("this")
private static final SimpleDateFormat cSimpleDateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy", Locale.US);
@ -180,4 +185,26 @@ public class SimpleDateFormatDateService implements DateService {
}
}
}
@Override
public final String rfc1123DateFormat(Date date) {
synchronized (rfc1123SimpleDateFormat) {
return rfc1123SimpleDateFormat.format(date);
}
}
@Override
public final String rfc1123DateFormat() {
return rfc1123DateFormat(new Date());
}
@Override
public final Date rfc1123DateParse(String toParse) {
synchronized (rfc1123SimpleDateFormat) {
try {
return rfc1123SimpleDateFormat.parse(toParse);
} catch (ParseException pe) {
throw new RuntimeException("Error parsing data at " + pe.getErrorOffset(), pe);
}
}
}
}

View File

@ -32,6 +32,7 @@ import static javax.ws.rs.core.HttpHeaders.CONTENT_ENCODING;
import static javax.ws.rs.core.HttpHeaders.CONTENT_LANGUAGE;
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 static org.jclouds.util.Patterns.PATTERN_THAT_BREAKS_URI;
import static org.jclouds.util.Patterns.URI_PATTERN;
@ -45,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;
@ -62,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;
@ -183,23 +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()));
return builder.build();
}
public static byte[] toByteArrayOrNull(PayloadEnclosing response) {
if (response.getPayload() != null) {
InputStream input = response.getPayload().getInput();
@ -238,6 +219,7 @@ public class HttpUtils {
toMd.setContentDisposition(fromMd.getContentDisposition());
toMd.setContentEncoding(fromMd.getContentEncoding());
toMd.setContentLanguage(fromMd.getContentLanguage());
toMd.setExpires(fromMd.getExpires());
}
public static URI parseEndPoint(String hostHeader) {
@ -342,6 +324,9 @@ public class HttpUtils {
if (message.getPayload().getContentMetadata().getContentLanguage() != null)
logger.debug("%s %s: %s", prefix, CONTENT_LANGUAGE, message.getPayload().getContentMetadata()
.getContentLanguage());
if (message.getPayload().getContentMetadata().getExpires() != null)
logger.debug("%s %s: %s", prefix, EXPIRES, message.getPayload().getContentMetadata()
.getExpires());
}
}
@ -392,6 +377,10 @@ public class HttpUtils {
message.getPayload() == null || message.getFirstHeaderOrNull(CONTENT_LANGUAGE) == null,
"configuration error please use request.getPayload().getContentMetadata().setContentLanguage(value) as opposed to adding a content language header: "
+ message);
checkArgument(
message.getPayload() == null || message.getFirstHeaderOrNull(EXPIRES) == null,
"configuration error please use request.getPayload().getContentMetadata().setExpires(value) as opposed to adding an expires header: "
+ message);
}
public static void releasePayload(HttpMessage from) {

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,16 +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());
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/>
@ -86,6 +92,16 @@ public interface ContentMetadata {
@Nullable
String getContentLanguage();
ContentMetadataBuilder toBuilder();
/**
* 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
Date getExpires();
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,31 +44,7 @@ public class ContentMetadataBuilder implements Serializable {
protected String contentDisposition;
protected String contentLanguage;
protected String contentEncoding;
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());
}
}
return this;
}
protected Date expires;
public ContentMetadataBuilder contentLength(@Nullable Long contentLength) {
this.contentLength = contentLength;
@ -113,28 +82,26 @@ public class ContentMetadataBuilder implements Serializable {
return this;
}
public ContentMetadataBuilder expires(@Nullable Date expires) {
this.expires = expires;
return this;
}
public ContentMetadata build() {
return new BaseImmutableContentMetadata(contentType, contentLength, contentMD5, contentDisposition,
contentLanguage, contentEncoding);
contentLanguage, contentEncoding, expires);
}
public static ContentMetadataBuilder fromContentMetadata(ContentMetadata in) {
return new ContentMetadataBuilder().contentType(in.getContentType()).contentLength(in.getContentLength())
.contentMD5(in.getContentMD5()).contentDisposition(in.getContentDisposition()).contentLanguage(
in.getContentLanguage()).contentEncoding(in.getContentEncoding());
in.getContentLanguage()).contentEncoding(in.getContentEncoding()).expires(in.getExpires());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((contentDisposition == null) ? 0 : contentDisposition.hashCode());
result = prime * result + ((contentEncoding == null) ? 0 : contentEncoding.hashCode());
result = prime * result + ((contentLanguage == null) ? 0 : contentLanguage.hashCode());
result = prime * result + ((contentLength == null) ? 0 : contentLength.hashCode());
result = prime * result + Arrays.hashCode(contentMD5);
result = prime * result + ((contentType == null) ? 0 : contentType.hashCode());
return result;
return Objects.hashCode(contentDisposition, contentEncoding, contentLanguage, contentLength,
contentMD5, contentType, expires);
}
@Override
@ -151,13 +118,14 @@ public class ContentMetadataBuilder implements Serializable {
Objects.equal(contentLanguage, other.contentLanguage) &&
Objects.equal(contentLength, other.contentLength) &&
Arrays.equals(contentMD5, other.contentMD5) &&
Objects.equal(contentType, other.contentType);
Objects.equal(contentType, other.contentType) &&
Objects.equal(expires, other.expires);
}
@Override
public String toString() {
return "[contentDisposition=" + contentDisposition + ", contentEncoding=" + contentEncoding
+ ", contentLanguage=" + contentLanguage + ", contentLength=" + contentLength + ", contentMD5="
+ Arrays.toString(contentMD5) + ", contentType=" + contentType + "]";
+ Arrays.toString(contentMD5) + ", contentType=" + contentType + ", expires=" + expires + "]";
}
}
}

View File

@ -0,0 +1,124 @@
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.DateCodecFactory;
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;
import com.google.inject.Inject;
@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;
@Inject
public DefaultContentMetadataCodec(DateCodecFactory dateCodecs) {
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,4 +62,5 @@ public interface MutableContentMetadata extends ContentMetadata {
*/
void setContentEncoding(@Nullable String contentEncoding);
void setExpires(@Nullable Date expires);
}

View File

@ -19,11 +19,16 @@
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;
import com.google.common.base.Objects;
/**
* @author Adrian Cole
*/
@ -37,15 +42,17 @@ public class BaseImmutableContentMetadata implements ContentMetadata, Serializab
protected String contentDisposition;
protected String contentLanguage;
protected String contentEncoding;
protected Date expires;
public BaseImmutableContentMetadata(String contentType, Long contentLength, byte[] contentMD5,
String contentDisposition, String contentLanguage, String contentEncoding) {
String contentDisposition, String contentLanguage, String contentEncoding, Date expires) {
this.contentType = contentType;
this.contentLength = contentLength;
this.contentMD5 = contentMD5;
this.contentDisposition = contentDisposition;
this.contentLanguage = contentLanguage;
this.contentEncoding = contentEncoding;
this.expires = expires;
}
/**
@ -102,24 +109,25 @@ public class BaseImmutableContentMetadata implements ContentMetadata, Serializab
return this.contentEncoding;
}
/**
* {@inheritDoc}
*/
@Override
public Date getExpires() {
return expires;
}
@Override
public String toString() {
return "[contentType=" + contentType + ", contentLength=" + contentLength + ", contentDisposition="
+ contentDisposition + ", contentEncoding=" + contentEncoding + ", contentLanguage=" + contentLanguage
+ ", contentMD5=" + Arrays.toString(contentMD5) + "]";
+ ", contentMD5=" + Arrays.toString(contentMD5) + ", expires = " + expires + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((contentDisposition == null) ? 0 : contentDisposition.hashCode());
result = prime * result + ((contentEncoding == null) ? 0 : contentEncoding.hashCode());
result = prime * result + ((contentLanguage == null) ? 0 : contentLanguage.hashCode());
result = prime * result + ((contentLength == null) ? 0 : contentLength.hashCode());
result = prime * result + Arrays.hashCode(contentMD5);
result = prime * result + ((contentType == null) ? 0 : contentType.hashCode());
return result;
return Objects.hashCode(contentDisposition, contentEncoding, contentLanguage, contentLength,
contentMD5, contentType, expires);
}
@Override
@ -158,6 +166,9 @@ public class BaseImmutableContentMetadata implements ContentMetadata, Serializab
return false;
} else if (!contentType.equals(other.contentType))
return false;
if (!Objects.equal(expires, other.expires)) {
return false;
}
return true;
}

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}
*/
@ -141,6 +137,22 @@ public class BaseMutableContentMetadata extends ContentMetadataBuilder implement
return this.contentEncoding;
}
/**
* {@inheritDoc}
*/
@Override
public void setExpires(@Nullable Date expires) {
expires(expires);
}
/**
* {@inheritDoc}
*/
@Override
public Date getExpires() {
return expires;
}
@Override
public BaseMutableContentMetadata toBuilder() {
return BaseMutableContentMetadata.fromContentMetadata(this);
@ -150,6 +162,6 @@ public class BaseMutableContentMetadata extends ContentMetadataBuilder implement
return (BaseMutableContentMetadata) new BaseMutableContentMetadata().contentType(in.getContentType())
.contentLength(in.getContentLength()).contentMD5(in.getContentMD5()).contentDisposition(
in.getContentDisposition()).contentLanguage(in.getContentLanguage()).contentEncoding(
in.getContentEncoding());
in.getContentEncoding()).expires(in.getExpires());
}
}

View File

@ -138,6 +138,7 @@ public abstract class Wire {
wiredMd.setContentDisposition(oldMd.getContentDisposition());
wiredMd.setContentEncoding(oldMd.getContentEncoding());
wiredMd.setContentLanguage(oldMd.getContentLanguage());
wiredMd.setExpires(oldMd.getExpires());
}
@SuppressWarnings("unchecked")

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

@ -34,6 +34,8 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.ws.rs.core.UriBuilder;
import org.jclouds.date.internal.SimpleDateCodecFactory;
import org.jclouds.date.internal.SimpleDateFormatDateService;
import org.jclouds.http.BaseJettyTest;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpResponse;
@ -44,7 +46,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 +109,17 @@ 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(
new SimpleDateCodecFactory(new SimpleDateFormatDateService()));
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;
@ -49,6 +49,8 @@ import org.jclouds.apis.ApiMetadata;
import org.jclouds.concurrent.MoreExecutors;
import org.jclouds.concurrent.SingleThreaded;
import org.jclouds.concurrent.config.ConfiguresExecutorService;
import org.jclouds.date.internal.SimpleDateCodecFactory;
import org.jclouds.date.internal.SimpleDateFormatDateService;
import org.jclouds.http.HttpCommandExecutorService;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
@ -59,6 +61,8 @@ 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.ContentMetadataCodec.DefaultContentMetadataCodec;
import org.jclouds.io.CopyInputStreamInputSupplierMap;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads;
@ -118,6 +122,9 @@ public abstract class BaseRestClientExpectTest<S> {
protected String provider = "mock";
protected ContentMetadataCodec contentMetadataCodec = new DefaultContentMetadataCodec(
new SimpleDateCodecFactory(new SimpleDateFormatDateService()));
/**
* 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 +195,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 +479,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;
@ -86,11 +86,22 @@ public abstract class BaseRestClientTest {
}
protected void assertPayloadEquals(HttpRequest request, String toMatch, String contentType, boolean contentMD5) {
assertPayloadEquals(request, toMatch, contentType, null, null, null, contentMD5);
assertPayloadEquals(request, toMatch, contentType, contentMD5, null);
}
protected void assertPayloadEquals(HttpRequest request, String toMatch, String contentType, boolean contentMD5, Date expires) {
assertPayloadEquals(request, toMatch, contentType, null, null, null, contentMD5, expires);
}
protected void assertPayloadEquals(HttpRequest request, String toMatch, String contentType,
String contentDispositon, String contentEncoding, String contentLanguage, boolean contentMD5) {
assertPayloadEquals(request, toMatch, contentType, contentDispositon, contentEncoding, contentLanguage,
contentMD5, null);
}
protected void assertPayloadEquals(HttpRequest request, String toMatch, String contentType,
String contentDispositon, String contentEncoding, String contentLanguage, boolean contentMD5,
Date expires) {
if (request.getPayload() == null) {
assertNull(toMatch);
} else {
@ -104,7 +115,7 @@ public abstract class BaseRestClientTest {
Long length = new Long(payload.getBytes().length);
try {
assertContentHeadersEqual(request, contentType, contentDispositon, contentEncoding, contentLanguage,
length, contentMD5 ? CryptoStreams.md5(request.getPayload()) : null);
length, contentMD5 ? CryptoStreams.md5(request.getPayload()) : null, expires);
} catch (IOException e) {
propagate(e);
}
@ -112,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 contentEncoding, String contentLanguage, Long length, byte[] contentMD5, Date expires) {
MutableContentMetadata md = request.getPayload().getContentMetadata();
if (request.getFirstHeaderOrNull(TRANSFER_ENCODING) == null) {
assertEquals(md.getContentLength(), length);
@ -125,6 +136,7 @@ public abstract class BaseRestClientTest {
assertEquals(md.getContentEncoding(), contentEncoding);
assertEquals(md.getContentLanguage(), contentLanguage);
assertEquals(md.getContentMD5(), contentMD5);
assertEquals(md.getExpires(), expires);
}
// FIXME Shouldn't be assertPayloadHeadersEqual?

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,16 +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());
// 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

@ -32,9 +32,12 @@ import javax.ws.rs.HttpMethod;
import javax.ws.rs.core.HttpHeaders;
import org.jclouds.crypto.Crypto;
import org.jclouds.date.internal.SimpleDateCodecFactory;
import org.jclouds.date.internal.SimpleDateFormatDateService;
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 +74,9 @@ 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(
new SimpleDateCodecFactory(new SimpleDateFormatDateService())));
}
@Test

View File

@ -34,8 +34,11 @@ import java.util.List;
import javax.ws.rs.core.HttpHeaders;
import org.jclouds.crypto.Crypto;
import org.jclouds.date.internal.SimpleDateCodecFactory;
import org.jclouds.date.internal.SimpleDateFormatDateService;
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 +70,8 @@ public class ConvertToJcloudsResponseTest {
@BeforeTest
void setupClient() throws MalformedURLException {
endPoint = URI.create("http://localhost:80/foo");
req = new ConvertToJcloudsResponse();
req = new ConvertToJcloudsResponse(new DefaultContentMetadataCodec(
new SimpleDateCodecFactory(new SimpleDateFormatDateService())));
}
@Test

View File

@ -53,6 +53,9 @@ public class JodaDateService implements DateService {
private static final DateTimeFormatter iso8601DateFormatter = DateTimeFormat.forPattern(
"yyyy-MM-dd'T'HH:mm:ss.SSSZ").withLocale(Locale.US).withZone(DateTimeZone.forID("GMT"));
private static final DateTimeFormatter rfc1123DateFormat = DateTimeFormat.forPattern(
"EEE, dd MMM yyyyy HH:mm:ss Z").withLocale(Locale.US).withZone(DateTimeZone.forID("GMT"));
public final Date fromSeconds(long seconds) {
return new Date(seconds * 1000);
}
@ -124,4 +127,19 @@ public class JodaDateService implements DateService {
toParse += tz;
return iso8601SecondsDateFormatter.parseDateTime(toParse).toDate();
}
@Override
public final String rfc1123DateFormat(Date dateTime) {
return rfc1123DateFormat.print(new DateTime(dateTime));
}
@Override
public final String rfc1123DateFormat() {
return rfc1123DateFormat(new Date());
}
@Override
public final Date rfc1123DateParse(String toParse) {
return rfc1123DateFormat.parseDateTime(toParse).toDate();
}
}

View File

@ -23,17 +23,29 @@ import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.net.MalformedURLException;
import java.text.ParseException;
import java.util.Date;
import java.util.NoSuchElementException;
import java.util.Set;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.domain.BlobMetadata;
import org.jclouds.date.DateCodec;
import org.jclouds.date.internal.SimpleDateCodecFactory;
import org.jclouds.date.internal.SimpleDateFormatDateService;
import org.jclouds.domain.Location;
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;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.inject.AbstractModule;
import com.google.inject.Module;
/**
* @author Adrian Cole
@ -44,6 +56,68 @@ public class AWSS3ContainerLiveTest extends S3ContainerLiveTest {
provider = "aws-s3";
}
@Test(groups = { "live" })
public void testCreateBlobWithExpiry() throws InterruptedException, MalformedURLException, IOException {
final String containerName = getScratchContainerName();
BlobStore blobStore = view.getBlobStore();
try {
final String blobName = "hello";
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());
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(new SimpleDateCodecFactory(new SimpleDateFormatDateService())) {
@Override
protected DateCodec getExpiresDateCodec() {
return new DateCodec() {
@Override public Date toDate(String date) throws ParseException {
return new 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);
}
}
@Test(groups = { "live" })
public void testCreateBlobInLocation() throws InterruptedException, MalformedURLException, IOException {
String payload = "my data";
@ -88,5 +162,4 @@ public class AWSS3ContainerLiveTest extends S3ContainerLiveTest {
}
throw new NoSuchElementException("No location found with id '"+id+"'; contenders were "+locs);
}
}

View File

@ -55,7 +55,8 @@ 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, LeaseStatus leaseStatus, Map<String, String> metadata) {
@Nullable String contentLanguage, @Nullable Date currentExpires, LeaseStatus leaseStatus,
Map<String, String> metadata) {
this.type = checkNotNull(type, "type");
this.leaseStatus = checkNotNull(leaseStatus, "leaseStatus");
this.name = checkNotNull(name, "name");
@ -64,7 +65,7 @@ public class BlobPropertiesImpl implements Serializable, BlobProperties {
this.lastModified = checkNotNull(lastModified, "lastModified");
this.eTag = checkNotNull(eTag, "eTag");
this.contentMetadata = new BaseImmutableContentMetadata(contentType, size, contentMD5, null, contentLanguage,
contentMetadata);
contentMetadata, currentExpires);
this.metadata.putAll(checkNotNull(metadata, "metadata"));
}

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,6 +72,7 @@ public class ContainerNameEnumerationResultsHandler extends ParseSax.HandlerWith
private String currentContentEncoding;
private String currentContentLanguage;
private BlobType currentBlobType;
private Date currentExpires;
private boolean inBlob;
private boolean inBlobPrefix;
private boolean inMetadata;
@ -79,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() {
@ -131,8 +135,8 @@ public class ContainerNameEnumerationResultsHandler extends ParseSax.HandlerWith
} else if (qName.equals("Blob")) {
BlobProperties md = new BlobPropertiesImpl(currentBlobType, currentName, containerUrl.getPath().replace("/",
""), currentUrl, currentLastModified, currentETag, currentSize, currentContentType,
currentContentMD5, currentContentEncoding, currentContentLanguage, currentLeaseStatus,
currentMetadata);
currentContentMD5, currentContentEncoding, currentContentLanguage, currentExpires,
currentLeaseStatus, currentMetadata);
blobMetadata.add(md);
currentBlobType = null;
currentName = null;
@ -145,6 +149,7 @@ public class ContainerNameEnumerationResultsHandler extends ParseSax.HandlerWith
currentContentLanguage = null;
currentContentMD5 = null;
currentLeaseStatus = null;
currentExpires = null;
currentMetadata = Maps.newHashMap();
} else if (qName.equals("Url")) {
currentUrl = HttpUtils.createUri(currentText.toString().trim());
@ -172,6 +177,13 @@ public class ContainerNameEnumerationResultsHandler extends ParseSax.HandlerWith
currentContentLanguage = currentText.toString().trim();
if (currentContentLanguage.equals(""))
currentContentLanguage = null;
} else if (qName.equals("Expires")) {
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,6 +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(new Date(1000));
HttpRequest request = signer.signPutBlob("container", blob);
@ -93,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 });
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

@ -63,17 +63,17 @@ public class ContainerNameEnumerationResultsHandlerTest extends BaseHandlerTest
new BlobPropertiesImpl(BlobType.BLOCK_BLOB, "blob1.txt", "mycontainer", URI
.create("http://myaccount.blob.core.windows.net/mycontainer/blob1.txt"), dateService
.rfc822DateParse("Thu, 18 Sep 2008 18:41:57 GMT"), "0x8CAE7D55D050B8B", 8,
"text/plain; charset=UTF-8", null, null, null, LeaseStatus.UNLOCKED, ImmutableMap
"text/plain; charset=UTF-8", null, null, null, null, LeaseStatus.UNLOCKED, ImmutableMap
.<String, String> of()),
new BlobPropertiesImpl(BlobType.BLOCK_BLOB, "blob2.txt", "mycontainer", URI
.create("http://myaccount.blob.core.windows.net/mycontainer/blob2.txt"), dateService
.rfc822DateParse("Thu, 18 Sep 2008 18:41:57 GMT"), "0x8CAE7D55CF6C339", 14,
"text/plain; charset=UTF-8", null, null, null, LeaseStatus.UNLOCKED, ImmutableMap
"text/plain; charset=UTF-8", null, null, null, null, LeaseStatus.UNLOCKED, ImmutableMap
.<String, String> of()),
new BlobPropertiesImpl(BlobType.PAGE_BLOB, "newblob1.txt", "mycontainer", URI
.create("http://myaccount.blob.core.windows.net/mycontainer/newblob1.txt"), dateService
.rfc822DateParse("Thu, 18 Sep 2008 18:41:57 GMT"), "0x8CAE7D55CF6C339", 25,
"text/plain; charset=UTF-8", null, null, null, LeaseStatus.UNLOCKED, ImmutableMap
"text/plain; charset=UTF-8", null, null, null, null, LeaseStatus.UNLOCKED, ImmutableMap
.<String, String> of()));
ListBlobsResponse list = new HashSetListBlobsResponse(contents,
@ -91,7 +91,7 @@ public class ContainerNameEnumerationResultsHandlerTest extends BaseHandlerTest
Set<BlobProperties> contents = ImmutableSet.<BlobProperties> of(new BlobPropertiesImpl(BlobType.BLOCK_BLOB, "a",
"adriancole-blobstore3", URI.create("https://jclouds.blob.core.windows.net/adriancole-blobstore3/a"),
dateService.rfc822DateParse("Sat, 30 Jan 2010 17:46:15 GMT"), "0x8CC6FEB41736428", 8,
"application/octet-stream", null, null, null, LeaseStatus.UNLOCKED, ImmutableMap.<String, String> of()));
"application/octet-stream", null, null, null, null, LeaseStatus.UNLOCKED, ImmutableMap.<String, String> of()));
ListBlobsResponse list = new HashSetListBlobsResponse(contents,
URI.create("https://jclouds.blob.core.windows.net/adriancole-blobstore3"),