Issue 773. Support S3 Multi-Object Delete (API)

This commit is contained in:
Andrei Savu 2012-10-09 02:51:06 +03:00 committed by Adrian Cole
parent 65c08cbb54
commit bd76d17642
12 changed files with 686 additions and 3 deletions

View File

@ -80,7 +80,7 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
private static final Set<String> SIGNED_PARAMETERS = ImmutableSet.of("acl", "torrent", "logging", "location", "policy",
"requestPayment", "versioning", "versions", "versionId", "notification", "uploadId", "uploads",
"partNumber", "website", "response-content-type", "response-content-language", "response-expires",
"response-cache-control", "response-content-disposition", "response-content-encoding");
"response-cache-control", "response-content-disposition", "response-content-encoding", "delete");
private final SignatureWire signatureWire;
private final String accessKey;

View File

@ -29,8 +29,11 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import org.jclouds.aws.s3.binders.BindIterableAsPayloadToDeleteRequest;
import org.jclouds.aws.s3.binders.BindObjectMetadataToRequest;
import org.jclouds.aws.s3.binders.BindPartIdsAndETagsToRequest;
import org.jclouds.aws.s3.domain.DeleteResult;
import org.jclouds.aws.s3.xml.DeleteResultHandler;
import org.jclouds.aws.s3.functions.ETagFromHttpResponseViaRegex;
import org.jclouds.aws.s3.functions.ObjectMetadataKey;
import org.jclouds.aws.s3.functions.UploadIdFromHttpResponseViaRegex;
@ -46,6 +49,7 @@ import org.jclouds.rest.annotations.QueryParams;
import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.ResponseParser;
import org.jclouds.rest.annotations.SkipEncoding;
import org.jclouds.rest.annotations.XMLResponseParser;
import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404;
import org.jclouds.s3.Bucket;
import org.jclouds.s3.S3AsyncClient;
@ -112,4 +116,15 @@ public interface AWSS3AsyncClient extends S3AsyncClient {
@PathParam("key") String key, @QueryParam("uploadId") String uploadId,
@BinderParam(BindPartIdsAndETagsToRequest.class) Map<Integer, String> parts);
/**
* @see AWSS3Client#deleteObjects
*/
@POST
@Path("/")
@QueryParams(keys = "delete")
@XMLResponseParser(DeleteResultHandler.class)
ListenableFuture<DeleteResult> deleteObjects(
@Bucket @EndpointParam(parser = AssignCorrectHostnameForBucket.class) @BinderParam(BindAsHostPrefixIfConfigured.class) @ParamValidators(BucketNameValidator.class) String bucketName,
@BinderParam(BindIterableAsPayloadToDeleteRequest.class) Iterable<String> keys);
}

View File

@ -19,8 +19,10 @@
package org.jclouds.aws.s3;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.jclouds.aws.s3.domain.DeleteResult;
import org.jclouds.concurrent.Timeout;
import org.jclouds.io.Payload;
import org.jclouds.s3.S3Client;
@ -143,4 +145,26 @@ public interface AWSS3Client extends S3Client {
* @return ETag of the content uploaded
*/
String completeMultipartUpload(String bucketName, String key, String uploadId, Map<Integer, String> parts);
/**
* The Multi-Object Delete operation enables you to delete multiple objects from a bucket using a
* single HTTP request. If you know the object keys that you want to delete, then this operation
* provides a suitable alternative to sending individual delete requests (see DELETE Object),
* reducing per-request overhead.
*
* The Multi-Object Delete request contains a set of up to 1000 keys that you want to delete.
*
* If a key does not exist is considered to be deleted.
*
* The Multi-Object Delete operation supports two modes for the response; verbose and quiet.
* By default, the operation uses verbose mode in which the response includes the result of
* deletion of each key in your request.
*
* @see <a href="http://docs.amazonwebservices.com/AmazonS3/latest/API/multiobjectdeleteapi.html" />
* @param bucketName
* namespace of the objects you are deleting
* @param keys
* set of unique keys identifying objects
*/
DeleteResult deleteObjects(String bucketName, Iterable<String> keys);
}

View File

