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; package org.jclouds.atmos.domain.internal;
import java.net.URI; import java.net.URI;
import java.util.Date;
import org.jclouds.atmos.domain.MutableContentMetadata; import org.jclouds.atmos.domain.MutableContentMetadata;
import org.jclouds.io.ContentMetadataBuilder; import org.jclouds.io.ContentMetadataBuilder;
@ -154,17 +155,12 @@ public class DelegatingMutableContentMetadata implements MutableContentMetadata
} }
@Override @Override
public void setPropertiesFromHttpHeaders(Multimap<String, String> headers) { public void setExpires(Date expires) {
delegate.setPropertiesFromHttpHeaders(headers);
}
@Override
public void setExpires(String expires) {
delegate.setExpires(expires); delegate.setExpires(expires);
} }
@Override @Override
public String getExpires() { public Date getExpires() {
return delegate.getExpires(); return delegate.getExpires();
} }

View File

@ -21,6 +21,9 @@ package org.jclouds.atmos.blobstore;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import java.io.IOException; import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.jclouds.apis.ApiMetadata; import org.jclouds.apis.ApiMetadata;
import org.jclouds.atmos.AtmosApiMetadata; 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.blobstore.domain.Blob.Factory;
import org.jclouds.date.TimeStamp; import org.jclouds.date.TimeStamp;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.io.ContentMetadata;
import org.jclouds.rest.ConfiguresRestClient; import org.jclouds.rest.ConfiguresRestClient;
import org.jclouds.rest.internal.BaseAsyncClientTest; import org.jclouds.rest.internal.BaseAsyncClientTest;
import org.jclouds.rest.internal.RestAnnotationProcessor; import org.jclouds.rest.internal.RestAnnotationProcessor;
@ -89,7 +93,7 @@ public class AtmosBlobRequestSignerTest extends BaseAsyncClientTest<AtmosAsyncCl
blob.getPayload().getContentMetadata().setContentLength(2l); blob.getPayload().getContentMetadata().setContentLength(2l);
blob.getPayload().getContentMetadata().setContentMD5(new byte[] { 0, 2, 4, 8 }); blob.getPayload().getContentMetadata().setContentMD5(new byte[] { 0, 2, 4, 8 });
blob.getPayload().getContentMetadata().setContentType("text/plain"); 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); HttpRequest request = signer.signPutBlob("container", blob);
@ -99,7 +103,7 @@ public class AtmosBlobRequestSignerTest extends BaseAsyncClientTest<AtmosAsyncCl
request, request,
"Accept: */*\nDate: Thu, 05 Jun 2008 16:38:19 GMT\nx-emc-signature: aLpB1oQaCA27AXT6Nzam7s0f0pI=\nx-emc-uid: identity\n"); "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); assertEquals(request.getFilters().size(), 0);
} }

View File

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

View File

