diff --git a/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignature.java b/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignature.java index f47afe965f..eeabc877cd 100644 --- a/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignature.java +++ b/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignature.java @@ -80,7 +80,7 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign private static final Set 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"); + "response-cache-control", "response-content-disposition", "response-content-encoding", "delete"); private final SignatureWire signatureWire; private final String accessKey; diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/AWSS3AsyncClient.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/AWSS3AsyncClient.java index 83ab3d3454..2e45d9a614 100644 --- a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/AWSS3AsyncClient.java +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/AWSS3AsyncClient.java @@ -29,8 +29,11 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; +import org.jclouds.aws.s3.binders.BindIterableAsPayloadToDeleteRequest; import org.jclouds.aws.s3.binders.BindObjectMetadataToRequest; import org.jclouds.aws.s3.binders.BindPartIdsAndETagsToRequest; +import org.jclouds.aws.s3.domain.DeleteResult; +import org.jclouds.aws.s3.xml.DeleteResultHandler; import org.jclouds.aws.s3.functions.ETagFromHttpResponseViaRegex; import org.jclouds.aws.s3.functions.ObjectMetadataKey; import org.jclouds.aws.s3.functions.UploadIdFromHttpResponseViaRegex; @@ -46,6 +49,7 @@ import org.jclouds.rest.annotations.QueryParams; import org.jclouds.rest.annotations.RequestFilters; import org.jclouds.rest.annotations.ResponseParser; import org.jclouds.rest.annotations.SkipEncoding; +import org.jclouds.rest.annotations.XMLResponseParser; import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404; import org.jclouds.s3.Bucket; import org.jclouds.s3.S3AsyncClient; @@ -112,4 +116,15 @@ public interface AWSS3AsyncClient extends S3AsyncClient { @PathParam("key") String key, @QueryParam("uploadId") String uploadId, @BinderParam(BindPartIdsAndETagsToRequest.class) Map parts); + /** + * @see AWSS3Client#deleteObjects + */ + @POST + @Path("/") + @QueryParams(keys = "delete") + @XMLResponseParser(DeleteResultHandler.class) + ListenableFuture deleteObjects( + @Bucket @EndpointParam(parser = AssignCorrectHostnameForBucket.class) @BinderParam(BindAsHostPrefixIfConfigured.class) @ParamValidators(BucketNameValidator.class) String bucketName, + @BinderParam(BindIterableAsPayloadToDeleteRequest.class) Iterable keys); + } diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/AWSS3Client.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/AWSS3Client.java index 5426847372..b561a1a5a2 100644 --- a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/AWSS3Client.java +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/AWSS3Client.java @@ -19,8 +19,10 @@ package org.jclouds.aws.s3; import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; +import org.jclouds.aws.s3.domain.DeleteResult; import org.jclouds.concurrent.Timeout; import org.jclouds.io.Payload; import org.jclouds.s3.S3Client; @@ -143,4 +145,26 @@ public interface AWSS3Client extends S3Client { * @return ETag of the content uploaded */ String completeMultipartUpload(String bucketName, String key, String uploadId, Map parts); + + /** + * The Multi-Object Delete operation enables you to delete multiple objects from a bucket using a + * single HTTP request. If you know the object keys that you want to delete, then this operation + * provides a suitable alternative to sending individual delete requests (see DELETE Object), + * reducing per-request overhead. + * + * The Multi-Object Delete request contains a set of up to 1000 keys that you want to delete. + * + * If a key does not exist is considered to be deleted. + * + * The Multi-Object Delete operation supports two modes for the response; verbose and quiet. + * By default, the operation uses verbose mode in which the response includes the result of + * deletion of each key in your request. + * + * @see + * @param bucketName + * namespace of the objects you are deleting + * @param keys + * set of unique keys identifying objects + */ + DeleteResult deleteObjects(String bucketName, Iterable keys); } diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/binders/BindIterableAsPayloadToDeleteRequest.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/binders/BindIterableAsPayloadToDeleteRequest.java new file mode 100644 index 0000000000..d3b84526b4 --- /dev/null +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/binders/BindIterableAsPayloadToDeleteRequest.java @@ -0,0 +1,63 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.aws.s3.binders; + +import org.jclouds.crypto.CryptoStreams; +import org.jclouds.http.HttpRequest; +import org.jclouds.io.Payload; +import org.jclouds.io.Payloads; +import org.jclouds.rest.Binder; + +import javax.ws.rs.core.MediaType; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * @author Andrei Savu + */ +public class BindIterableAsPayloadToDeleteRequest implements Binder { + + @SuppressWarnings("unchecked") + @Override + public R bindToRequest(R request, Object input) { + checkArgument(checkNotNull(input, "input is null") instanceof Iterable, + "this binder is only valid for an Iterable"); + checkNotNull(request, "request is null"); + + Iterable keys = (Iterable) input; + StringBuilder builder = new StringBuilder(); + for (String key : keys) { + builder.append(String.format("%s", key)); + } + + final String objects = builder.toString(); + checkArgument(!objects.isEmpty(), "The list of keys should not be empty."); + + final String content = String.format("\n" + + "%s", objects); + + Payload payload = Payloads.newStringPayload(content); + payload.getContentMetadata().setContentType(MediaType.TEXT_XML); + payload.getContentMetadata().setContentMD5(CryptoStreams.md5(content.getBytes())); + + request.setPayload(payload); + return request; + } +} diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/domain/DeleteResult.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/domain/DeleteResult.java new file mode 100644 index 0000000000..bf419518e7 --- /dev/null +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/domain/DeleteResult.java @@ -0,0 +1,192 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.aws.s3.domain; + +import com.google.common.base.Objects; +import com.google.common.collect.ForwardingSet; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +import java.util.Map; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Multi-object delete API response + *

