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