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 2016/06/13 23:28:24 UTC

[1/3] jclouds git commit: JCLOUDS-1125: portable list multipart uploads

Repository: jclouds
Updated Branches:
  refs/heads/master c1ce819f6 -> 0bd295941


JCLOUDS-1125: portable list multipart uploads

Only Azure, B2, and S3 support this operation.  Some MultipartUpload
fields become nullable.


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/0bd29594
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/0bd29594
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/0bd29594

Branch: refs/heads/master
Commit: 0bd29594104b9a77178aed2b721e66dd7e81da77
Parents: 445664c
Author: Andrew Gaul <ga...@apache.org>
Authored: Sat Jun 11 11:17:19 2016 -0700
Committer: Andrew Gaul <ga...@apache.org>
Committed: Mon Jun 13 16:26:43 2016 -0700

----------------------------------------------------------------------
 .../jclouds/atmos/blobstore/AtmosBlobStore.java |  5 +++
 .../integration/AtmosIntegrationLiveTest.java   |  9 ++++
 .../FilesystemBlobIntegrationTest.java          |  9 ++++
 .../blobstore/RegionScopedSwiftBlobStore.java   |  5 +++
 .../SwiftBlobIntegrationLiveTest.java           |  9 ++++
 .../org/jclouds/s3/blobstore/S3BlobStore.java   | 20 +++++++++
 .../java/org/jclouds/blobstore/BlobStore.java   |  3 ++
 .../blobstore/config/LocalBlobStore.java        |  5 +++
 .../blobstore/domain/MultipartUpload.java       |  9 ++--
 .../blobstore/util/ForwardingBlobStore.java     |  5 +++
 .../blobstore/util/ReadOnlyBlobStore.java       |  6 +++
 .../TransientBlobIntegrationTest.java           |  9 ++++
 .../internal/BaseBlobIntegrationTest.java       | 44 ++++++++++++++++++++
 .../azureblob/blobstore/AzureBlobStore.java     | 31 ++++++++++++++
 14 files changed, 165 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/0bd29594/apis/atmos/src/main/java/org/jclouds/atmos/blobstore/AtmosBlobStore.java
----------------------------------------------------------------------
diff --git a/apis/atmos/src/main/java/org/jclouds/atmos/blobstore/AtmosBlobStore.java b/apis/atmos/src/main/java/org/jclouds/atmos/blobstore/AtmosBlobStore.java
index a263af9..1a0e74b 100644
--- a/apis/atmos/src/main/java/org/jclouds/atmos/blobstore/AtmosBlobStore.java
+++ b/apis/atmos/src/main/java/org/jclouds/atmos/blobstore/AtmosBlobStore.java
@@ -322,6 +322,11 @@ public class AtmosBlobStore extends BaseBlobStore {
    }
 
    @Override
+   public List<MultipartUpload> listMultipartUploads(String container) {
+      throw new UnsupportedOperationException("Atmos does not support multipart uploads");
+   }
+
+   @Override
    public long getMinimumMultipartPartSize() {
       throw new UnsupportedOperationException("Atmos does not support multipart uploads");
    }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/0bd29594/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/integration/AtmosIntegrationLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/integration/AtmosIntegrationLiveTest.java b/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/integration/AtmosIntegrationLiveTest.java
index 640524f..4f6e991 100644
--- a/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/integration/AtmosIntegrationLiveTest.java
+++ b/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/integration/AtmosIntegrationLiveTest.java
@@ -150,6 +150,15 @@ public class AtmosIntegrationLiveTest extends BaseBlobIntegrationTest {
    }
 
    @Override
+   public void testListMultipartUploads() throws Exception {
+      try {
+         super.testListMultipartUploads();
+      } catch (UnsupportedOperationException uoe) {
+         throw new SkipException("Atmos does not support multipart uploads", uoe);
+      }
+   }
+
+   @Override
    public void testPutMultipartByteSource() throws Exception {
       throw new SkipException("Atmos does not support multipart uploads");
    }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/0bd29594/apis/filesystem/src/test/java/org/jclouds/filesystem/integration/FilesystemBlobIntegrationTest.java
