JCLOUDS-1125: S3 list multipart uploads
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/445664c9 Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/445664c9 Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/445664c9 Branch: refs/heads/master Commit: 445664c9f1da25ddd8e0b85ad089583fd7d189ac Parents: 61ab28c Author: Andrew Gaul <[email protected]> Authored: Thu Jun 9 17:23:38 2016 -0700 Committer: Andrew Gaul <[email protected]> Committed: Mon Jun 13 16:26:43 2016 -0700 ---------------------------------------------------------------------- apis/s3/pom.xml | 5 + .../src/main/java/org/jclouds/s3/S3Client.java | 13 ++ .../s3/domain/ListMultipartUploadsResponse.java | 57 ++++++++ .../s3/xml/ListMultipartUploadsHandler.java | 129 +++++++++++++++++++ .../java/org/jclouds/s3/S3ClientLiveTest.java | 30 +++++ .../test/java/org/jclouds/s3/S3ClientTest.java | 18 +++ 6 files changed, 252 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/jclouds/blob/445664c9/apis/s3/pom.xml ---------------------------------------------------------------------- diff --git a/apis/s3/pom.xml b/apis/s3/pom.xml index f0fc2a5..8f4c738 100644 --- a/apis/s3/pom.xml +++ b/apis/s3/pom.xml @@ -100,6 +100,11 @@ <artifactId>auto-service</artifactId> <optional>true</optional> </dependency> + <dependency> + <groupId>com.google.auto.value</groupId> + <artifactId>auto-value</artifactId> + <scope>provided</scope> + </dependency> </dependencies> <profiles> http://git-wip-us.apache.org/repos/asf/jclouds/blob/445664c9/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 fe3caea..cb1d9bd 100644 --- a/apis/s3/src/main/java/org/jclouds/s3/S3Client.java +++ b/apis/s3/src/main/java/org/jclouds/s3/S3Client.java @@ -75,6 +75,7 @@ import org.jclouds.s3.domain.BucketMetadata; import org.jclouds.s3.domain.CannedAccessPolicy; import org.jclouds.s3.domain.DeleteResult; import org.jclouds.s3.domain.ListBucketResponse; +import org.jclouds.s3.domain.ListMultipartUploadsResponse; import org.jclouds.s3.domain.ObjectMetadata; import org.jclouds.s3.domain.Payer; import org.jclouds.s3.domain.S3Object; @@ -100,6 +101,7 @@ 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.ListMultipartUploadsHandler; import org.jclouds.s3.xml.LocationConstraintHandler; import org.jclouds.s3.xml.PartIdsFromHttpResponse; import org.jclouds.s3.xml.PayerHandler; @@ -785,4 +787,15 @@ public interface S3Client extends Closeable { Map<Integer, String> listMultipartParts(@Bucket @EndpointParam(parser = AssignCorrectHostnameForBucket.class) @BinderParam(BindAsHostPrefixIfConfigured.class) @ParamValidators(BucketNameValidator.class) String bucketName, @PathParam("key") String key, @QueryParam("uploadId") String uploadId); + + @Named("ListMultipartUploads") + @GET + @Path("/") + @QueryParams(keys = "uploads") + @XMLResponseParser(ListMultipartUploadsHandler.class) + ListMultipartUploadsResponse listMultipartUploads(@Bucket @EndpointParam(parser = AssignCorrectHostnameForBucket.class) + @BinderParam(BindAsHostPrefixIfConfigured.class) @ParamValidators(BucketNameValidator.class) String bucketName, + @QueryParam("delimiter") @Nullable String delimiter, @QueryParam("max-uploads") @Nullable Integer maxUploads, + @QueryParam("key-marker") @Nullable String keyMarker, @QueryParam("prefix") @Nullable String prefix, + @QueryParam("upload-id-marker") @Nullable String uploadIdMarker); } http://git-wip-us.apache.org/repos/asf/jclouds/blob/445664c9/apis/s3/src/main/java/org/jclouds/s3/domain/ListMultipartUploadsResponse.java ---------------------------------------------------------------------- diff --git a/apis/s3/src/main/java/org/jclouds/s3/domain/ListMultipartUploadsResponse.java b/apis/s3/src/main/java/org/jclouds/s3/domain/ListMultipartUploadsResponse.java new file mode 100644 index 0000000..16ebaa8 --- /dev/null +++ b/apis/s3/src/main/java/org/jclouds/s3/domain/ListMultipartUploadsResponse.java @@ -0,0 +1,57 @@ +/* + * 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 java.util.Date; +import java.util.List; + +import org.jclouds.javax.annotation.Nullable; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; + +@AutoValue +public abstract class ListMultipartUploadsResponse { + public abstract String bucket(); + @Nullable public abstract String keyMarker(); + @Nullable public abstract String uploadIdMarker(); + @Nullable public abstract String nextKeyMarker(); + @Nullable public abstract String nextUploadIdMarker(); + public abstract int maxUploads(); + public abstract boolean isTruncated(); + public abstract List<Upload> uploads(); + + public static ListMultipartUploadsResponse create(String bucket, @Nullable String keyMarker, @Nullable String uploadIdMarker, @Nullable String nextKeyMarker, @Nullable String nextUploadIdMarker, int maxUploads, boolean isTruncated, List<Upload> uploads) { + uploads = ImmutableList.copyOf(uploads); + return new AutoValue_ListMultipartUploadsResponse(bucket, keyMarker, uploadIdMarker, nextKeyMarker, nextUploadIdMarker, maxUploads, isTruncated, uploads); + } + + @AutoValue + public abstract static class Upload { + public abstract String key(); + public abstract String uploadId(); + public abstract CanonicalUser initiator(); + public abstract CanonicalUser owner(); + public abstract ObjectMetadata.StorageClass storageClass(); + public abstract Date initiated(); + + public static Upload create(String key, String uploadId, CanonicalUser initiator, CanonicalUser owner, ObjectMetadata.StorageClass storageClass, Date initiated) { + initiated = (Date) initiated.clone(); + return new AutoValue_ListMultipartUploadsResponse_Upload(key, uploadId, initiator, owner, storageClass, initiated); + } + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/445664c9/apis/s3/src/main/java/org/jclouds/s3/xml/ListMultipartUploadsHandler.java ---------------------------------------------------------------------- diff --git a/apis/s3/src/main/java/org/jclouds/s3/xml/ListMultipartUploadsHandler.java b/apis/s3/src/main/java/org/jclouds/s3/xml/ListMultipartUploadsHandler.java new file mode 100644 index 0000000..fc855d3 --- /dev/null +++ b/apis/s3/src/main/java/org/jclouds/s3/xml/ListMultipartUploadsHandler.java @@ -0,0 +1,129 @@ +/* + * 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.currentOrNull; + +import java.util.Date; + +import javax.inject.Inject; + +import org.jclouds.date.DateService; +import org.jclouds.http.functions.ParseSax; +import org.jclouds.s3.domain.CanonicalUser; +import org.jclouds.s3.domain.ListMultipartUploadsResponse; +import org.jclouds.s3.domain.ObjectMetadata; +import org.xml.sax.Attributes; + +import com.google.common.collect.ImmutableList; + +public final class ListMultipartUploadsHandler extends ParseSax.HandlerWithResult<ListMultipartUploadsResponse> { + private String bucket; + private String keyMarker; + private String uploadIdMarker; + private String nextKeyMarker; + private String nextUploadIdMarker; + private int maxUploads; + private boolean isTruncated; + private final ImmutableList.Builder<ListMultipartUploadsResponse.Upload> uploads = ImmutableList.builder(); + + private String key; + private String uploadId; + private String id; + private String displayName; + private CanonicalUser initiator; + private CanonicalUser owner; + private ObjectMetadata.StorageClass storageClass; + private Date initiated; + + private final DateService dateParser; + private final StringBuilder currentText = new StringBuilder(); + private boolean inUpload; + private boolean inInitiator; + private boolean inOwner; + + @Inject + public ListMultipartUploadsHandler(DateService dateParser) { + this.dateParser = dateParser; + } + + public ListMultipartUploadsResponse getResult() { + return ListMultipartUploadsResponse.create(bucket, keyMarker, uploadIdMarker, nextKeyMarker, nextUploadIdMarker, maxUploads, isTruncated, uploads.build()); + } + + public void startElement(String uri, String name, String qName, Attributes attrs) { + if (qName.equals("Upload")) { + inUpload = true; + } else if (qName.equals("Initiator")) { + inInitiator = true; + } else if (qName.equals("Owner")) { + inOwner = true; + } + currentText.setLength(0); + } + + public void endElement(String uri, String name, String qName) { + if (qName.equals("Bucket")) { + bucket = currentOrNull(currentText); + } else if (qName.equals("KeyMarker")) { + keyMarker = currentOrNull(currentText); + } else if (qName.equals("UploadIdMarker")) { + uploadIdMarker = currentOrNull(currentText); + } else if (qName.equals("NextKeyMarker")) { + nextKeyMarker = currentOrNull(currentText); + } else if (qName.equals("NextUploadIdMarker")) { + nextUploadIdMarker = currentOrNull(currentText); + } else if (qName.equals("MaxUploads")) { + maxUploads = Integer.parseInt(currentOrNull(currentText)); + } else if (qName.equals("IsTruncated")) { + isTruncated = Boolean.parseBoolean(currentOrNull(currentText)); + } else if (qName.equals("Key")) { + key = currentOrNull(currentText); + } else if (qName.equals("UploadId")) { + uploadId = currentOrNull(currentText); + } else if (qName.equals("StorageClass")) { + storageClass = ObjectMetadata.StorageClass.valueOf(currentOrNull(currentText)); + } else if (qName.equals("Initiated")) { + initiated = dateParser.iso8601DateOrSecondsDateParse(currentOrNull(currentText)); + } else if (qName.equals("Upload")) { + uploads.add(ListMultipartUploadsResponse.Upload.create(key, uploadId, initiator, owner, storageClass, initiated)); + key = null; + uploadId = null; + id = null; + displayName = null; + initiator = null; + owner = null; + storageClass = null; + initiated = null; + inUpload = false; + } else if (qName.equals("Initiator")) { + initiator = new CanonicalUser(id, displayName); + id = null; + displayName = null; + inInitiator = false; + } else if (qName.equals("Owner")) { + owner = new CanonicalUser(id, displayName); + id = null; + displayName = null; + inOwner = false; + } + } + + public void characters(char ch[], int start, int length) { + currentText.append(ch, start, length); + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/445664c9/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 a6ba2c7..a4b87a1 100644 --- a/apis/s3/src/test/java/org/jclouds/s3/S3ClientLiveTest.java +++ b/apis/s3/src/test/java/org/jclouds/s3/S3ClientLiveTest.java @@ -56,6 +56,7 @@ 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.ListMultipartUploadsResponse; import org.jclouds.s3.domain.ObjectMetadata; import org.jclouds.s3.domain.ObjectMetadataBuilder; import org.jclouds.s3.domain.S3Object; @@ -566,6 +567,35 @@ public class S3ClientLiveTest extends BaseBlobStoreIntegrationTest { } } + public void testListMultipartUploads() throws Exception { + String containerName = getContainerName(); + String key = "testListMultipartUploads"; + String uploadId = null; + try { + ListMultipartUploadsResponse response = getApi().listMultipartUploads(containerName, null, null, null, null, null); + assertThat(response.bucket()).isEqualTo(containerName); + assertThat(response.isTruncated()).isFalse(); + assertThat(response.uploads()).isEmpty(); + + uploadId = getApi().initiateMultipartUpload(containerName, ObjectMetadataBuilder.create().key(key).build()); + + response = getApi().listMultipartUploads(containerName, null, null, null, null, null); + assertThat(response.bucket()).isEqualTo(containerName); + assertThat(response.isTruncated()).isFalse(); + assertThat(response.uploads()).hasSize(1); + + ListMultipartUploadsResponse.Upload upload = response.uploads().get(0); + assertThat(upload.key()).isEqualTo(key); + assertThat(upload.uploadId()).isEqualTo(uploadId); + assertThat(upload.storageClass()).isEqualTo(ObjectMetadata.StorageClass.STANDARD); + } finally { + if (uploadId != null) { + getApi().abortMultipartUpload(containerName, key, uploadId); + } + returnContainer(containerName); + } + } + public void testDeleteMultipleObjects() throws InterruptedException { String container = getContainerName(); try { http://git-wip-us.apache.org/repos/asf/jclouds/blob/445664c9/apis/s3/src/test/java/org/jclouds/s3/S3ClientTest.java ---------------------------------------------------------------------- diff --git a/apis/s3/src/test/java/org/jclouds/s3/S3ClientTest.java b/apis/s3/src/test/java/org/jclouds/s3/S3ClientTest.java index 75ff965..dae83a6 100644 --- a/apis/s3/src/test/java/org/jclouds/s3/S3ClientTest.java +++ b/apis/s3/src/test/java/org/jclouds/s3/S3ClientTest.java @@ -20,6 +20,7 @@ import static org.jclouds.reflect.Reflection2.method; import static org.testng.Assert.assertEquals; import java.io.IOException; +import java.util.Arrays; import java.util.Map; import org.jclouds.Fallbacks.VoidOnNotFoundOr404; @@ -68,6 +69,7 @@ import org.jclouds.s3.xml.BucketLoggingHandler; import org.jclouds.s3.xml.CopyObjectHandler; import org.jclouds.s3.xml.ListAllMyBucketsHandler; import org.jclouds.s3.xml.ListBucketHandler; +import org.jclouds.s3.xml.ListMultipartUploadsHandler; import org.jclouds.s3.xml.LocationConstraintHandler; import org.jclouds.s3.xml.PayerHandler; import org.jclouds.util.Strings2; @@ -625,6 +627,22 @@ public abstract class S3ClientTest<T extends S3Client> extends BaseS3ClientTest< checkFilters(request); } + public void testListMultipartUploads() throws Exception { + Invokable<?, ?> method = method(S3Client.class, "listMultipartUploads", String.class, String.class, + Integer.class, String.class, String.class, String.class); + GeneratedHttpRequest request = processor.createRequest(method, Arrays.<Object> asList("bucket", null, null, null, null, null)); + + assertRequestLineEquals(request, "GET https://bucket." + url + "/?uploads HTTP/1.1"); + assertNonPayloadHeadersEqual(request, "Host: bucket." + url + "\n"); + assertPayloadEquals(request, null, "application/unknown", false); + + assertResponseParserClassEquals(method, request, ParseSax.class); + assertSaxResponseParserClassEquals(method, ListMultipartUploadsHandler.class); + assertFallbackClassEquals(method, MapHttp4xxCodesToExceptions.class); + + checkFilters(request); + } + @ConfiguresHttpApi private static final class TestS3HttpApiModule extends S3HttpApiModule<S3Client> {