@ -0,0 +1,63 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.aws.s3.binders;
import org.jclouds.crypto.CryptoStreams;
import org.jclouds.http.HttpRequest;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads;
import org.jclouds.rest.Binder;
import javax.ws.rs.core.MediaType;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* @author Andrei Savu
*/
public class BindIterableAsPayloadToDeleteRequest implements Binder {
@SuppressWarnings("unchecked")
@Override
public <R 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);
payload.getContentMetadata().setContentMD5(CryptoStreams.md5(content.getBytes()));
request.setPayload(payload);
return request;
}
}

View File

@ -0,0 +1,192 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.aws.s3.domain;
import com.google.common.base.Objects;
import com.google.common.collect.ForwardingSet;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.Map;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Multi-object delete API response
* <p/>
* Contains a list of the keys that were deleted
*
* @author Andrei Savu
* @see <a href="http://docs.amazonwebservices.com/AmazonS3/latest/API/multiobjectdeleteapi.html" />
*/
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();
}
}

View File

@ -0,0 +1,108 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.aws.s3.xml;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.jclouds.aws.s3.domain.DeleteResult;
import org.jclouds.http.functions.ParseSax;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import static org.jclouds.util.SaxUtils.equalsOrSuffix;
/**
* @author Andrei Savu
*/
public class DeleteResultHandler extends ParseSax.HandlerForGeneratedRequestWithResult<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 = new StringBuilder();
} else if (parsingErrorEntry) {
errorEntryHandler.startElement(uri, name, qName, attributes);
}
}
@Override
public void characters(char[] chars, int start, int length) throws SAXException {
if (parsingDeletedEntry) {
deletedEntryAccumulator.append(chars, start, length);
} else if (parsingErrorEntry) {
errorEntryHandler.characters(chars, start, length);
}
}
/**
* {@inheritDoc}
*/
@Override
public void endElement(String uri, String name, String qName) throws SAXException {
if (equalsOrSuffix(qName, DELETED_TAG)) {
parsingDeletedEntry = false;
deleted.add(deletedEntryAccumulator.toString().trim());
} else if (equalsOrSuffix(qName, ERROR_TAG)) {
parsingErrorEntry = false;
errors.put(errorEntryHandler.getResult());
}
if (parsingErrorEntry) {
errorEntryHandler.endElement(uri, name, qName);
}
}
/**
* {@inheritDoc}
*/
@Override
public DeleteResult getResult() {
return new DeleteResult(deleted.build(), errors.build());
}
}

View File

@ -0,0 +1,44 @@
package org.jclouds.aws.s3.xml;
import com.google.common.collect.Maps;
import org.jclouds.aws.s3.domain.DeleteResult;
import org.jclouds.http.functions.ParseSax;
import org.xml.sax.SAXException;
import java.util.Map;
import static org.jclouds.util.SaxUtils.equalsOrSuffix;
/**
* @author Andrei Savu
*/
public class ErrorEntryHandler extends ParseSax.HandlerForGeneratedRequestWithResult<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 = new StringBuilder();
}
@Override
public Map.Entry<String, DeleteResult.Error> getResult() {
return Maps.immutableEntry(key, new DeleteResult.Error(code, message));
}
}

View File