----------------------------------------------------------------------
diff --git a/apis/filesystem/src/test/java/org/jclouds/filesystem/integration/FilesystemBlobIntegrationTest.java b/apis/filesystem/src/test/java/org/jclouds/filesystem/integration/FilesystemBlobIntegrationTest.java
index d469037..fd75a94 100644
--- a/apis/filesystem/src/test/java/org/jclouds/filesystem/integration/FilesystemBlobIntegrationTest.java
+++ b/apis/filesystem/src/test/java/org/jclouds/filesystem/integration/FilesystemBlobIntegrationTest.java
@@ -93,4 +93,13 @@ public class FilesystemBlobIntegrationTest extends BaseBlobIntegrationTest {
    public void testSetBlobAccess() throws Exception {
       throw new SkipException("filesystem does not support anonymous access");
    }
+
+   @Test(groups = { "integration", "live" })
+   public void testListMultipartUploads() throws Exception {
+      try {
+         super.testListMultipartUploads();
+      } catch (UnsupportedOperationException uoe) {
+         throw new SkipException("filesystem does not support listing multipart uploads");
+      }
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/0bd29594/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
index cdd305a..20bdaef 100644
--- a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
@@ -517,6 +517,11 @@ public class RegionScopedSwiftBlobStore implements BlobStore {
    }
 
    @Override
+   public List<MultipartUpload> listMultipartUploads(String container) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
    public long getMinimumMultipartPartSize() {
       return 1024 * 1024 + 1;
    }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/0bd29594/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobIntegrationLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobIntegrationLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobIntegrationLiveTest.java
index 441b6a2..c60586f 100644
--- a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobIntegrationLiveTest.java
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobIntegrationLiveTest.java
@@ -112,6 +112,15 @@ public class SwiftBlobIntegrationLiveTest extends BaseBlobIntegrationTest {
       super.testCopyIfNoneMatchNegative();
    }
 
+   @Override
+   public void testListMultipartUploads() throws Exception {
+      try {
+         super.testListMultipartUploads();
+      } catch (UnsupportedOperationException uoe) {
+         throw new SkipException("Swift does not support listing multipart uploads", uoe);
+      }
+   }
+
    // TODO: testCopyIfModifiedSinceNegative throws HTTP 304 not 412 error
 
    @Override

http://git-wip-us.apache.org/repos/asf/jclouds/blob/0bd29594/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobStore.java
----------------------------------------------------------------------
diff --git a/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobStore.java b/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobStore.java
index ef3729f..e5d56b9 100644
--- a/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobStore.java
+++ b/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobStore.java
@@ -64,6 +64,7 @@ import org.jclouds.s3.domain.AccessControlList.GroupGranteeURI;
 import org.jclouds.s3.domain.AccessControlList.Permission;
 import org.jclouds.s3.domain.BucketMetadata;
 import org.jclouds.s3.domain.CannedAccessPolicy;
+import org.jclouds.s3.domain.ListMultipartUploadsResponse;
 import org.jclouds.s3.options.CopyObjectOptions;
 import org.jclouds.s3.options.ListBucketOptions;
 import org.jclouds.s3.options.PutBucketOptions;
@@ -401,6 +402,25 @@ public class S3BlobStore extends BaseBlobStore {
    }
 
    @Override
+   public List<MultipartUpload> listMultipartUploads(String container) {
+      ImmutableList.Builder<MultipartUpload> builder = ImmutableList.builder();
+      String keyMarker = null;
+      String uploadIdMarker = null;
+      while (true) {
+         ListMultipartUploadsResponse response = sync.listMultipartUploads(container, null, null, keyMarker, null, uploadIdMarker);
+         for (ListMultipartUploadsResponse.Upload upload : response.uploads()) {
+            builder.add(MultipartUpload.create(container, upload.key(), upload.uploadId(), null, null));
+         }
+         keyMarker = response.keyMarker();
+         uploadIdMarker = response.uploadIdMarker();
+         if (response.uploads().isEmpty() || keyMarker == null || uploadIdMarker == null) {
+            break;
+         }
+      }
+      return builder.build();
+   }
+
+   @Override
    public long getMinimumMultipartPartSize() {
       return 5 * 1024 * 1024;
    }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/0bd29594/blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java b/blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java
index 4cbc92e..933675c 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java
@@ -349,6 +349,9 @@ public interface BlobStore {
    List<MultipartPart> listMultipartUpload(MultipartUpload mpu);
 
    @Beta
+   List<MultipartUpload> listMultipartUploads(String container);
+
+   @Beta
    long getMinimumMultipartPartSize();
 
    @Beta

http://git-wip-us.apache.org/repos/asf/jclouds/blob/0bd29594/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java b/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
index f48e2da..b441f1b 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
@@ -882,6 +882,11 @@ public final class LocalBlobStore implements BlobStore {
    }
 
    @Override
+   public List<MultipartUpload> listMultipartUploads(String container) {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
    public long getMinimumMultipartPartSize() {
       return 1;
    }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/0bd29594/blobstore/src/main/java/org/jclouds/blobstore/domain/MultipartUpload.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/domain/MultipartUpload.java b/blobstore/src/main/java/org/jclouds/blobstore/domain/MultipartUpload.java
index 94f2401..385a9a1 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/domain/MultipartUpload.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/domain/MultipartUpload.java
@@ -18,6 +18,7 @@
 package org.jclouds.blobstore.domain;
 
 import org.jclouds.blobstore.options.PutOptions;
+import org.jclouds.javax.annotation.Nullable;
 
 import com.google.auto.value.AutoValue;
 
@@ -26,11 +27,11 @@ public abstract class MultipartUpload {
    public abstract String containerName();
    public abstract String blobName();
    public abstract String id();
-   public abstract BlobMetadata blobMetadata();
-   public abstract PutOptions putOptions();
+   @Nullable public abstract BlobMetadata blobMetadata();
+   @Nullable public abstract PutOptions putOptions();
 
-   public static MultipartUpload create(String containerName, String blobName, String id, BlobMetadata blobMetadata,
-         PutOptions putOptions) {
+   public static MultipartUpload create(String containerName, String blobName, String id, @Nullable BlobMetadata blobMetadata,
+         @Nullable PutOptions putOptions) {
       return new AutoValue_MultipartUpload(containerName, blobName, id, blobMetadata, putOptions);
    }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/0bd29594/blobstore/src/main/java/org/jclouds/blobstore/util/ForwardingBlobStore.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/util/ForwardingBlobStore.java b/blobstore/src/main/java/org/jclouds/blobstore/util/ForwardingBlobStore.java
index 2a4631b..b25d56e 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/util/ForwardingBlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/util/ForwardingBlobStore.java
@@ -245,6 +245,11 @@ public abstract class ForwardingBlobStore extends ForwardingObject
    }
 
    @Override
+   public List<MultipartUpload> listMultipartUploads(String container) {
+      return delegate().listMultipartUploads(container);
+   }
+
+   @Override
    public long getMinimumMultipartPartSize() {
       return delegate().getMinimumMultipartPartSize();
    }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/0bd29594/blobstore/src/main/java/org/jclouds/blobstore/util/ReadOnlyBlobStore.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/util/ReadOnlyBlobStore.java b/blobstore/src/main/java/org/jclouds/blobstore/util/ReadOnlyBlobStore.java
index 3cfbb73..af82dab 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/util/ReadOnlyBlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/util/ReadOnlyBlobStore.java
@@ -159,4 +159,10 @@ public final class ReadOnlyBlobStore extends ForwardingBlobStore {
    public List<MultipartPart> listMultipartUpload(MultipartUpload mpu) {
       throw new UnsupportedOperationException("Read-only BlobStore");
    }
+
+   // TODO: should ReadOnlyBlobStore allow listing parts and uploads?
+   @Override
+   public List<MultipartUpload> listMultipartUploads(String container) {
+      throw new UnsupportedOperationException("Read-only BlobStore");
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/0bd29594/blobstore/src/test/java/org/jclouds/blobstore/integration/TransientBlobIntegrationTest.java
----------------------------------------------------------------------
diff --git a/blobstore/src/test/java/org/jclouds/blobstore/integration/TransientBlobIntegrationTest.java b/blobstore/src/test/java/org/jclouds/blobstore/integration/TransientBlobIntegrationTest.java
index e2b842e..739e7ba 100644
--- a/blobstore/src/test/java/org/jclouds/blobstore/integration/TransientBlobIntegrationTest.java
+++ b/blobstore/src/test/java/org/jclouds/blobstore/integration/TransientBlobIntegrationTest.java
@@ -31,4 +31,13 @@ public class TransientBlobIntegrationTest extends BaseBlobIntegrationTest {
    public void testSetBlobAccess() throws Exception {
       throw new SkipException("transient does not support anonymous access");
    }
+
+   @Test(groups = { "integration", "live" })
+   public void testListMultipartUploads() throws Exception {
+      try {
+         super.testListMultipartUploads();
+      } catch (UnsupportedOperationException uoe) {
+         throw new SkipException("transient does not support listing multipart uploads");
+      }
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/0bd29594/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
----------------------------------------------------------------------
diff --git a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
index b2a501e..2acb165 100644
--- a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
+++ b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
@@ -1275,6 +1275,50 @@ public class BaseBlobIntegrationTest extends BaseBlobStoreIntegrationTest {
       }
    }
 
+   @Test(groups = { "integration", "live" })
+   public void testListMultipartUploads() throws Exception {
+      BlobStore blobStore = view.getBlobStore();
+      String name = "blob-name";
+      PayloadBlobBuilder blobBuilder = blobStore.blobBuilder(name)
+            .userMetadata(ImmutableMap.of("key1", "value1", "key2", "value2"))
+            // TODO: fake payload to add content metadata
+            .payload(new byte[0]);
+      addContentMetadata(blobBuilder);
+      Blob blob = blobBuilder.build();
+      MultipartUpload mpu = null;
+
+      String container = getContainerName();
+      try {
+         List<MultipartUpload> uploads = blobStore.listMultipartUploads(container);
+         assertThat(uploads).isEmpty();
+
+         mpu = blobStore.initiateMultipartUpload(container, blob.getMetadata(), new PutOptions());
+
+         // some providers like Azure cannot list an MPU until the first blob is uploaded
+         assertThat(uploads.size()).isBetween(0, 1);
+
+         ByteSource byteSource = TestUtils.randomByteSource().slice(0, 1);
+         Payload payload = Payloads.newByteSourcePayload(byteSource);
+         payload.getContentMetadata().setContentLength(byteSource.size());
+         MultipartPart part = blobStore.uploadMultipartPart(mpu, 1, payload);
+
+         uploads = blobStore.listMultipartUploads(container);
+         assertThat(uploads).hasSize(1);
+
+         blobStore.completeMultipartUpload(mpu, ImmutableList.of(part));
+
+         uploads = blobStore.listMultipartUploads(container);
+         assertThat(uploads).isEmpty();
+
+         // cannot test abort since Azure does not have explicit support
+      } finally {
+         if (mpu != null) {
+            blobStore.abortMultipartUpload(mpu);
+         }
+         returnContainer(container);
+      }
+   }
+
    @Test(groups = { "integration", "live" }, expectedExceptions = {KeyNotFoundException.class})
    public void testCopy404BlobFail() throws Exception {
       BlobStore blobStore = view.getBlobStore();

http://git-wip-us.apache.org/repos/asf/jclouds/blob/0bd29594/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobStore.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobStore.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobStore.java
index d839024..c7007e5 100644
--- a/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobStore.java
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobStore.java
@@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
 import static org.jclouds.azure.storage.options.ListOptions.Builder.includeMetadata;
 
 import java.net.URI;
+import java.util.EnumSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -38,8 +39,11 @@ import org.jclouds.azureblob.blobstore.functions.ListBlobsResponseToResourceList
 import org.jclouds.azureblob.blobstore.functions.ListOptionsToListBlobsOptions;
 import org.jclouds.azureblob.domain.AzureBlob;
 import org.jclouds.azureblob.domain.BlobBlockProperties;
+import org.jclouds.azureblob.domain.BlobProperties;
 import org.jclouds.azureblob.domain.ContainerProperties;
 import org.jclouds.azureblob.domain.ListBlobBlocksResponse;
+import org.jclouds.azureblob.domain.ListBlobsInclude;
+import org.jclouds.azureblob.domain.ListBlobsResponse;
 import org.jclouds.azureblob.domain.PublicAccess;
 import org.jclouds.azureblob.options.CopyBlobOptions;
 import org.jclouds.azureblob.options.ListBlobsOptions;
@@ -454,6 +458,33 @@ public class AzureBlobStore extends BaseBlobStore {
    }
 
    @Override
+   public List<MultipartUpload> listMultipartUploads(String container) {
+      ImmutableList.Builder<MultipartUpload> builder = ImmutableList.builder();
+      String marker = null;
+      while (true) {
+         ListBlobsOptions options = new ListBlobsOptions().include(EnumSet.of(ListBlobsInclude.UNCOMMITTEDBLOBS));
+         if (marker != null) {
+            options.marker(marker);
+         }
+         ListBlobsResponse response = sync.listBlobs(container, options);
+         for (BlobProperties properties : response) {
+            // only uncommitted blobs lack ETags
+            if (properties.getETag() != null) {
+               continue;
+            }
+            // TODO: bogus uploadId
+            String uploadId = UUID.randomUUID().toString();
+            builder.add(MultipartUpload.create(properties.getContainer(), properties.getName(), uploadId, null, null));
+         }
+         marker = response.getNextMarker();
+         if (marker == null) {
+            break;
+         }
+      }
+      return builder.build();
+   }
+
+   @Override
    public long getMinimumMultipartPartSize() {
       return 1;
    }


[3/3] jclouds git commit: JCLOUDS-1125: Azure list other blobs

Posted by ga...@apache.org.
JCLOUDS-1125: Azure list other blobs

Some BlobProperties fields become nullable due to these new blob types.


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/61ab28cc
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/61ab28cc
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/61ab28cc

Branch: refs/heads/master
Commit: 61ab28cc50afa75abf026f7a382f2a2f736637bf
Parents: c1ce819
Author: Andrew Gaul <ga...@apache.org>
Authored: Fri Jun 10 18:31:39 2016 -0700
Committer: Andrew Gaul <ga...@apache.org>
Committed: Mon Jun 13 16:26:43 2016 -0700

----------------------------------------------------------------------
 .../azureblob/domain/ListBlobsInclude.java      | 33 ++++++++++++++++++++
 .../domain/internal/BlobPropertiesImpl.java     |  6 ++--
 .../azureblob/options/ListBlobsOptions.java     | 22 +++++++++++++
 .../azureblob/AzureBlobClientLiveTest.java      | 12 +++++++
 .../jclouds/azureblob/AzureBlobClientTest.java  | 16 ++++++++++
 5 files changed, 86 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/61ab28cc/providers/azureblob/src/main/java/org/jclouds/azureblob/domain/ListBlobsInclude.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/domain/ListBlobsInclude.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/domain/ListBlobsInclude.java
new file mode 100644
index 0000000..1964346
--- /dev/null
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/domain/ListBlobsInclude.java
@@ -0,0 +1,33 @@
+/*
+ * 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.azureblob.domain;
+
+public enum ListBlobsInclude {
+   COPY,
+   METADATA,
+   SNAPSHOTS,
+   UNCOMMITTEDBLOBS;
+
+   public static ListBlobsInclude fromValue(String symbol) {
+      return ListBlobsInclude.valueOf(symbol.toUpperCase());
+   }
+
+   @Override
+   public String toString() {
+      return name().toLowerCase();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/61ab28cc/providers/azureblob/src/main/java/org/jclouds/azureblob/domain/internal/BlobPropertiesImpl.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/domain/internal/BlobPropertiesImpl.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/domain/internal/BlobPropertiesImpl.java
index 27866db..c3c4005 100644
--- a/providers/azureblob/src/main/java/org/jclouds/azureblob/domain/internal/BlobPropertiesImpl.java
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/domain/internal/BlobPropertiesImpl.java
@@ -47,7 +47,7 @@ public class BlobPropertiesImpl implements BlobProperties {
    private final BaseImmutableContentMetadata contentMetadata;
 
    // TODO: should this take Cache-Control as well?
-   public BlobPropertiesImpl(BlobType type, String name, String container, URI url, Date lastModified, String eTag,
+   public BlobPropertiesImpl(BlobType type, String name, String container, URI url, @Nullable Date lastModified, @Nullable String eTag,
             long size, String contentType, @Nullable byte[] contentMD5, @Nullable String contentMetadata,
             @Nullable String contentLanguage, @Nullable Date currentExpires, LeaseStatus leaseStatus, 
             Map<String, String> metadata) {
@@ -56,8 +56,8 @@ public class BlobPropertiesImpl implements BlobProperties {
       this.name = checkNotNull(name, "name");
       this.container = checkNotNull(container, "container");
       this.url = checkNotNull(url, "url");
-      this.lastModified = checkNotNull(lastModified, "lastModified");
-      this.eTag = checkNotNull(eTag, "eTag");
+      this.lastModified = lastModified;
+      this.eTag = eTag;
       this.contentMetadata = new BaseImmutableContentMetadata(contentType, size, contentMD5, null, contentLanguage,
                contentMetadata, currentExpires);
       this.metadata.putAll(checkNotNull(metadata, "metadata"));

http://git-wip-us.apache.org/repos/asf/jclouds/blob/61ab28cc/providers/azureblob/src/main/java/org/jclouds/azureblob/options/ListBlobsOptions.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/options/ListBlobsOptions.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/options/ListBlobsOptions.java
index 72128a4..895d6a1 100644
--- a/providers/azureblob/src/main/java/org/jclouds/azureblob/options/ListBlobsOptions.java
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/options/ListBlobsOptions.java
@@ -16,7 +16,13 @@
  */
 package org.jclouds.azureblob.options;
 
+import java.util.Set;
+
 import org.jclouds.azure.storage.options.ListOptions;
+import org.jclouds.azureblob.domain.ListBlobsInclude;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
 
 /**
  * Contains options supported in the REST API for the List Blobs operation. <h2>
@@ -35,6 +41,7 @@ import org.jclouds.azure.storage.options.ListOptions;
  * @see <a href="http://msdn.microsoft.com/en-us/library/dd179466.aspx" />
  */
 public class ListBlobsOptions extends ListOptions {
+   private Set<String> datasets;
 
    /**
     * When the request includes this parameter, the operation returns a {@code BlobPrefix} element
@@ -94,6 +101,11 @@ public class ListBlobsOptions extends ListOptions {
          ListBlobsOptions options = new ListBlobsOptions();
          return options.maxResults(maxKeys);
       }
+
+      public static ListBlobsOptions include(Set<ListBlobsInclude> datasets) {
+         ListBlobsOptions options = new ListBlobsOptions();
+         return options.include(datasets);
+      }
    }
 
    /**
@@ -127,4 +139,14 @@ public class ListBlobsOptions extends ListOptions {
    public ListBlobsOptions prefix(String prefix) {
       return (ListBlobsOptions) super.prefix(prefix);
    }
+
+   public ListBlobsOptions include(Set<ListBlobsInclude> datasets) {
+      datasets = ImmutableSet.copyOf(datasets);
+      this.queryParameters.put("include", Joiner.on(",").join(datasets));
+      return this;
+   }
+
+   public Set<String> getInclude() {
+      return datasets;
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/61ab28cc/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java b/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java
index bef20c2..61f2320 100644
--- a/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java
+++ b/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java
@@ -31,6 +31,7 @@ import java.net.URI;
 import java.security.SecureRandom;
 import java.util.Arrays;
 import java.util.Date;
+import java.util.EnumSet;
 import java.util.Map;
 import java.util.Set;
 
@@ -41,6 +42,7 @@ import org.jclouds.azureblob.domain.AzureBlob;
 import org.jclouds.azureblob.domain.BlobProperties;
 import org.jclouds.azureblob.domain.ContainerProperties;
 import org.jclouds.azureblob.domain.ListBlobBlocksResponse;
+import org.jclouds.azureblob.domain.ListBlobsInclude;
 import org.jclouds.azureblob.domain.ListBlobsResponse;
 import org.jclouds.azureblob.domain.PublicAccess;
 import org.jclouds.azureblob.options.CopyBlobOptions;
@@ -349,11 +351,20 @@ public class AzureBlobClientLiveTest extends BaseBlobStoreIntegrationTest {
       String blockIdA = BaseEncoding.base64().encode((blockBlob + "-" + A).getBytes());
       String blockIdB = BaseEncoding.base64().encode((blockBlob + "-" + B).getBytes());
       String blockIdC = BaseEncoding.base64().encode((blockBlob + "-" + C).getBytes());
+
       getApi().createContainer(blockContainer);
+
       getApi().putBlock(blockContainer, blockBlob, blockIdA, Payloads.newByteArrayPayload(A.getBytes()));
       getApi().putBlock(blockContainer, blockBlob, blockIdB, Payloads.newByteArrayPayload(B.getBytes()));
       getApi().putBlock(blockContainer, blockBlob, blockIdC, Payloads.newByteArrayPayload(C.getBytes()));
+
+      ListBlobsResponse blobs = getApi().listBlobs(blockContainer);
+      assertThat(blobs).isEmpty();
+      blobs = getApi().listBlobs(blockContainer, new ListBlobsOptions().include(EnumSet.allOf(ListBlobsInclude.class)));
+      assertThat(blobs).hasSize(1);
+
       getApi().putBlockList(blockContainer, blockBlob, Arrays.asList(blockIdA, blockIdB, blockIdC));
+
       ListBlobBlocksResponse blocks = getApi().getBlockList(blockContainer, blockBlob);
       assertEquals(3, blocks.getBlocks().size());
       assertEquals(blockIdA, blocks.getBlocks().get(0).getBlockName());
@@ -362,6 +373,7 @@ public class AzureBlobClientLiveTest extends BaseBlobStoreIntegrationTest {
       assertEquals(1, blocks.getBlocks().get(0).getContentLength());
       assertEquals(1, blocks.getBlocks().get(1).getContentLength());
       assertEquals(1, blocks.getBlocks().get(2).getContentLength());
+
       getApi().deleteContainer(blockContainer);
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/61ab28cc/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientTest.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientTest.java b/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientTest.java
index c6fa723..c117d36 100644
--- a/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientTest.java
+++ b/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientTest.java
@@ -24,6 +24,7 @@ import static org.testng.Assert.assertEquals;
 import java.io.IOException;
 import java.net.URI;
 import java.util.Date;
+import java.util.EnumSet;
 import java.util.Map;
 
 import org.jclouds.ContextBuilder;
@@ -33,6 +34,7 @@ import org.jclouds.azure.storage.filters.SharedKeyLiteAuthentication;
 import org.jclouds.azure.storage.options.ListOptions;
 import org.jclouds.azureblob.AzureBlobFallbacks.FalseIfContainerAlreadyExists;
 import org.jclouds.azureblob.domain.AzureBlob;
+import org.jclouds.azureblob.domain.ListBlobsInclude;
 import org.jclouds.azureblob.domain.PublicAccess;
 import org.jclouds.azureblob.functions.ParseBlobFromHeadersAndHttpContent;
 import org.jclouds.azureblob.functions.ParseContainerPropertiesFromHeaders;
@@ -205,6 +207,20 @@ public class AzureBlobClientTest extends BaseRestAnnotationProcessingTest<AzureB
       assertFallbackClassEquals(method, null);
    }
 
+   public void testListBlobsWithOptions() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(AzureBlobClient.class, "listBlobs", String.class, ListBlobsOptions[].class);
+      GeneratedHttpRequest request = processor.createRequest(method, ImmutableList.<Object> of("container", new ListBlobsOptions().include(EnumSet.allOf(ListBlobsInclude.class))));
+
+      assertRequestLineEquals(request,
+               "GET https://identity.blob.core.windows.net/container?restype=container&comp=list&include=copy,metadata,snapshots,uncommittedblobs HTTP/1.1");
+      assertNonPayloadHeadersEqual(request, "x-ms-version: 2013-08-15\n");
+      assertPayloadEquals(request, null, null, false);
+
+      assertResponseParserClassEquals(method, request, ParseSax.class);
+      assertSaxResponseParserClassEquals(method, ContainerNameEnumerationResultsHandler.class);
+      assertFallbackClassEquals(method, null);
+   }
+
    public void testListRootBlobs() throws SecurityException, NoSuchMethodException, IOException {
       Invokable<?, ?> method = method(AzureBlobClient.class, "listBlobs", ListBlobsOptions[].class);
       GeneratedHttpRequest request = processor.createRequest(method, ImmutableList.of());


[2/3] jclouds git commit: JCLOUDS-1125: S3 list multipart uploads

Posted by ga...@apache.org.
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 <ga...@apache.org>
Authored: Thu Jun 9 17:23:38 2016 -0700
Committer: Andrew Gaul <ga...@apache.org>
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> {