@ -21,6 +21,7 @@ package org.jclouds.openstack.swift.blobstore;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import java.io.IOException; import java.io.IOException;
import java.util.Date;
import org.jclouds.blobstore.BlobRequestSigner; import org.jclouds.blobstore.BlobRequestSigner;
import org.jclouds.blobstore.domain.Blob; import org.jclouds.blobstore.domain.Blob;
@ -72,13 +73,13 @@ public class SwiftBlobRequestSignerTest extends CommonSwiftClientTest {
blob.getPayload().getContentMetadata().setContentLength(2l); blob.getPayload().getContentMetadata().setContentLength(2l);
blob.getPayload().getContentMetadata().setContentMD5(new byte[] { 0, 2, 4, 8 }); blob.getPayload().getContentMetadata().setContentMD5(new byte[] { 0, 2, 4, 8 });
blob.getPayload().getContentMetadata().setContentType("text/plain"); 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); HttpRequest request = signer.signPutBlob("container", blob);
assertRequestLineEquals(request, "PUT http://storage/container/name HTTP/1.1"); assertRequestLineEquals(request, "PUT http://storage/container/name HTTP/1.1");
assertNonPayloadHeadersEqual(request, "X-Auth-Token: testtoken\n"); 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); 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.HttpUtils;
import org.jclouds.http.options.HttpRequestOptions; import org.jclouds.http.options.HttpRequestOptions;
import org.jclouds.io.ContentMetadata; import org.jclouds.io.ContentMetadata;
import org.jclouds.io.ContentMetadataCodec;
import org.jclouds.io.MutableContentMetadata; import org.jclouds.io.MutableContentMetadata;
import org.jclouds.io.Payload; import org.jclouds.io.Payload;
import org.jclouds.io.Payloads; import org.jclouds.io.Payloads;
@ -127,6 +128,7 @@ public class TransientAsyncBlobStore extends BaseAsyncBlobStore {
protected final IfDirectoryReturnNameStrategy ifDirectoryReturnName; protected final IfDirectoryReturnNameStrategy ifDirectoryReturnName;
protected final Factory blobFactory; protected final Factory blobFactory;
protected final TransientStorageStrategy storageStrategy; protected final TransientStorageStrategy storageStrategy;
protected final ContentMetadataCodec contentMetadataCodec;
@Inject @Inject
protected TransientAsyncBlobStore(BlobStoreContext context, protected TransientAsyncBlobStore(BlobStoreContext context,
@ -137,7 +139,8 @@ public class TransientAsyncBlobStore extends BaseAsyncBlobStore {
@Named(Constants.PROPERTY_USER_THREADS) ExecutorService service, @Named(Constants.PROPERTY_USER_THREADS) ExecutorService service,
Supplier<Location> defaultLocation, Supplier<Location> defaultLocation,
@Memoized Supplier<Set<? extends Location>> locations, @Memoized Supplier<Set<? extends Location>> locations,
Factory blobFactory, Provider<UriBuilder> uriBuilders) { Factory blobFactory, Provider<UriBuilder> uriBuilders,
ContentMetadataCodec contentMetadataCodec) {
super(context, blobUtils, service, defaultLocation, locations); super(context, blobUtils, service, defaultLocation, locations);
this.blobFactory = blobFactory; this.blobFactory = blobFactory;
this.dateService = dateService; this.dateService = dateService;
@ -146,6 +149,7 @@ public class TransientAsyncBlobStore extends BaseAsyncBlobStore {
this.httpGetOptionsConverter = httpGetOptionsConverter; this.httpGetOptionsConverter = httpGetOptionsConverter;
this.ifDirectoryReturnName = ifDirectoryReturnName; this.ifDirectoryReturnName = ifDirectoryReturnName;
this.storageStrategy = new TransientStorageStrategy(defaultLocation); this.storageStrategy = new TransientStorageStrategy(defaultLocation);
this.contentMetadataCodec = contentMetadataCodec;
} }
/** /**
@ -521,7 +525,7 @@ public class TransientAsyncBlobStore extends BaseAsyncBlobStore {
} }
private void copyPayloadHeadersToBlob(Payload payload, Blob blob) { 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.HttpRequest;
import org.jclouds.http.HttpUtils; import org.jclouds.http.HttpUtils;
import org.jclouds.http.filters.BasicAuthentication; import org.jclouds.http.filters.BasicAuthentication;
import org.jclouds.io.ContentMetadataCodec;
import org.jclouds.location.Provider; import org.jclouds.location.Provider;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
@ -45,12 +46,15 @@ public class TransientBlobRequestSigner implements BlobRequestSigner {
private final BasicAuthentication basicAuth; private final BasicAuthentication basicAuth;
private final BlobToHttpGetOptions blob2HttpGetOptions; private final BlobToHttpGetOptions blob2HttpGetOptions;
private final Supplier<URI> endpoint; private final Supplier<URI> endpoint;
private final ContentMetadataCodec contentMetadataCodec;
@Inject @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.basicAuth = checkNotNull(basicAuth, "basicAuth");
this.blob2HttpGetOptions = checkNotNull(blob2HttpGetOptions, "blob2HttpGetOptions"); this.blob2HttpGetOptions = checkNotNull(blob2HttpGetOptions, "blob2HttpGetOptions");
this.endpoint = endpoint; this.endpoint = endpoint;
this.contentMetadataCodec = contentMetadataCodec;
} }
@Override @Override
@ -64,7 +68,7 @@ public class TransientBlobRequestSigner implements BlobRequestSigner {
HttpRequest request = HttpRequest.builder().method("PUT").endpoint( HttpRequest request = HttpRequest.builder().method("PUT").endpoint(
URI.create(String.format("%s/%s/%s", endpoint.get(), container, blob.getMetadata().getName()))).payload( URI.create(String.format("%s/%s/%s", endpoint.get(), container, blob.getMetadata().getName()))).payload(
blob.getPayload()).headers( blob.getPayload()).headers(
HttpUtils.getContentHeadersFromMetadata(blob.getMetadata().getContentMetadata())).build(); contentMetadataCodec.toHeaders(blob.getMetadata().getContentMetadata())).build();
return basicAuth.filter(request); return basicAuth.filter(request);
} }

View File

@ -21,6 +21,7 @@ package org.jclouds.blobstore.domain;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Date;
import java.util.Map; import java.util.Map;
import org.jclouds.blobstore.domain.internal.BlobBuilderImpl; import org.jclouds.blobstore.domain.internal.BlobBuilderImpl;
@ -117,7 +118,7 @@ public interface BlobBuilder {
PayloadBlobBuilder contentEncoding(String contentEncoding); 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.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Date;
import java.util.Map; import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
@ -222,7 +223,7 @@ public class BlobBuilderImpl implements BlobBuilder {
} }
@Override @Override
public PayloadBlobBuilder expires(String expires) { public PayloadBlobBuilder expires(Date expires) {
payload.getContentMetadata().setExpires(expires); payload.getContentMetadata().setExpires(expires);
return this; return this;
} }

View File

@ -23,6 +23,7 @@ import static org.jclouds.blobstore.util.BlobStoreUtils.getContentAsStringOrNull
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import java.io.IOException; import java.io.IOException;
import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; 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) protected void assertConsistencyAwareBlobInLocation(final String containerName, final String blobName, final Location loc)
throws InterruptedException { throws InterruptedException {
assertConsistencyAware(new Runnable() { 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.Named;
import javax.inject.Singleton; import javax.inject.Singleton;
import javax.ws.rs.core.HttpHeaders;
import org.jclouds.Constants; import org.jclouds.Constants;
import org.jclouds.crypto.CryptoStreams; 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.Joiner;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Splitter; 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.Multimap;
import com.google.common.collect.SortedSetMultimap; import com.google.common.collect.SortedSetMultimap;
import com.google.common.collect.TreeMultimap; 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) { public static byte[] toByteArrayOrNull(PayloadEnclosing response) {
if (response.getPayload() != null) { if (response.getPayload() != null) {
InputStream input = response.getPayload().getInput(); 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.IOExceptionRetryHandler;
import org.jclouds.http.handlers.DelegatingErrorHandler; import org.jclouds.http.handlers.DelegatingErrorHandler;
import org.jclouds.http.handlers.DelegatingRetryHandler; import org.jclouds.http.handlers.DelegatingRetryHandler;
import org.jclouds.io.ContentMetadataCodec;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
import org.jclouds.util.Throwables2; import org.jclouds.util.Throwables2;
@ -56,6 +57,7 @@ import com.google.common.io.NullOutputStream;
*/ */
public abstract class BaseHttpCommandExecutorService<Q> implements HttpCommandExecutorService { public abstract class BaseHttpCommandExecutorService<Q> implements HttpCommandExecutorService {
protected final HttpUtils utils; protected final HttpUtils utils;
protected final ContentMetadataCodec contentMetadataCodec;
private final DelegatingRetryHandler retryHandler; private final DelegatingRetryHandler retryHandler;
private final IOExceptionRetryHandler ioRetryHandler; private final IOExceptionRetryHandler ioRetryHandler;
@ -71,11 +73,12 @@ public abstract class BaseHttpCommandExecutorService<Q> implements HttpCommandEx
protected final HttpWire wire; protected final HttpWire wire;
@Inject @Inject
protected BaseHttpCommandExecutorService(HttpUtils utils, protected BaseHttpCommandExecutorService(HttpUtils utils, ContentMetadataCodec contentMetadataCodec,
@Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioWorkerExecutor, @Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioWorkerExecutor,
DelegatingRetryHandler retryHandler, IOExceptionRetryHandler ioRetryHandler, DelegatingRetryHandler retryHandler, IOExceptionRetryHandler ioRetryHandler,
DelegatingErrorHandler errorHandler, HttpWire wire) { DelegatingErrorHandler errorHandler, HttpWire wire) {
this.utils = checkNotNull(utils, "utils"); this.utils = checkNotNull(utils, "utils");
this.contentMetadataCodec = checkNotNull(contentMetadataCodec, "contentMetadataCodec");
this.retryHandler = checkNotNull(retryHandler, "retryHandler"); this.retryHandler = checkNotNull(retryHandler, "retryHandler");
this.ioRetryHandler = checkNotNull(ioRetryHandler, "ioRetryHandler"); this.ioRetryHandler = checkNotNull(ioRetryHandler, "ioRetryHandler");
this.errorHandler = checkNotNull(errorHandler, "errorHandler"); this.errorHandler = checkNotNull(errorHandler, "errorHandler");

View File

@ -54,7 +54,6 @@ import javax.ws.rs.core.HttpHeaders;
import org.jclouds.Constants; import org.jclouds.Constants;
import org.jclouds.JcloudsVersion; import org.jclouds.JcloudsVersion;
import org.jclouds.crypto.CryptoStreams;
import org.jclouds.http.HttpCommandExecutorService; import org.jclouds.http.HttpCommandExecutorService;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponse;
@ -62,6 +61,7 @@ import org.jclouds.http.HttpUtils;
import org.jclouds.http.IOExceptionRetryHandler; import org.jclouds.http.IOExceptionRetryHandler;
import org.jclouds.http.handlers.DelegatingErrorHandler; import org.jclouds.http.handlers.DelegatingErrorHandler;
import org.jclouds.http.handlers.DelegatingRetryHandler; import org.jclouds.http.handlers.DelegatingRetryHandler;
import org.jclouds.io.ContentMetadataCodec;
import org.jclouds.io.MutableContentMetadata; import org.jclouds.io.MutableContentMetadata;
import org.jclouds.io.Payload; import org.jclouds.io.Payload;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
@ -90,13 +90,13 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe
private final Field methodField; private final Field methodField;
@Inject @Inject
public JavaUrlHttpCommandExecutorService(HttpUtils utils, public JavaUrlHttpCommandExecutorService(HttpUtils utils, ContentMetadataCodec contentMetadataCodec,
@Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioWorkerExecutor, @Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioWorkerExecutor,
DelegatingRetryHandler retryHandler, IOExceptionRetryHandler ioRetryHandler, DelegatingRetryHandler retryHandler, IOExceptionRetryHandler ioRetryHandler,
DelegatingErrorHandler errorHandler, HttpWire wire, @Named("untrusted") HostnameVerifier verifier, DelegatingErrorHandler errorHandler, HttpWire wire, @Named("untrusted") HostnameVerifier verifier,
@Named("untrusted") Supplier<SSLContext> untrustedSSLContextProvider) throws SecurityException, @Named("untrusted") Supplier<SSLContext> untrustedSSLContextProvider) throws SecurityException,
NoSuchFieldException { NoSuchFieldException {
super(utils, ioWorkerExecutor, retryHandler, ioRetryHandler, errorHandler, wire); super(utils, contentMetadataCodec, ioWorkerExecutor, retryHandler, ioRetryHandler, errorHandler, wire);
if (utils.getMaxConnections() > 0) if (utils.getMaxConnections() > 0)
System.setProperty("http.maxConnections", String.valueOf(checkNotNull(utils, "utils").getMaxConnections())); System.setProperty("http.maxConnections", String.valueOf(checkNotNull(utils, "utils").getMaxConnections()));
this.untrustedSSLContextProvider = checkNotNull(untrustedSSLContextProvider, "untrustedSSLContextProvider"); this.untrustedSSLContextProvider = checkNotNull(untrustedSSLContextProvider, "untrustedSSLContextProvider");
@ -136,7 +136,7 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe
ImmutableMultimap<String, String> headers = headerBuilder.build(); ImmutableMultimap<String, String> headers = headerBuilder.build();
if (in != null) { if (in != null) {
Payload payload = newInputStreamPayload(in); Payload payload = newInputStreamPayload(in);
payload.getContentMetadata().setPropertiesFromHttpHeaders(headers); contentMetadataCodec.fromHeaders(payload.getContentMetadata(), headers);
builder.payload(payload); builder.payload(payload);
} }
builder.headers(RestAnnotationProcessor.filterOutContentHeaders(headers)); builder.headers(RestAnnotationProcessor.filterOutContentHeaders(headers));
@ -214,18 +214,9 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe
if (request.getPayload() != null) { if (request.getPayload() != null) {
MutableContentMetadata md = request.getPayload().getContentMetadata(); MutableContentMetadata md = request.getPayload().getContentMetadata();
if (md.getContentMD5() != null) for (Map.Entry<String,String> entry : contentMetadataCodec.toHeaders(md).entries()) {
connection.setRequestProperty("Content-MD5", CryptoStreams.base64(md.getContentMD5())); connection.setRequestProperty(entry.getKey(), entry.getValue());
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());
if (chunked) { if (chunked) {
connection.setChunkedStreamingMode(8196); connection.setChunkedStreamingMode(8196);
} else { } 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_LENGTH;
import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE; 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 java.util.Set;
import org.jclouds.javax.annotation.Nullable; import org.jclouds.javax.annotation.Nullable;
@ -32,7 +34,11 @@ import com.google.common.collect.ImmutableSet;
*/ */
public interface ContentMetadata { public interface ContentMetadata {
public static final Set<String> HTTP_HEADERS = ImmutableSet.of(CONTENT_LENGTH, "Content-MD5", CONTENT_TYPE, 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. * Returns the total size of the payload, or the chunk that's available.
@ -89,10 +95,12 @@ public interface ContentMetadata {
/** /**
* Gives the date/time after which the response is considered stale. * 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"/> * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21"/>
*/ */
@Nullable @Nullable
String getExpires(); Date getExpires();
ContentMetadataBuilder toBuilder(); ContentMetadataBuilder toBuilder();

View File

@ -18,21 +18,14 @@
*/ */
package org.jclouds.io; 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.io.Serializable;
import java.util.Arrays; 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.io.payloads.BaseImmutableContentMetadata;
import org.jclouds.javax.annotation.Nullable; import org.jclouds.javax.annotation.Nullable;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.Multimap;
/** /**
* @author Adrian Cole * @author Adrian Cole
@ -51,34 +44,7 @@ public class ContentMetadataBuilder implements Serializable {
protected String contentDisposition; protected String contentDisposition;
protected String contentLanguage; protected String contentLanguage;
protected String contentEncoding; protected String contentEncoding;
protected String expires; protected Date 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;
}
public ContentMetadataBuilder contentLength(@Nullable Long contentLength) { public ContentMetadataBuilder contentLength(@Nullable Long contentLength) {
this.contentLength = contentLength; this.contentLength = contentLength;
@ -116,7 +82,7 @@ public class ContentMetadataBuilder implements Serializable {
return this; return this;
} }
public ContentMetadataBuilder expires(@Nullable String expires) { public ContentMetadataBuilder expires(@Nullable Date expires) {
this.expires = expires; this.expires = expires;
return this; 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; package org.jclouds.io;
import java.util.Date;
import org.jclouds.javax.annotation.Nullable; import org.jclouds.javax.annotation.Nullable;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
@ -26,12 +28,6 @@ import com.google.common.collect.Multimap;
* @author Adrian Cole * @author Adrian Cole
*/ */
public interface MutableContentMetadata extends ContentMetadata { 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); void setContentLength(@Nullable Long contentLength);
@ -66,5 +62,5 @@ public interface MutableContentMetadata extends ContentMetadata {
*/ */
void setContentEncoding(@Nullable String contentEncoding); 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; package org.jclouds.io.payloads;
import java.io.Serializable; import java.io.Serializable;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date;
import org.jclouds.io.ContentMetadata; import org.jclouds.io.ContentMetadata;
import org.jclouds.io.ContentMetadataBuilder; import org.jclouds.io.ContentMetadataBuilder;
@ -39,10 +42,10 @@ public class BaseImmutableContentMetadata implements ContentMetadata, Serializab
protected String contentDisposition; protected String contentDisposition;
protected String contentLanguage; protected String contentLanguage;
protected String contentEncoding; protected String contentEncoding;
protected String expires; protected Date expires;
public BaseImmutableContentMetadata(String contentType, Long contentLength, byte[] contentMD5, 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.contentType = contentType;
this.contentLength = contentLength; this.contentLength = contentLength;
this.contentMD5 = contentMD5; this.contentMD5 = contentMD5;
@ -110,8 +113,8 @@ public class BaseImmutableContentMetadata implements ContentMetadata, Serializab
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public String getExpires() { public Date getExpires() {
return this.expires; return expires;
} }
@Override @Override

View File

@ -19,6 +19,7 @@
package org.jclouds.io.payloads; package org.jclouds.io.payloads;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date;
import org.jclouds.io.ContentMetadata; import org.jclouds.io.ContentMetadata;
import org.jclouds.io.ContentMetadataBuilder; import org.jclouds.io.ContentMetadataBuilder;
@ -34,11 +35,6 @@ public class BaseMutableContentMetadata extends ContentMetadataBuilder implement
/** The serialVersionUID */ /** The serialVersionUID */
private static final long serialVersionUID = 8364286391963469370L; private static final long serialVersionUID = 8364286391963469370L;
@Override
public void setPropertiesFromHttpHeaders(Multimap<String, String> headers) {
fromHttpHeaders(headers);
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@ -145,7 +141,7 @@ public class BaseMutableContentMetadata extends ContentMetadataBuilder implement
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public void setExpires(@Nullable String expires) { public void setExpires(@Nullable Date expires) {
expires(expires); expires(expires);
} }
@ -153,8 +149,8 @@ public class BaseMutableContentMetadata extends ContentMetadataBuilder implement
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public String getExpires() { public Date getExpires() {
return this.expires; return expires;
} }
@Override @Override

View File

@ -89,6 +89,7 @@ import org.jclouds.http.options.HttpRequestOptions;
import org.jclouds.http.utils.ModifyRequest; import org.jclouds.http.utils.ModifyRequest;
import org.jclouds.internal.ClassMethodArgs; import org.jclouds.internal.ClassMethodArgs;
import org.jclouds.io.ContentMetadata; import org.jclouds.io.ContentMetadata;
import org.jclouds.io.ContentMetadataCodec;
import org.jclouds.io.Payload; import org.jclouds.io.Payload;
import org.jclouds.io.PayloadEnclosing; import org.jclouds.io.PayloadEnclosing;
import org.jclouds.io.Payloads; import org.jclouds.io.Payloads;
@ -255,6 +256,7 @@ public class RestAnnotationProcessor<T> {
private final ParseSax.Factory parserFactory; private final ParseSax.Factory parserFactory;
private final HttpUtils utils; private final HttpUtils utils;
private final ContentMetadataCodec contentMetadataCodec;
private final Provider<UriBuilder> uriBuilderProvider; private final Provider<UriBuilder> uriBuilderProvider;
private final LoadingCache<Class<?>, Boolean> seedAnnotationCache; private final LoadingCache<Class<?>, Boolean> seedAnnotationCache;
private final String apiVersion; private final String apiVersion;
@ -321,11 +323,12 @@ public class RestAnnotationProcessor<T> {
@Inject @Inject
public RestAnnotationProcessor(Injector injector, LoadingCache<Class<?>, Boolean> seedAnnotationCache, public RestAnnotationProcessor(Injector injector, LoadingCache<Class<?>, Boolean> seedAnnotationCache,
@ApiVersion String apiVersion, @BuildVersion String buildVersion, ParseSax.Factory parserFactory, @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.declaring = (Class<T>) typeLiteral.getRawType();
this.injector = injector; this.injector = injector;
this.parserFactory = parserFactory; this.parserFactory = parserFactory;
this.utils = utils; this.utils = utils;
this.contentMetadataCodec = contentMetadataCodec;
this.uriBuilderProvider = injector.getProvider(UriBuilder.class); this.uriBuilderProvider = injector.getProvider(UriBuilder.class);
this.seedAnnotationCache = seedAnnotationCache; this.seedAnnotationCache = seedAnnotationCache;
seedAnnotationCache.get(declaring); seedAnnotationCache.get(declaring);
@ -545,8 +548,9 @@ public class RestAnnotationProcessor<T> {
request = decorateRequest(request); request = decorateRequest(request);
} }
if (request.getPayload() != null) if (request.getPayload() != null) {
request.getPayload().getContentMetadata().setPropertiesFromHttpHeaders(headers); contentMetadataCodec.fromHeaders(request.getPayload().getContentMetadata(), headers);
}
utils.checkRequestHasRequiredProperties(request); utils.checkRequestHasRequiredProperties(request);
return request; return request;
} catch (ExecutionException e) { } catch (ExecutionException e) {

View File

@ -44,7 +44,9 @@ import org.jclouds.http.TransformingHttpCommandImpl;
import org.jclouds.http.functions.ReturnStringIf2xx; import org.jclouds.http.functions.ReturnStringIf2xx;
import org.jclouds.http.internal.HttpWire; import org.jclouds.http.internal.HttpWire;
import org.jclouds.http.internal.JavaUrlHttpCommandExecutorService; import org.jclouds.http.internal.JavaUrlHttpCommandExecutorService;
import org.jclouds.io.ContentMetadataCodec;
import org.jclouds.io.Payloads; import org.jclouds.io.Payloads;
import org.jclouds.io.ContentMetadataCodec.DefaultContentMetadataCodec;
import org.jclouds.rest.internal.RestAnnotationProcessor; import org.jclouds.rest.internal.RestAnnotationProcessor;
import org.testng.annotations.BeforeTest; import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@ -105,15 +107,16 @@ public class BackoffLimitedRetryHandlerTest {
} }
}; };
private HttpUtils utils;
@BeforeTest @BeforeTest
void setupExecutorService() throws Exception { void setupExecutorService() throws Exception {
ExecutorService execService = Executors.newCachedThreadPool(); ExecutorService execService = Executors.newCachedThreadPool();
BackoffLimitedRetryHandler backoff = new BackoffLimitedRetryHandler(); 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); 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 DelegatingRetryHandler(backoff, retry), new BackoffLimitedRetryHandler(),
new DelegatingErrorHandler(), new HttpWire(), new HostnameVerifier() { 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.IOExceptionRetryHandler;
import org.jclouds.http.handlers.DelegatingErrorHandler; import org.jclouds.http.handlers.DelegatingErrorHandler;
import org.jclouds.http.handlers.DelegatingRetryHandler; import org.jclouds.http.handlers.DelegatingRetryHandler;
import org.jclouds.io.ContentMetadataCodec;
import org.jclouds.rest.internal.GeneratedHttpRequest; import org.jclouds.rest.internal.GeneratedHttpRequest;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
@ -86,13 +87,13 @@ public class TrackingJavaUrlHttpCommandExecutorService extends JavaUrlHttpComman
} }
@Inject @Inject
public TrackingJavaUrlHttpCommandExecutorService(HttpUtils utils, public TrackingJavaUrlHttpCommandExecutorService(HttpUtils utils, ContentMetadataCodec contentMetadataCodec,
@Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioWorkerExecutor, @Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioWorkerExecutor,
DelegatingRetryHandler retryHandler, IOExceptionRetryHandler ioRetryHandler, DelegatingRetryHandler retryHandler, IOExceptionRetryHandler ioRetryHandler,
DelegatingErrorHandler errorHandler, HttpWire wire, @Named("untrusted") HostnameVerifier verifier, DelegatingErrorHandler errorHandler, HttpWire wire, @Named("untrusted") HostnameVerifier verifier,
@Named("untrusted") Supplier<SSLContext> untrustedSSLContextProvider, List<HttpCommand> commandsInvoked) @Named("untrusted") Supplier<SSLContext> untrustedSSLContextProvider, List<HttpCommand> commandsInvoked)
throws SecurityException, NoSuchFieldException { throws SecurityException, NoSuchFieldException {
super(utils, ioWorkerExecutor, retryHandler, ioRetryHandler, errorHandler, wire, verifier, super(utils, contentMetadataCodec, ioWorkerExecutor, retryHandler, ioRetryHandler, errorHandler, wire, verifier,
untrustedSSLContextProvider); untrustedSSLContextProvider);
this.commandsInvoked = commandsInvoked; this.commandsInvoked = commandsInvoked;
} }

View File

@ -25,9 +25,9 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Properties; import java.util.Properties;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger; 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.handlers.DelegatingRetryHandler;
import org.jclouds.http.internal.BaseHttpCommandExecutorService; import org.jclouds.http.internal.BaseHttpCommandExecutorService;
import org.jclouds.http.internal.HttpWire; import org.jclouds.http.internal.HttpWire;
import org.jclouds.io.ContentMetadataCodec;
import org.jclouds.io.CopyInputStreamInputSupplierMap; import org.jclouds.io.CopyInputStreamInputSupplierMap;
import org.jclouds.io.Payload; import org.jclouds.io.Payload;
import org.jclouds.io.Payloads; import org.jclouds.io.Payloads;
import org.jclouds.io.ContentMetadataCodec.DefaultContentMetadataCodec;
import org.jclouds.logging.config.NullLoggingModule; import org.jclouds.logging.config.NullLoggingModule;
import org.jclouds.providers.ProviderMetadata; import org.jclouds.providers.ProviderMetadata;
import org.jclouds.rest.RestApiMetadata; import org.jclouds.rest.RestApiMetadata;
@ -118,6 +120,8 @@ public abstract class BaseRestClientExpectTest<S> {
protected String provider = "mock"; 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 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. * override suppliers of dates so that the test results are predicatable.
@ -188,10 +192,11 @@ public abstract class BaseRestClientExpectTest<S> {
@Inject @Inject
public ExpectHttpCommandExecutorService(Function<HttpRequest, HttpResponse> fn, HttpUtils utils, public ExpectHttpCommandExecutorService(Function<HttpRequest, HttpResponse> fn, HttpUtils utils,
ContentMetadataCodec contentMetadataCodec,
@Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioExecutor, @Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioExecutor,
IOExceptionRetryHandler ioRetryHandler, DelegatingRetryHandler retryHandler, IOExceptionRetryHandler ioRetryHandler, DelegatingRetryHandler retryHandler,
DelegatingErrorHandler errorHandler, HttpWire wire) { DelegatingErrorHandler errorHandler, HttpWire wire) {
super(utils, ioExecutor, retryHandler, ioRetryHandler, errorHandler, wire); super(utils, contentMetadataCodec, ioExecutor, retryHandler, ioRetryHandler, errorHandler, wire);
this.fn = checkNotNull(fn, "fn"); this.fn = checkNotNull(fn, "fn");
} }
@ -471,7 +476,7 @@ public abstract class BaseRestClientExpectTest<S> {
builder.append(header.getKey()).append(": ").append(header.getValue()).append('\n'); builder.append(header.getKey()).append(": ").append(header.getValue()).append('\n');
} }
if (request.getPayload() != null) { if (request.getPayload() != null) {
for (Entry<String, String> header : HttpUtils.getContentHeadersFromMetadata( for (Entry<String, String> header : contentMetadataCodec.toHeaders(
request.getPayload().getContentMetadata()).entries()) { request.getPayload().getContentMetadata()).entries()) {
builder.append(header.getKey()).append(": ").append(header.getValue()).append('\n'); 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.io.IOException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Date;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import org.jclouds.Constants; import org.jclouds.Constants;
@ -42,7 +43,6 @@ import org.jclouds.http.functions.ParseSax;
import org.jclouds.io.MutableContentMetadata; import org.jclouds.io.MutableContentMetadata;
import org.jclouds.javax.annotation.Nullable; import org.jclouds.javax.annotation.Nullable;
import org.jclouds.rest.functions.MapHttp4xxCodesToExceptions; import org.jclouds.rest.functions.MapHttp4xxCodesToExceptions;
import org.jclouds.rest.internal.RestAnnotationProcessor;
import org.jclouds.util.Strings2; import org.jclouds.util.Strings2;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@ -89,7 +89,7 @@ public abstract class BaseRestClientTest {
assertPayloadEquals(request, toMatch, contentType, contentMD5, null); 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); 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, protected void assertPayloadEquals(HttpRequest request, String toMatch, String contentType,
String contentDispositon, String contentEncoding, String contentLanguage, boolean contentMD5, String contentDispositon, String contentEncoding, String contentLanguage, boolean contentMD5,
String expires) { Date expires) {
if (request.getPayload() == null) { if (request.getPayload() == null) {
assertNull(toMatch); assertNull(toMatch);
} else { } else {
@ -123,7 +123,7 @@ public abstract class BaseRestClientTest {
} }
protected void assertContentHeadersEqual(HttpRequest request, String contentType, String contentDispositon, 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(); MutableContentMetadata md = request.getPayload().getContentMetadata();
if (request.getFirstHeaderOrNull(TRANSFER_ENCODING) == null) { if (request.getFirstHeaderOrNull(TRANSFER_ENCODING) == null) {
assertEquals(md.getContentLength(), length); 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.handlers.DelegatingRetryHandler;
import org.jclouds.http.internal.BaseHttpCommandExecutorService; import org.jclouds.http.internal.BaseHttpCommandExecutorService;
import org.jclouds.http.internal.HttpWire; import org.jclouds.http.internal.HttpWire;
import org.jclouds.io.ContentMetadataCodec;
import org.jclouds.io.Payload; import org.jclouds.io.Payload;
import org.jclouds.io.Payloads; import org.jclouds.io.Payloads;
import org.jclouds.rest.internal.RestAnnotationProcessor; import org.jclouds.rest.internal.RestAnnotationProcessor;
@ -55,19 +56,21 @@ import com.google.inject.Inject;
*/ */
public class ApacheHCHttpCommandExecutorService extends BaseHttpCommandExecutorService<HttpUriRequest> { public class ApacheHCHttpCommandExecutorService extends BaseHttpCommandExecutorService<HttpUriRequest> {
private final HttpClient client; private final HttpClient client;
private final ApacheHCUtils apacheHCUtils;
@Inject @Inject
ApacheHCHttpCommandExecutorService(HttpUtils utils, ApacheHCHttpCommandExecutorService(HttpUtils utils, ContentMetadataCodec contentMetadataCodec,
@Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioWorkerExecutor, @Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioWorkerExecutor,
DelegatingRetryHandler retryHandler, IOExceptionRetryHandler ioRetryHandler, DelegatingRetryHandler retryHandler, IOExceptionRetryHandler ioRetryHandler,
DelegatingErrorHandler errorHandler, HttpWire wire, HttpClient client) { 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.client = client;
this.apacheHCUtils = new ApacheHCUtils(contentMetadataCodec);
} }
@Override @Override
protected HttpUriRequest convert(HttpRequest request) throws IOException { 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) if (request.getPayload() != null && request.getPayload().getContentMetadata().getContentMD5() != null)
returnVal.addHeader("Content-MD5", CryptoStreams.md5Base64(request.getPayload())); returnVal.addHeader("Content-MD5", CryptoStreams.md5Base64(request.getPayload()));
return returnVal; return returnVal;
@ -93,8 +96,9 @@ public class ApacheHCHttpCommandExecutorService extends BaseHttpCommandExecutorS
for (Header header : apacheResponse.getAllHeaders()) { for (Header header : apacheResponse.getAllHeaders()) {
headers.put(header.getName(), header.getValue()); headers.put(header.getName(), header.getValue());
} }
if (payload != null) if (payload != null) {
payload.getContentMetadata().setPropertiesFromHttpHeaders(headers); contentMetadataCodec.fromHeaders(payload.getContentMetadata(), headers);
}
return new HttpResponse(apacheResponse.getStatusLine().getStatusCode(), apacheResponse.getStatusLine() return new HttpResponse(apacheResponse.getStatusLine().getStatusCode(), apacheResponse.getStatusLine()
.getReasonPhrase(), payload, RestAnnotationProcessor.filterOutContentHeaders(headers)); .getReasonPhrase(), payload, RestAnnotationProcessor.filterOutContentHeaders(headers));
} }

View File

@ -23,6 +23,8 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.Set;
import javax.inject.Singleton; import javax.inject.Singleton;
import javax.ws.rs.HttpMethod; import javax.ws.rs.HttpMethod;
@ -45,6 +47,8 @@ import org.apache.http.entity.StringEntity;
import org.apache.http.params.CoreProtocolPNames; import org.apache.http.params.CoreProtocolPNames;
import org.jclouds.JcloudsVersion; import org.jclouds.JcloudsVersion;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.io.ContentMetadataCodec;
import org.jclouds.io.MutableContentMetadata;
import org.jclouds.io.Payload; import org.jclouds.io.Payload;
import org.jclouds.io.payloads.BasePayload; import org.jclouds.io.payloads.BasePayload;
import org.jclouds.io.payloads.ByteArrayPayload; import org.jclouds.io.payloads.ByteArrayPayload;
@ -53,6 +57,7 @@ import org.jclouds.io.payloads.FilePayload;
import org.jclouds.io.payloads.StringPayload; import org.jclouds.io.payloads.StringPayload;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
/** /**
* *
@ -63,7 +68,13 @@ public class ApacheHCUtils {
//TODO: look up httpclient version //TODO: look up httpclient version
public static final String USER_AGENT = String.format("jclouds/%s httpclient/%s", JcloudsVersion.get(), "4.1.1"); public static final String USER_AGENT = String.format("jclouds/%s httpclient/%s", JcloudsVersion.get(), "4.1.1");
public static HttpUriRequest convertToApacheRequest(HttpRequest request) { private final ContentMetadataCodec contentMetadataCodec;
public ApacheHCUtils(ContentMetadataCodec contentMetadataCodec) {
this.contentMetadataCodec = contentMetadataCodec;
}
public HttpUriRequest convertToApacheRequest(HttpRequest request) {
HttpUriRequest apacheRequest; HttpUriRequest apacheRequest;
if (request.getMethod().equals(HttpMethod.HEAD)) { if (request.getMethod().equals(HttpMethod.HEAD)) {
apacheRequest = new HttpHead(request.getEndpoint()); apacheRequest = new HttpHead(request.getEndpoint());
@ -120,7 +131,7 @@ public class ApacheHCUtils {
return apacheRequest; 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; payload = payload instanceof DelegatingPayload ? DelegatingPayload.class.cast(payload).getDelegate() : payload;
if (payload instanceof StringPayload) { if (payload instanceof StringPayload) {
StringEntity nStringEntity = null; StringEntity nStringEntity = null;
@ -142,18 +153,20 @@ public class ApacheHCUtils {
InputStream inputStream = payload.getInput(); InputStream inputStream = payload.getInput();
if (payload.getContentMetadata().getContentLength() == null) if (payload.getContentMetadata().getContentLength() == null)
throw new IllegalArgumentException("you must specify size when content is an InputStream"); throw new IllegalArgumentException("you must specify size when content is an InputStream");
InputStreamEntity Entity = new InputStreamEntity(inputStream, payload.getContentMetadata().getContentLength()); InputStreamEntity entity = new InputStreamEntity(inputStream, payload.getContentMetadata().getContentLength());
Entity.setContentType(payload.getContentMetadata().getContentType()); entity.setContentType(payload.getContentMetadata().getContentType());
apacheRequest.setEntity(Entity); apacheRequest.setEntity(entity);
} }
if (payload.getContentMetadata().getContentDisposition() != null)
apacheRequest.addHeader("Content-Disposition", payload.getContentMetadata().getContentDisposition()); // TODO Reproducing old behaviour exactly; ignoring Content-Type, Content-Length and Content-MD5
if (payload.getContentMetadata().getContentEncoding() != null) Set<String> desiredHeaders = ImmutableSet.of("Content-Disposition", "Content-Encoding", "Content-Language", "Expires");
apacheRequest.addHeader("Content-Encoding", payload.getContentMetadata().getContentEncoding()); MutableContentMetadata md = payload.getContentMetadata();
if (payload.getContentMetadata().getContentLanguage() != null) for (Map.Entry<String,String> entry : contentMetadataCodec.toHeaders(md).entries()) {
apacheRequest.addHeader("Content-Language", payload.getContentMetadata().getContentLanguage()); if (desiredHeaders.contains(entry.getKey())) {
if (payload.getContentMetadata().getExpires() != null) apacheRequest.addHeader(entry.getKey(), entry.getValue());
apacheRequest.addHeader("Expires", payload.getContentMetadata().getExpires()); }
}
assert (apacheRequest.getEntity() != null); 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.HttpRequest;
import org.jclouds.http.HttpUtils; import org.jclouds.http.HttpUtils;
import org.jclouds.io.ContentMetadataCodec;
import org.jclouds.io.Payload; import org.jclouds.io.Payload;
import com.google.appengine.api.urlfetch.FetchOptions; import com.google.appengine.api.urlfetch.FetchOptions;
@ -52,14 +53,17 @@ import com.google.common.io.Closeables;
@Singleton @Singleton
public class ConvertToGaeRequest implements Function<HttpRequest, HTTPRequest> { public class ConvertToGaeRequest implements Function<HttpRequest, HTTPRequest> {
public static final String USER_AGENT = "jclouds/1.0 urlfetch/1.4.3"; 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 // http://code.google.com/appengine/docs/java/urlfetch/overview.html
public final Set<String> prohibitedHeaders = ImmutableSet.of("Accept-Encoding", "Content-Length", "Host", "Var", public final Set<String> prohibitedHeaders = ImmutableSet.of("Accept-Encoding", "Content-Length", "Host", "Var",
"X-Forwarded-For"); "X-Forwarded-For");
protected final HttpUtils utils;
protected final ContentMetadataCodec contentMetadataCodec;
@Inject @Inject
ConvertToGaeRequest(HttpUtils utils) { ConvertToGaeRequest(HttpUtils utils, ContentMetadataCodec contentMetadataCodec) {
this.utils = utils; this.utils = utils;
this.contentMetadataCodec = contentMetadataCodec;
} }
/** /**
@ -115,7 +119,7 @@ public class ConvertToGaeRequest implements Function<HttpRequest, HTTPRequest> {
Closeables.closeQuietly(input); Closeables.closeQuietly(input);
} }
for (Entry<String, String> header : HttpUtils.getContentHeadersFromMetadata( for (Entry<String, String> header : contentMetadataCodec.toHeaders(
request.getPayload().getContentMetadata()).entries()) { request.getPayload().getContentMetadata()).entries()) {
if (!prohibitedHeaders.contains(header.getKey())) if (!prohibitedHeaders.contains(header.getKey()))
gaeRequest.setHeader(new HTTPHeader(header.getKey(), header.getValue())); gaeRequest.setHeader(new HTTPHeader(header.getKey(), header.getValue()));

View File

@ -21,6 +21,7 @@ package org.jclouds.gae;
import javax.inject.Singleton; import javax.inject.Singleton;
import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponse;
import org.jclouds.io.ContentMetadataCodec;
import org.jclouds.io.Payload; import org.jclouds.io.Payload;
import org.jclouds.io.Payloads; import org.jclouds.io.Payloads;
import org.jclouds.rest.internal.RestAnnotationProcessor; 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.base.Function;
import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.inject.Inject;
/** /**
* *
@ -38,6 +40,13 @@ import com.google.common.collect.Multimap;
@Singleton @Singleton
public class ConvertToJcloudsResponse implements Function<HTTPResponse, HttpResponse> { public class ConvertToJcloudsResponse implements Function<HTTPResponse, HttpResponse> {
private final ContentMetadataCodec contentMetadataCodec;
@Inject
public ConvertToJcloudsResponse(ContentMetadataCodec contentMetadataCodec) {
this.contentMetadataCodec = contentMetadataCodec;
}
@Override @Override
public HttpResponse apply(HTTPResponse gaeResponse) { public HttpResponse apply(HTTPResponse gaeResponse) {
Payload payload = gaeResponse.getContent() != null ? Payloads.newByteArrayPayload(gaeResponse.getContent()) 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()); headers.put(header.getName(), header.getValue());
} }
if (payload != null) if (payload != null) {
payload.getContentMetadata().setPropertiesFromHttpHeaders(headers); contentMetadataCodec.fromHeaders(payload.getContentMetadata(), headers);
}
return new HttpResponse(gaeResponse.getResponseCode(), message, payload, return new HttpResponse(gaeResponse.getResponseCode(), message, payload,
RestAnnotationProcessor.filterOutContentHeaders(headers)); 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.handlers.DelegatingRetryHandler;
import org.jclouds.http.internal.BaseHttpCommandExecutorService; import org.jclouds.http.internal.BaseHttpCommandExecutorService;
import org.jclouds.http.internal.HttpWire; import org.jclouds.http.internal.HttpWire;
import org.jclouds.io.ContentMetadataCodec;
import com.google.appengine.api.urlfetch.HTTPRequest; import com.google.appengine.api.urlfetch.HTTPRequest;
import com.google.appengine.api.urlfetch.HTTPResponse; import com.google.appengine.api.urlfetch.HTTPResponse;
@ -60,11 +61,12 @@ public class GaeHttpCommandExecutorService extends BaseHttpCommandExecutorServic
@Inject @Inject
public GaeHttpCommandExecutorService(URLFetchService urlFetchService, HttpUtils utils, public GaeHttpCommandExecutorService(URLFetchService urlFetchService, HttpUtils utils,
ContentMetadataCodec contentMetadataCodec,
@Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioExecutor, @Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioExecutor,
IOExceptionRetryHandler ioRetryHandler, DelegatingRetryHandler retryHandler, IOExceptionRetryHandler ioRetryHandler, DelegatingRetryHandler retryHandler,
DelegatingErrorHandler errorHandler, HttpWire wire, ConvertToGaeRequest convertToGaeRequest, DelegatingErrorHandler errorHandler, HttpWire wire, ConvertToGaeRequest convertToGaeRequest,
ConvertToJcloudsResponse convertToJcloudsResponse) { ConvertToJcloudsResponse convertToJcloudsResponse) {
super(utils, ioExecutor, retryHandler, ioRetryHandler, errorHandler, wire); super(utils, contentMetadataCodec, ioExecutor, retryHandler, ioRetryHandler, errorHandler, wire);
this.urlFetchService = urlFetchService; this.urlFetchService = urlFetchService;
this.convertToGaeRequest = convertToGaeRequest; this.convertToGaeRequest = convertToGaeRequest;
this.convertToJcloudsResponse = convertToJcloudsResponse; this.convertToJcloudsResponse = convertToJcloudsResponse;

View File

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

View File

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

View File

@ -19,7 +19,6 @@
package org.jclouds.aws.s3.blobstore.integration; package org.jclouds.aws.s3.blobstore.integration;
import static org.jclouds.blobstore.options.CreateContainerOptions.Builder.publicRead; import static org.jclouds.blobstore.options.CreateContainerOptions.Builder.publicRead;
<<<<<<< HEAD
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import java.io.IOException; import java.io.IOException;
@ -30,24 +29,35 @@ import java.util.Set;
import org.jclouds.blobstore.BlobStore; import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.domain.BlobMetadata; import org.jclouds.blobstore.domain.BlobMetadata;
import org.jclouds.domain.Location; import org.jclouds.domain.Location;
=======
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.text.SimpleDateFormat; import java.text.ParseException;
import java.util.Date; import java.util.Date;
import org.jclouds.blobstore.BlobStore; 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.s3.blobstore.integration.S3ContainerLiveTest;
import org.jclouds.util.Strings2; import org.jclouds.util.Strings2;
import org.testng.annotations.Test; import org.testng.annotations.Test;
<<<<<<< HEAD
import com.google.common.base.Strings; import com.google.common.base.Strings;
=======
import com.google.common.base.Throwables; 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 * @author Adrian Cole
@ -63,23 +73,57 @@ public class AWSS3ContainerLiveTest extends S3ContainerLiveTest {
final String containerName = getScratchContainerName(); final String containerName = getScratchContainerName();
BlobStore blobStore = view.getBlobStore(); BlobStore blobStore = view.getBlobStore();
try { try {
final String RFC1123_PATTERN = "EEE, dd MMM yyyyy HH:mm:ss z";
final String blobName = "hello"; 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.createContainerInLocation(null, containerName, publicRead());
blobStore.putBlob(containerName, blobStore.blobBuilder(blobName).payload(TEST_STRING).expires(expires).build()); blobStore.putBlob(containerName, blobStore.blobBuilder(blobName).payload(TEST_STRING).expires(expires).build());
assertConsistencyAware(new Runnable() { assertConsistencyAwareBlobExpiryMetadata(containerName, blobName, expires);
public void run() {
} 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 { try {
String actualExpires = view.getBlobStore().getBlob(containerName, blobName).getPayload().getContentMetadata().getExpires(); final String blobName = "hello";
assert expires.equals(actualExpires) : "expires="+actualExpires+"; expected="+expires;
} catch (Exception e) { naughtyBlobStore.createContainerInLocation(null, containerName, publicRead());
Throwables.propagate(e); naughtyBlobStore.putBlob(containerName, naughtyBlobStore.blobBuilder(blobName)
} .payload(TEST_STRING).expires(new Date(System.currentTimeMillis() + 60*1000)).build());
}
}); assertConsistencyAwareBlobExpiryMetadata(containerName, blobName, new Date(0));
} finally { } finally {
recycleContainer(containerName); 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, public BlobPropertiesImpl(BlobType type, String name, String container, URI url, Date lastModified, String eTag,
long size, String contentType, @Nullable byte[] contentMD5, @Nullable String contentMetadata, 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) { Map<String, String> metadata) {
this.type = checkNotNull(type, "type"); this.type = checkNotNull(type, "type");
this.leaseStatus = checkNotNull(leaseStatus, "leaseStatus"); this.leaseStatus = checkNotNull(leaseStatus, "leaseStatus");

View File

@ -35,6 +35,7 @@ import org.jclouds.crypto.CryptoStreams;
import org.jclouds.date.DateService; import org.jclouds.date.DateService;
import org.jclouds.http.HttpUtils; import org.jclouds.http.HttpUtils;
import org.jclouds.http.functions.ParseSax; import org.jclouds.http.functions.ParseSax;
import org.jclouds.io.ContentMetadataCodec;
import org.xml.sax.Attributes; import org.xml.sax.Attributes;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
@ -63,6 +64,7 @@ public class ContainerNameEnumerationResultsHandler extends ParseSax.HandlerWith
private StringBuilder currentText = new StringBuilder(); private StringBuilder currentText = new StringBuilder();
private final DateService dateParser; private final DateService dateParser;
private final ContentMetadataCodec contentMetadataCodec;
private String delimiter; private String delimiter;
private String currentName; private String currentName;
private long currentSize; private long currentSize;
@ -70,7 +72,7 @@ public class ContainerNameEnumerationResultsHandler extends ParseSax.HandlerWith
private String currentContentEncoding; private String currentContentEncoding;
private String currentContentLanguage; private String currentContentLanguage;
private BlobType currentBlobType; private BlobType currentBlobType;
private String currentExpires; private Date currentExpires;
private boolean inBlob; private boolean inBlob;
private boolean inBlobPrefix; private boolean inBlobPrefix;
private boolean inMetadata; private boolean inMetadata;
@ -80,8 +82,9 @@ public class ContainerNameEnumerationResultsHandler extends ParseSax.HandlerWith
private LeaseStatus currentLeaseStatus; private LeaseStatus currentLeaseStatus;
@Inject @Inject
public ContainerNameEnumerationResultsHandler(DateService dateParser) { public ContainerNameEnumerationResultsHandler(DateService dateParser, ContentMetadataCodec contentMetadataCodec) {
this.dateParser = dateParser; this.dateParser = dateParser;
this.contentMetadataCodec = contentMetadataCodec;
} }
public ListBlobsResponse getResult() { public ListBlobsResponse getResult() {
@ -175,9 +178,12 @@ public class ContainerNameEnumerationResultsHandler extends ParseSax.HandlerWith
if (currentContentLanguage.equals("")) if (currentContentLanguage.equals(""))
currentContentLanguage = null; currentContentLanguage = null;
} else if (qName.equals("Expires")) { } else if (qName.equals("Expires")) {
currentExpires = currentText.toString().trim(); String trimmedCurrentText = currentText.toString().trim();
if (currentExpires.equals("")) if (trimmedCurrentText.equals("")) {
currentExpires= null; currentExpires = null;
} else {
currentExpires = contentMetadataCodec.parseExpires(trimmedCurrentText);
}
} }
currentText = new StringBuilder(); currentText = new StringBuilder();
} }

View File

@ -21,6 +21,7 @@ package org.jclouds.azureblob.blobstore;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import java.io.IOException; import java.io.IOException;
import java.util.Date;
import org.jclouds.azureblob.AzureBlobAsyncClient; import org.jclouds.azureblob.AzureBlobAsyncClient;
import org.jclouds.azureblob.AzureBlobProviderMetadata; import org.jclouds.azureblob.AzureBlobProviderMetadata;
@ -86,7 +87,7 @@ public class AzureBlobRequestSignerTest extends BaseAsyncClientTest<AzureBlobAsy
blob.getPayload().getContentMetadata().setContentLength(2l); blob.getPayload().getContentMetadata().setContentLength(2l);
blob.getPayload().getContentMetadata().setContentMD5(new byte[] { 0, 2, 4, 8 }); blob.getPayload().getContentMetadata().setContentMD5(new byte[] { 0, 2, 4, 8 });
blob.getPayload().getContentMetadata().setContentType("text/plain"); 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); HttpRequest request = signer.signPutBlob("container", blob);
@ -94,7 +95,7 @@ public class AzureBlobRequestSignerTest extends BaseAsyncClientTest<AzureBlobAsy
assertNonPayloadHeadersEqual( assertNonPayloadHeadersEqual(
request, 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"); "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); assertEquals(request.getFilters().size(), 0);
} }