mirror of https://github.com/apache/jclouds.git
JCLOUDS-458: Multipart Related Upload
This commit is contained in:
parent
0c55612bb1
commit
72e42dbfba
|
@ -90,6 +90,12 @@
|
||||||
<artifactId>logback-classic</artifactId>
|
<artifactId>logback-classic</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.assertj</groupId>
|
||||||
|
<artifactId>assertj-core</artifactId>
|
||||||
|
<version>1.6.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<profiles>
|
<profiles>
|
||||||
<profile>
|
<profile>
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* 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.googlecloudstorage.binders;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
|
import org.jclouds.googlecloudstorage.domain.templates.ObjectTemplate;
|
||||||
|
import org.jclouds.http.HttpRequest;
|
||||||
|
import org.jclouds.io.Payload;
|
||||||
|
import org.jclouds.io.Payloads;
|
||||||
|
import org.jclouds.io.payloads.MultipartForm;
|
||||||
|
import org.jclouds.io.payloads.Part;
|
||||||
|
import org.jclouds.io.payloads.StringPayload;
|
||||||
|
import org.jclouds.rest.MapBinder;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
public class MultipartUploadBinder implements MapBinder {
|
||||||
|
|
||||||
|
private final String BOUNDARY_HEADER = "multipart_boundary";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <R extends HttpRequest> R bindToRequest(R request, Map<String, Object> postParams)
|
||||||
|
throws IllegalArgumentException {
|
||||||
|
|
||||||
|
ObjectTemplate template = (ObjectTemplate) postParams.get("template");
|
||||||
|
Payload payload = (Payload) postParams.get("payload");
|
||||||
|
|
||||||
|
String contentType = checkNotNull(template.getContentType(), "contentType");
|
||||||
|
Long length = checkNotNull(template.getSize(), "contetLength");
|
||||||
|
|
||||||
|
StringPayload jsonPayload = Payloads.newStringPayload(new Gson().toJson(template));
|
||||||
|
|
||||||
|
payload.getContentMetadata().setContentLength(length);
|
||||||
|
|
||||||
|
Part jsonPart = Part.create("Metadata", jsonPayload,
|
||||||
|
new Part.PartOptions().contentType(MediaType.APPLICATION_JSON));
|
||||||
|
Part mediaPart = Part.create(template.getName(), payload, new Part.PartOptions().contentType(contentType));
|
||||||
|
|
||||||
|
MultipartForm compPayload = new MultipartForm(BOUNDARY_HEADER, jsonPart, mediaPart);
|
||||||
|
request.setPayload(compPayload);
|
||||||
|
// HeaderPart
|
||||||
|
request.toBuilder().replaceHeader(HttpHeaders.CONTENT_TYPE, "Multipart/related; boundary= " + BOUNDARY_HEADER)
|
||||||
|
.build();
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <R extends HttpRequest> R bindToRequest(R request, Object input) {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,6 +35,7 @@ import org.jclouds.Fallbacks.FalseOnNotFoundOr404;
|
||||||
import org.jclouds.Fallbacks.NullOnNotFoundOr404;
|
import org.jclouds.Fallbacks.NullOnNotFoundOr404;
|
||||||
import org.jclouds.Fallbacks.TrueOnNotFoundOr404;
|
import org.jclouds.Fallbacks.TrueOnNotFoundOr404;
|
||||||
import org.jclouds.googlecloudstorage.binders.ComposeObjectBinder;
|
import org.jclouds.googlecloudstorage.binders.ComposeObjectBinder;
|
||||||
|
import org.jclouds.googlecloudstorage.binders.MultipartUploadBinder;
|
||||||
import org.jclouds.googlecloudstorage.binders.UploadBinder;
|
import org.jclouds.googlecloudstorage.binders.UploadBinder;
|
||||||
import org.jclouds.googlecloudstorage.domain.GCSObject;
|
import org.jclouds.googlecloudstorage.domain.GCSObject;
|
||||||
import org.jclouds.googlecloudstorage.domain.ListPage;
|
import org.jclouds.googlecloudstorage.domain.ListPage;
|
||||||
|
@ -459,4 +460,27 @@ public interface ObjectApi {
|
||||||
@PathParam("destinationObject") String destinationObject, @PathParam("sourceBucket") String sourceBucket,
|
@PathParam("destinationObject") String destinationObject, @PathParam("sourceBucket") String sourceBucket,
|
||||||
@PathParam("sourceObject") String sourceObject, CopyObjectOptions options);
|
@PathParam("sourceObject") String sourceObject, CopyObjectOptions options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a new object with metadata.
|
||||||
|
*
|
||||||
|
* @see https://developers.google.com/storage/docs/json_api/v1/how-tos/upload#multipart
|
||||||
|
*
|
||||||
|
* @param bucketName
|
||||||
|
* Name of the bucket in which the object to be stored
|
||||||
|
* @param objectTemplate
|
||||||
|
* Supply an {@link ObjectTemplate}.
|
||||||
|
*
|
||||||
|
* @return a {@link GCSObject}
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Named("Object:multipartUpload")
|
||||||
|
@POST
|
||||||
|
@QueryParams(keys = "uploadType", values = "multipart")
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@Path("/upload/storage/v1/b/{bucket}/o")
|
||||||
|
@OAuthScopes(STORAGE_FULLCONTROL_SCOPE)
|
||||||
|
@MapBinder(MultipartUploadBinder.class)
|
||||||
|
GCSObject multipartUpload(@PathParam("bucket") String bucketName,
|
||||||
|
@PayloadParam("template") ObjectTemplate objectTemplate, @PayloadParam("payload") Payload payload);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ package org.jclouds.googlecloudstorage.features;
|
||||||
|
|
||||||
import static org.testng.Assert.assertEquals;
|
import static org.testng.Assert.assertEquals;
|
||||||
import static org.testng.Assert.assertNotNull;
|
import static org.testng.Assert.assertNotNull;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.entry;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -55,6 +57,7 @@ import org.testng.annotations.BeforeClass;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
import com.beust.jcommander.internal.Sets;
|
import com.beust.jcommander.internal.Sets;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.hash.HashCode;
|
import com.google.common.hash.HashCode;
|
||||||
import com.google.common.hash.HashFunction;
|
import com.google.common.hash.HashFunction;
|
||||||
import com.google.common.hash.Hashing;
|
import com.google.common.hash.Hashing;
|
||||||
|
@ -66,6 +69,7 @@ public class ObjectApiLiveTest extends BaseGoogleCloudStorageApiLiveTest {
|
||||||
private static final String BUCKET_NAME2 = "jcloudobjectdestination" + UUID.randomUUID();
|
private static final String BUCKET_NAME2 = "jcloudobjectdestination" + UUID.randomUUID();
|
||||||
private static final String UPLOAD_OBJECT_NAME = "objectOperation.txt";
|
private static final String UPLOAD_OBJECT_NAME = "objectOperation.txt";
|
||||||
private static final String UPLOAD_OBJECT_NAME2 = "jcloudslogo.jpg";
|
private static final String UPLOAD_OBJECT_NAME2 = "jcloudslogo.jpg";
|
||||||
|
private static final String MULTIPART_UPLOAD_OBJECT = "multipart_related.jpg";
|
||||||
private static final String COPIED_OBJECT_NAME = "copyofObjectOperation.txt";
|
private static final String COPIED_OBJECT_NAME = "copyofObjectOperation.txt";
|
||||||
private static final String COMPOSED_OBJECT = "ComposedObject1.txt";
|
private static final String COMPOSED_OBJECT = "ComposedObject1.txt";
|
||||||
private static final String COMPOSED_OBJECT2 = "ComposedObject2.json";
|
private static final String COMPOSED_OBJECT2 = "ComposedObject2.json";
|
||||||
|
@ -196,7 +200,7 @@ public class ObjectApiLiveTest extends BaseGoogleCloudStorageApiLiveTest {
|
||||||
assertEquals(gcsObject.getName(), COPIED_OBJECT_NAME);
|
assertEquals(gcsObject.getName(), COPIED_OBJECT_NAME);
|
||||||
assertEquals(gcsObject.getContentType(), "text/plain");
|
assertEquals(gcsObject.getContentType(), "text/plain");
|
||||||
|
|
||||||
//Test for data
|
// Test for data
|
||||||
|
|
||||||
PayloadEnclosingImpl impl = api().download(BUCKET_NAME2, COPIED_OBJECT_NAME);
|
PayloadEnclosingImpl impl = api().download(BUCKET_NAME2, COPIED_OBJECT_NAME);
|
||||||
assertNotNull(impl);
|
assertNotNull(impl);
|
||||||
|
@ -365,12 +369,53 @@ public class ObjectApiLiveTest extends BaseGoogleCloudStorageApiLiveTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(groups = "live", dependsOnMethods = "testPatchObjectsWithOptions")
|
@Test(groups = "live", dependsOnMethods = "testPatchObjectsWithOptions")
|
||||||
|
public void testMultipartJpegUpload() throws IOException {
|
||||||
|
long contentLength = 32 * 1024L;
|
||||||
|
ByteSource byteSource = TestUtils.randomByteSource().slice(0, contentLength);
|
||||||
|
ByteSourcePayload payload = Payloads.newByteSourcePayload(byteSource);
|
||||||
|
PayloadEnclosingImpl payloadImpl = new PayloadEnclosingImpl(payload);
|
||||||
|
|
||||||
|
ObjectTemplate template = new ObjectTemplate();
|
||||||
|
|
||||||
|
ObjectAccessControls oacl = ObjectAccessControls.builder().bucket(BUCKET_NAME).entity("allUsers")
|
||||||
|
.role(ObjectRole.OWNER).build();
|
||||||
|
|
||||||
|
// This would trigger server side validation of md5
|
||||||
|
hcMd5 = byteSource.hash(Hashing.md5());
|
||||||
|
|
||||||
|
// This would trigger server side validation of crc32c
|
||||||
|
hcCrc32c = byteSource.hash(Hashing.crc32c());
|
||||||
|
|
||||||
|
template.contentType("image/jpeg").addAcl(oacl).size(contentLength).name(MULTIPART_UPLOAD_OBJECT)
|
||||||
|
.contentLanguage("en").contentDisposition("attachment").md5Hash(hcMd5)
|
||||||
|
.customMetadata("custommetakey1", "custommetavalue1").crc32c(hcCrc32c)
|
||||||
|
.customMetadata(ImmutableMap.of("Adrian", "powderpuff"));
|
||||||
|
|
||||||
|
GCSObject gcsObject = api().multipartUpload(BUCKET_NAME, template, payloadImpl.getPayload());
|
||||||
|
|
||||||
|
assertThat(gcsObject.getBucket()).isEqualTo(BUCKET_NAME);
|
||||||
|
assertThat(gcsObject.getName()).isEqualTo(MULTIPART_UPLOAD_OBJECT);
|
||||||
|
assertThat(gcsObject.getMd5HashCode()).isEqualTo(hcMd5);
|
||||||
|
assertThat(gcsObject.getCrc32cHashcode()).isEqualTo(hcCrc32c);
|
||||||
|
|
||||||
|
assertThat(gcsObject.getAllMetadata()).contains(entry("custommetakey1", "custommetavalue1"),
|
||||||
|
entry("Adrian", "powderpuff")).doesNotContainKey("adrian");
|
||||||
|
|
||||||
|
PayloadEnclosingImpl impl = api().download(BUCKET_NAME, MULTIPART_UPLOAD_OBJECT);
|
||||||
|
|
||||||
|
assertThat(ByteStreams2.toByteArrayAndClose(impl.getPayload().openStream())).isEqualTo(
|
||||||
|
ByteStreams2.toByteArrayAndClose(payloadImpl.getPayload().openStream()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(groups = "live", dependsOnMethods = "testMultipartJpegUpload")
|
||||||
public void testDeleteObject() {
|
public void testDeleteObject() {
|
||||||
api().deleteObject(BUCKET_NAME2, UPLOAD_OBJECT_NAME);
|
api().deleteObject(BUCKET_NAME2, UPLOAD_OBJECT_NAME);
|
||||||
api().deleteObject(BUCKET_NAME2, COMPOSED_OBJECT2);
|
api().deleteObject(BUCKET_NAME2, COMPOSED_OBJECT2);
|
||||||
api().deleteObject(BUCKET_NAME2, COMPOSED_OBJECT);
|
api().deleteObject(BUCKET_NAME2, COMPOSED_OBJECT);
|
||||||
api().deleteObject(BUCKET_NAME2, COPIED_OBJECT_NAME);
|
api().deleteObject(BUCKET_NAME2, COPIED_OBJECT_NAME);
|
||||||
|
api().deleteObject(BUCKET_NAME, UPLOAD_OBJECT_NAME);
|
||||||
api().deleteObject(BUCKET_NAME, UPLOAD_OBJECT_NAME2);
|
api().deleteObject(BUCKET_NAME, UPLOAD_OBJECT_NAME2);
|
||||||
|
api().deleteObject(BUCKET_NAME, MULTIPART_UPLOAD_OBJECT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(groups = "live", dependsOnMethods = "testPatchObjectsWithOptions")
|
@Test(groups = "live", dependsOnMethods = "testPatchObjectsWithOptions")
|
||||||
|
|
Loading…
Reference in New Issue