JCLOUDS-912: JCLOUDS-1547: GCS InputStream single-part upload
Previously this provider worked around a RestAnnotationProcessor quirk
by using multi-part uploads for InputStream payloads. Instead work
around the quirk another way which allows a single-part upload. This
allows inclusion of the Content-MD5 header during object creation.
Backfill tests with both ByteSource and InputStream inputs.
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 ef5bd7f..a814c00 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
@@ -267,7 +267,7 @@
}
}
- private void putBlobWithMd5(byte[] payload, HashCode contentMD5) throws InterruptedException, IOException {
+ private void putBlobWithMd5(Payload payload, long contentLength, HashCode contentMD5) throws InterruptedException, IOException {
String container = getContainerName();
BlobStore blobStore = view.getBlobStore();
try {
@@ -275,6 +275,7 @@
Blob blob = blobStore
.blobBuilder(blobName)
.payload(payload)
+ .contentLength(contentLength)
.contentMD5(contentMD5)
.build();
blobStore.putBlob(container, blob);
@@ -288,18 +289,39 @@
}
@Test(groups = { "integration", "live" })
- public void testPutCorrectContentMD5() throws InterruptedException, IOException {
- byte[] payload = createTestInput(1024).read();
- HashCode contentMD5 = md5().hashBytes(payload);
- putBlobWithMd5(payload, contentMD5);
+ public void testPutCorrectContentMD5ByteSource() throws InterruptedException, IOException {
+ ByteSource payload = createTestInput(1024);
+ HashCode contentMD5 = md5().hashBytes(payload.read());
+ putBlobWithMd5(new ByteSourcePayload(payload), payload.size(), contentMD5);
}
@Test(groups = { "integration", "live" })
- public void testPutIncorrectContentMD5() throws InterruptedException, IOException {
- byte[] payload = createTestInput(1024).read();
+ public void testPutIncorrectContentMD5ByteSource() throws InterruptedException, IOException {
+ ByteSource payload = createTestInput(1024);
HashCode contentMD5 = md5().hashBytes(new byte[0]);
try {
- putBlobWithMd5(payload, contentMD5);
+ putBlobWithMd5(new ByteSourcePayload(payload), payload.size(), contentMD5);
+ fail();
+ } catch (HttpResponseException hre) {
+ if (hre.getResponse().getStatusCode() != getIncorrectContentMD5StatusCode()) {
+ throw hre;
+ }
+ }
+ }
+
+ @Test(groups = { "integration", "live" })
+ public void testPutCorrectContentMD5InputStream() throws InterruptedException, IOException {
+ ByteSource payload = createTestInput(1024);
+ HashCode contentMD5 = md5().hashBytes(payload.read());
+ putBlobWithMd5(new InputStreamPayload(payload.openStream()), payload.size(), contentMD5);
+ }
+
+ @Test(groups = { "integration", "live" })
+ public void testPutIncorrectContentMD5InputStream() throws InterruptedException, IOException {
+ ByteSource payload = createTestInput(1024);
+ HashCode contentMD5 = md5().hashBytes(new byte[0]);
+ try {
+ putBlobWithMd5(new InputStreamPayload(payload.openStream()), payload.size(), contentMD5);
fail();
} catch (HttpResponseException hre) {
if (hre.getResponse().getStatusCode() != getIncorrectContentMD5StatusCode()) {
diff --git a/core/src/main/java/org/jclouds/http/internal/PayloadEnclosingImpl.java b/core/src/main/java/org/jclouds/http/internal/PayloadEnclosingImpl.java
index a8b5196..bff39b6 100644
--- a/core/src/main/java/org/jclouds/http/internal/PayloadEnclosingImpl.java
+++ b/core/src/main/java/org/jclouds/http/internal/PayloadEnclosingImpl.java
@@ -98,6 +98,14 @@
}
@Override
+ public void resetPayload(boolean release) {
+ if (release && payload != null) {
+ payload.release();
+ }
+ payload = null;
+ }
+
+ @Override
public int hashCode() {
final int prime = 31;
int result = 1;
diff --git a/core/src/main/java/org/jclouds/io/PayloadEnclosing.java b/core/src/main/java/org/jclouds/io/PayloadEnclosing.java
index 3ed2983..dd3f28b 100644
--- a/core/src/main/java/org/jclouds/io/PayloadEnclosing.java
+++ b/core/src/main/java/org/jclouds/io/PayloadEnclosing.java
@@ -48,4 +48,5 @@
@Nullable
Payload getPayload();
+ void resetPayload(boolean release);
}
diff --git a/providers/b2/src/test/java/org/jclouds/b2/blobstore/integration/B2BlobIntegrationLiveTest.java b/providers/b2/src/test/java/org/jclouds/b2/blobstore/integration/B2BlobIntegrationLiveTest.java
index 0ecb6bd..3f45077 100644
--- a/providers/b2/src/test/java/org/jclouds/b2/blobstore/integration/B2BlobIntegrationLiveTest.java
+++ b/providers/b2/src/test/java/org/jclouds/b2/blobstore/integration/B2BlobIntegrationLiveTest.java
@@ -80,9 +80,19 @@
}
@Override
- public void testPutIncorrectContentMD5() throws InterruptedException, IOException {
+ public void testPutIncorrectContentMD5ByteSource() throws InterruptedException, IOException {
try {
- super.testPutIncorrectContentMD5();
+ super.testPutIncorrectContentMD5ByteSource();
+ failBecauseExceptionWasNotThrown(AssertionError.class);
+ } catch (AssertionError ae) {
+ throw new SkipException("B2 does not enforce Content-MD5", ae);
+ }
+ }
+
+ @Override
+ public void testPutIncorrectContentMD5InputStream() throws InterruptedException, IOException {
+ try {
+ super.testPutIncorrectContentMD5InputStream();
failBecauseExceptionWasNotThrown(AssertionError.class);
} catch (AssertionError ae) {
throw new SkipException("B2 does not enforce Content-MD5", ae);
diff --git a/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/binders/MultipartUploadBinder.java b/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/binders/MultipartUploadBinder.java
index ca6422b..848b8c6 100644
--- a/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/binders/MultipartUploadBinder.java
+++ b/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/binders/MultipartUploadBinder.java
@@ -57,6 +57,7 @@
Part jsonPart = Part.create("Metadata", jsonPayload, new Part.PartOptions().contentType(APPLICATION_JSON));
Part mediaPart = Part.create(template.name(), payload, new Part.PartOptions().contentType(contentType));
+ request.resetPayload(/*release=*/ false);
request.setPayload(new MultipartForm(BOUNDARY_HEADER, jsonPart, mediaPart));
// HeaderPart
request.toBuilder().replaceHeader(CONTENT_TYPE, "Multipart/related; boundary= " + BOUNDARY_HEADER).build();
diff --git a/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/blobstore/GoogleCloudStorageBlobStore.java b/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/blobstore/GoogleCloudStorageBlobStore.java
index 029ca03..8777643 100644
--- a/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/blobstore/GoogleCloudStorageBlobStore.java
+++ b/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/blobstore/GoogleCloudStorageBlobStore.java
@@ -211,7 +211,7 @@
public String putBlob(String container, Blob blob, PutOptions options) {
long length = checkNotNull(blob.getPayload().getContentMetadata().getContentLength());
- if (length != 0 && (options.isMultipart() || !blob.getPayload().isRepeatable())) {
+ if (length != 0 && options.isMultipart()) {
// JCLOUDS-912 prevents using single-part uploads with InputStream payloads.
// Work around this with multi-part upload which buffers parts in-memory.
return putMultipartBlob(container, blob, options);