diff --git a/src/main/java/org/gaul/s3proxy/Quirks.java b/src/main/java/org/gaul/s3proxy/Quirks.java index 922d686..92022b6 100644 --- a/src/main/java/org/gaul/s3proxy/Quirks.java +++ b/src/main/java/org/gaul/s3proxy/Quirks.java @@ -61,6 +61,16 @@ final class Quirks { "azureblob" ); + static final Set NO_LIST_MULTIPART_UPLOADS = ImmutableSet.of( + "atmos", + "filesystem", + "google-cloud-storage", + "openstack-swift", + "rackspace-cloudfiles-uk", + "rackspace-cloudfiles-us", + "transient" + ); + /** Blobstores which do not allow listing zero keys. */ static final Set NO_LIST_ZERO_KEYS = ImmutableSet.of( "atmos", diff --git a/src/main/java/org/gaul/s3proxy/S3ProxyHandler.java b/src/main/java/org/gaul/s3proxy/S3ProxyHandler.java index 4891894..0b0618d 100644 --- a/src/main/java/org/gaul/s3proxy/S3ProxyHandler.java +++ b/src/main/java/org/gaul/s3proxy/S3ProxyHandler.java @@ -501,8 +501,8 @@ final class S3ProxyHandler extends AbstractHandler { handleContainerLocation(response, blobStore, path[1]); return; } else if ("".equals(request.getParameter("uploads"))) { - handleListMultipartUploads(response, blobStore, - uploadId); + handleListMultipartUploads(request, response, blobStore, + path[1]); return; } handleBlobList(request, response, blobStore, path[1]); @@ -974,11 +974,64 @@ final class S3ProxyHandler extends AbstractHandler { } } - private void handleListMultipartUploads(HttpServletResponse response, - BlobStore blobStore, String uploadId) - throws IOException, S3Exception { - // TODO: list all blobs starting with uploadId - throw new S3Exception(S3ErrorCode.NOT_IMPLEMENTED); + private void handleListMultipartUploads(HttpServletRequest request, + HttpServletResponse response, BlobStore blobStore, + String container) throws IOException, S3Exception { + if (request.getParameter("delimiter") != null || + request.getParameter("prefix") != null || + request.getParameter("max-uploads") != null || + request.getParameter("key-marker") != null || + request.getParameter("upload-id-marker") != null) { + throw new UnsupportedOperationException(); + } + + List uploads = blobStore.listMultipartUploads( + container); + + try (Writer writer = response.getWriter()) { + XMLStreamWriter xml = xmlOutputFactory.createXMLStreamWriter( + writer); + xml.writeStartDocument(); + xml.writeStartElement("ListMultipartUploadsResult"); + xml.writeDefaultNamespace(AWS_XMLNS); + + writeSimpleElement(xml, "Bucket", container); + + // TODO: bogus values + xml.writeEmptyElement("KeyMarker"); + xml.writeEmptyElement("UploadIdMarker"); + xml.writeEmptyElement("NextKeyMarker"); + xml.writeEmptyElement("NextUploadIdMarker"); + xml.writeEmptyElement("Delimiter"); + xml.writeEmptyElement("Prefix"); + writeSimpleElement(xml, "MaxUploads", "1000"); + writeSimpleElement(xml, "IsTruncated", "false"); + + for (MultipartUpload upload : uploads) { + xml.writeStartElement("Upload"); + + writeSimpleElement(xml, "Key", upload.blobName()); + writeSimpleElement(xml, "UploadId", upload.id()); + writeInitiatorStanza(xml); + writeOwnerStanza(xml); + writeSimpleElement(xml, "StorageClass", "STANDARD"); + + // TODO: bogus value + writeSimpleElement(xml, "Initiated", + blobStore.getContext().utils().date() + .iso8601DateFormat(new Date())); + + xml.writeEndElement(); + } + + xml.writeEmptyElement("CommonPrefixes"); + + xml.writeEndElement(); + + xml.flush(); + } catch (XMLStreamException xse) { + throw new IOException(xse); + } } private void handleContainerExists(HttpServletResponse response, @@ -1923,18 +1976,8 @@ final class S3ProxyHandler extends AbstractHandler { writeSimpleElement(xml, "Key", encodeBlob( encodingType, blobName)); writeSimpleElement(xml, "UploadId", uploadId); - - // TODO: bogus values - xml.writeStartElement("Initiator"); - - writeSimpleElement(xml, "ID", FAKE_INITIATOR_ID); - writeSimpleElement(xml, "DisplayName", - FAKE_INITIATOR_DISPLAY_NAME); - - xml.writeEndElement(); - + writeInitiatorStanza(xml); writeOwnerStanza(xml); - writeSimpleElement(xml, "StorageClass", "STANDARD"); // TODO: pagination @@ -2604,6 +2647,18 @@ final class S3ProxyHandler extends AbstractHandler { } } + // TODO: bogus values + private static void writeInitiatorStanza(XMLStreamWriter xml) + throws XMLStreamException { + xml.writeStartElement("Initiator"); + + writeSimpleElement(xml, "ID", FAKE_INITIATOR_ID); + writeSimpleElement(xml, "DisplayName", + FAKE_INITIATOR_DISPLAY_NAME); + + xml.writeEndElement(); + } + // TODO: bogus values private static void writeOwnerStanza(XMLStreamWriter xml) throws XMLStreamException { diff --git a/src/test/java/org/gaul/s3proxy/JcloudsS3BlobIntegrationLiveTest.java b/src/test/java/org/gaul/s3proxy/JcloudsS3BlobIntegrationLiveTest.java index 7cf091b..4fb0fb0 100644 --- a/src/test/java/org/gaul/s3proxy/JcloudsS3BlobIntegrationLiveTest.java +++ b/src/test/java/org/gaul/s3proxy/JcloudsS3BlobIntegrationLiveTest.java @@ -123,6 +123,14 @@ public final class JcloudsS3BlobIntegrationLiveTest super.testCopyIfNoneMatchNegative(); } + @Override + public void testListMultipartUploads() throws Exception { + if (Quirks.NO_LIST_MULTIPART_UPLOADS.contains(blobStoreType)) { + throw new SkipException("list multipart uploads not supported"); + } + super.testListMultipartUploads(); + } + @Override protected void checkCacheControl(Blob blob, String cacheControl) { if (!Quirks.NO_CACHE_CONTROL_SUPPORT.contains(blobStoreType)) { diff --git a/src/test/java/org/gaul/s3proxy/JcloudsS3ClientLiveTest.java b/src/test/java/org/gaul/s3proxy/JcloudsS3ClientLiveTest.java index d925c42..60f3b36 100644 --- a/src/test/java/org/gaul/s3proxy/JcloudsS3ClientLiveTest.java +++ b/src/test/java/org/gaul/s3proxy/JcloudsS3ClientLiveTest.java @@ -162,6 +162,14 @@ public final class JcloudsS3ClientLiveTest extends S3ClientLiveTest { super.testUpdateObjectCannedACL(); } + @Override + public void testListMultipartUploads() throws Exception { + if (Quirks.NO_LIST_MULTIPART_UPLOADS.contains(blobStoreType)) { + throw new SkipException("list multipart uploads not supported"); + } + super.testListMultipartUploads(); + } + @Override protected void assertCacheControl(S3Object newObject, String string) { if (Quirks.NO_CACHE_CONTROL_SUPPORT.contains(blobStoreType)) {