@ -19,12 +19,20 @@
package org.jclouds.aws.s3;
import static org.jclouds.aws.s3.blobstore.options.AWSS3PutObjectOptions.Builder.storageClass;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import com.google.common.collect.ImmutableSet;
import org.jclouds.aws.s3.domain.DeleteResult;
import org.jclouds.aws.s3.internal.BaseAWSS3ClientExpectTest;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.domain.BlobBuilder;
import org.jclouds.crypto.CryptoStreams;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads;
import org.jclouds.s3.blobstore.functions.BlobToObject;
import org.jclouds.s3.domain.ObjectMetadata.StorageClass;
import org.testng.annotations.Test;
@ -38,13 +46,14 @@ import com.google.inject.Injector;
*/
@Test
public class AWSS3ClientExpectTest extends BaseAWSS3ClientExpectTest {
HttpRequest bucketLocationRequest = HttpRequest.builder()
.method("GET")
.endpoint("https://test.s3.amazonaws.com/?location")
.addHeader("Host", "test.s3.amazonaws.com")
.addHeader("Date", CONSTANT_DATE)
.addHeader("Authorization", "AWS identity:D1rymKrEdvzvhmZXeg+Z0R+tiug=").build();
HttpResponse bucketLocationResponse = HttpResponse.builder()
.statusCode(200)
.payload(payloadFromStringWithContentType("<LocationConstraint xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">eu-west-1</LocationConstraint>", "application/xml"))
@ -52,13 +61,14 @@ public class AWSS3ClientExpectTest extends BaseAWSS3ClientExpectTest {
.addHeader("x-amz-request-id", "51BF4F45D49B1B34")
.addHeader("Date", CONSTANT_DATE)
.addHeader("Server", "AmazonS3").build();
@Test
public void testPutWithReducedRedundancy() {
Injector injector = createInjector(Functions.forMap(ImmutableMap.<HttpRequest, HttpResponse>of()), createModule(), setupProperties());
Blob blob = injector.getInstance(BlobBuilder.class).name("test").payload("content").build();
BlobToObject blobToObject = injector.getInstance(BlobToObject.class);
AWSS3Client client = requestsSendResponses(bucketLocationRequest, bucketLocationResponse,
HttpRequest.builder()
.method("PUT")
@ -80,4 +90,62 @@ public class AWSS3ClientExpectTest extends BaseAWSS3ClientExpectTest {
client.putObject("test", blobToObject.apply(blob),
storageClass(StorageClass.REDUCED_REDUNDANCY));
}
@Test
public void testDeleteMultipleObjects() {
final String request = "<?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(CryptoStreams.md5(request.getBytes()));
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"));
}
}

View File

@ -27,6 +27,7 @@ import static org.jclouds.io.Payloads.newByteArrayPayload;
import static org.jclouds.s3.options.ListBucketOptions.Builder.withPrefix;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
import java.io.ByteArrayInputStream;
@ -34,10 +35,13 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Set;
import java.util.UUID;
import java.util.zip.GZIPInputStream;
import org.jclouds.aws.AWSResponseException;
import org.jclouds.aws.domain.Region;
import org.jclouds.aws.s3.domain.DeleteResult;
import org.jclouds.blobstore.AsyncBlobStore;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.KeyNotFoundException;
@ -259,5 +263,33 @@ 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);
}
}
}

View File

@ -0,0 +1,66 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.aws.s3.binders;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.jclouds.http.HttpRequest;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads;
import org.testng.annotations.Test;
import javax.ws.rs.core.MediaType;
import static org.testng.Assert.assertEquals;
/**
* @author Andrei Savu
*/
public class BindIterableAsPayloadToDeleteRequestTest {
private final BindIterableAsPayloadToDeleteRequest binder = new BindIterableAsPayloadToDeleteRequest();
private final HttpRequest request = HttpRequest.builder().method("POST").endpoint("http://localhost/").build();
@Test
public void testWithASmallSet() {
HttpRequest result = binder.bindToRequest(request, ImmutableSet.of("key1", "key2"));
Payload payload = Payloads
.newStringPayload("<?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());
}
}

View File

@ -0,0 +1,57 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.aws.s3.xml;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.jclouds.aws.s3.domain.DeleteResult;
import org.jclouds.http.functions.BaseHandlerTest;
import org.testng.annotations.Test;
import java.io.InputStream;
import static org.testng.Assert.assertEquals;
/**
* @author Andrei Savu
*/
// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire
@Test(groups = "unit", testName = "DeleteResultHandlerTest")
public class DeleteResultHandlerTest extends BaseHandlerTest {
@Test
public void test() {
InputStream is = getClass().getResourceAsStream("/delete-result.xml");
DeleteResult expected = expected();
DeleteResultHandler handler = injector.getInstance(DeleteResultHandler.class);
DeleteResult result = factory.create(handler).parse(is);
assertEquals(result.toString(), expected.toString());
}
private DeleteResult expected() {
return DeleteResult.builder()
.add("key1")
.add("key1.1")
.putError("key2", new DeleteResult.Error("AccessDenied", "Access Denied"))
.build();
}
}

View File

@ -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>