+ * Contains a list of the keys that were deleted + * + * @author Andrei Savu + * @see + */ +public class DeleteResult extends ForwardingSet { + + public static class Error { + + private final String code; + private final String message; + + public Error(String code, String message) { + this.code = checkNotNull(code, "code is null"); + this.message = checkNotNull(message, "message is null"); + } + + public String getCode() { + return code; + } + + public String getMessage() { + return message; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Error)) return false; + + Error that = (Error) o; + + return Objects.equal(code, that.code) + && Objects.equal(message, that.message); + } + + @Override + public int hashCode() { + return Objects.hashCode(code, message); + } + + @Override + public String toString() { + return Objects.toStringHelper(this).omitNullValues() + .add("code", code).add("message", message).toString(); + } + } + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return builder().fromDeleteResult(this); + } + + public static class Builder { + + private ImmutableSet.Builder deleted = ImmutableSet.builder(); + private ImmutableMap.Builder errors = ImmutableMap.builder(); + + /** + * @see DeleteResult#getErrors + */ + public Builder putError(String key, Error error) { + this.errors.put(key, error); + return this; + } + + /** + * @see DeleteResult#getErrors + */ + public Builder errors(Map errors) { + this.errors = ImmutableMap.builder().putAll(errors); + return this; + } + + /** + * @see DeleteResult#getDeleted + */ + public Builder deleted(Iterable deleted) { + this.deleted = ImmutableSet.builder().addAll(deleted); + return this; + } + + /** + * @see DeleteResult#getDeleted + */ + public Builder add(String key) { + this.deleted.add(key); + return this; + } + + /** + * @see DeleteResult#getDeleted + */ + public Builder addAll(Iterable key) { + this.deleted.addAll(key); + return this; + } + + public DeleteResult build() { + return new DeleteResult(deleted.build(), errors.build()); + } + + public Builder fromDeleteResult(DeleteResult result) { + return addAll(result.getDeleted()).errors(result.getErrors()); + } + } + + private final Set deleted; + private final Map errors; + + public DeleteResult(Set deleted, Map errors) { + this.deleted = ImmutableSet.copyOf(deleted); + this.errors = ImmutableMap.copyOf(errors); + } + + /** + * Get the set of successfully deleted keys + */ + public Set getDeleted() { + return deleted; + } + + /** + * Get a map with details about failed delete operations indexed by object name + */ + public Map getErrors() { + return errors; + } + + @Override + protected Set delegate() { + return deleted; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DeleteResult)) return false; + + DeleteResult that = (DeleteResult) o; + + return Objects.equal(errors, that.errors) + && Objects.equal(deleted, that.deleted); + } + + @Override + public int hashCode() { + return Objects.hashCode(deleted, errors); + } + + @Override + public String toString() { + return Objects.toStringHelper(this).omitNullValues() + .add("deleted", deleted).add("errors", errors).toString(); + } +} diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/xml/DeleteResultHandler.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/xml/DeleteResultHandler.java new file mode 100644 index 0000000000..fc09bdc7f0 --- /dev/null +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/xml/DeleteResultHandler.java @@ -0,0 +1,108 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.aws.s3.xml; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import org.jclouds.aws.s3.domain.DeleteResult; +import org.jclouds.http.functions.ParseSax; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +import static org.jclouds.util.SaxUtils.equalsOrSuffix; + +/** + * @author Andrei Savu + */ +public class DeleteResultHandler extends ParseSax.HandlerForGeneratedRequestWithResult { + + public static final String DELETED_TAG = "Deleted"; + public static final String ERROR_TAG = "Error"; + + private final ErrorEntryHandler errorEntryHandler = new ErrorEntryHandler(); + + private StringBuilder deletedEntryAccumulator = new StringBuilder(); + + /** + * Accumulator for the set of successfully deleted files + */ + private final ImmutableSet.Builder deleted = ImmutableSet.builder(); + + /** + * Accumulator for the set of errors + */ + private final ImmutableMap.Builder errors = ImmutableMap.builder(); + + private boolean parsingDeletedEntry = false; + private boolean parsingErrorEntry = false; + + /** + * {@inheritDoc} + */ + @Override + public void startElement(String uri, String name, String qName, Attributes attributes) + throws SAXException { + if (equalsOrSuffix(qName, DELETED_TAG)) { + parsingDeletedEntry = true; + } else if (equalsOrSuffix(qName, ERROR_TAG)) { + parsingErrorEntry = true; + } + + if (parsingDeletedEntry) { + deletedEntryAccumulator = new StringBuilder(); + } else if (parsingErrorEntry) { + errorEntryHandler.startElement(uri, name, qName, attributes); + } + } + + @Override + public void characters(char[] chars, int start, int length) throws SAXException { + if (parsingDeletedEntry) { + deletedEntryAccumulator.append(chars, start, length); + } else if (parsingErrorEntry) { + errorEntryHandler.characters(chars, start, length); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void endElement(String uri, String name, String qName) throws SAXException { + if (equalsOrSuffix(qName, DELETED_TAG)) { + parsingDeletedEntry = false; + deleted.add(deletedEntryAccumulator.toString().trim()); + } else if (equalsOrSuffix(qName, ERROR_TAG)) { + parsingErrorEntry = false; + errors.put(errorEntryHandler.getResult()); + } + + if (parsingErrorEntry) { + errorEntryHandler.endElement(uri, name, qName); + } + } + + /** + * {@inheritDoc} + */ + @Override + public DeleteResult getResult() { + return new DeleteResult(deleted.build(), errors.build()); + } +} diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/xml/ErrorEntryHandler.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/xml/ErrorEntryHandler.java new file mode 100644 index 0000000000..947f934e47 --- /dev/null +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/xml/ErrorEntryHandler.java @@ -0,0 +1,44 @@ +package org.jclouds.aws.s3.xml; + +import com.google.common.collect.Maps; +import org.jclouds.aws.s3.domain.DeleteResult; +import org.jclouds.http.functions.ParseSax; +import org.xml.sax.SAXException; + +import java.util.Map; + +import static org.jclouds.util.SaxUtils.equalsOrSuffix; + +/** + * @author Andrei Savu + */ +public class ErrorEntryHandler extends ParseSax.HandlerForGeneratedRequestWithResult> { + + private StringBuilder accumulator = new StringBuilder(); + + private String key; + private String code; + private String message; + + @Override + public void characters(char[] chars, int start, int length) throws SAXException { + accumulator.append(chars, start, length); + } + + @Override + public void endElement(String uri, String name, String qName) throws SAXException { + if (equalsOrSuffix(qName, "Key")) { + key = accumulator.toString().trim(); + } else if (equalsOrSuffix(qName, "Code")) { + code = accumulator.toString().trim(); + } else if (equalsOrSuffix(qName, "Message")) { + message = accumulator.toString().trim(); + } + accumulator = new StringBuilder(); + } + + @Override + public Map.Entry getResult() { + return Maps.immutableEntry(key, new DeleteResult.Error(code, message)); + } +} diff --git a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/AWSS3ClientExpectTest.java b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/AWSS3ClientExpectTest.java index 2ab0a0d2f3..b504462de6 100644 --- a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/AWSS3ClientExpectTest.java +++ b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/AWSS3ClientExpectTest.java @@ -19,12 +19,20 @@ package org.jclouds.aws.s3; import static org.jclouds.aws.s3.blobstore.options.AWSS3PutObjectOptions.Builder.storageClass; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; +import com.google.common.collect.ImmutableSet; +import org.jclouds.aws.s3.domain.DeleteResult; import org.jclouds.aws.s3.internal.BaseAWSS3ClientExpectTest; import org.jclouds.blobstore.domain.Blob; import org.jclouds.blobstore.domain.BlobBuilder; +import org.jclouds.crypto.CryptoStreams; import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpResponse; +import org.jclouds.io.Payload; +import org.jclouds.io.Payloads; import org.jclouds.s3.blobstore.functions.BlobToObject; import org.jclouds.s3.domain.ObjectMetadata.StorageClass; import org.testng.annotations.Test; @@ -38,13 +46,14 @@ import com.google.inject.Injector; */ @Test public class AWSS3ClientExpectTest extends BaseAWSS3ClientExpectTest { + HttpRequest bucketLocationRequest = HttpRequest.builder() .method("GET") .endpoint("https://test.s3.amazonaws.com/?location") .addHeader("Host", "test.s3.amazonaws.com") .addHeader("Date", CONSTANT_DATE) .addHeader("Authorization", "AWS identity:D1rymKrEdvzvhmZXeg+Z0R+tiug=").build(); - + HttpResponse bucketLocationResponse = HttpResponse.builder() .statusCode(200) .payload(payloadFromStringWithContentType("eu-west-1", "application/xml")) @@ -52,13 +61,14 @@ public class AWSS3ClientExpectTest extends BaseAWSS3ClientExpectTest { .addHeader("x-amz-request-id", "51BF4F45D49B1B34") .addHeader("Date", CONSTANT_DATE) .addHeader("Server", "AmazonS3").build(); + @Test public void testPutWithReducedRedundancy() { Injector injector = createInjector(Functions.forMap(ImmutableMap.of()), createModule(), setupProperties()); Blob blob = injector.getInstance(BlobBuilder.class).name("test").payload("content").build(); BlobToObject blobToObject = injector.getInstance(BlobToObject.class); - + AWSS3Client client = requestsSendResponses(bucketLocationRequest, bucketLocationResponse, HttpRequest.builder() .method("PUT") @@ -80,4 +90,62 @@ public class AWSS3ClientExpectTest extends BaseAWSS3ClientExpectTest { client.putObject("test", blobToObject.apply(blob), storageClass(StorageClass.REDUCED_REDUNDANCY)); } + + @Test + public void testDeleteMultipleObjects() { + final String request = "\n" + + "" + + "key1" + + "key2" + + ""; + + final Payload requestPayload = Payloads.newStringPayload(request); + requestPayload.getContentMetadata().setContentType("text/xml"); + requestPayload.getContentMetadata().setContentMD5(CryptoStreams.md5(request.getBytes())); + + final String response = "\n" + + "\n" + + " \n" + + " key1\n" + + " \n" + + " \n" + + " key1.1\n" + + " \n" + + " \n" + + " key2\n" + + " AccessDenied\n" + + " Access Denied\n" + + " \n" + + ""; + + final Payload responsePayload = Payloads.newStringPayload(response); + responsePayload.getContentMetadata().setContentType("text/xml"); + + AWSS3Client client = requestsSendResponses(bucketLocationRequest, bucketLocationResponse, + HttpRequest.builder() + .method("POST") + .endpoint("https://test.s3-eu-west-1.amazonaws.com/?delete") + .addHeader("Host", "test.s3-eu-west-1.amazonaws.com") + .addHeader("Date", CONSTANT_DATE) + .addHeader("Authorization", "AWS identity:/k3HQNVVyAQMsr9qhx6hajocVu4=") + .payload(requestPayload) + .build(), + HttpResponse.builder() + .statusCode(200) + .addHeader("x-amz-request-id", "7A84C3CD4437A4C0") + .addHeader("Date", CONSTANT_DATE) + .addHeader("ETag", "437b930db84b8079c2dd804a71936b5f") + .addHeader("Server", "AmazonS3") + .payload(responsePayload) + .build() + ); + + DeleteResult result = client.deleteObjects("test", ImmutableSet.of("key1", "key2")); + assertNotNull(result, "result is null"); + + assertEquals(result.getDeleted(), ImmutableSet.of("key1", "key1.1")); + assertEquals(result.getErrors().size(), 1); + + assertEquals(result.getErrors().get("key2"), new DeleteResult.Error("AccessDenied", "Access Denied")); + } } diff --git a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/AWSS3ClientLiveTest.java b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/AWSS3ClientLiveTest.java index ec9338c49e..a29844b211 100644 --- a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/AWSS3ClientLiveTest.java +++ b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/AWSS3ClientLiveTest.java @@ -27,16 +27,20 @@ import static org.jclouds.io.Payloads.newByteArrayPayload; import static org.jclouds.s3.options.ListBucketOptions.Builder.withPrefix; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.Set; +import java.util.UUID; import java.util.zip.GZIPInputStream; import org.jclouds.aws.AWSResponseException; import org.jclouds.aws.domain.Region; +import org.jclouds.aws.s3.domain.DeleteResult; import org.jclouds.blobstore.AsyncBlobStore; import org.jclouds.blobstore.BlobStore; import org.jclouds.blobstore.KeyNotFoundException; @@ -255,5 +259,33 @@ public class AWSS3ClientLiveTest extends S3ClientLiveTest { assertEquals("InvalidBucketName", e.getError().getCode()); } } + + public void testDeleteMultipleObjects() throws InterruptedException { + String container = getContainerName(); + try { + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (int i = 0; i < 5; i++) { + String key = UUID.randomUUID().toString(); + + Blob blob = view.getBlobStore().blobBuilder(key).payload("").build(); + view.getBlobStore().putBlob(container, blob); + + builder.add(key); + } + + Set keys = builder.build(); + DeleteResult result = getApi().deleteObjects(container, keys); + + assertTrue(result.getDeleted().containsAll(keys)); + assertEquals(result.getErrors().size(), 0); + + for (String key : keys) { + assertConsistencyAwareBlobDoesntExist(container, key); + } + + } finally { + returnContainer(container); + } + } } diff --git a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/binders/BindIterableAsPayloadToDeleteRequestTest.java b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/binders/BindIterableAsPayloadToDeleteRequestTest.java new file mode 100644 index 0000000000..9e90d04afb --- /dev/null +++ b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/binders/BindIterableAsPayloadToDeleteRequestTest.java @@ -0,0 +1,66 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.aws.s3.binders; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import org.jclouds.http.HttpRequest; +import org.jclouds.io.Payload; +import org.jclouds.io.Payloads; +import org.testng.annotations.Test; + +import javax.ws.rs.core.MediaType; + +import static org.testng.Assert.assertEquals; + +/** + * @author Andrei Savu + */ +public class BindIterableAsPayloadToDeleteRequestTest { + + private final BindIterableAsPayloadToDeleteRequest binder = new BindIterableAsPayloadToDeleteRequest(); + private final HttpRequest request = HttpRequest.builder().method("POST").endpoint("http://localhost/").build(); + + @Test + public void testWithASmallSet() { + HttpRequest result = binder.bindToRequest(request, ImmutableSet.of("key1", "key2")); + + Payload payload = Payloads + .newStringPayload("\n" + + "key1key2"); + payload.getContentMetadata().setContentType(MediaType.TEXT_XML); + + assertEquals(result.getPayload(), payload); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testEmptySetThrowsException() { + binder.bindToRequest(request, ImmutableSet.of()); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testFailsOnNullSet() { + binder.bindToRequest(request, null); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testExpectedASetInstance() { + binder.bindToRequest(request, ImmutableList.of()); + } +} diff --git a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/xml/DeleteResultHandlerTest.java b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/xml/DeleteResultHandlerTest.java new file mode 100644 index 0000000000..792f07a19a --- /dev/null +++ b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/xml/DeleteResultHandlerTest.java @@ -0,0 +1,57 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.aws.s3.xml; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import org.jclouds.aws.s3.domain.DeleteResult; +import org.jclouds.http.functions.BaseHandlerTest; +import org.testng.annotations.Test; + +import java.io.InputStream; + +import static org.testng.Assert.assertEquals; + +/** + * @author Andrei Savu + */ +// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire +@Test(groups = "unit", testName = "DeleteResultHandlerTest") +public class DeleteResultHandlerTest extends BaseHandlerTest { + + @Test + public void test() { + InputStream is = getClass().getResourceAsStream("/delete-result.xml"); + + DeleteResult expected = expected(); + + DeleteResultHandler handler = injector.getInstance(DeleteResultHandler.class); + DeleteResult result = factory.create(handler).parse(is); + + assertEquals(result.toString(), expected.toString()); + } + + private DeleteResult expected() { + return DeleteResult.builder() + .add("key1") + .add("key1.1") + .putError("key2", new DeleteResult.Error("AccessDenied", "Access Denied")) + .build(); + } +} diff --git a/providers/aws-s3/src/test/resources/delete-result.xml b/providers/aws-s3/src/test/resources/delete-result.xml new file mode 100644 index 0000000000..acc3bae248 --- /dev/null +++ b/providers/aws-s3/src/test/resources/delete-result.xml @@ -0,0 +1,14 @@ + + + + key1 + + + key1.1 + + + key2 + AccessDenied + Access Denied + + \ No newline at end of file