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:
Adrian Cole 2011-02-26 21:55:22 -08:00
parent 71eb7ea8b7
commit 01ef7140db
12 changed files with 368 additions and 296 deletions

View File

@ -32,6 +32,7 @@ import static org.jclouds.s3.reference.S3Constants.PROPERTY_S3_VIRTUAL_HOST_BUCK
import java.util.Properties;
import org.jclouds.PropertiesBuilder;
import org.jclouds.s3.reference.S3Headers;
/**
* Builds properties used in S3 Connections
@ -44,7 +45,7 @@ public class S3PropertiesBuilder extends PropertiesBuilder {
Properties properties = super.defaultProperties();
properties.setProperty(PROPERTY_API_VERSION, S3AsyncClient.VERSION);
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_VIRTUAL_HOST_BUCKETS, "true");
properties.setProperty(PROPERTY_RELAX_HOSTNAME, "true");
@ -67,8 +68,8 @@ public class S3PropertiesBuilder extends PropertiesBuilder {
protected void setMetaPrefix() {
if (properties.getProperty(PROPERTY_USER_METADATA_PREFIX) == null) {
properties.setProperty(PROPERTY_USER_METADATA_PREFIX,
String.format("x-%s-meta-", properties.getProperty(PROPERTY_HEADER_TAG)));
properties.setProperty(PROPERTY_USER_METADATA_PREFIX, String.format("x-%s-meta-", properties
.getProperty(PROPERTY_HEADER_TAG)));
}
}

View File

@ -20,13 +20,17 @@
package org.jclouds.s3.filters;
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_IDENTITY;
import static org.jclouds.aws.reference.AWSConstants.PROPERTY_AUTH_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_VIRTUAL_HOST_BUCKETS;
import static org.jclouds.util.Strings2.toInputStream;
import java.lang.annotation.Annotation;
import java.util.Arrays;
@ -43,8 +47,6 @@ import javax.inject.Singleton;
import javax.ws.rs.core.HttpHeaders;
import org.jclouds.Constants;
import org.jclouds.s3.Bucket;
import org.jclouds.s3.reference.S3Headers;
import org.jclouds.crypto.Crypto;
import org.jclouds.crypto.CryptoStreams;
import org.jclouds.date.TimeStamp;
@ -53,43 +55,44 @@ import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpRequestFilter;
import org.jclouds.http.HttpUtils;
import org.jclouds.http.internal.SignatureWire;
import org.jclouds.http.utils.ModifyRequest;
import org.jclouds.io.InputSuppliers;
import org.jclouds.logging.Logger;
import org.jclouds.rest.RequestSigner;
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.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.Iterables;
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.TreeMultimap;
/**
* 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
*
*/
@Singleton
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 };
/** Prefix for general Amazon headers: x-amz- */
public static final String AMAZON_PREFIX = "x-amz-";
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");
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 String accessKey;
private final String secretKey;
@ -133,31 +136,27 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
}
HttpRequest replaceAuthorizationHeader(HttpRequest request, String signature) {
request = ModifyRequest.replaceHeader(request, HttpHeaders.AUTHORIZATION, authTag + " " + accessKey + ":"
+ signature);
request = replaceHeader(request, HttpHeaders.AUTHORIZATION, authTag + " " + accessKey + ":" + signature);
return request;
}
HttpRequest replaceDateHeader(HttpRequest request) {
Builder<String, String> builder = ImmutableMap.builder();
String date = timeStampProvider.get();
builder.put(HttpHeaders.DATE, date);
request = ModifyRequest.replaceHeaders(request, Multimaps.forMap(builder.build()));
request = replaceHeader(request, HttpHeaders.DATE, timeStampProvider.get());
return request;
}
public String createStringToSign(HttpRequest request) {
utils.logRequest(signatureLog, request, ">>");
SortedSetMultimap<String, String> canonicalizedHeaders = TreeMultimap.create();
SortedSetMultimap<String, String> canonicalizedHeaders = TreeMultimap.create();
StringBuilder buffer = new StringBuilder();
// re-sign the request
appendMethod(request, buffer);
appendPayloadMetadata(request, buffer);
appendHttpHeaders(request, canonicalizedHeaders);
// Remove default date timestamp if "x-amz-date" is set.
if (canonicalizedHeaders.containsKey(S3Headers.ALTERNATE_DATE)) {
canonicalizedHeaders.put("date", "");
if (canonicalizedHeaders.containsKey("x-" + headerTag + "-date")) {
canonicalizedHeaders.removeAll("date");
}
appendAmzHeaders(canonicalizedHeaders, buffer);
@ -172,7 +171,7 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
String calculateSignature(String toSign) throws HttpException {
String signature = sign(toSign);
if (signatureWire.enabled())
signatureWire.input(Strings2.toInputStream(signature));
signatureWire.input(toInputStream(signature));
return signature;
}
@ -196,35 +195,35 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
for (Entry<String, String> header : canonicalizedHeaders.entries()) {
String key = header.getKey();
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) {
// 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(
utils.valueOrEmpty(request.getPayload() == null ? null : request.getPayload().getContentMetadata()
.getContentMD5())).append("\n");
request.getPayload() == null ? utils.valueOrEmpty(request.getFirstHeaderOrNull("Content-MD5")) : utils
.valueOrEmpty(request.getPayload() == null ? null : request.getPayload().getContentMetadata()
.getContentMD5())).append("\n");
buffer.append(
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)
buffer.append(valueOrEmpty(request.getHeaders().get(header))).append("\n");
}
@VisibleForTesting
void appendHttpHeaders(HttpRequest request,
SortedSetMultimap<String, String> canonicalizedHeaders) {
void appendHttpHeaders(HttpRequest request, SortedSetMultimap<String, String> canonicalizedHeaders) {
Multimap<String, String> headers = request.getHeaders();
for (Entry<String, String> header : headers.entries()) {
if (header.getKey() == null)
continue;
String key = header.getKey().toString()
.toLowerCase(Locale.getDefault());
String key = header.getKey().toString().toLowerCase(Locale.getDefault());
// Ignore any headers that are not particularly interesting.
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());
}
}
@ -238,12 +237,7 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
String bucketName = null;
for (int i = 0; i < request.getJavaMethod().getParameterAnnotations().length; i++) {
if (Iterables.any(Arrays.asList(request.getJavaMethod().getParameterAnnotations()[i]),
new Predicate<Annotation>() {
public boolean apply(Annotation input) {
return input.annotationType().equals(Bucket.class);
}
})) {
if (any(Arrays.asList(request.getJavaMethod().getParameterAnnotations()[i]), ANNOTATIONTYPE_BUCKET)) {
bucketName = (String) request.getArgs().get(i);
break;
}
@ -261,20 +255,14 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
// ...however, there are a few exceptions that must be included in the
// signed URI.
if (request.getEndpoint().getQuery() != null) {
SortedSetMultimap<String, String> sortedParams = TreeMultimap.create();
String[] params = request.getEndpoint().getQuery().split("&");
for (String param : params) {
String[] paramNameAndValue = param.split("=");
sortedParams.put(paramNameAndValue[0], paramNameAndValue.length == 2 ? paramNameAndValue[1] : null);
}
Multimap<String, String> params = parseQueryToMap(request.getEndpoint().getQuery());
char separator = '?';
for (Entry<String, String> param: sortedParams.entries()) {
String paramName = param.getKey();
for (String paramName : Ordering.natural().sortedCopy(params.keySet())) {
// 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);
String paramValue = param.getValue();
String paramValue = get(params.get(paramName), 0);
if (paramValue != null) {
toSign.append("=").append(paramValue);
}

View File

@ -21,18 +21,21 @@ package org.jclouds.s3.functions;
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.Named;
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.functions.ParseSystemAndUserMetadataFromHeaders;
import org.jclouds.crypto.CryptoStreams;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
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.base.Function;
@ -45,19 +48,23 @@ import com.google.common.base.Function;
* @author Adrian Cole
*/
public class ParseObjectMetadataFromHeaders implements Function<HttpResponse, MutableObjectMetadata>,
InvocationContext<ParseObjectMetadataFromHeaders> {
InvocationContext<ParseObjectMetadataFromHeaders> {
private final ParseSystemAndUserMetadataFromHeaders blobMetadataParser;
private final BlobToObjectMetadata blobToObjectMetadata;
private final String userMdPrefix;
@Inject
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.blobToObjectMetadata = blobToObjectMetadata;
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
* {@link org.jclouds.s3.domain.internal.MutableObjectMetadata} object.
@ -65,13 +72,20 @@ public class ParseObjectMetadataFromHeaders implements Function<HttpResponse, Mu
public MutableObjectMetadata apply(HttpResponse from) {
BlobMetadata base = blobMetadataParser.apply(from);
MutableObjectMetadata to = blobToObjectMetadata.apply(base);
addETagTo(from, to);
if (to.getContentMetadata().getContentMD5() == null && to.getETag() != null) {
byte[] md5 = CryptoStreams.hex(to.getETag().replaceAll("\"", ""));
// it is possible others will look at the http payload directly
from.getPayload().getContentMetadata().setContentMD5(md5);
to.getContentMetadata().setContentMD5(md5);
Matcher md5Matcher = MD5_FROM_ETAG.matcher(to.getETag());
if (md5Matcher.find()) {
byte[] md5 = CryptoStreams.hex(md5Matcher.group(1));
// 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));
return to;
}
@ -95,4 +109,8 @@ public class ParseObjectMetadataFromHeaders implements Function<HttpResponse, Mu
return this;
}
public ParseObjectMetadataFromHeaders setKey(String key) {
blobMetadataParser.setName(key);
return this;
}
}

View File

@ -23,6 +23,13 @@ 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.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.util.Date;
@ -32,14 +39,13 @@ import java.util.Map.Entry;
import javax.inject.Inject;
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.internal.SimpleDateFormatDateService;
import org.jclouds.http.options.BaseHttpRequestOptions;
import org.jclouds.s3.domain.CannedAccessPolicy;
import com.google.common.base.Preconditions;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
/**
@ -99,7 +105,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
public CopyObjectOptions overrideAcl(CannedAccessPolicy acl) {
this.acl = checkNotNull(acl, "acl");
if (!acl.equals(CannedAccessPolicy.PRIVATE))
this.replaceHeader(S3Headers.CANNED_ACL, acl.toString());
this.replaceHeader(CANNED_ACL, acl.toString());
return this;
}
@ -124,7 +130,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
* @see CopyObjectOptions#ifSourceModifiedSince(Date)
*/
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)
*/
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)
*/
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)
*/
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) {
checkState(getIfMatch() == null, "ifETagMatches() is not compatible with ifModifiedSince()");
checkState(getIfUnmodifiedSince() == null,
"ifUnmodifiedSince() is not compatible with ifModifiedSince()");
replaceHeader("x-amz-copy-source-if-modified-since", dateService
.rfc822DateFormat(checkNotNull(ifModifiedSince, "ifModifiedSince")));
checkState(getIfUnmodifiedSince() == null, "ifUnmodifiedSince() is not compatible with ifModifiedSince()");
replaceHeader(COPY_SOURCE_IF_MODIFIED_SINCE, dateService.rfc822DateFormat(checkNotNull(ifModifiedSince,
"ifModifiedSince")));
return this;
}
@ -206,12 +211,10 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
* {@link #ifSourceModifiedSince(Date)}
*/
public CopyObjectOptions ifSourceUnmodifiedSince(Date ifUnmodifiedSince) {
checkState(getIfNoneMatch() == null,
"ifETagDoesntMatch() is not compatible with ifUnmodifiedSince()");
checkState(getIfModifiedSince() == null,
"ifModifiedSince() is not compatible with ifUnmodifiedSince()");
replaceHeader("x-amz-copy-source-if-unmodified-since", dateService
.rfc822DateFormat(checkNotNull(ifUnmodifiedSince, "ifUnmodifiedSince")));
checkState(getIfNoneMatch() == null, "ifETagDoesntMatch() is not compatible with ifUnmodifiedSince()");
checkState(getIfModifiedSince() == null, "ifModifiedSince() is not compatible with ifUnmodifiedSince()");
replaceHeader(COPY_SOURCE_IF_UNMODIFIED_SINCE, dateService.rfc822DateFormat(checkNotNull(ifUnmodifiedSince,
"ifUnmodifiedSince")));
return this;
}
@ -226,12 +229,9 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
* hash representing the payload
*/
public CopyObjectOptions ifSourceETagMatches(String eTag) throws UnsupportedEncodingException {
checkState(getIfNoneMatch() == null,
"ifETagDoesntMatch() is not compatible with ifETagMatches()");
checkState(getIfModifiedSince() == null,
"ifModifiedSince() is not compatible with ifETagMatches()");
replaceHeader("x-amz-copy-source-if-match", String.format("\"%1$s\"", checkNotNull(eTag,
"eTag")));
checkState(getIfNoneMatch() == null, "ifETagDoesntMatch() is not compatible with ifETagMatches()");
checkState(getIfModifiedSince() == null, "ifModifiedSince() is not compatible with ifETagMatches()");
replaceHeader(COPY_SOURCE_IF_MATCH, String.format("\"%1$s\"", checkNotNull(eTag, "eTag")));
return this;
}
@ -246,13 +246,11 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
* @throws UnsupportedEncodingException
* if there was a problem converting this into an S3 eTag string
*/
public CopyObjectOptions ifSourceETagDoesntMatch(String eTag)
throws UnsupportedEncodingException {
public CopyObjectOptions ifSourceETagDoesntMatch(String eTag) throws UnsupportedEncodingException {
checkState(getIfMatch() == null, "ifETagMatches() is not compatible with ifETagDoesntMatch()");
Preconditions.checkState(getIfUnmodifiedSince() == null,
"ifUnmodifiedSince() is not compatible with ifETagDoesntMatch()");
replaceHeader("x-amz-copy-source-if-none-match", String.format("\"%1$s\"", checkNotNull(eTag,
"ifETagDoesntMatch")));
replaceHeader(COPY_SOURCE_IF_NO_MATCH, String.format("\"%s\"", checkNotNull(eTag, "ifETagDoesntMatch")));
return this;
}
@ -260,18 +258,17 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
public Multimap<String, String> buildRequestHeaders() {
checkState(headerTag != null, "headerTag 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()) {
returnVal.put(entry.getKey().replace("amz", headerTag), entry.getValue());
returnVal.put(entry.getKey().replace(DEFAULT_AMAZON_HEADERTAG, headerTag), entry.getValue());
}
if (metadata != null) {
returnVal.put(METADATA_DIRECTIVE.replace(DEFAULT_AMAZON_HEADERTAG, headerTag), "REPLACE");
for (String key : metadata.keySet()) {
returnVal.put(key.startsWith(metadataPrefix) ? key : metadataPrefix + key, metadata
.get(key));
returnVal.put(key.startsWith(metadataPrefix) ? key : metadataPrefix + key, metadata.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)
*/
public static CopyObjectOptions ifSourceETagMatches(String eTag)
throws UnsupportedEncodingException {
public static CopyObjectOptions ifSourceETagMatches(String eTag) throws UnsupportedEncodingException {
CopyObjectOptions options = new CopyObjectOptions();
return options.ifSourceETagMatches(eTag);
}
@ -320,8 +316,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
/**
* @see CopyObjectOptions#ifSourceETagDoesntMatch(String)
*/
public static CopyObjectOptions ifSourceETagDoesntMatch(String eTag)
throws UnsupportedEncodingException {
public static CopyObjectOptions ifSourceETagDoesntMatch(String eTag) throws UnsupportedEncodingException {
CopyObjectOptions options = new CopyObjectOptions();
return options.ifSourceETagDoesntMatch(eTag);
}

View File

@ -19,28 +19,30 @@
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 javax.inject.Inject;
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.s3.domain.CannedAccessPolicy;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.ImmutableMultimap;
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.
* <p/>
* <h2>
* Usage</h2> The recommended way to instantiate a PutObjectOptions object is to
* statically import PutObjectOptions.Builder.* and invoke a static creation
* method followed by an instance mutator (if needed):
* Usage</h2> The recommended way to instantiate a PutObjectOptions object is to statically import
* PutObjectOptions.Builder.* and invoke a static creation method followed by an instance mutator
* (if needed):
* <p/>
* <code>
* 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));
* <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
*
*/
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 String headerTag;
private CannedAccessPolicy acl = CannedAccessPolicy.PRIVATE;
@Inject
public void setHeaderTag(@Named(PROPERTY_HEADER_TAG) String headerTag) {
this.headerTag = headerTag;
}
private String headerTag;
@Override
public Multimap<String, String> buildRequestHeaders() {
checkState(headerTag != null, "headerTag should have been injected!");
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;
}
@Inject
public void setHeaderTag(@Named(PROPERTY_HEADER_TAG) String headerTag) {
this.headerTag = headerTag;
}
/**
* @see PutObjectOptions#withAcl(CannedAccessPolicy)
*/
public CannedAccessPolicy getAcl() {
return acl;
}
@Override
public Multimap<String, String> buildRequestHeaders() {
checkState(headerTag != null, "headerTag should have been injected!");
ImmutableMultimap.Builder<String, String> returnVal = ImmutableMultimap.<String, String> builder();
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)
*/
public static PutObjectOptions withAcl(CannedAccessPolicy acl) {
PutObjectOptions options = new PutObjectOptions();
return options.withAcl(acl);
}
}
/**
* @see PutObjectOptions#withAcl(CannedAccessPolicy)
*/
public CannedAccessPolicy getAcl() {
return acl;
}
public static class Builder {
/**
* @see PutObjectOptions#withAcl(CannedAccessPolicy)
*/
public static PutObjectOptions withAcl(CannedAccessPolicy acl) {
PutObjectOptions options = new PutObjectOptions();
return options.withAcl(acl);
}
}
}

View File

@ -30,83 +30,105 @@ package org.jclouds.s3.reference;
*/
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 AMAZON_PREFIX = "x-amz-";
public static final String HEADER_PREFIX = "x-" + DEFAULT_AMAZON_HEADERTAG + "-";
/**
* 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
* Policy.
*/
public static final String CANNED_ACL = "x-amz-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";
public static final String CANNED_ACL = HEADER_PREFIX + "acl";
/** Prefix for user metadata: x-amz-meta- */
public static final String USER_METADATA_PREFIX = "x-amz-meta-";
public static final String AMZ_ETAG = HEADER_PREFIX + "meta-object-eTag";
/** 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";
/** Initialization vector (IV) header that is used in the symmetric and envelope encryption mechanisms */
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";
/**
* Instruction file header to be placed in the metadata of instruction files
*/
public static final String CRYPTO_INSTRUCTION_FILE = HEADER_PREFIX + "crypto-instr-file";
}

View File

@ -36,6 +36,7 @@ import org.jclouds.s3.domain.AccessControlList;
import org.jclouds.s3.domain.CannedAccessPolicy;
import org.jclouds.s3.domain.S3Object;
import org.jclouds.s3.options.PutObjectOptions;
import org.jclouds.s3.reference.S3Headers;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@ -72,13 +73,13 @@ public class RequestAuthorizeSignatureTest extends BaseS3AsyncClientTest<S3Async
*/
@Test(threadPoolSize = 3, dataProvider = "dataProvider", timeOut = 10000)
void testIdempotent(HttpRequest request) {
filter.filter(request);
request = filter.filter(request);
String signature = request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION);
String date = request.getFirstHeaderOrNull(HttpHeaders.DATE);
int iterations = 1;
while (request.getFirstHeaderOrNull(HttpHeaders.DATE).equals(date)) {
date = request.getFirstHeaderOrNull(HttpHeaders.DATE);
filter.filter(request);
request = filter.filter(request);
if (request.getFirstHeaderOrNull(HttpHeaders.DATE).equals(date))
assert signature.equals(request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION)) : String.format(
"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);
StringBuilder builder = new StringBuilder();
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 {
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,
S3Object.class, PutObjectOptions[].class), "bucket", object);
return request;

View File

@ -19,31 +19,34 @@
package org.jclouds.s3.functions;
import static org.easymock.EasyMock.expect;
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 java.util.Date;
import java.util.Map;
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.ObjectMetadata.StorageClass;
import org.jclouds.s3.domain.internal.MutableObjectMetadataImpl;
import org.jclouds.blobstore.domain.MutableBlobMetadata;
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.jclouds.s3.reference.S3Headers;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
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
@ -52,65 +55,79 @@ import com.google.common.collect.ImmutableMultimap;
public class ParseObjectMetadataFromHeadersTest {
@Test
void testNormal() throws Exception {
void testNormalParsesETagIntoMD5AndMetadataHeaders() throws Exception {
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().setContentDisposition("contentDisposition");
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);
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);
}
@Test
void testAmzEtag() throws Exception {
void testMultipartDoesntAttemptToParseETagIntoMD5() throws Exception {
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().setContentDisposition("contentDisposition");
http.getPayload().getContentMetadata().setContentEncoding("encoding");
ParseObjectMetadataFromHeaders parser = new ParseObjectMetadataFromHeaders(blobParser(http, null),
blobToObjectMetadata, "x-amz-meta-");
http.getPayload().getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM);
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);
}
Date now = new Date();
Map<String, String> userMetadata = ImmutableMap.of("foo", "bar");
private MutableObjectMetadataImpl expects;
BlobToObjectMetadata blobToObjectMetadata;
@Test
void testAmzEtagStillParsesToMD5AndDoesntMistakeAmzEtagForUserMetadata() throws Exception {
private ParseSystemAndUserMetadataFromHeaders blobParser(HttpResponse response, String etag) {
ParseSystemAndUserMetadataFromHeaders parser = createMock(ParseSystemAndUserMetadataFromHeaders.class);
MutableBlobMetadata md = new MutableBlobMetadataImpl();
md.getContentMetadata().setContentType("type");
md.setETag(etag);
md.setName("key");
md.setLastModified(now);
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;
}
HttpResponse http = new HttpResponse(400, "boa", Payloads.newStringPayload(""), ImmutableMultimap.of(
S3Headers.USER_METADATA_PREFIX + "foo", "bar", HttpHeaders.LAST_MODIFIED, lastModified,
HttpHeaders.CACHE_CONTROL, "cacheControl", S3Headers.AMZ_ETAG, "\"abcd\""));
http.getPayload().getContentMetadata().setContentLength(1025l);
http.getPayload().getContentMetadata().setContentDisposition("contentDisposition");
http.getPayload().getContentMetadata().setContentEncoding("encoding");
http.getPayload().getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM);
@BeforeTest
void setUp() {
blobToObjectMetadata = new BlobToObjectMetadata();
expects = new MutableObjectMetadataImpl();
MutableObjectMetadata response = parser.apply(http);
MutableObjectMetadataImpl expects = new MutableObjectMetadataImpl();
expects.setCacheControl("cacheControl");
expects.getContentMetadata().setContentDisposition("contentDisposition");
expects.getContentMetadata().setContentEncoding("encoding");
expects.getContentMetadata().setContentMD5(CryptoStreams.hex("abcd"));
expects.getContentMetadata().setContentType("type");
expects.getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM);
expects.getContentMetadata().setContentLength(1025l);
expects.setETag("\"abcd\"");
expects.setKey("key");
@ -118,5 +135,29 @@ public class ParseObjectMetadataFromHeadersTest {
expects.setOwner(null);
expects.setStorageClass(StorageClass.STANDARD);
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");
}
}

View File

@ -39,6 +39,7 @@ import org.jclouds.http.HttpResponse;
import org.jclouds.http.functions.config.SaxParserModule;
import org.jclouds.io.Payloads;
import org.jclouds.rest.RequestSigner;
import org.jclouds.s3.reference.S3Headers;
import org.jclouds.util.Strings2;
import org.testng.annotations.Test;
@ -57,16 +58,17 @@ public class ParseS3ErrorFromXmlContentTest {
@Test
public void test404ContainerNotFoundExceptionPath() {
assertCodeMakes("GET", URI
.create("http://partnercloud.eucalyptus.com:8773/services/Walrus/adriancole-blobstore58/"), 404, "HTTP/1.1 404 Not Found", false,
"<Error><Code>Monster.NotFound</Code></Error>", ContainerNotFoundException.class);
.create("http://partnercloud.eucalyptus.com:8773/services/Walrus/adriancole-blobstore58/"), 404,
"HTTP/1.1 404 Not Found", false, "<Error><Code>Monster.NotFound</Code></Error>",
ContainerNotFoundException.class);
}
@Test
public void test404KeyNotFoundExceptionPath() {
assertCodeMakes("GET", URI
.create("http://partnercloud.eucalyptus.com:8773/services/Walrus/adriancole-blobstore58/apples"), 404, "HTTP/1.1 404 Not Found", false,
"<Error><Code>Monster.NotFound</Code></Error>", KeyNotFoundException.class);
.create("http://partnercloud.eucalyptus.com:8773/services/Walrus/adriancole-blobstore58/apples"), 404,
"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,
@ -77,7 +79,7 @@ public class ParseS3ErrorFromXmlContentTest {
@Override
protected void configure() {
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_VIRTUAL_HOST_BUCKETS)).to(virtualHost);
}

View File

@ -19,12 +19,19 @@
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.ifSourceETagMatches;
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.overrideAcl;
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.assertNull;
import static org.testng.Assert.assertTrue;
@ -33,13 +40,12 @@ import java.io.UnsupportedEncodingException;
import java.util.Date;
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.s3.domain.CannedAccessPolicy;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.collect.Maps;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
/**
@ -54,14 +60,10 @@ public class CopyObjectOptionsTest {
private Date now;
private String nowExpected;
private Map<String, String> goodMeta;
private Map<String, String> badMeta;
@BeforeMethod
void setUp() {
goodMeta = Maps.newHashMap();
goodMeta.put("x-amz-meta-adrian", "foo");
badMeta = Maps.newHashMap();
badMeta.put("x-google-meta-adrian", "foo");
goodMeta = ImmutableMap.of(USER_METADATA_PREFIX + "adrian", "foo");
now = new Date();
nowExpected = new SimpleDateFormatDateService().rfc822DateFormat(now);
etag = "mama";
@ -70,8 +72,8 @@ public class CopyObjectOptionsTest {
@Test
void testGoodMetaStatic() {
CopyObjectOptions options = overrideMetadataWith(goodMeta);
options.setMetadataPrefix("x-amz-meta-");
options.setHeaderTag("amz");
options.setMetadataPrefix(USER_METADATA_PREFIX);
options.setHeaderTag(DEFAULT_AMAZON_HEADERTAG);
assertGoodMeta(options);
}
@ -85,17 +87,17 @@ public class CopyObjectOptionsTest {
assert options.getMetadata() != null;
Multimap<String, String> headers = options.buildRequestHeaders();
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(headers.get("x-amz-meta-adrian").iterator().next(), "foo");
assertEquals(options.getMetadata().get("x-amz-meta-adrian"), "foo");
assertEquals(headers.get(USER_METADATA_PREFIX + "adrian").iterator().next(), "foo");
assertEquals(options.getMetadata().get(USER_METADATA_PREFIX + "adrian"), "foo");
}
@Test
void testGoodMeta() {
CopyObjectOptions options = new CopyObjectOptions();
options.setHeaderTag("amz");
options.setMetadataPrefix("x-amz-meta-");
options.setHeaderTag(DEFAULT_AMAZON_HEADERTAG);
options.setMetadataPrefix(USER_METADATA_PREFIX);
options.overrideMetadataWith(goodMeta);
assertGoodMeta(options);
}
@ -272,25 +274,24 @@ public class CopyObjectOptionsTest {
@Test
void testBuildRequestHeadersWhenMetadataNull() throws UnsupportedEncodingException {
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;
}
@Test
void testBuildRequestHeaders() throws UnsupportedEncodingException {
CopyObjectOptions options = ifSourceModifiedSince(now).ifSourceETagDoesntMatch(etag)
.overrideMetadataWith(goodMeta);
options.setHeaderTag("amz");
CopyObjectOptions options = ifSourceModifiedSince(now).ifSourceETagDoesntMatch(etag).overrideMetadataWith(
goodMeta);
options.setHeaderTag(DEFAULT_AMAZON_HEADERTAG);
options.setMetadataPrefix("x-amz-meta-");
options.setMetadataPrefix(USER_METADATA_PREFIX);
Multimap<String, String> headers = options.buildRequestHeaders();
assertEquals(headers.get("x-amz-copy-source-if-modified-since").iterator().next(),
new SimpleDateFormatDateService().rfc822DateFormat(now));
assertEquals(headers.get("x-amz-copy-source-if-none-match").iterator().next(), "\"" + etag
+ "\"");
assertEquals(getOnlyElement(headers.get(COPY_SOURCE_IF_MODIFIED_SINCE)), new SimpleDateFormatDateService()
.rfc822DateFormat(now));
assertEquals(getOnlyElement(headers.get(COPY_SOURCE_IF_NO_MATCH)), "\"" + etag + "\"");
for (String value : goodMeta.values())
assertTrue(headers.containsValue(value));
@ -311,13 +312,12 @@ public class CopyObjectOptionsTest {
@Test
void testBuildRequestHeadersACL() throws UnsupportedEncodingException {
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();
assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(),
CannedAccessPolicy.AUTHENTICATED_READ.toString());
assertEquals(headers.get(CANNED_ACL).iterator().next(), CannedAccessPolicy.AUTHENTICATED_READ.toString());
}
}

View File

@ -55,7 +55,7 @@ public class PutBucketOptionsTest {
PutBucketOptions options = withBucketAcl(CannedAccessPolicy.AUTHENTICATED_READ);
options.setHeaderTag("amz");
options.setHeaderTag(S3Headers.DEFAULT_AMAZON_HEADERTAG);
Multimap<String, String> headers = options.buildRequestHeaders();
assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(),
CannedAccessPolicy.AUTHENTICATED_READ.toString());

View File

@ -54,7 +54,7 @@ public class PutObjectOptionsTest {
void testBuildRequestHeaders() throws UnsupportedEncodingException {
PutObjectOptions options = withAcl(CannedAccessPolicy.AUTHENTICATED_READ);
options.setHeaderTag("amz");
options.setHeaderTag(S3Headers.DEFAULT_AMAZON_HEADERTAG);
Multimap<String, String> headers = options.buildRequestHeaders();
assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(),