You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by ga...@apache.org on 2015/02/13 23:37:39 UTC
jclouds git commit: JCLOUDS-820: Support multi-delete for generic S3
Repository: jclouds
Updated Branches:
refs/heads/master b46ec7eb2 -> 4bb319a0c
JCLOUDS-820: Support multi-delete for generic S3
Tested against AWS and DreamObjects. This commit only moves and
renames code.
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/4bb319a0
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/4bb319a0
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/4bb319a0
Branch: refs/heads/master
Commit: 4bb319a0ccd6501fc4e6389e7ee7f04d766d7e2b
Parents: b46ec7e
Author: Andrew Gaul <ga...@apache.org>
Authored: Fri Feb 13 05:43:16 2015 -0800
Committer: Andrew Gaul <ga...@apache.org>
Committed: Fri Feb 13 06:23:16 2015 -0800
----------------------------------------------------------------------
.../src/main/java/org/jclouds/s3/S3Client.java | 31 +++
.../BindIterableAsPayloadToDeleteRequest.java | 59 ++++++
.../org/jclouds/s3/domain/DeleteResult.java | 187 +++++++++++++++++++
.../org/jclouds/s3/xml/DeleteResultHandler.java | 104 +++++++++++
.../org/jclouds/s3/xml/ErrorEntryHandler.java | 58 ++++++
.../java/org/jclouds/s3/S3ClientExpectTest.java | 65 +++++++
.../java/org/jclouds/s3/S3ClientLiveTest.java | 33 ++++
...indIterableAsPayloadToDeleteRequestTest.java | 62 ++++++
.../jclouds/s3/xml/DeleteResultHandlerTest.java | 50 +++++
apis/s3/src/test/resources/delete-result.xml | 14 ++
.../java/org/jclouds/aws/s3/AWSS3Client.java | 44 -----
.../BindIterableAsPayloadToDeleteRequest.java | 59 ------
.../org/jclouds/aws/s3/domain/DeleteResult.java | 187 -------------------
.../jclouds/aws/s3/xml/DeleteResultHandler.java | 104 -----------
.../jclouds/aws/s3/xml/ErrorEntryHandler.java | 58 ------
.../jclouds/aws/s3/AWSS3ClientExpectTest.java | 66 -------
.../org/jclouds/aws/s3/AWSS3ClientLiveTest.java | 33 ----
...indIterableAsPayloadToDeleteRequestTest.java | 62 ------
.../aws/s3/xml/DeleteResultHandlerTest.java | 50 -----
.../aws-s3/src/test/resources/delete-result.xml | 14 --
20 files changed, 663 insertions(+), 677 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4bb319a0/apis/s3/src/main/java/org/jclouds/s3/S3Client.java
----------------------------------------------------------------------
diff --git a/apis/s3/src/main/java/org/jclouds/s3/S3Client.java b/apis/s3/src/main/java/org/jclouds/s3/S3Client.java
index 76d246a..7c7f6ff 100644
--- a/apis/s3/src/main/java/org/jclouds/s3/S3Client.java
+++ b/apis/s3/src/main/java/org/jclouds/s3/S3Client.java
@@ -62,6 +62,7 @@ import org.jclouds.rest.annotations.XMLResponseParser;
import org.jclouds.s3.binders.BindACLToXMLPayload;
import org.jclouds.s3.binders.BindAsHostPrefixIfConfigured;
import org.jclouds.s3.binders.BindBucketLoggingToXmlPayload;
+import org.jclouds.s3.binders.BindIterableAsPayloadToDeleteRequest;
import org.jclouds.s3.binders.BindNoBucketLoggingToXmlPayload;
import org.jclouds.s3.binders.BindObjectMetadataToRequest;
import org.jclouds.s3.binders.BindPartIdsAndETagsToRequest;
@@ -70,6 +71,7 @@ import org.jclouds.s3.binders.BindS3ObjectMetadataToRequest;
import org.jclouds.s3.domain.AccessControlList;
import org.jclouds.s3.domain.BucketLogging;
import org.jclouds.s3.domain.BucketMetadata;
+import org.jclouds.s3.domain.DeleteResult;
import org.jclouds.s3.domain.ListBucketResponse;
import org.jclouds.s3.domain.ObjectMetadata;
import org.jclouds.s3.domain.Payer;
@@ -93,6 +95,7 @@ import org.jclouds.s3.predicates.validators.BucketNameValidator;
import org.jclouds.s3.xml.AccessControlListHandler;
import org.jclouds.s3.xml.BucketLoggingHandler;
import org.jclouds.s3.xml.CopyObjectHandler;
+import org.jclouds.s3.xml.DeleteResultHandler;
import org.jclouds.s3.xml.ListAllMyBucketsHandler;
import org.jclouds.s3.xml.ListBucketHandler;
import org.jclouds.s3.xml.LocationConstraintHandler;
@@ -204,6 +207,34 @@ public interface S3Client extends Closeable {
@PathParam("key") String key);
/**
+ * 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.
+ *
+ * @param bucketName
+ * namespace of the objects you are deleting
+ * @param keys
+ * set of unique keys identifying objects
+ */
+ @Named("DeleteObject")
+ @POST
+ @Path("/")
+ @QueryParams(keys = "delete")
+ @XMLResponseParser(DeleteResultHandler.class)
+ DeleteResult deleteObjects(@Bucket @EndpointParam(parser = AssignCorrectHostnameForBucket.class) @BinderParam(
+ BindAsHostPrefixIfConfigured.class) @ParamValidators(BucketNameValidator.class) String bucketName,
+ @BinderParam(BindIterableAsPayloadToDeleteRequest.class) Iterable<String> keys);
+
+ /**
* Store data by creating or overwriting an object.
* <p/>
* This method will store the object with the default <code>private</code acl.
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4bb319a0/apis/s3/src/main/java/org/jclouds/s3/binders/BindIterableAsPayloadToDeleteRequest.java
----------------------------------------------------------------------
diff --git a/apis/s3/src/main/java/org/jclouds/s3/binders/BindIterableAsPayloadToDeleteRequest.java b/apis/s3/src/main/java/org/jclouds/s3/binders/BindIterableAsPayloadToDeleteRequest.java
new file mode 100644
index 0000000..baa7f56
--- /dev/null
+++ b/apis/s3/src/main/java/org/jclouds/s3/binders/BindIterableAsPayloadToDeleteRequest.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.s3.binders;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.hash.Hashing.md5;
+
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.io.Payload;
+import org.jclouds.io.Payloads;
+import org.jclouds.rest.Binder;
+
+public class BindIterableAsPayloadToDeleteRequest implements Binder {
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <R extends HttpRequest> 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<String> keys = (Iterable<String>) input;
+ StringBuilder builder = new StringBuilder();
+ for (String key : keys) {
+ builder.append(String.format("<Object><Key>%s</Key></Object>", key));
+ }
+
+ final String objects = builder.toString();
+ checkArgument(!objects.isEmpty(), "The list of keys should not be empty.");
+
+ final String content = String.format("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<Delete>%s</Delete>", objects);
+
+ Payload payload = Payloads.newStringPayload(content);
+ payload.getContentMetadata().setContentType(MediaType.TEXT_XML);
+ byte[] md5 = md5().hashString(content, UTF_8).asBytes();
+ payload.getContentMetadata().setContentMD5(md5);
+ request.setPayload(payload);
+ return request;
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4bb319a0/apis/s3/src/main/java/org/jclouds/s3/domain/DeleteResult.java
----------------------------------------------------------------------
diff --git a/apis/s3/src/main/java/org/jclouds/s3/domain/DeleteResult.java b/apis/s3/src/main/java/org/jclouds/s3/domain/DeleteResult.java
new file mode 100644
index 0000000..cfc6503
--- /dev/null
+++ b/apis/s3/src/main/java/org/jclouds/s3/domain/DeleteResult.java
@@ -0,0 +1,187 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.s3.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ForwardingSet;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Multi-object delete API response
+ * <p/>
+ * Contains a list of the keys that were deleted
+ */
+public class DeleteResult extends ForwardingSet<String> {
+
+ 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<String> deleted = ImmutableSet.builder();
+ private ImmutableMap.Builder<String, Error> 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<String, Error> errors) {
+ this.errors = ImmutableMap.<String, Error>builder().putAll(errors);
+ return this;
+ }
+
+ /**
+ * @see DeleteResult#getDeleted
+ */
+ public Builder deleted(Iterable<String> deleted) {
+ this.deleted = ImmutableSet.<String>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<String> 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<String> deleted;
+ private final Map<String, Error> errors;
+
+ public DeleteResult(Set<String> deleted, Map<String, Error> errors) {
+ this.deleted = ImmutableSet.copyOf(deleted);
+ this.errors = ImmutableMap.copyOf(errors);
+ }
+
+ /**
+ * Get the set of successfully deleted keys
+ */
+ public Set<String> getDeleted() {
+ return deleted;
+ }
+
+ /**
+ * Get a map with details about failed delete operations indexed by object name
+ */
+ public Map<String, Error> getErrors() {
+ return errors;
+ }
+
+ @Override
+ protected Set<String> 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();
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4bb319a0/apis/s3/src/main/java/org/jclouds/s3/xml/DeleteResultHandler.java
----------------------------------------------------------------------
diff --git a/apis/s3/src/main/java/org/jclouds/s3/xml/DeleteResultHandler.java b/apis/s3/src/main/java/org/jclouds/s3/xml/DeleteResultHandler.java
new file mode 100644
index 0000000..beada70
--- /dev/null
+++ b/apis/s3/src/main/java/org/jclouds/s3/xml/DeleteResultHandler.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.s3.xml;
+
+import static org.jclouds.util.SaxUtils.equalsOrSuffix;
+
+import org.jclouds.s3.domain.DeleteResult;
+import org.jclouds.http.functions.ParseSax;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class DeleteResultHandler extends ParseSax.HandlerForGeneratedRequestWithResult<DeleteResult> {
+
+ 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<String> deleted = ImmutableSet.builder();
+
+ /**
+ * Accumulator for the set of errors
+ */
+ private final ImmutableMap.Builder<String, DeleteResult.Error> 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.setLength(0);
+ } 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());
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4bb319a0/apis/s3/src/main/java/org/jclouds/s3/xml/ErrorEntryHandler.java
----------------------------------------------------------------------
diff --git a/apis/s3/src/main/java/org/jclouds/s3/xml/ErrorEntryHandler.java b/apis/s3/src/main/java/org/jclouds/s3/xml/ErrorEntryHandler.java
new file mode 100644
index 0000000..f239c9f
--- /dev/null
+++ b/apis/s3/src/main/java/org/jclouds/s3/xml/ErrorEntryHandler.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.s3.xml;
+
+import static org.jclouds.util.SaxUtils.equalsOrSuffix;
+
+import java.util.Map;
+
+import org.jclouds.s3.domain.DeleteResult;
+import org.jclouds.http.functions.ParseSax;
+import org.xml.sax.SAXException;
+
+import com.google.common.collect.Maps;
+
+public class ErrorEntryHandler extends ParseSax.HandlerForGeneratedRequestWithResult<Map.Entry<String, DeleteResult.Error>> {
+
+ 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.setLength(0);
+ }
+
+ @Override
+ public Map.Entry<String, DeleteResult.Error> getResult() {
+ return Maps.immutableEntry(key, new DeleteResult.Error(code, message));
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4bb319a0/apis/s3/src/test/java/org/jclouds/s3/S3ClientExpectTest.java
----------------------------------------------------------------------
diff --git a/apis/s3/src/test/java/org/jclouds/s3/S3ClientExpectTest.java b/apis/s3/src/test/java/org/jclouds/s3/S3ClientExpectTest.java
index 0a22cb1..1c6bb43 100644
--- a/apis/s3/src/test/java/org/jclouds/s3/S3ClientExpectTest.java
+++ b/apis/s3/src/test/java/org/jclouds/s3/S3ClientExpectTest.java
@@ -16,14 +16,23 @@
*/
package org.jclouds.s3;
+import static com.google.common.base.Charsets.UTF_8;
+import static com.google.common.hash.Hashing.md5;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
import java.net.URI;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
+import org.jclouds.io.Payload;
+import org.jclouds.io.Payloads;
+import org.jclouds.s3.domain.DeleteResult;
import org.jclouds.s3.internal.BaseS3ClientExpectTest;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
@Test(groups = "unit", testName = "S3ClientExpectTest")
public class S3ClientExpectTest extends BaseS3ClientExpectTest {
@@ -46,4 +55,60 @@ public class S3ClientExpectTest extends BaseS3ClientExpectTest {
}
+ @Test
+ public void testDeleteMultipleObjects() {
+ final String request = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<Delete>" +
+ "<Object><Key>key1</Key></Object>" +
+ "<Object><Key>key2</Key></Object>" +
+ "</Delete>";
+
+ final Payload requestPayload = Payloads.newStringPayload(request);
+ requestPayload.getContentMetadata().setContentType("text/xml");
+ requestPayload.getContentMetadata().setContentMD5(md5().hashString(request, UTF_8));
+
+ final String response = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<DeleteResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\n" +
+ " <Deleted>\n" +
+ " <Key>key1</Key>\n" +
+ " </Deleted>\n" +
+ " <Deleted>\n" +
+ " <Key>key1.1</Key>\n" +
+ " </Deleted>\n" +
+ " <Error>\n" +
+ " <Key>key2</Key>\n" +
+ " <Code>AccessDenied</Code>\n" +
+ " <Message>Access Denied</Message>\n" +
+ " </Error>\n" +
+ "</DeleteResult>";
+
+ final Payload responsePayload = Payloads.newStringPayload(response);
+ responsePayload.getContentMetadata().setContentType("text/xml");
+
+ S3Client client = requestSendsResponse(
+ HttpRequest.builder()
+ .method("POST")
+ .endpoint("http://localhost/test?delete")
+ .addHeader("Date", CONSTANT_DATE)
+ .addHeader("Authorization", "AWS identity:XptAJrBvfz68TEfPkhXj4R58uvE=")
+ .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"));
+ }
}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4bb319a0/apis/s3/src/test/java/org/jclouds/s3/S3ClientLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/s3/src/test/java/org/jclouds/s3/S3ClientLiveTest.java b/apis/s3/src/test/java/org/jclouds/s3/S3ClientLiveTest.java
index dba1d26..0d0b4cf 100644
--- a/apis/s3/src/test/java/org/jclouds/s3/S3ClientLiveTest.java
+++ b/apis/s3/src/test/java/org/jclouds/s3/S3ClientLiveTest.java
@@ -36,10 +36,13 @@ import java.net.URI;
import java.net.URL;
import java.util.Date;
import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import org.jclouds.blobstore.KeyNotFoundException;
+import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.integration.internal.BaseBlobStoreIntegrationTest;
import org.jclouds.http.HttpResponseException;
import org.jclouds.io.ByteStreams2;
@@ -50,6 +53,7 @@ import org.jclouds.s3.domain.AccessControlList.EmailAddressGrantee;
import org.jclouds.s3.domain.AccessControlList.GroupGranteeURI;
import org.jclouds.s3.domain.AccessControlList.Permission;
import org.jclouds.s3.domain.CannedAccessPolicy;
+import org.jclouds.s3.domain.DeleteResult;
import org.jclouds.s3.domain.ObjectMetadata;
import org.jclouds.s3.domain.ObjectMetadataBuilder;
import org.jclouds.s3.domain.S3Object;
@@ -60,6 +64,7 @@ import org.testng.annotations.Test;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.hash.HashCode;
import com.google.common.io.ByteSource;
@@ -538,6 +543,34 @@ public class S3ClientLiveTest extends BaseBlobStoreIntegrationTest {
}
}
+ public void testDeleteMultipleObjects() throws InterruptedException {
+ String container = getContainerName();
+ try {
+ ImmutableSet.Builder<String> 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<String> 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);
+ }
+ }
+
private void checkGrants(AccessControlList acl) {
String ownerId = acl.getOwner().getId();
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4bb319a0/apis/s3/src/test/java/org/jclouds/s3/binders/BindIterableAsPayloadToDeleteRequestTest.java
----------------------------------------------------------------------
diff --git a/apis/s3/src/test/java/org/jclouds/s3/binders/BindIterableAsPayloadToDeleteRequestTest.java b/apis/s3/src/test/java/org/jclouds/s3/binders/BindIterableAsPayloadToDeleteRequestTest.java
new file mode 100644
index 0000000..218f562
--- /dev/null
+++ b/apis/s3/src/test/java/org/jclouds/s3/binders/BindIterableAsPayloadToDeleteRequestTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.s3.binders;
+
+import static org.testng.Assert.assertEquals;
+
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.io.Payload;
+import org.jclouds.io.Payloads;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Delete>" +
+ "<Object><Key>key1</Key></Object><Object><Key>key2</Key></Object></Delete>");
+ 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());
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4bb319a0/apis/s3/src/test/java/org/jclouds/s3/xml/DeleteResultHandlerTest.java
----------------------------------------------------------------------
diff --git a/apis/s3/src/test/java/org/jclouds/s3/xml/DeleteResultHandlerTest.java b/apis/s3/src/test/java/org/jclouds/s3/xml/DeleteResultHandlerTest.java
new file mode 100644
index 0000000..7293f1c
--- /dev/null
+++ b/apis/s3/src/test/java/org/jclouds/s3/xml/DeleteResultHandlerTest.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.s3.xml;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.InputStream;
+
+import org.jclouds.http.functions.BaseHandlerTest;
+import org.jclouds.s3.domain.DeleteResult;
+import org.testng.annotations.Test;
+
+// 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();
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4bb319a0/apis/s3/src/test/resources/delete-result.xml
----------------------------------------------------------------------
diff --git a/apis/s3/src/test/resources/delete-result.xml b/apis/s3/src/test/resources/delete-result.xml
new file mode 100644
index 0000000..acc3bae
--- /dev/null
+++ b/apis/s3/src/test/resources/delete-result.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<DeleteResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
+ <Deleted>
+ <Key>key1</Key>
+ </Deleted>
+ <Deleted>
+ <Key>key1.1</Key>
+ </Deleted>
+ <Error>
+ <Key>key2</Key>
+ <Code>AccessDenied</Code>
+ <Message>Access Denied</Message>
+ </Error>
+</DeleteResult>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4bb319a0/providers/aws-s3/src/main/java/org/jclouds/aws/s3/AWSS3Client.java
----------------------------------------------------------------------
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 0f9176c..3bce557 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
@@ -18,26 +18,10 @@ package org.jclouds.aws.s3;
import static org.jclouds.blobstore.attr.BlobScopes.CONTAINER;
-import javax.inject.Named;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-
-import org.jclouds.aws.s3.binders.BindIterableAsPayloadToDeleteRequest;
-import org.jclouds.aws.s3.domain.DeleteResult;
-import org.jclouds.aws.s3.xml.DeleteResultHandler;
import org.jclouds.blobstore.attr.BlobScope;
-import org.jclouds.rest.annotations.BinderParam;
-import org.jclouds.rest.annotations.EndpointParam;
-import org.jclouds.rest.annotations.ParamValidators;
-import org.jclouds.rest.annotations.QueryParams;
import org.jclouds.rest.annotations.RequestFilters;
-import org.jclouds.rest.annotations.XMLResponseParser;
-import org.jclouds.s3.Bucket;
import org.jclouds.s3.S3Client;
-import org.jclouds.s3.binders.BindAsHostPrefixIfConfigured;
import org.jclouds.s3.filters.RequestAuthorizeSignature;
-import org.jclouds.s3.functions.AssignCorrectHostnameForBucket;
-import org.jclouds.s3.predicates.validators.BucketNameValidator;
/**
* Provides access to amazon-specific S3 features
@@ -45,32 +29,4 @@ import org.jclouds.s3.predicates.validators.BucketNameValidator;
@RequestFilters(RequestAuthorizeSignature.class)
@BlobScope(CONTAINER)
public interface AWSS3Client extends S3Client {
-
- /**
- * 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.
- *
- * @param bucketName
- * namespace of the objects you are deleting
- * @param keys
- * set of unique keys identifying objects
- */
- @Named("DeleteObject")
- @POST
- @Path("/")
- @QueryParams(keys = "delete")
- @XMLResponseParser(DeleteResultHandler.class)
- DeleteResult deleteObjects(@Bucket @EndpointParam(parser = AssignCorrectHostnameForBucket.class) @BinderParam(
- BindAsHostPrefixIfConfigured.class) @ParamValidators(BucketNameValidator.class) String bucketName,
- @BinderParam(BindIterableAsPayloadToDeleteRequest.class) Iterable<String> keys);
}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4bb319a0/providers/aws-s3/src/main/java/org/jclouds/aws/s3/binders/BindIterableAsPayloadToDeleteRequest.java
----------------------------------------------------------------------
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
deleted file mode 100644
index 95aec48..0000000
--- a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/binders/BindIterableAsPayloadToDeleteRequest.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF 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 static com.google.common.base.Charsets.UTF_8;
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.hash.Hashing.md5;
-
-import javax.ws.rs.core.MediaType;
-
-import org.jclouds.http.HttpRequest;
-import org.jclouds.io.Payload;
-import org.jclouds.io.Payloads;
-import org.jclouds.rest.Binder;
-
-public class BindIterableAsPayloadToDeleteRequest implements Binder {
-
- @SuppressWarnings("unchecked")
- @Override
- public <R extends HttpRequest> 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<String> keys = (Iterable<String>) input;
- StringBuilder builder = new StringBuilder();
- for (String key : keys) {
- builder.append(String.format("<Object><Key>%s</Key></Object>", key));
- }
-
- final String objects = builder.toString();
- checkArgument(!objects.isEmpty(), "The list of keys should not be empty.");
-
- final String content = String.format("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
- "<Delete>%s</Delete>", objects);
-
- Payload payload = Payloads.newStringPayload(content);
- payload.getContentMetadata().setContentType(MediaType.TEXT_XML);
- byte[] md5 = md5().hashString(content, UTF_8).asBytes();
- payload.getContentMetadata().setContentMD5(md5);
- request.setPayload(payload);
- return request;
- }
-}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4bb319a0/providers/aws-s3/src/main/java/org/jclouds/aws/s3/domain/DeleteResult.java
----------------------------------------------------------------------
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
deleted file mode 100644
index a1d5202..0000000
--- a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/domain/DeleteResult.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF 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 static com.google.common.base.Preconditions.checkNotNull;
-
-import java.util.Map;
-import java.util.Set;
-
-import com.google.common.base.Objects;
-import com.google.common.collect.ForwardingSet;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-
-/**
- * Multi-object delete API response
- * <p/>
- * Contains a list of the keys that were deleted
- */
-public class DeleteResult extends ForwardingSet<String> {
-
- 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<String> deleted = ImmutableSet.builder();
- private ImmutableMap.Builder<String, Error> 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<String, Error> errors) {
- this.errors = ImmutableMap.<String, Error>builder().putAll(errors);
- return this;
- }
-
- /**
- * @see DeleteResult#getDeleted
- */
- public Builder deleted(Iterable<String> deleted) {
- this.deleted = ImmutableSet.<String>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<String> 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<String> deleted;
- private final Map<String, Error> errors;
-
- public DeleteResult(Set<String> deleted, Map<String, Error> errors) {
- this.deleted = ImmutableSet.copyOf(deleted);
- this.errors = ImmutableMap.copyOf(errors);
- }
-
- /**
- * Get the set of successfully deleted keys
- */
- public Set<String> getDeleted() {
- return deleted;
- }
-
- /**
- * Get a map with details about failed delete operations indexed by object name
- */
- public Map<String, Error> getErrors() {
- return errors;
- }
-
- @Override
- protected Set<String> 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();
- }
-}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4bb319a0/providers/aws-s3/src/main/java/org/jclouds/aws/s3/xml/DeleteResultHandler.java
----------------------------------------------------------------------
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
deleted file mode 100644
index 96ac8d4..0000000
--- a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/xml/DeleteResultHandler.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF 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 static org.jclouds.util.SaxUtils.equalsOrSuffix;
-
-import org.jclouds.aws.s3.domain.DeleteResult;
-import org.jclouds.http.functions.ParseSax;
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-
-public class DeleteResultHandler extends ParseSax.HandlerForGeneratedRequestWithResult<DeleteResult> {
-
- 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<String> deleted = ImmutableSet.builder();
-
- /**
- * Accumulator for the set of errors
- */
- private final ImmutableMap.Builder<String, DeleteResult.Error> 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.setLength(0);
- } 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());
- }
-}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4bb319a0/providers/aws-s3/src/main/java/org/jclouds/aws/s3/xml/ErrorEntryHandler.java
----------------------------------------------------------------------
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
deleted file mode 100644
index 95305a3..0000000
--- a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/xml/ErrorEntryHandler.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF 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 static org.jclouds.util.SaxUtils.equalsOrSuffix;
-
-import java.util.Map;
-
-import org.jclouds.aws.s3.domain.DeleteResult;
-import org.jclouds.http.functions.ParseSax;
-import org.xml.sax.SAXException;
-
-import com.google.common.collect.Maps;
-
-public class ErrorEntryHandler extends ParseSax.HandlerForGeneratedRequestWithResult<Map.Entry<String, DeleteResult.Error>> {
-
- 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.setLength(0);
- }
-
- @Override
- public Map.Entry<String, DeleteResult.Error> getResult() {
- return Maps.immutableEntry(key, new DeleteResult.Error(code, message));
- }
-}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4bb319a0/providers/aws-s3/src/test/java/org/jclouds/aws/s3/AWSS3ClientExpectTest.java
----------------------------------------------------------------------
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 8aa760b..b4e493c 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
@@ -16,27 +16,19 @@
*/
package org.jclouds.aws.s3;
-import static com.google.common.base.Charsets.UTF_8;
-import static com.google.common.hash.Hashing.md5;
import static org.jclouds.aws.s3.blobstore.options.AWSS3PutObjectOptions.Builder.storageClass;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-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.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;
import com.google.common.base.Functions;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
import com.google.inject.Injector;
@Test
@@ -86,62 +78,4 @@ public class AWSS3ClientExpectTest extends BaseAWSS3ClientExpectTest {
client.putObject("test", blobToObject.apply(blob),
storageClass(StorageClass.REDUCED_REDUNDANCY));
}
-
- @Test
- public void testDeleteMultipleObjects() {
- final String request = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
- "<Delete>" +
- "<Object><Key>key1</Key></Object>" +
- "<Object><Key>key2</Key></Object>" +
- "</Delete>";
-
- final Payload requestPayload = Payloads.newStringPayload(request);
- requestPayload.getContentMetadata().setContentType("text/xml");
- requestPayload.getContentMetadata().setContentMD5(md5().hashString(request, UTF_8));
-
- final String response = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
- "<DeleteResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\n" +
- " <Deleted>\n" +
- " <Key>key1</Key>\n" +
- " </Deleted>\n" +
- " <Deleted>\n" +
- " <Key>key1.1</Key>\n" +
- " </Deleted>\n" +
- " <Error>\n" +
- " <Key>key2</Key>\n" +
- " <Code>AccessDenied</Code>\n" +
- " <Message>Access Denied</Message>\n" +
- " </Error>\n" +
- "</DeleteResult>";
-
- 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"));
- }
}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4bb319a0/providers/aws-s3/src/test/java/org/jclouds/aws/s3/AWSS3ClientLiveTest.java
----------------------------------------------------------------------
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 f660b8d..8c898f6 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
@@ -20,15 +20,10 @@ import static org.jclouds.aws.s3.blobstore.options.AWSS3PutOptions.Builder.stora
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.util.Set;
-import java.util.UUID;
-
import org.jclouds.aws.AWSResponseException;
import org.jclouds.aws.domain.Region;
-import org.jclouds.aws.s3.domain.DeleteResult;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.domain.StorageMetadata;
@@ -139,34 +134,6 @@ public class AWSS3ClientLiveTest extends S3ClientLiveTest {
assertEquals("InvalidBucketName", e.getError().getCode());
}
}
-
- public void testDeleteMultipleObjects() throws InterruptedException {
- String container = getContainerName();
- try {
- ImmutableSet.Builder<String> 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<String> 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);
- }
- }
public void testDirectoryEndingWithSlash() throws InterruptedException {
String containerName = getContainerName();
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4bb319a0/providers/aws-s3/src/test/java/org/jclouds/aws/s3/binders/BindIterableAsPayloadToDeleteRequestTest.java
----------------------------------------------------------------------
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
deleted file mode 100644
index 0560ce5..0000000
--- a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/binders/BindIterableAsPayloadToDeleteRequestTest.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF 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 static org.testng.Assert.assertEquals;
-
-import javax.ws.rs.core.MediaType;
-
-import org.jclouds.http.HttpRequest;
-import org.jclouds.io.Payload;
-import org.jclouds.io.Payloads;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-
-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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Delete>" +
- "<Object><Key>key1</Key></Object><Object><Key>key2</Key></Object></Delete>");
- 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());
- }
-}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4bb319a0/providers/aws-s3/src/test/java/org/jclouds/aws/s3/xml/DeleteResultHandlerTest.java
----------------------------------------------------------------------
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
deleted file mode 100644
index 85ce10a..0000000
--- a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/xml/DeleteResultHandlerTest.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF 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 static org.testng.Assert.assertEquals;
-
-import java.io.InputStream;
-
-import org.jclouds.aws.s3.domain.DeleteResult;
-import org.jclouds.http.functions.BaseHandlerTest;
-import org.testng.annotations.Test;
-
-// 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();
- }
-}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4bb319a0/providers/aws-s3/src/test/resources/delete-result.xml
----------------------------------------------------------------------
diff --git a/providers/aws-s3/src/test/resources/delete-result.xml b/providers/aws-s3/src/test/resources/delete-result.xml
deleted file mode 100644
index acc3bae..0000000
--- a/providers/aws-s3/src/test/resources/delete-result.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<DeleteResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
- <Deleted>
- <Key>key1</Key>
- </Deleted>
- <Deleted>
- <Key>key1.1</Key>
- </Deleted>
- <Error>
- <Key>key2</Key>
- <Code>AccessDenied</Code>
- <Message>Access Denied</Message>
- </Error>
-</DeleteResult>
\ No newline at end of file