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>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>1.6.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<profiles>
|
||||
<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.TrueOnNotFoundOr404;
|
||||
import org.jclouds.googlecloudstorage.binders.ComposeObjectBinder;
|
||||
import org.jclouds.googlecloudstorage.binders.MultipartUploadBinder;
|
||||
import org.jclouds.googlecloudstorage.binders.UploadBinder;
|
||||
import org.jclouds.googlecloudstorage.domain.GCSObject;
|
||||
import org.jclouds.googlecloudstorage.domain.ListPage;
|
||||
|
@ -459,4 +460,27 @@ public interface ObjectApi {
|
|||
@PathParam("destinationObject") String destinationObject, @PathParam("sourceBucket") String sourceBucket,
|
||||
@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.assertNotNull;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
@ -55,6 +57,7 @@ import org.testng.annotations.BeforeClass;
|
|||
import org.testng.annotations.Test;
|
||||
|
||||
import com.beust.jcommander.internal.Sets;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.hash.HashCode;
|
||||
import com.google.common.hash.HashFunction;
|
||||
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 UPLOAD_OBJECT_NAME = "objectOperation.txt";
|
||||
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 COMPOSED_OBJECT = "ComposedObject1.txt";
|
||||
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.getContentType(), "text/plain");
|
||||
|
||||
//Test for data
|
||||
// Test for data
|
||||
|
||||
PayloadEnclosingImpl impl = api().download(BUCKET_NAME2, COPIED_OBJECT_NAME);
|
||||
assertNotNull(impl);
|
||||
|
@ -365,12 +369,53 @@ public class ObjectApiLiveTest extends BaseGoogleCloudStorageApiLiveTest {
|
|||
}
|
||||
|
||||
@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() {
|
||||
api().deleteObject(BUCKET_NAME2, UPLOAD_OBJECT_NAME);
|
||||
api().deleteObject(BUCKET_NAME2, COMPOSED_OBJECT2);
|
||||
api().deleteObject(BUCKET_NAME2, COMPOSED_OBJECT);
|
||||
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, MULTIPART_UPLOAD_OBJECT);
|
||||
}
|
||||
|
||||
@Test(groups = "live", dependsOnMethods = "testPatchObjectsWithOptions")
|
||||
|
|
Loading…
Reference in New Issue