mirror of https://github.com/apache/jclouds.git
simplified request authorize syntax, fixed a couple issues in the signature relating to spaces. normalized to constants, rewrote metadata test to be easier to maintain
This commit is contained in:
parent
71eb7ea8b7
commit
01ef7140db
|
@ -32,6 +32,7 @@ import static org.jclouds.s3.reference.S3Constants.PROPERTY_S3_VIRTUAL_HOST_BUCK
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import org.jclouds.PropertiesBuilder;
|
import org.jclouds.PropertiesBuilder;
|
||||||
|
import org.jclouds.s3.reference.S3Headers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds properties used in S3 Connections
|
* Builds properties used in S3 Connections
|
||||||
|
@ -44,7 +45,7 @@ public class S3PropertiesBuilder extends PropertiesBuilder {
|
||||||
Properties properties = super.defaultProperties();
|
Properties properties = super.defaultProperties();
|
||||||
properties.setProperty(PROPERTY_API_VERSION, S3AsyncClient.VERSION);
|
properties.setProperty(PROPERTY_API_VERSION, S3AsyncClient.VERSION);
|
||||||
properties.setProperty(PROPERTY_AUTH_TAG, "AWS");
|
properties.setProperty(PROPERTY_AUTH_TAG, "AWS");
|
||||||
properties.setProperty(PROPERTY_HEADER_TAG, "amz");
|
properties.setProperty(PROPERTY_HEADER_TAG, S3Headers.DEFAULT_AMAZON_HEADERTAG);
|
||||||
properties.setProperty(PROPERTY_S3_SERVICE_PATH, "/");
|
properties.setProperty(PROPERTY_S3_SERVICE_PATH, "/");
|
||||||
properties.setProperty(PROPERTY_S3_VIRTUAL_HOST_BUCKETS, "true");
|
properties.setProperty(PROPERTY_S3_VIRTUAL_HOST_BUCKETS, "true");
|
||||||
properties.setProperty(PROPERTY_RELAX_HOSTNAME, "true");
|
properties.setProperty(PROPERTY_RELAX_HOSTNAME, "true");
|
||||||
|
@ -67,8 +68,8 @@ public class S3PropertiesBuilder extends PropertiesBuilder {
|
||||||
|
|
||||||
protected void setMetaPrefix() {
|
protected void setMetaPrefix() {
|
||||||
if (properties.getProperty(PROPERTY_USER_METADATA_PREFIX) == null) {
|
if (properties.getProperty(PROPERTY_USER_METADATA_PREFIX) == null) {
|
||||||
properties.setProperty(PROPERTY_USER_METADATA_PREFIX,
|
properties.setProperty(PROPERTY_USER_METADATA_PREFIX, String.format("x-%s-meta-", properties
|
||||||
String.format("x-%s-meta-", properties.getProperty(PROPERTY_HEADER_TAG)));
|
.getProperty(PROPERTY_HEADER_TAG)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,13 +20,17 @@
|
||||||
package org.jclouds.s3.filters;
|
package org.jclouds.s3.filters;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.collect.Iterables.any;
|
||||||
|
import static com.google.common.collect.Iterables.get;
|
||||||
import static org.jclouds.Constants.PROPERTY_CREDENTIAL;
|
import static org.jclouds.Constants.PROPERTY_CREDENTIAL;
|
||||||
import static org.jclouds.Constants.PROPERTY_IDENTITY;
|
import static org.jclouds.Constants.PROPERTY_IDENTITY;
|
||||||
import static org.jclouds.aws.reference.AWSConstants.PROPERTY_AUTH_TAG;
|
import static org.jclouds.aws.reference.AWSConstants.PROPERTY_AUTH_TAG;
|
||||||
import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG;
|
import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG;
|
||||||
|
import static org.jclouds.http.utils.ModifyRequest.parseQueryToMap;
|
||||||
|
import static org.jclouds.http.utils.ModifyRequest.replaceHeader;
|
||||||
import static org.jclouds.s3.reference.S3Constants.PROPERTY_S3_SERVICE_PATH;
|
import static org.jclouds.s3.reference.S3Constants.PROPERTY_S3_SERVICE_PATH;
|
||||||
import static org.jclouds.s3.reference.S3Constants.PROPERTY_S3_VIRTUAL_HOST_BUCKETS;
|
import static org.jclouds.s3.reference.S3Constants.PROPERTY_S3_VIRTUAL_HOST_BUCKETS;
|
||||||
|
import static org.jclouds.util.Strings2.toInputStream;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -43,8 +47,6 @@ import javax.inject.Singleton;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
|
||||||
import org.jclouds.Constants;
|
import org.jclouds.Constants;
|
||||||
import org.jclouds.s3.Bucket;
|
|
||||||
import org.jclouds.s3.reference.S3Headers;
|
|
||||||
import org.jclouds.crypto.Crypto;
|
import org.jclouds.crypto.Crypto;
|
||||||
import org.jclouds.crypto.CryptoStreams;
|
import org.jclouds.crypto.CryptoStreams;
|
||||||
import org.jclouds.date.TimeStamp;
|
import org.jclouds.date.TimeStamp;
|
||||||
|
@ -53,43 +55,44 @@ import org.jclouds.http.HttpRequest;
|
||||||
import org.jclouds.http.HttpRequestFilter;
|
import org.jclouds.http.HttpRequestFilter;
|
||||||
import org.jclouds.http.HttpUtils;
|
import org.jclouds.http.HttpUtils;
|
||||||
import org.jclouds.http.internal.SignatureWire;
|
import org.jclouds.http.internal.SignatureWire;
|
||||||
import org.jclouds.http.utils.ModifyRequest;
|
|
||||||
import org.jclouds.io.InputSuppliers;
|
import org.jclouds.io.InputSuppliers;
|
||||||
import org.jclouds.logging.Logger;
|
import org.jclouds.logging.Logger;
|
||||||
import org.jclouds.rest.RequestSigner;
|
import org.jclouds.rest.RequestSigner;
|
||||||
import org.jclouds.rest.internal.GeneratedHttpRequest;
|
import org.jclouds.rest.internal.GeneratedHttpRequest;
|
||||||
import org.jclouds.util.Strings2;
|
import org.jclouds.s3.Bucket;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.common.collect.ImmutableMap.Builder;
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
import com.google.common.collect.Multimaps;
|
import com.google.common.collect.Ordering;
|
||||||
import com.google.common.collect.SortedSetMultimap;
|
import com.google.common.collect.SortedSetMultimap;
|
||||||
import com.google.common.collect.TreeMultimap;
|
import com.google.common.collect.TreeMultimap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signs the S3 request.
|
* Signs the S3 request.
|
||||||
*
|
*
|
||||||
* @see <a href= "http://docs.amazonwebservices.com/AmazonS3/2006-03-01/dev/index.html?RESTAuthentication.html" />
|
* @see <a href=
|
||||||
|
* "http://docs.amazonwebservices.com/AmazonS3/2006-03-01/dev/index.html?RESTAuthentication.html"
|
||||||
|
* />
|
||||||
* @author Adrian Cole
|
* @author Adrian Cole
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSigner {
|
public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSigner {
|
||||||
|
private static final Predicate<Annotation> ANNOTATIONTYPE_BUCKET = new Predicate<Annotation>() {
|
||||||
|
public boolean apply(Annotation input) {
|
||||||
|
return input.annotationType().equals(Bucket.class);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private final String[] firstHeadersToSign = new String[] { HttpHeaders.DATE };
|
private final String[] firstHeadersToSign = new String[] { HttpHeaders.DATE };
|
||||||
|
|
||||||
/** Prefix for general Amazon headers: x-amz- */
|
public static Set<String> SIGNED_PARAMETERS = ImmutableSet.of("acl", "torrent", "logging", "location", "policy",
|
||||||
public static final String AMAZON_PREFIX = "x-amz-";
|
"requestPayment", "versioning", "versions", "versionId", "notification", "uploadId", "uploads",
|
||||||
|
"partNumber", "website", "response-content-type", "response-content-language", "response-expires",
|
||||||
|
"response-cache-control", "response-content-disposition", "response-content-encoding");
|
||||||
|
|
||||||
public static Set<String> SIGNED_PARAMETERS = ImmutableSet.of("acl", "torrent", "logging", "location", "policy", "requestPayment", "versioning",
|
|
||||||
"versions", "versionId", "notification", "uploadId", "uploads", "partNumber", "website",
|
|
||||||
"response-content-type", "response-content-language", "response-expires",
|
|
||||||
"response-cache-control", "response-content-disposition", "response-content-encoding");
|
|
||||||
|
|
||||||
private final SignatureWire signatureWire;
|
private final SignatureWire signatureWire;
|
||||||
private final String accessKey;
|
private final String accessKey;
|
||||||
private final String secretKey;
|
private final String secretKey;
|
||||||
|
@ -133,31 +136,27 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpRequest replaceAuthorizationHeader(HttpRequest request, String signature) {
|
HttpRequest replaceAuthorizationHeader(HttpRequest request, String signature) {
|
||||||
request = ModifyRequest.replaceHeader(request, HttpHeaders.AUTHORIZATION, authTag + " " + accessKey + ":"
|
request = replaceHeader(request, HttpHeaders.AUTHORIZATION, authTag + " " + accessKey + ":" + signature);
|
||||||
+ signature);
|
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpRequest replaceDateHeader(HttpRequest request) {
|
HttpRequest replaceDateHeader(HttpRequest request) {
|
||||||
Builder<String, String> builder = ImmutableMap.builder();
|
request = replaceHeader(request, HttpHeaders.DATE, timeStampProvider.get());
|
||||||
String date = timeStampProvider.get();
|
|
||||||
builder.put(HttpHeaders.DATE, date);
|
|
||||||
request = ModifyRequest.replaceHeaders(request, Multimaps.forMap(builder.build()));
|
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String createStringToSign(HttpRequest request) {
|
public String createStringToSign(HttpRequest request) {
|
||||||
utils.logRequest(signatureLog, request, ">>");
|
utils.logRequest(signatureLog, request, ">>");
|
||||||
SortedSetMultimap<String, String> canonicalizedHeaders = TreeMultimap.create();
|
SortedSetMultimap<String, String> canonicalizedHeaders = TreeMultimap.create();
|
||||||
StringBuilder buffer = new StringBuilder();
|
StringBuilder buffer = new StringBuilder();
|
||||||
// re-sign the request
|
// re-sign the request
|
||||||
appendMethod(request, buffer);
|
appendMethod(request, buffer);
|
||||||
appendPayloadMetadata(request, buffer);
|
appendPayloadMetadata(request, buffer);
|
||||||
appendHttpHeaders(request, canonicalizedHeaders);
|
appendHttpHeaders(request, canonicalizedHeaders);
|
||||||
|
|
||||||
// Remove default date timestamp if "x-amz-date" is set.
|
// Remove default date timestamp if "x-amz-date" is set.
|
||||||
if (canonicalizedHeaders.containsKey(S3Headers.ALTERNATE_DATE)) {
|
if (canonicalizedHeaders.containsKey("x-" + headerTag + "-date")) {
|
||||||
canonicalizedHeaders.put("date", "");
|
canonicalizedHeaders.removeAll("date");
|
||||||
}
|
}
|
||||||
|
|
||||||
appendAmzHeaders(canonicalizedHeaders, buffer);
|
appendAmzHeaders(canonicalizedHeaders, buffer);
|
||||||
|
@ -172,7 +171,7 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
|
||||||
String calculateSignature(String toSign) throws HttpException {
|
String calculateSignature(String toSign) throws HttpException {
|
||||||
String signature = sign(toSign);
|
String signature = sign(toSign);
|
||||||
if (signatureWire.enabled())
|
if (signatureWire.enabled())
|
||||||
signatureWire.input(Strings2.toInputStream(signature));
|
signatureWire.input(toInputStream(signature));
|
||||||
return signature;
|
return signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,35 +195,35 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
|
||||||
for (Entry<String, String> header : canonicalizedHeaders.entries()) {
|
for (Entry<String, String> header : canonicalizedHeaders.entries()) {
|
||||||
String key = header.getKey();
|
String key = header.getKey();
|
||||||
if (key.startsWith("x-" + headerTag + "-")) {
|
if (key.startsWith("x-" + headerTag + "-")) {
|
||||||
toSign.append(String.format("%s: %s\n", key.toLowerCase(), header.getValue()));
|
toSign.append(String.format("%s:%s\n", key.toLowerCase(), header.getValue()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void appendPayloadMetadata(HttpRequest request, StringBuilder buffer) {
|
void appendPayloadMetadata(HttpRequest request, StringBuilder buffer) {
|
||||||
// the following request parameters are positional in their nature
|
// note that we fall back to headers, and some requests such as ?uploads do not have a
|
||||||
|
// payload, yet specify payload related parameters
|
||||||
buffer.append(
|
buffer.append(
|
||||||
utils.valueOrEmpty(request.getPayload() == null ? null : request.getPayload().getContentMetadata()
|
request.getPayload() == null ? utils.valueOrEmpty(request.getFirstHeaderOrNull("Content-MD5")) : utils
|
||||||
.getContentMD5())).append("\n");
|
.valueOrEmpty(request.getPayload() == null ? null : request.getPayload().getContentMetadata()
|
||||||
|
.getContentMD5())).append("\n");
|
||||||
buffer.append(
|
buffer.append(
|
||||||
utils.valueOrEmpty(request.getPayload() == null ? request.getFirstHeaderOrNull(HttpHeaders.CONTENT_TYPE)
|
utils.valueOrEmpty(request.getPayload() == null ? request.getFirstHeaderOrNull(HttpHeaders.CONTENT_TYPE)
|
||||||
: request.getPayload().getContentMetadata().getContentType())).append("\n");
|
: request.getPayload().getContentMetadata().getContentType())).append("\n");
|
||||||
for (String header : firstHeadersToSign)
|
for (String header : firstHeadersToSign)
|
||||||
buffer.append(valueOrEmpty(request.getHeaders().get(header))).append("\n");
|
buffer.append(valueOrEmpty(request.getHeaders().get(header))).append("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void appendHttpHeaders(HttpRequest request,
|
void appendHttpHeaders(HttpRequest request, SortedSetMultimap<String, String> canonicalizedHeaders) {
|
||||||
SortedSetMultimap<String, String> canonicalizedHeaders) {
|
|
||||||
Multimap<String, String> headers = request.getHeaders();
|
Multimap<String, String> headers = request.getHeaders();
|
||||||
for (Entry<String, String> header : headers.entries()) {
|
for (Entry<String, String> header : headers.entries()) {
|
||||||
if (header.getKey() == null)
|
if (header.getKey() == null)
|
||||||
continue;
|
continue;
|
||||||
String key = header.getKey().toString()
|
String key = header.getKey().toString().toLowerCase(Locale.getDefault());
|
||||||
.toLowerCase(Locale.getDefault());
|
|
||||||
// Ignore any headers that are not particularly interesting.
|
// Ignore any headers that are not particularly interesting.
|
||||||
if (key.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE) || key.equalsIgnoreCase("Content-MD5")
|
if (key.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE) || key.equalsIgnoreCase("Content-MD5")
|
||||||
|| key.equalsIgnoreCase(HttpHeaders.DATE) || key.startsWith(AMAZON_PREFIX)) {
|
|| key.equalsIgnoreCase(HttpHeaders.DATE) || key.startsWith("x-" + headerTag + "-")) {
|
||||||
canonicalizedHeaders.put(key, header.getValue());
|
canonicalizedHeaders.put(key, header.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -238,12 +237,7 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
|
||||||
String bucketName = null;
|
String bucketName = null;
|
||||||
|
|
||||||
for (int i = 0; i < request.getJavaMethod().getParameterAnnotations().length; i++) {
|
for (int i = 0; i < request.getJavaMethod().getParameterAnnotations().length; i++) {
|
||||||
if (Iterables.any(Arrays.asList(request.getJavaMethod().getParameterAnnotations()[i]),
|
if (any(Arrays.asList(request.getJavaMethod().getParameterAnnotations()[i]), ANNOTATIONTYPE_BUCKET)) {
|
||||||
new Predicate<Annotation>() {
|
|
||||||
public boolean apply(Annotation input) {
|
|
||||||
return input.annotationType().equals(Bucket.class);
|
|
||||||
}
|
|
||||||
})) {
|
|
||||||
bucketName = (String) request.getArgs().get(i);
|
bucketName = (String) request.getArgs().get(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -261,20 +255,14 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
|
||||||
// ...however, there are a few exceptions that must be included in the
|
// ...however, there are a few exceptions that must be included in the
|
||||||
// signed URI.
|
// signed URI.
|
||||||
if (request.getEndpoint().getQuery() != null) {
|
if (request.getEndpoint().getQuery() != null) {
|
||||||
SortedSetMultimap<String, String> sortedParams = TreeMultimap.create();
|
Multimap<String, String> params = parseQueryToMap(request.getEndpoint().getQuery());
|
||||||
String[] params = request.getEndpoint().getQuery().split("&");
|
|
||||||
for (String param : params) {
|
|
||||||
String[] paramNameAndValue = param.split("=");
|
|
||||||
sortedParams.put(paramNameAndValue[0], paramNameAndValue.length == 2 ? paramNameAndValue[1] : null);
|
|
||||||
}
|
|
||||||
char separator = '?';
|
char separator = '?';
|
||||||
for (Entry<String, String> param: sortedParams.entries()) {
|
for (String paramName : Ordering.natural().sortedCopy(params.keySet())) {
|
||||||
String paramName = param.getKey();
|
|
||||||
// Skip any parameters that aren't part of the canonical signed string
|
// Skip any parameters that aren't part of the canonical signed string
|
||||||
if (SIGNED_PARAMETERS.contains(paramName) == false) continue;
|
if (SIGNED_PARAMETERS.contains(paramName) == false)
|
||||||
|
continue;
|
||||||
toSign.append(separator).append(paramName);
|
toSign.append(separator).append(paramName);
|
||||||
String paramValue = param.getValue();
|
String paramValue = get(params.get(paramName), 0);
|
||||||
if (paramValue != null) {
|
if (paramValue != null) {
|
||||||
toSign.append("=").append(paramValue);
|
toSign.append("=").append(paramValue);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,18 +21,21 @@ package org.jclouds.s3.functions;
|
||||||
|
|
||||||
import static org.jclouds.blobstore.reference.BlobStoreConstants.PROPERTY_USER_METADATA_PREFIX;
|
import static org.jclouds.blobstore.reference.BlobStoreConstants.PROPERTY_USER_METADATA_PREFIX;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
|
||||||
import org.jclouds.s3.blobstore.functions.BlobToObjectMetadata;
|
|
||||||
import org.jclouds.s3.domain.MutableObjectMetadata;
|
|
||||||
import org.jclouds.blobstore.domain.BlobMetadata;
|
import org.jclouds.blobstore.domain.BlobMetadata;
|
||||||
import org.jclouds.blobstore.functions.ParseSystemAndUserMetadataFromHeaders;
|
import org.jclouds.blobstore.functions.ParseSystemAndUserMetadataFromHeaders;
|
||||||
import org.jclouds.crypto.CryptoStreams;
|
import org.jclouds.crypto.CryptoStreams;
|
||||||
import org.jclouds.http.HttpRequest;
|
import org.jclouds.http.HttpRequest;
|
||||||
import org.jclouds.http.HttpResponse;
|
import org.jclouds.http.HttpResponse;
|
||||||
import org.jclouds.rest.InvocationContext;
|
import org.jclouds.rest.InvocationContext;
|
||||||
|
import org.jclouds.s3.blobstore.functions.BlobToObjectMetadata;
|
||||||
|
import org.jclouds.s3.domain.MutableObjectMetadata;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
|
@ -45,19 +48,23 @@ import com.google.common.base.Function;
|
||||||
* @author Adrian Cole
|
* @author Adrian Cole
|
||||||
*/
|
*/
|
||||||
public class ParseObjectMetadataFromHeaders implements Function<HttpResponse, MutableObjectMetadata>,
|
public class ParseObjectMetadataFromHeaders implements Function<HttpResponse, MutableObjectMetadata>,
|
||||||
InvocationContext<ParseObjectMetadataFromHeaders> {
|
InvocationContext<ParseObjectMetadataFromHeaders> {
|
||||||
private final ParseSystemAndUserMetadataFromHeaders blobMetadataParser;
|
private final ParseSystemAndUserMetadataFromHeaders blobMetadataParser;
|
||||||
private final BlobToObjectMetadata blobToObjectMetadata;
|
private final BlobToObjectMetadata blobToObjectMetadata;
|
||||||
private final String userMdPrefix;
|
private final String userMdPrefix;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ParseObjectMetadataFromHeaders(ParseSystemAndUserMetadataFromHeaders blobMetadataParser,
|
public ParseObjectMetadataFromHeaders(ParseSystemAndUserMetadataFromHeaders blobMetadataParser,
|
||||||
BlobToObjectMetadata blobToObjectMetadata, @Named(PROPERTY_USER_METADATA_PREFIX) String userMdPrefix) {
|
BlobToObjectMetadata blobToObjectMetadata, @Named(PROPERTY_USER_METADATA_PREFIX) String userMdPrefix) {
|
||||||
this.blobMetadataParser = blobMetadataParser;
|
this.blobMetadataParser = blobMetadataParser;
|
||||||
this.blobToObjectMetadata = blobToObjectMetadata;
|
this.blobToObjectMetadata = blobToObjectMetadata;
|
||||||
this.userMdPrefix = userMdPrefix;
|
this.userMdPrefix = userMdPrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eTag pattern can be "a34d7e626b350d2e326196085dfa52f4-1", which is opaque and shouldn't be
|
||||||
|
// used as content-md5, so filter etags that contain hyphens
|
||||||
|
static final Pattern MD5_FROM_ETAG = Pattern.compile("^\"?([0-9a-f]+)\"?$");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* parses the http response headers to create a new
|
* parses the http response headers to create a new
|
||||||
* {@link org.jclouds.s3.domain.internal.MutableObjectMetadata} object.
|
* {@link org.jclouds.s3.domain.internal.MutableObjectMetadata} object.
|
||||||
|
@ -65,13 +72,20 @@ public class ParseObjectMetadataFromHeaders implements Function<HttpResponse, Mu
|
||||||
public MutableObjectMetadata apply(HttpResponse from) {
|
public MutableObjectMetadata apply(HttpResponse from) {
|
||||||
BlobMetadata base = blobMetadataParser.apply(from);
|
BlobMetadata base = blobMetadataParser.apply(from);
|
||||||
MutableObjectMetadata to = blobToObjectMetadata.apply(base);
|
MutableObjectMetadata to = blobToObjectMetadata.apply(base);
|
||||||
|
|
||||||
addETagTo(from, to);
|
addETagTo(from, to);
|
||||||
if (to.getContentMetadata().getContentMD5() == null && to.getETag() != null) {
|
if (to.getContentMetadata().getContentMD5() == null && to.getETag() != null) {
|
||||||
byte[] md5 = CryptoStreams.hex(to.getETag().replaceAll("\"", ""));
|
Matcher md5Matcher = MD5_FROM_ETAG.matcher(to.getETag());
|
||||||
// it is possible others will look at the http payload directly
|
if (md5Matcher.find()) {
|
||||||
from.getPayload().getContentMetadata().setContentMD5(md5);
|
byte[] md5 = CryptoStreams.hex(md5Matcher.group(1));
|
||||||
to.getContentMetadata().setContentMD5(md5);
|
// it is possible others will look at the http payload directly
|
||||||
|
if (from.getPayload() != null)
|
||||||
|
from.getPayload().getContentMetadata().setContentMD5(md5);
|
||||||
|
to.getContentMetadata().setContentMD5(md5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// amz has an etag, but matches syntax for usermetadata
|
||||||
|
to.getUserMetadata().remove("object-etag");
|
||||||
to.setCacheControl(from.getFirstHeaderOrNull(HttpHeaders.CACHE_CONTROL));
|
to.setCacheControl(from.getFirstHeaderOrNull(HttpHeaders.CACHE_CONTROL));
|
||||||
return to;
|
return to;
|
||||||
}
|
}
|
||||||
|
@ -95,4 +109,8 @@ public class ParseObjectMetadataFromHeaders implements Function<HttpResponse, Mu
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ParseObjectMetadataFromHeaders setKey(String key) {
|
||||||
|
blobMetadataParser.setName(key);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,13 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG;
|
import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG;
|
||||||
import static org.jclouds.blobstore.reference.BlobStoreConstants.PROPERTY_USER_METADATA_PREFIX;
|
import static org.jclouds.blobstore.reference.BlobStoreConstants.PROPERTY_USER_METADATA_PREFIX;
|
||||||
|
import static org.jclouds.s3.reference.S3Headers.CANNED_ACL;
|
||||||
|
import static org.jclouds.s3.reference.S3Headers.COPY_SOURCE_IF_MATCH;
|
||||||
|
import static org.jclouds.s3.reference.S3Headers.COPY_SOURCE_IF_MODIFIED_SINCE;
|
||||||
|
import static org.jclouds.s3.reference.S3Headers.COPY_SOURCE_IF_NO_MATCH;
|
||||||
|
import static org.jclouds.s3.reference.S3Headers.COPY_SOURCE_IF_UNMODIFIED_SINCE;
|
||||||
|
import static org.jclouds.s3.reference.S3Headers.DEFAULT_AMAZON_HEADERTAG;
|
||||||
|
import static org.jclouds.s3.reference.S3Headers.METADATA_DIRECTIVE;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
@ -32,14 +39,13 @@ import java.util.Map.Entry;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
import org.jclouds.s3.domain.CannedAccessPolicy;
|
|
||||||
import org.jclouds.s3.reference.S3Headers;
|
|
||||||
import org.jclouds.date.DateService;
|
import org.jclouds.date.DateService;
|
||||||
import org.jclouds.date.internal.SimpleDateFormatDateService;
|
import org.jclouds.date.internal.SimpleDateFormatDateService;
|
||||||
import org.jclouds.http.options.BaseHttpRequestOptions;
|
import org.jclouds.http.options.BaseHttpRequestOptions;
|
||||||
|
import org.jclouds.s3.domain.CannedAccessPolicy;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.LinkedHashMultimap;
|
import com.google.common.collect.ImmutableMultimap;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -99,7 +105,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
||||||
public CopyObjectOptions overrideAcl(CannedAccessPolicy acl) {
|
public CopyObjectOptions overrideAcl(CannedAccessPolicy acl) {
|
||||||
this.acl = checkNotNull(acl, "acl");
|
this.acl = checkNotNull(acl, "acl");
|
||||||
if (!acl.equals(CannedAccessPolicy.PRIVATE))
|
if (!acl.equals(CannedAccessPolicy.PRIVATE))
|
||||||
this.replaceHeader(S3Headers.CANNED_ACL, acl.toString());
|
this.replaceHeader(CANNED_ACL, acl.toString());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +130,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
||||||
* @see CopyObjectOptions#ifSourceModifiedSince(Date)
|
* @see CopyObjectOptions#ifSourceModifiedSince(Date)
|
||||||
*/
|
*/
|
||||||
public String getIfModifiedSince() {
|
public String getIfModifiedSince() {
|
||||||
return getFirstHeaderOrNull("x-amz-copy-source-if-modified-since");
|
return getFirstHeaderOrNull(COPY_SOURCE_IF_MODIFIED_SINCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -141,7 +147,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
||||||
* @see CopyObjectOptions#ifSourceUnmodifiedSince(Date)
|
* @see CopyObjectOptions#ifSourceUnmodifiedSince(Date)
|
||||||
*/
|
*/
|
||||||
public String getIfUnmodifiedSince() {
|
public String getIfUnmodifiedSince() {
|
||||||
return getFirstHeaderOrNull("x-amz-copy-source-if-unmodified-since");
|
return getFirstHeaderOrNull(COPY_SOURCE_IF_UNMODIFIED_SINCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -156,7 +162,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
||||||
* @see CopyObjectOptions#ifSourceETagMatches(String)
|
* @see CopyObjectOptions#ifSourceETagMatches(String)
|
||||||
*/
|
*/
|
||||||
public String getIfMatch() {
|
public String getIfMatch() {
|
||||||
return getFirstHeaderOrNull("x-amz-copy-source-if-match");
|
return getFirstHeaderOrNull(COPY_SOURCE_IF_MATCH);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -171,7 +177,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
||||||
* @see CopyObjectOptions#ifSourceETagDoesntMatch(String)
|
* @see CopyObjectOptions#ifSourceETagDoesntMatch(String)
|
||||||
*/
|
*/
|
||||||
public String getIfNoneMatch() {
|
public String getIfNoneMatch() {
|
||||||
return getFirstHeaderOrNull("x-amz-copy-source-if-none-match");
|
return getFirstHeaderOrNull(COPY_SOURCE_IF_NO_MATCH);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -192,10 +198,9 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
||||||
*/
|
*/
|
||||||
public CopyObjectOptions ifSourceModifiedSince(Date ifModifiedSince) {
|
public CopyObjectOptions ifSourceModifiedSince(Date ifModifiedSince) {
|
||||||
checkState(getIfMatch() == null, "ifETagMatches() is not compatible with ifModifiedSince()");
|
checkState(getIfMatch() == null, "ifETagMatches() is not compatible with ifModifiedSince()");
|
||||||
checkState(getIfUnmodifiedSince() == null,
|
checkState(getIfUnmodifiedSince() == null, "ifUnmodifiedSince() is not compatible with ifModifiedSince()");
|
||||||
"ifUnmodifiedSince() is not compatible with ifModifiedSince()");
|
replaceHeader(COPY_SOURCE_IF_MODIFIED_SINCE, dateService.rfc822DateFormat(checkNotNull(ifModifiedSince,
|
||||||
replaceHeader("x-amz-copy-source-if-modified-since", dateService
|
"ifModifiedSince")));
|
||||||
.rfc822DateFormat(checkNotNull(ifModifiedSince, "ifModifiedSince")));
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,12 +211,10 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
||||||
* {@link #ifSourceModifiedSince(Date)}
|
* {@link #ifSourceModifiedSince(Date)}
|
||||||
*/
|
*/
|
||||||
public CopyObjectOptions ifSourceUnmodifiedSince(Date ifUnmodifiedSince) {
|
public CopyObjectOptions ifSourceUnmodifiedSince(Date ifUnmodifiedSince) {
|
||||||
checkState(getIfNoneMatch() == null,
|
checkState(getIfNoneMatch() == null, "ifETagDoesntMatch() is not compatible with ifUnmodifiedSince()");
|
||||||
"ifETagDoesntMatch() is not compatible with ifUnmodifiedSince()");
|
checkState(getIfModifiedSince() == null, "ifModifiedSince() is not compatible with ifUnmodifiedSince()");
|
||||||
checkState(getIfModifiedSince() == null,
|
replaceHeader(COPY_SOURCE_IF_UNMODIFIED_SINCE, dateService.rfc822DateFormat(checkNotNull(ifUnmodifiedSince,
|
||||||
"ifModifiedSince() is not compatible with ifUnmodifiedSince()");
|
"ifUnmodifiedSince")));
|
||||||
replaceHeader("x-amz-copy-source-if-unmodified-since", dateService
|
|
||||||
.rfc822DateFormat(checkNotNull(ifUnmodifiedSince, "ifUnmodifiedSince")));
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,12 +229,9 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
||||||
* hash representing the payload
|
* hash representing the payload
|
||||||
*/
|
*/
|
||||||
public CopyObjectOptions ifSourceETagMatches(String eTag) throws UnsupportedEncodingException {
|
public CopyObjectOptions ifSourceETagMatches(String eTag) throws UnsupportedEncodingException {
|
||||||
checkState(getIfNoneMatch() == null,
|
checkState(getIfNoneMatch() == null, "ifETagDoesntMatch() is not compatible with ifETagMatches()");
|
||||||
"ifETagDoesntMatch() is not compatible with ifETagMatches()");
|
checkState(getIfModifiedSince() == null, "ifModifiedSince() is not compatible with ifETagMatches()");
|
||||||
checkState(getIfModifiedSince() == null,
|
replaceHeader(COPY_SOURCE_IF_MATCH, String.format("\"%1$s\"", checkNotNull(eTag, "eTag")));
|
||||||
"ifModifiedSince() is not compatible with ifETagMatches()");
|
|
||||||
replaceHeader("x-amz-copy-source-if-match", String.format("\"%1$s\"", checkNotNull(eTag,
|
|
||||||
"eTag")));
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,13 +246,11 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
||||||
* @throws UnsupportedEncodingException
|
* @throws UnsupportedEncodingException
|
||||||
* if there was a problem converting this into an S3 eTag string
|
* if there was a problem converting this into an S3 eTag string
|
||||||
*/
|
*/
|
||||||
public CopyObjectOptions ifSourceETagDoesntMatch(String eTag)
|
public CopyObjectOptions ifSourceETagDoesntMatch(String eTag) throws UnsupportedEncodingException {
|
||||||
throws UnsupportedEncodingException {
|
|
||||||
checkState(getIfMatch() == null, "ifETagMatches() is not compatible with ifETagDoesntMatch()");
|
checkState(getIfMatch() == null, "ifETagMatches() is not compatible with ifETagDoesntMatch()");
|
||||||
Preconditions.checkState(getIfUnmodifiedSince() == null,
|
Preconditions.checkState(getIfUnmodifiedSince() == null,
|
||||||
"ifUnmodifiedSince() is not compatible with ifETagDoesntMatch()");
|
"ifUnmodifiedSince() is not compatible with ifETagDoesntMatch()");
|
||||||
replaceHeader("x-amz-copy-source-if-none-match", String.format("\"%1$s\"", checkNotNull(eTag,
|
replaceHeader(COPY_SOURCE_IF_NO_MATCH, String.format("\"%s\"", checkNotNull(eTag, "ifETagDoesntMatch")));
|
||||||
"ifETagDoesntMatch")));
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,18 +258,17 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
||||||
public Multimap<String, String> buildRequestHeaders() {
|
public Multimap<String, String> buildRequestHeaders() {
|
||||||
checkState(headerTag != null, "headerTag should have been injected!");
|
checkState(headerTag != null, "headerTag should have been injected!");
|
||||||
checkState(metadataPrefix != null, "metadataPrefix should have been injected!");
|
checkState(metadataPrefix != null, "metadataPrefix should have been injected!");
|
||||||
Multimap<String, String> returnVal = LinkedHashMultimap.create();
|
ImmutableMultimap.Builder<String, String> returnVal = ImmutableMultimap.<String, String> builder();
|
||||||
for (Entry<String, String> entry : headers.entries()) {
|
for (Entry<String, String> entry : headers.entries()) {
|
||||||
returnVal.put(entry.getKey().replace("amz", headerTag), entry.getValue());
|
returnVal.put(entry.getKey().replace(DEFAULT_AMAZON_HEADERTAG, headerTag), entry.getValue());
|
||||||
}
|
}
|
||||||
if (metadata != null) {
|
if (metadata != null) {
|
||||||
|
returnVal.put(METADATA_DIRECTIVE.replace(DEFAULT_AMAZON_HEADERTAG, headerTag), "REPLACE");
|
||||||
for (String key : metadata.keySet()) {
|
for (String key : metadata.keySet()) {
|
||||||
returnVal.put(key.startsWith(metadataPrefix) ? key : metadataPrefix + key, metadata
|
returnVal.put(key.startsWith(metadataPrefix) ? key : metadataPrefix + key, metadata.get(key));
|
||||||
.get(key));
|
|
||||||
}
|
}
|
||||||
returnVal.put("x-" + headerTag + "-metadata-directive", "REPLACE");
|
|
||||||
}
|
}
|
||||||
return returnVal;
|
return returnVal.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -311,8 +308,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
||||||
/**
|
/**
|
||||||
* @see CopyObjectOptions#ifSourceETagMatches(String)
|
* @see CopyObjectOptions#ifSourceETagMatches(String)
|
||||||
*/
|
*/
|
||||||
public static CopyObjectOptions ifSourceETagMatches(String eTag)
|
public static CopyObjectOptions ifSourceETagMatches(String eTag) throws UnsupportedEncodingException {
|
||||||
throws UnsupportedEncodingException {
|
|
||||||
CopyObjectOptions options = new CopyObjectOptions();
|
CopyObjectOptions options = new CopyObjectOptions();
|
||||||
return options.ifSourceETagMatches(eTag);
|
return options.ifSourceETagMatches(eTag);
|
||||||
}
|
}
|
||||||
|
@ -320,8 +316,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
||||||
/**
|
/**
|
||||||
* @see CopyObjectOptions#ifSourceETagDoesntMatch(String)
|
* @see CopyObjectOptions#ifSourceETagDoesntMatch(String)
|
||||||
*/
|
*/
|
||||||
public static CopyObjectOptions ifSourceETagDoesntMatch(String eTag)
|
public static CopyObjectOptions ifSourceETagDoesntMatch(String eTag) throws UnsupportedEncodingException {
|
||||||
throws UnsupportedEncodingException {
|
|
||||||
CopyObjectOptions options = new CopyObjectOptions();
|
CopyObjectOptions options = new CopyObjectOptions();
|
||||||
return options.ifSourceETagDoesntMatch(eTag);
|
return options.ifSourceETagDoesntMatch(eTag);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,28 +19,30 @@
|
||||||
|
|
||||||
package org.jclouds.s3.options;
|
package org.jclouds.s3.options;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG;
|
||||||
|
import static org.jclouds.s3.reference.S3Headers.CANNED_ACL;
|
||||||
|
import static org.jclouds.s3.reference.S3Headers.DEFAULT_AMAZON_HEADERTAG;
|
||||||
|
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
import org.jclouds.s3.domain.CannedAccessPolicy;
|
|
||||||
import org.jclouds.s3.reference.S3Headers;
|
|
||||||
import org.jclouds.http.options.BaseHttpRequestOptions;
|
import org.jclouds.http.options.BaseHttpRequestOptions;
|
||||||
|
import org.jclouds.s3.domain.CannedAccessPolicy;
|
||||||
|
|
||||||
import com.google.common.collect.LinkedHashMultimap;
|
import com.google.common.collect.ImmutableMultimap;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.*;
|
|
||||||
import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains options supported in the REST API for the PUT object operation.
|
* Contains options supported in the REST API for the PUT object operation.
|
||||||
* <p/>
|
* <p/>
|
||||||
* <h2>
|
* <h2>
|
||||||
* Usage</h2> The recommended way to instantiate a PutObjectOptions object is to
|
* Usage</h2> The recommended way to instantiate a PutObjectOptions object is to statically import
|
||||||
* statically import PutObjectOptions.Builder.* and invoke a static creation
|
* PutObjectOptions.Builder.* and invoke a static creation method followed by an instance mutator
|
||||||
* method followed by an instance mutator (if needed):
|
* (if needed):
|
||||||
* <p/>
|
* <p/>
|
||||||
* <code>
|
* <code>
|
||||||
* import static org.jclouds.s3.commands.options.PutObjectOptions.Builder.*
|
* import static org.jclouds.s3.commands.options.PutObjectOptions.Builder.*
|
||||||
|
@ -50,60 +52,62 @@ import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG;
|
||||||
* Future<Boolean> publicly readable = connection.putObject("bucketName",new S3Object("key","value"), withAcl(CannedAccessPolicy.PUBLIC_READ));
|
* Future<Boolean> publicly readable = connection.putObject("bucketName",new S3Object("key","value"), withAcl(CannedAccessPolicy.PUBLIC_READ));
|
||||||
* <code>
|
* <code>
|
||||||
*
|
*
|
||||||
* @see <a href="http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTObjectPUT.html?"
|
* @see <a
|
||||||
|
* href="http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTObjectPUT.html?"
|
||||||
* />
|
* />
|
||||||
*
|
*
|
||||||
* @author Adrian Cole
|
* @author Adrian Cole
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class PutObjectOptions extends BaseHttpRequestOptions {
|
public class PutObjectOptions extends BaseHttpRequestOptions {
|
||||||
public static final PutObjectOptions NONE = new PutObjectOptions();
|
public static final PutObjectOptions NONE = new PutObjectOptions();
|
||||||
|
|
||||||
private CannedAccessPolicy acl = CannedAccessPolicy.PRIVATE;
|
private CannedAccessPolicy acl = CannedAccessPolicy.PRIVATE;
|
||||||
|
|
||||||
private String headerTag;
|
|
||||||
|
|
||||||
@Inject
|
private String headerTag;
|
||||||
public void setHeaderTag(@Named(PROPERTY_HEADER_TAG) String headerTag) {
|
|
||||||
this.headerTag = headerTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Inject
|
||||||
public Multimap<String, String> buildRequestHeaders() {
|
public void setHeaderTag(@Named(PROPERTY_HEADER_TAG) String headerTag) {
|
||||||
checkState(headerTag != null, "headerTag should have been injected!");
|
this.headerTag = headerTag;
|
||||||
Multimap<String, String> returnVal = LinkedHashMultimap.create();
|
}
|
||||||
for (Entry<String, String> entry : headers.entries()) {
|
|
||||||
returnVal.put(entry.getKey().replace("amz", headerTag), entry.getValue());
|
|
||||||
}
|
|
||||||
return returnVal;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Override the default ACL (private) with the specified one.
|
|
||||||
*
|
|
||||||
* @see CannedAccessPolicy
|
|
||||||
*/
|
|
||||||
public PutObjectOptions withAcl(CannedAccessPolicy acl) {
|
|
||||||
this.acl = checkNotNull(acl, "acl");
|
|
||||||
if (!acl.equals(CannedAccessPolicy.PRIVATE))
|
|
||||||
this.replaceHeader(S3Headers.CANNED_ACL, acl.toString());
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* @see PutObjectOptions#withAcl(CannedAccessPolicy)
|
public Multimap<String, String> buildRequestHeaders() {
|
||||||
*/
|
checkState(headerTag != null, "headerTag should have been injected!");
|
||||||
public CannedAccessPolicy getAcl() {
|
ImmutableMultimap.Builder<String, String> returnVal = ImmutableMultimap.<String, String> builder();
|
||||||
return acl;
|
for (Entry<String, String> entry : headers.entries()) {
|
||||||
}
|
returnVal.put(entry.getKey().replace(DEFAULT_AMAZON_HEADERTAG, headerTag), entry.getValue());
|
||||||
|
}
|
||||||
|
return returnVal.build();
|
||||||
|
}
|
||||||
|
|
||||||
public static class Builder {
|
/**
|
||||||
|
* Override the default ACL (private) with the specified one.
|
||||||
|
*
|
||||||
|
* @see CannedAccessPolicy
|
||||||
|
*/
|
||||||
|
public PutObjectOptions withAcl(CannedAccessPolicy acl) {
|
||||||
|
this.acl = checkNotNull(acl, "acl");
|
||||||
|
if (!acl.equals(CannedAccessPolicy.PRIVATE))
|
||||||
|
this.replaceHeader(CANNED_ACL, acl.toString());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see PutObjectOptions#withAcl(CannedAccessPolicy)
|
* @see PutObjectOptions#withAcl(CannedAccessPolicy)
|
||||||
*/
|
*/
|
||||||
public static PutObjectOptions withAcl(CannedAccessPolicy acl) {
|
public CannedAccessPolicy getAcl() {
|
||||||
PutObjectOptions options = new PutObjectOptions();
|
return acl;
|
||||||
return options.withAcl(acl);
|
}
|
||||||
}
|
|
||||||
}
|
public static class Builder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see PutObjectOptions#withAcl(CannedAccessPolicy)
|
||||||
|
*/
|
||||||
|
public static PutObjectOptions withAcl(CannedAccessPolicy acl) {
|
||||||
|
PutObjectOptions options = new PutObjectOptions();
|
||||||
|
return options.withAcl(acl);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,83 +30,105 @@ package org.jclouds.s3.reference;
|
||||||
*/
|
*/
|
||||||
public interface S3Headers {
|
public interface S3Headers {
|
||||||
|
|
||||||
public static final String CONTENT_MD5 = "Content-MD5";
|
/**
|
||||||
|
* Amazon S3 has clones, which often replace this with their particular tag.
|
||||||
|
*/
|
||||||
|
public static final String DEFAULT_AMAZON_HEADERTAG = "amz";
|
||||||
|
|
||||||
/** Prefix for general Amazon headers: x-amz- */
|
public static final String HEADER_PREFIX = "x-" + DEFAULT_AMAZON_HEADERTAG + "-";
|
||||||
public static final String AMAZON_PREFIX = "x-amz-";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The canned ACL to apply to the object. Options include private, public-read,
|
* The canned ACL to apply to the object. Options include private, public-read,
|
||||||
* public-read-write, and authenticated-read. For more information, see REST Access Control
|
* public-read-write, and authenticated-read. For more information, see REST Access Control
|
||||||
* Policy.
|
* Policy.
|
||||||
*/
|
*/
|
||||||
public static final String CANNED_ACL = "x-amz-acl";
|
public static final String CANNED_ACL = HEADER_PREFIX + "acl";
|
||||||
|
|
||||||
public static final String AMZ_MD5 = "x-amz-meta-object-eTag";
|
|
||||||
|
|
||||||
/** Amazon's alternative date header: x-amz-date */
|
|
||||||
public static final String ALTERNATE_DATE = "x-amz-date";
|
|
||||||
|
|
||||||
/** Prefix for user metadata: x-amz-meta- */
|
public static final String AMZ_ETAG = HEADER_PREFIX + "meta-object-eTag";
|
||||||
public static final String USER_METADATA_PREFIX = "x-amz-meta-";
|
|
||||||
|
|
||||||
/** version ID header */
|
/**
|
||||||
public static final String VERSION_ID = "x-amz-version-id";
|
* Amazon's alternative date header
|
||||||
|
*/
|
||||||
|
public static final String ALTERNATE_DATE = HEADER_PREFIX + "date";
|
||||||
|
|
||||||
/** Multi-Factor Authentication header */
|
/**
|
||||||
public static final String MFA = "x-amz-mfa";
|
* Prefix for user metadata
|
||||||
|
*/
|
||||||
|
public static final String USER_METADATA_PREFIX = HEADER_PREFIX + "meta-";
|
||||||
|
|
||||||
/** response header for a request's AWS request ID */
|
/**
|
||||||
public static final String REQUEST_ID = "x-amz-request-id";
|
* version ID header
|
||||||
|
*/
|
||||||
|
public static final String VERSION_ID = HEADER_PREFIX + "version-id";
|
||||||
|
|
||||||
/** response header for a request's extended debugging ID */
|
/**
|
||||||
public static final String EXTENDED_REQUEST_ID = "x-amz-id-2";
|
* Multi-Factor Authentication header
|
||||||
|
*/
|
||||||
|
public static final String MFA = HEADER_PREFIX + "mfa";
|
||||||
|
|
||||||
/** request header indicating how to handle metadata when copying an object */
|
/**
|
||||||
public static final String METADATA_DIRECTIVE = "x-amz-metadata-directive";
|
* response header for a request's AWS request ID
|
||||||
|
*/
|
||||||
|
public static final String REQUEST_ID = HEADER_PREFIX + "request-id";
|
||||||
|
|
||||||
/** DevPay token header */
|
/**
|
||||||
public static final String SECURITY_TOKEN = "x-amz-security-token";
|
* response header for a request's extended debugging ID
|
||||||
|
*/
|
||||||
|
public static final String EXTENDED_REQUEST_ID = HEADER_PREFIX + "id-2";
|
||||||
|
|
||||||
/** Header describing what class of storage a user wants */
|
/**
|
||||||
public static final String STORAGE_CLASS = "x-amz-storage-class";
|
* request header indicating how to handle metadata when copying an object
|
||||||
|
*/
|
||||||
|
public static final String METADATA_DIRECTIVE = HEADER_PREFIX + "metadata-directive";
|
||||||
|
|
||||||
/** ETag matching constraint header for the copy object request */
|
/**
|
||||||
public static final String COPY_SOURCE_IF_MATCH = "x-amz-copy-source-if-match";
|
* DevPay token header
|
||||||
|
*/
|
||||||
|
public static final String SECURITY_TOKEN = HEADER_PREFIX + "security-token";
|
||||||
|
|
||||||
/** ETag non-matching constraint header for the copy object request */
|
/**
|
||||||
public static final String COPY_SOURCE_IF_NO_MATCH = "x-amz-copy-source-if-none-match";
|
* Header describing what class of storage a user wants
|
||||||
|
*/
|
||||||
|
public static final String STORAGE_CLASS = HEADER_PREFIX + "storage-class";
|
||||||
|
|
||||||
/** Unmodified since constraint header for the copy object request */
|
/**
|
||||||
public static final String COPY_SOURCE_IF_UNMODIFIED_SINCE = "x-amz-copy-source-if-unmodified-since";
|
* ETag matching constraint header for the copy object request
|
||||||
|
*/
|
||||||
|
public static final String COPY_SOURCE_IF_MATCH = HEADER_PREFIX + "copy-source-if-match";
|
||||||
|
|
||||||
/** Modified since constraint header for the copy object request */
|
/**
|
||||||
public static final String COPY_SOURCE_IF_MODIFIED_SINCE = "x-amz-copy-source-if-modified-since";
|
* ETag non-matching constraint header for the copy object request
|
||||||
|
*/
|
||||||
|
public static final String COPY_SOURCE_IF_NO_MATCH = HEADER_PREFIX + "copy-source-if-none-match";
|
||||||
|
|
||||||
/** Range header for the get object request */
|
/**
|
||||||
public static final String RANGE = "Range";
|
* Unmodified since constraint header for the copy object request
|
||||||
|
*/
|
||||||
|
public static final String COPY_SOURCE_IF_UNMODIFIED_SINCE = HEADER_PREFIX + "copy-source-if-unmodified-since";
|
||||||
|
|
||||||
/** Modified since constraint header for the get object request */
|
/**
|
||||||
public static final String GET_OBJECT_IF_MODIFIED_SINCE = "If-Modified-Since";
|
* Modified since constraint header for the copy object request
|
||||||
|
*/
|
||||||
|
public static final String COPY_SOURCE_IF_MODIFIED_SINCE = HEADER_PREFIX + "copy-source-if-modified-since";
|
||||||
|
|
||||||
/** Unmodified since constraint header for the get object request */
|
/**
|
||||||
public static final String GET_OBJECT_IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
|
* Encrypted symmetric key header that is used in the envelope encryption mechanism
|
||||||
|
*/
|
||||||
|
public static final String CRYPTO_KEY = HEADER_PREFIX + "key";
|
||||||
|
|
||||||
/** ETag matching constraint header for the get object request */
|
/**
|
||||||
public static final String GET_OBJECT_IF_MATCH = "If-Match";
|
* Initialization vector (IV) header that is used in the symmetric and envelope encryption
|
||||||
|
* mechanisms
|
||||||
|
*/
|
||||||
|
public static final String CRYPTO_IV = HEADER_PREFIX + "iv";
|
||||||
|
|
||||||
/** ETag non-matching constraint header for the get object request */
|
/**
|
||||||
public static final String GET_OBJECT_IF_NONE_MATCH = "If-None-Match";
|
* JSON-encoded description of encryption materials used during encryption
|
||||||
|
*/
|
||||||
|
public static final String MATERIALS_DESCRIPTION = HEADER_PREFIX + "matdesc";
|
||||||
|
|
||||||
/** Encrypted symmetric key header that is used in the envelope encryption mechanism */
|
/**
|
||||||
public static final String CRYPTO_KEY = "x-amz-key";
|
* Instruction file header to be placed in the metadata of instruction files
|
||||||
|
*/
|
||||||
/** Initialization vector (IV) header that is used in the symmetric and envelope encryption mechanisms */
|
public static final String CRYPTO_INSTRUCTION_FILE = HEADER_PREFIX + "crypto-instr-file";
|
||||||
public static final String CRYPTO_IV = "x-amz-iv";
|
|
||||||
|
|
||||||
/** JSON-encoded description of encryption materials used during encryption */
|
|
||||||
public static final String MATERIALS_DESCRIPTION = "x-amz-matdesc";
|
|
||||||
|
|
||||||
/** Instruction file header to be placed in the metadata of instruction files */
|
|
||||||
public static final String CRYPTO_INSTRUCTION_FILE = "x-amz-crypto-instr-file";
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import org.jclouds.s3.domain.AccessControlList;
|
||||||
import org.jclouds.s3.domain.CannedAccessPolicy;
|
import org.jclouds.s3.domain.CannedAccessPolicy;
|
||||||
import org.jclouds.s3.domain.S3Object;
|
import org.jclouds.s3.domain.S3Object;
|
||||||
import org.jclouds.s3.options.PutObjectOptions;
|
import org.jclouds.s3.options.PutObjectOptions;
|
||||||
|
import org.jclouds.s3.reference.S3Headers;
|
||||||
import org.testng.annotations.DataProvider;
|
import org.testng.annotations.DataProvider;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
@ -72,13 +73,13 @@ public class RequestAuthorizeSignatureTest extends BaseS3AsyncClientTest<S3Async
|
||||||
*/
|
*/
|
||||||
@Test(threadPoolSize = 3, dataProvider = "dataProvider", timeOut = 10000)
|
@Test(threadPoolSize = 3, dataProvider = "dataProvider", timeOut = 10000)
|
||||||
void testIdempotent(HttpRequest request) {
|
void testIdempotent(HttpRequest request) {
|
||||||
filter.filter(request);
|
request = filter.filter(request);
|
||||||
String signature = request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION);
|
String signature = request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION);
|
||||||
String date = request.getFirstHeaderOrNull(HttpHeaders.DATE);
|
String date = request.getFirstHeaderOrNull(HttpHeaders.DATE);
|
||||||
int iterations = 1;
|
int iterations = 1;
|
||||||
while (request.getFirstHeaderOrNull(HttpHeaders.DATE).equals(date)) {
|
while (request.getFirstHeaderOrNull(HttpHeaders.DATE).equals(date)) {
|
||||||
date = request.getFirstHeaderOrNull(HttpHeaders.DATE);
|
date = request.getFirstHeaderOrNull(HttpHeaders.DATE);
|
||||||
filter.filter(request);
|
request = filter.filter(request);
|
||||||
if (request.getFirstHeaderOrNull(HttpHeaders.DATE).equals(date))
|
if (request.getFirstHeaderOrNull(HttpHeaders.DATE).equals(date))
|
||||||
assert signature.equals(request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION)) : String.format(
|
assert signature.equals(request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION)) : String.format(
|
||||||
"sig: %s != %s on attempt %s", signature, request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION),
|
"sig: %s != %s on attempt %s", signature, request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION),
|
||||||
|
@ -135,13 +136,13 @@ public class RequestAuthorizeSignatureTest extends BaseS3AsyncClientTest<S3Async
|
||||||
filter.appendHttpHeaders(request, canonicalizedHeaders);
|
filter.appendHttpHeaders(request, canonicalizedHeaders);
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
filter.appendAmzHeaders(canonicalizedHeaders, builder);
|
filter.appendAmzHeaders(canonicalizedHeaders, builder);
|
||||||
assertEquals(builder.toString(), "x-amz-meta-x-amz-adrian: foo\n");
|
assertEquals(builder.toString(), S3Headers.USER_METADATA_PREFIX + "adrian:foo\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpRequest putObject() throws NoSuchMethodException {
|
private HttpRequest putObject() throws NoSuchMethodException {
|
||||||
S3Object object = blobToS3Object.apply(BindBlobToMultipartFormTest.TEST_BLOB);
|
S3Object object = blobToS3Object.apply(BindBlobToMultipartFormTest.TEST_BLOB);
|
||||||
|
|
||||||
object.getMetadata().getUserMetadata().put("x-amz-Adrian", "foo");
|
object.getMetadata().getUserMetadata().put("Adrian", "foo");
|
||||||
HttpRequest request = processor.createRequest(S3AsyncClient.class.getMethod("putObject", String.class,
|
HttpRequest request = processor.createRequest(S3AsyncClient.class.getMethod("putObject", String.class,
|
||||||
S3Object.class, PutObjectOptions[].class), "bucket", object);
|
S3Object.class, PutObjectOptions[].class), "bucket", object);
|
||||||
return request;
|
return request;
|
||||||
|
|
|
@ -19,31 +19,34 @@
|
||||||
|
|
||||||
package org.jclouds.s3.functions;
|
package org.jclouds.s3.functions;
|
||||||
|
|
||||||
import static org.easymock.EasyMock.expect;
|
|
||||||
import static org.easymock.classextension.EasyMock.createMock;
|
import static org.easymock.classextension.EasyMock.createMock;
|
||||||
import static org.easymock.classextension.EasyMock.replay;
|
import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG;
|
||||||
|
import static org.jclouds.blobstore.reference.BlobStoreConstants.PROPERTY_USER_METADATA_PREFIX;
|
||||||
import static org.testng.Assert.assertEquals;
|
import static org.testng.Assert.assertEquals;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
import org.jclouds.s3.blobstore.functions.BlobToObjectMetadata;
|
import org.jclouds.crypto.CryptoStreams;
|
||||||
|
import org.jclouds.date.internal.SimpleDateFormatDateService;
|
||||||
|
import org.jclouds.http.HttpResponse;
|
||||||
|
import org.jclouds.io.Payloads;
|
||||||
|
import org.jclouds.rest.RequestSigner;
|
||||||
import org.jclouds.s3.domain.MutableObjectMetadata;
|
import org.jclouds.s3.domain.MutableObjectMetadata;
|
||||||
import org.jclouds.s3.domain.ObjectMetadata.StorageClass;
|
import org.jclouds.s3.domain.ObjectMetadata.StorageClass;
|
||||||
import org.jclouds.s3.domain.internal.MutableObjectMetadataImpl;
|
import org.jclouds.s3.domain.internal.MutableObjectMetadataImpl;
|
||||||
import org.jclouds.blobstore.domain.MutableBlobMetadata;
|
import org.jclouds.s3.reference.S3Headers;
|
||||||
import org.jclouds.blobstore.domain.internal.MutableBlobMetadataImpl;
|
|
||||||
import org.jclouds.blobstore.functions.ParseSystemAndUserMetadataFromHeaders;
|
|
||||||
import org.jclouds.crypto.CryptoStreams;
|
|
||||||
import org.jclouds.http.HttpResponse;
|
|
||||||
import org.jclouds.io.Payloads;
|
|
||||||
import org.testng.annotations.BeforeTest;
|
import org.testng.annotations.BeforeTest;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableMultimap;
|
import com.google.common.collect.ImmutableMultimap;
|
||||||
|
import com.google.inject.AbstractModule;
|
||||||
|
import com.google.inject.Guice;
|
||||||
|
import com.google.inject.name.Names;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Adrian Cole
|
* @author Adrian Cole
|
||||||
|
@ -52,65 +55,79 @@ import com.google.common.collect.ImmutableMultimap;
|
||||||
public class ParseObjectMetadataFromHeadersTest {
|
public class ParseObjectMetadataFromHeadersTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testNormal() throws Exception {
|
void testNormalParsesETagIntoMD5AndMetadataHeaders() throws Exception {
|
||||||
HttpResponse http = new HttpResponse(400, "boa", Payloads.newStringPayload(""), ImmutableMultimap.of(
|
HttpResponse http = new HttpResponse(400, "boa", Payloads.newStringPayload(""), ImmutableMultimap.of(
|
||||||
HttpHeaders.CACHE_CONTROL, "cacheControl"));
|
S3Headers.USER_METADATA_PREFIX + "foo", "bar", HttpHeaders.LAST_MODIFIED, lastModified,
|
||||||
|
HttpHeaders.ETAG, "\"abcd\"", HttpHeaders.CACHE_CONTROL, "cacheControl"));
|
||||||
http.getPayload().getContentMetadata().setContentLength(1025l);
|
http.getPayload().getContentMetadata().setContentLength(1025l);
|
||||||
http.getPayload().getContentMetadata().setContentDisposition("contentDisposition");
|
http.getPayload().getContentMetadata().setContentDisposition("contentDisposition");
|
||||||
http.getPayload().getContentMetadata().setContentEncoding("encoding");
|
http.getPayload().getContentMetadata().setContentEncoding("encoding");
|
||||||
|
http.getPayload().getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
|
||||||
ParseObjectMetadataFromHeaders parser = new ParseObjectMetadataFromHeaders(blobParser(http, "\"abcd\""),
|
|
||||||
blobToObjectMetadata, "x-amz-meta-");
|
|
||||||
MutableObjectMetadata response = parser.apply(http);
|
MutableObjectMetadata response = parser.apply(http);
|
||||||
|
|
||||||
|
MutableObjectMetadataImpl expects = new MutableObjectMetadataImpl();
|
||||||
|
expects.setCacheControl("cacheControl");
|
||||||
|
expects.getContentMetadata().setContentDisposition("contentDisposition");
|
||||||
|
expects.getContentMetadata().setContentEncoding("encoding");
|
||||||
|
expects.getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
expects.getContentMetadata().setContentLength(1025l);
|
||||||
|
expects.getContentMetadata().setContentMD5(CryptoStreams.hex("abcd"));
|
||||||
|
expects.setETag("\"abcd\"");
|
||||||
|
expects.setKey("key");
|
||||||
|
expects.setLastModified(now);
|
||||||
|
expects.setOwner(null);
|
||||||
|
expects.setStorageClass(StorageClass.STANDARD);
|
||||||
|
expects.setUserMetadata(userMetadata);
|
||||||
assertEquals(response, expects);
|
assertEquals(response, expects);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testAmzEtag() throws Exception {
|
void testMultipartDoesntAttemptToParseETagIntoMD5() throws Exception {
|
||||||
|
|
||||||
HttpResponse http = new HttpResponse(400, "boa", Payloads.newStringPayload(""), ImmutableMultimap.of(
|
HttpResponse http = new HttpResponse(400, "boa", Payloads.newStringPayload(""), ImmutableMultimap.of(
|
||||||
HttpHeaders.CACHE_CONTROL, "cacheControl", "x-amz-meta-object-eTag", "\"abcd\""));
|
S3Headers.USER_METADATA_PREFIX + "foo", "bar", HttpHeaders.LAST_MODIFIED, lastModified,
|
||||||
|
HttpHeaders.ETAG, "\"abcd-1\"", HttpHeaders.CACHE_CONTROL, "cacheControl"));
|
||||||
http.getPayload().getContentMetadata().setContentLength(1025l);
|
http.getPayload().getContentMetadata().setContentLength(1025l);
|
||||||
http.getPayload().getContentMetadata().setContentDisposition("contentDisposition");
|
http.getPayload().getContentMetadata().setContentDisposition("contentDisposition");
|
||||||
http.getPayload().getContentMetadata().setContentEncoding("encoding");
|
http.getPayload().getContentMetadata().setContentEncoding("encoding");
|
||||||
ParseObjectMetadataFromHeaders parser = new ParseObjectMetadataFromHeaders(blobParser(http, null),
|
http.getPayload().getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||||
blobToObjectMetadata, "x-amz-meta-");
|
|
||||||
MutableObjectMetadata response = parser.apply(http);
|
MutableObjectMetadata response = parser.apply(http);
|
||||||
|
|
||||||
|
MutableObjectMetadataImpl expects = new MutableObjectMetadataImpl();
|
||||||
|
expects.setCacheControl("cacheControl");
|
||||||
|
expects.getContentMetadata().setContentDisposition("contentDisposition");
|
||||||
|
expects.getContentMetadata().setContentEncoding("encoding");
|
||||||
|
expects.getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
expects.getContentMetadata().setContentLength(1025l);
|
||||||
|
expects.setETag("\"abcd-1\"");
|
||||||
|
expects.setKey("key");
|
||||||
|
expects.setLastModified(now);
|
||||||
|
expects.setOwner(null);
|
||||||
|
expects.setStorageClass(StorageClass.STANDARD);
|
||||||
|
expects.setUserMetadata(userMetadata);
|
||||||
assertEquals(response, expects);
|
assertEquals(response, expects);
|
||||||
}
|
}
|
||||||
|
|
||||||
Date now = new Date();
|
@Test
|
||||||
Map<String, String> userMetadata = ImmutableMap.of("foo", "bar");
|
void testAmzEtagStillParsesToMD5AndDoesntMistakeAmzEtagForUserMetadata() throws Exception {
|
||||||
private MutableObjectMetadataImpl expects;
|
|
||||||
BlobToObjectMetadata blobToObjectMetadata;
|
|
||||||
|
|
||||||
private ParseSystemAndUserMetadataFromHeaders blobParser(HttpResponse response, String etag) {
|
HttpResponse http = new HttpResponse(400, "boa", Payloads.newStringPayload(""), ImmutableMultimap.of(
|
||||||
ParseSystemAndUserMetadataFromHeaders parser = createMock(ParseSystemAndUserMetadataFromHeaders.class);
|
S3Headers.USER_METADATA_PREFIX + "foo", "bar", HttpHeaders.LAST_MODIFIED, lastModified,
|
||||||
MutableBlobMetadata md = new MutableBlobMetadataImpl();
|
HttpHeaders.CACHE_CONTROL, "cacheControl", S3Headers.AMZ_ETAG, "\"abcd\""));
|
||||||
md.getContentMetadata().setContentType("type");
|
http.getPayload().getContentMetadata().setContentLength(1025l);
|
||||||
md.setETag(etag);
|
http.getPayload().getContentMetadata().setContentDisposition("contentDisposition");
|
||||||
md.setName("key");
|
http.getPayload().getContentMetadata().setContentEncoding("encoding");
|
||||||
md.setLastModified(now);
|
http.getPayload().getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||||
md.getContentMetadata().setContentLength(1025l);
|
|
||||||
md.getContentMetadata().setContentDisposition("contentDisposition");
|
|
||||||
md.getContentMetadata().setContentEncoding("encoding");
|
|
||||||
md.getContentMetadata().setContentMD5(CryptoStreams.hex("abcd"));
|
|
||||||
md.setUserMetadata(userMetadata);
|
|
||||||
expect(parser.apply(response)).andReturn(md);
|
|
||||||
replay(parser);
|
|
||||||
return parser;
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeTest
|
MutableObjectMetadata response = parser.apply(http);
|
||||||
void setUp() {
|
|
||||||
blobToObjectMetadata = new BlobToObjectMetadata();
|
MutableObjectMetadataImpl expects = new MutableObjectMetadataImpl();
|
||||||
expects = new MutableObjectMetadataImpl();
|
|
||||||
expects.setCacheControl("cacheControl");
|
expects.setCacheControl("cacheControl");
|
||||||
expects.getContentMetadata().setContentDisposition("contentDisposition");
|
expects.getContentMetadata().setContentDisposition("contentDisposition");
|
||||||
expects.getContentMetadata().setContentEncoding("encoding");
|
expects.getContentMetadata().setContentEncoding("encoding");
|
||||||
expects.getContentMetadata().setContentMD5(CryptoStreams.hex("abcd"));
|
expects.getContentMetadata().setContentMD5(CryptoStreams.hex("abcd"));
|
||||||
expects.getContentMetadata().setContentType("type");
|
expects.getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||||
expects.getContentMetadata().setContentLength(1025l);
|
expects.getContentMetadata().setContentLength(1025l);
|
||||||
expects.setETag("\"abcd\"");
|
expects.setETag("\"abcd\"");
|
||||||
expects.setKey("key");
|
expects.setKey("key");
|
||||||
|
@ -118,5 +135,29 @@ public class ParseObjectMetadataFromHeadersTest {
|
||||||
expects.setOwner(null);
|
expects.setOwner(null);
|
||||||
expects.setStorageClass(StorageClass.STANDARD);
|
expects.setStorageClass(StorageClass.STANDARD);
|
||||||
expects.setUserMetadata(userMetadata);
|
expects.setUserMetadata(userMetadata);
|
||||||
|
|
||||||
|
assertEquals(response, expects);
|
||||||
|
}
|
||||||
|
|
||||||
|
String lastModified = new SimpleDateFormatDateService().rfc822DateFormat(new Date());
|
||||||
|
// rfc isn't accurate down to nanos, so we'll parse back to ensure tests pass
|
||||||
|
Date now = new SimpleDateFormatDateService().rfc822DateParse(lastModified);
|
||||||
|
|
||||||
|
Map<String, String> userMetadata = ImmutableMap.of("foo", "bar");
|
||||||
|
ParseObjectMetadataFromHeaders parser;
|
||||||
|
|
||||||
|
@BeforeTest
|
||||||
|
void setUp() {
|
||||||
|
parser = Guice.createInjector(new AbstractModule() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
bind(RequestSigner.class).toInstance(createMock(RequestSigner.class));
|
||||||
|
bindConstant().annotatedWith(Names.named(PROPERTY_HEADER_TAG)).to(S3Headers.DEFAULT_AMAZON_HEADERTAG);
|
||||||
|
bindConstant().annotatedWith(Names.named(PROPERTY_USER_METADATA_PREFIX)).to(S3Headers.USER_METADATA_PREFIX);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}).getInstance(ParseObjectMetadataFromHeaders.class).setKey("key");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ import org.jclouds.http.HttpResponse;
|
||||||
import org.jclouds.http.functions.config.SaxParserModule;
|
import org.jclouds.http.functions.config.SaxParserModule;
|
||||||
import org.jclouds.io.Payloads;
|
import org.jclouds.io.Payloads;
|
||||||
import org.jclouds.rest.RequestSigner;
|
import org.jclouds.rest.RequestSigner;
|
||||||
|
import org.jclouds.s3.reference.S3Headers;
|
||||||
import org.jclouds.util.Strings2;
|
import org.jclouds.util.Strings2;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
@ -57,16 +58,17 @@ public class ParseS3ErrorFromXmlContentTest {
|
||||||
@Test
|
@Test
|
||||||
public void test404ContainerNotFoundExceptionPath() {
|
public void test404ContainerNotFoundExceptionPath() {
|
||||||
assertCodeMakes("GET", URI
|
assertCodeMakes("GET", URI
|
||||||
.create("http://partnercloud.eucalyptus.com:8773/services/Walrus/adriancole-blobstore58/"), 404, "HTTP/1.1 404 Not Found", false,
|
.create("http://partnercloud.eucalyptus.com:8773/services/Walrus/adriancole-blobstore58/"), 404,
|
||||||
"<Error><Code>Monster.NotFound</Code></Error>", ContainerNotFoundException.class);
|
"HTTP/1.1 404 Not Found", false, "<Error><Code>Monster.NotFound</Code></Error>",
|
||||||
|
ContainerNotFoundException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test404KeyNotFoundExceptionPath() {
|
public void test404KeyNotFoundExceptionPath() {
|
||||||
assertCodeMakes("GET", URI
|
assertCodeMakes("GET", URI
|
||||||
.create("http://partnercloud.eucalyptus.com:8773/services/Walrus/adriancole-blobstore58/apples"), 404, "HTTP/1.1 404 Not Found", false,
|
.create("http://partnercloud.eucalyptus.com:8773/services/Walrus/adriancole-blobstore58/apples"), 404,
|
||||||
"<Error><Code>Monster.NotFound</Code></Error>", KeyNotFoundException.class);
|
"HTTP/1.1 404 Not Found", false, "<Error><Code>Monster.NotFound</Code></Error>",
|
||||||
|
KeyNotFoundException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertCodeMakes(String method, URI uri, int statusCode, String message, final boolean virtualHost,
|
private void assertCodeMakes(String method, URI uri, int statusCode, String message, final boolean virtualHost,
|
||||||
|
@ -77,7 +79,7 @@ public class ParseS3ErrorFromXmlContentTest {
|
||||||
@Override
|
@Override
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
bind(RequestSigner.class).toInstance(createMock(RequestSigner.class));
|
bind(RequestSigner.class).toInstance(createMock(RequestSigner.class));
|
||||||
bindConstant().annotatedWith(Names.named(PROPERTY_HEADER_TAG)).to("amz");
|
bindConstant().annotatedWith(Names.named(PROPERTY_HEADER_TAG)).to(S3Headers.DEFAULT_AMAZON_HEADERTAG);
|
||||||
bindConstant().annotatedWith(Names.named(PROPERTY_S3_SERVICE_PATH)).to(SERVICE_PATH);
|
bindConstant().annotatedWith(Names.named(PROPERTY_S3_SERVICE_PATH)).to(SERVICE_PATH);
|
||||||
bindConstant().annotatedWith(Names.named(PROPERTY_S3_VIRTUAL_HOST_BUCKETS)).to(virtualHost);
|
bindConstant().annotatedWith(Names.named(PROPERTY_S3_VIRTUAL_HOST_BUCKETS)).to(virtualHost);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,19 @@
|
||||||
|
|
||||||
package org.jclouds.s3.options;
|
package org.jclouds.s3.options;
|
||||||
|
|
||||||
|
import static com.google.common.collect.Iterables.getOnlyElement;
|
||||||
import static org.jclouds.s3.options.CopyObjectOptions.Builder.ifSourceETagDoesntMatch;
|
import static org.jclouds.s3.options.CopyObjectOptions.Builder.ifSourceETagDoesntMatch;
|
||||||
import static org.jclouds.s3.options.CopyObjectOptions.Builder.ifSourceETagMatches;
|
import static org.jclouds.s3.options.CopyObjectOptions.Builder.ifSourceETagMatches;
|
||||||
import static org.jclouds.s3.options.CopyObjectOptions.Builder.ifSourceModifiedSince;
|
import static org.jclouds.s3.options.CopyObjectOptions.Builder.ifSourceModifiedSince;
|
||||||
import static org.jclouds.s3.options.CopyObjectOptions.Builder.ifSourceUnmodifiedSince;
|
import static org.jclouds.s3.options.CopyObjectOptions.Builder.ifSourceUnmodifiedSince;
|
||||||
import static org.jclouds.s3.options.CopyObjectOptions.Builder.overrideAcl;
|
import static org.jclouds.s3.options.CopyObjectOptions.Builder.overrideAcl;
|
||||||
import static org.jclouds.s3.options.CopyObjectOptions.Builder.overrideMetadataWith;
|
import static org.jclouds.s3.options.CopyObjectOptions.Builder.overrideMetadataWith;
|
||||||
|
import static org.jclouds.s3.reference.S3Headers.CANNED_ACL;
|
||||||
|
import static org.jclouds.s3.reference.S3Headers.COPY_SOURCE_IF_MODIFIED_SINCE;
|
||||||
|
import static org.jclouds.s3.reference.S3Headers.COPY_SOURCE_IF_NO_MATCH;
|
||||||
|
import static org.jclouds.s3.reference.S3Headers.DEFAULT_AMAZON_HEADERTAG;
|
||||||
|
import static org.jclouds.s3.reference.S3Headers.METADATA_DIRECTIVE;
|
||||||
|
import static org.jclouds.s3.reference.S3Headers.USER_METADATA_PREFIX;
|
||||||
import static org.testng.Assert.assertEquals;
|
import static org.testng.Assert.assertEquals;
|
||||||
import static org.testng.Assert.assertNull;
|
import static org.testng.Assert.assertNull;
|
||||||
import static org.testng.Assert.assertTrue;
|
import static org.testng.Assert.assertTrue;
|
||||||
|
@ -33,13 +40,12 @@ import java.io.UnsupportedEncodingException;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.jclouds.s3.domain.CannedAccessPolicy;
|
|
||||||
import org.jclouds.s3.reference.S3Headers;
|
|
||||||
import org.jclouds.date.internal.SimpleDateFormatDateService;
|
import org.jclouds.date.internal.SimpleDateFormatDateService;
|
||||||
|
import org.jclouds.s3.domain.CannedAccessPolicy;
|
||||||
import org.testng.annotations.BeforeMethod;
|
import org.testng.annotations.BeforeMethod;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,14 +60,10 @@ public class CopyObjectOptionsTest {
|
||||||
private Date now;
|
private Date now;
|
||||||
private String nowExpected;
|
private String nowExpected;
|
||||||
private Map<String, String> goodMeta;
|
private Map<String, String> goodMeta;
|
||||||
private Map<String, String> badMeta;
|
|
||||||
|
|
||||||
@BeforeMethod
|
@BeforeMethod
|
||||||
void setUp() {
|
void setUp() {
|
||||||
goodMeta = Maps.newHashMap();
|
goodMeta = ImmutableMap.of(USER_METADATA_PREFIX + "adrian", "foo");
|
||||||
goodMeta.put("x-amz-meta-adrian", "foo");
|
|
||||||
badMeta = Maps.newHashMap();
|
|
||||||
badMeta.put("x-google-meta-adrian", "foo");
|
|
||||||
now = new Date();
|
now = new Date();
|
||||||
nowExpected = new SimpleDateFormatDateService().rfc822DateFormat(now);
|
nowExpected = new SimpleDateFormatDateService().rfc822DateFormat(now);
|
||||||
etag = "mama";
|
etag = "mama";
|
||||||
|
@ -70,8 +72,8 @@ public class CopyObjectOptionsTest {
|
||||||
@Test
|
@Test
|
||||||
void testGoodMetaStatic() {
|
void testGoodMetaStatic() {
|
||||||
CopyObjectOptions options = overrideMetadataWith(goodMeta);
|
CopyObjectOptions options = overrideMetadataWith(goodMeta);
|
||||||
options.setMetadataPrefix("x-amz-meta-");
|
options.setMetadataPrefix(USER_METADATA_PREFIX);
|
||||||
options.setHeaderTag("amz");
|
options.setHeaderTag(DEFAULT_AMAZON_HEADERTAG);
|
||||||
assertGoodMeta(options);
|
assertGoodMeta(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,17 +87,17 @@ public class CopyObjectOptionsTest {
|
||||||
assert options.getMetadata() != null;
|
assert options.getMetadata() != null;
|
||||||
Multimap<String, String> headers = options.buildRequestHeaders();
|
Multimap<String, String> headers = options.buildRequestHeaders();
|
||||||
assertEquals(headers.size(), 2);
|
assertEquals(headers.size(), 2);
|
||||||
assertEquals(headers.get("x-amz-metadata-directive").iterator().next(), "REPLACE");
|
assertEquals(headers.get(METADATA_DIRECTIVE).iterator().next(), "REPLACE");
|
||||||
assertEquals(options.getMetadata().size(), 1);
|
assertEquals(options.getMetadata().size(), 1);
|
||||||
assertEquals(headers.get("x-amz-meta-adrian").iterator().next(), "foo");
|
assertEquals(headers.get(USER_METADATA_PREFIX + "adrian").iterator().next(), "foo");
|
||||||
assertEquals(options.getMetadata().get("x-amz-meta-adrian"), "foo");
|
assertEquals(options.getMetadata().get(USER_METADATA_PREFIX + "adrian"), "foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGoodMeta() {
|
void testGoodMeta() {
|
||||||
CopyObjectOptions options = new CopyObjectOptions();
|
CopyObjectOptions options = new CopyObjectOptions();
|
||||||
options.setHeaderTag("amz");
|
options.setHeaderTag(DEFAULT_AMAZON_HEADERTAG);
|
||||||
options.setMetadataPrefix("x-amz-meta-");
|
options.setMetadataPrefix(USER_METADATA_PREFIX);
|
||||||
options.overrideMetadataWith(goodMeta);
|
options.overrideMetadataWith(goodMeta);
|
||||||
assertGoodMeta(options);
|
assertGoodMeta(options);
|
||||||
}
|
}
|
||||||
|
@ -272,25 +274,24 @@ public class CopyObjectOptionsTest {
|
||||||
@Test
|
@Test
|
||||||
void testBuildRequestHeadersWhenMetadataNull() throws UnsupportedEncodingException {
|
void testBuildRequestHeadersWhenMetadataNull() throws UnsupportedEncodingException {
|
||||||
CopyObjectOptions options = new CopyObjectOptions();
|
CopyObjectOptions options = new CopyObjectOptions();
|
||||||
options.setHeaderTag("amz");
|
options.setHeaderTag(DEFAULT_AMAZON_HEADERTAG);
|
||||||
|
|
||||||
options.setMetadataPrefix("x-amz-meta-");
|
options.setMetadataPrefix(USER_METADATA_PREFIX);
|
||||||
assert options.buildRequestHeaders() != null;
|
assert options.buildRequestHeaders() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testBuildRequestHeaders() throws UnsupportedEncodingException {
|
void testBuildRequestHeaders() throws UnsupportedEncodingException {
|
||||||
CopyObjectOptions options = ifSourceModifiedSince(now).ifSourceETagDoesntMatch(etag)
|
CopyObjectOptions options = ifSourceModifiedSince(now).ifSourceETagDoesntMatch(etag).overrideMetadataWith(
|
||||||
.overrideMetadataWith(goodMeta);
|
goodMeta);
|
||||||
options.setHeaderTag("amz");
|
options.setHeaderTag(DEFAULT_AMAZON_HEADERTAG);
|
||||||
|
|
||||||
options.setMetadataPrefix("x-amz-meta-");
|
options.setMetadataPrefix(USER_METADATA_PREFIX);
|
||||||
|
|
||||||
Multimap<String, String> headers = options.buildRequestHeaders();
|
Multimap<String, String> headers = options.buildRequestHeaders();
|
||||||
assertEquals(headers.get("x-amz-copy-source-if-modified-since").iterator().next(),
|
assertEquals(getOnlyElement(headers.get(COPY_SOURCE_IF_MODIFIED_SINCE)), new SimpleDateFormatDateService()
|
||||||
new SimpleDateFormatDateService().rfc822DateFormat(now));
|
.rfc822DateFormat(now));
|
||||||
assertEquals(headers.get("x-amz-copy-source-if-none-match").iterator().next(), "\"" + etag
|
assertEquals(getOnlyElement(headers.get(COPY_SOURCE_IF_NO_MATCH)), "\"" + etag + "\"");
|
||||||
+ "\"");
|
|
||||||
for (String value : goodMeta.values())
|
for (String value : goodMeta.values())
|
||||||
assertTrue(headers.containsValue(value));
|
assertTrue(headers.containsValue(value));
|
||||||
|
|
||||||
|
@ -311,13 +312,12 @@ public class CopyObjectOptionsTest {
|
||||||
@Test
|
@Test
|
||||||
void testBuildRequestHeadersACL() throws UnsupportedEncodingException {
|
void testBuildRequestHeadersACL() throws UnsupportedEncodingException {
|
||||||
CopyObjectOptions options = overrideAcl(CannedAccessPolicy.AUTHENTICATED_READ);
|
CopyObjectOptions options = overrideAcl(CannedAccessPolicy.AUTHENTICATED_READ);
|
||||||
options.setHeaderTag("amz");
|
options.setHeaderTag(DEFAULT_AMAZON_HEADERTAG);
|
||||||
|
|
||||||
options.setMetadataPrefix("x-amz-meta-");
|
options.setMetadataPrefix(USER_METADATA_PREFIX);
|
||||||
|
|
||||||
Multimap<String, String> headers = options.buildRequestHeaders();
|
Multimap<String, String> headers = options.buildRequestHeaders();
|
||||||
|
|
||||||
assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(),
|
assertEquals(headers.get(CANNED_ACL).iterator().next(), CannedAccessPolicy.AUTHENTICATED_READ.toString());
|
||||||
CannedAccessPolicy.AUTHENTICATED_READ.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ public class PutBucketOptionsTest {
|
||||||
|
|
||||||
PutBucketOptions options = withBucketAcl(CannedAccessPolicy.AUTHENTICATED_READ);
|
PutBucketOptions options = withBucketAcl(CannedAccessPolicy.AUTHENTICATED_READ);
|
||||||
|
|
||||||
options.setHeaderTag("amz");
|
options.setHeaderTag(S3Headers.DEFAULT_AMAZON_HEADERTAG);
|
||||||
Multimap<String, String> headers = options.buildRequestHeaders();
|
Multimap<String, String> headers = options.buildRequestHeaders();
|
||||||
assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(),
|
assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(),
|
||||||
CannedAccessPolicy.AUTHENTICATED_READ.toString());
|
CannedAccessPolicy.AUTHENTICATED_READ.toString());
|
||||||
|
|
|
@ -54,7 +54,7 @@ public class PutObjectOptionsTest {
|
||||||
void testBuildRequestHeaders() throws UnsupportedEncodingException {
|
void testBuildRequestHeaders() throws UnsupportedEncodingException {
|
||||||
|
|
||||||
PutObjectOptions options = withAcl(CannedAccessPolicy.AUTHENTICATED_READ);
|
PutObjectOptions options = withAcl(CannedAccessPolicy.AUTHENTICATED_READ);
|
||||||
options.setHeaderTag("amz");
|
options.setHeaderTag(S3Headers.DEFAULT_AMAZON_HEADERTAG);
|
||||||
|
|
||||||
Multimap<String, String> headers = options.buildRequestHeaders();
|
Multimap<String, String> headers = options.buildRequestHeaders();
|
||||||
assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(),
|
assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(),
|
||||||
|
|
Loading…
Reference in New Issue