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 2014/08/31 23:16:21 UTC
git commit: JCLOUDS-458: Resumable Upload with live tests
Repository: jclouds-labs-google
Updated Branches:
refs/heads/master 626a03a03 -> 98c28a6b9
JCLOUDS-458: Resumable Upload with live tests
Project: http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/commit/98c28a6b
Tree: http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/tree/98c28a6b
Diff: http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/diff/98c28a6b
Branch: refs/heads/master
Commit: 98c28a6b9563abf0f99f568bfc333e44f1a4d6e5
Parents: 626a03a
Author: hsbhathiya <hs...@gmail.com>
Authored: Sat Aug 30 02:27:06 2014 +0530
Committer: Andrew Gaul <ga...@apache.org>
Committed: Sun Aug 31 14:15:11 2014 -0700
----------------------------------------------------------------------
.../GoogleCloudStorageApi.java | 8 +
.../binders/ResumableUploadBinder.java | 44 +++++
.../googlecloudstorage/domain/DomainUtils.java | 4 +
.../domain/ResumableUpload.java | 133 +++++++++++++
.../features/ResumableUploadApi.java | 197 +++++++++++++++++++
.../GoogleCloudStorageErrorHandler.java | 2 +
.../GoogleCloudStorageRedirectRetryHandler.java | 47 +++++
.../parser/ParseToResumableUpload.java | 66 +++++++
.../features/ResumableUploadApiLiveTest.java | 174 ++++++++++++++++
9 files changed, 675 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/98c28a6b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/GoogleCloudStorageApi.java
----------------------------------------------------------------------
diff --git a/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/GoogleCloudStorageApi.java b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/GoogleCloudStorageApi.java
index e4d3e6d..eb930df 100644
--- a/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/GoogleCloudStorageApi.java
+++ b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/GoogleCloudStorageApi.java
@@ -25,6 +25,7 @@ import org.jclouds.googlecloudstorage.features.BucketApi;
import org.jclouds.googlecloudstorage.features.DefaultObjectAccessControlsApi;
import org.jclouds.googlecloudstorage.features.ObjectAccessControlsApi;
import org.jclouds.googlecloudstorage.features.ObjectApi;
+import org.jclouds.googlecloudstorage.features.ResumableUploadApi;
import org.jclouds.rest.annotations.Delegate;
/**
@@ -68,4 +69,11 @@ public interface GoogleCloudStorageApi extends Closeable {
@Delegate
@Path("")
ObjectApi getObjectApi();
+
+ /**
+ * Provides access to Google Cloud Storage ResumableUpload features
+ */
+ @Delegate
+ @Path("")
+ ResumableUploadApi getResumableUploadApi();
}
http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/98c28a6b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/binders/ResumableUploadBinder.java
----------------------------------------------------------------------
diff --git a/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/binders/ResumableUploadBinder.java b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/binders/ResumableUploadBinder.java
new file mode 100644
index 0000000..bfa29c9
--- /dev/null
+++ b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/binders/ResumableUploadBinder.java
@@ -0,0 +1,44 @@
+/*
+ * 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.inject.Inject;
+
+import org.jclouds.googlecloudstorage.domain.templates.ObjectTemplate;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.rest.MapBinder;
+import org.jclouds.rest.binders.BindToJsonPayload;
+
+public class ResumableUploadBinder implements MapBinder {
+
+ @Inject
+ private BindToJsonPayload jsonBinder;
+
+ @Override
+ public <R extends HttpRequest> R bindToRequest(R request, Map<String, Object> postParams)
+ throws IllegalArgumentException {
+ ObjectTemplate template = (ObjectTemplate) postParams.get("template");
+ return bindToRequest(request, template);
+ }
+
+ @Override
+ public <R extends HttpRequest> R bindToRequest(R request, Object input) {
+ return jsonBinder.bindToRequest(request, input);
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/98c28a6b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/domain/DomainUtils.java
----------------------------------------------------------------------
diff --git a/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/domain/DomainUtils.java b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/domain/DomainUtils.java
index d028e7b..c175691 100644
--- a/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/domain/DomainUtils.java
+++ b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/domain/DomainUtils.java
@@ -31,4 +31,8 @@ public final class DomainUtils {
List<Byte> reversedList = Lists.reverse(hashByte);
return Bytes.toArray(reversedList);
}
+
+ public static String generateContentRange(Long lowerLimit, Long upperLimit, Long totalSize) {
+ return "bytes " + lowerLimit + "-" + upperLimit + "/" + totalSize;
+ }
}
http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/98c28a6b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/domain/ResumableUpload.java
----------------------------------------------------------------------
diff --git a/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/domain/ResumableUpload.java b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/domain/ResumableUpload.java
new file mode 100644
index 0000000..caeda2b
--- /dev/null
+++ b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/domain/ResumableUpload.java
@@ -0,0 +1,133 @@
+/*
+ * 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.domain;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkArgument;
+
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.base.MoreObjects.ToStringHelper;
+
+/**
+ * Represents results of resumable upload response.
+ */
+public class ResumableUpload {
+
+ protected final Integer statusCode;
+ protected final String uploadId;
+ protected final String contentLength;
+ protected final Long rangeUpperValue;
+ protected final Long rangeLowerValue;
+
+ private ResumableUpload(Integer statusCode, @Nullable String uploadId, @Nullable String contentLength,
+ @Nullable Long rangeLowerValue, @Nullable Long rangeUpperValue) {
+ if (rangeLowerValue != null && rangeUpperValue != null) {
+ checkArgument(rangeLowerValue < rangeUpperValue, "lower range must less than upper range, was: %s - %s",
+ rangeLowerValue, rangeUpperValue);
+ }
+ this.statusCode = checkNotNull(statusCode, "statusCode");
+ this.uploadId = uploadId;
+ this.contentLength = contentLength;
+ this.rangeUpperValue = rangeUpperValue;
+ this.rangeLowerValue = rangeLowerValue;
+ }
+
+ public String getUploadId() {
+ return uploadId;
+ }
+
+ public Integer getStatusCode() {
+ return statusCode;
+ }
+
+ public String getContentLength() {
+ return contentLength;
+ }
+
+ public Long getRangeUpperValue() {
+ return rangeUpperValue;
+ }
+
+ public Long getRangeLowerValue() {
+ return rangeLowerValue;
+ }
+
+ protected ToStringHelper string() {
+ return toStringHelper(this).add("statusCode", statusCode).add("uploadId", uploadId)
+ .add("contentLength", contentLength).add("rangeUpperValue", rangeUpperValue)
+ .add("rangeLowerValue", rangeLowerValue);
+ }
+
+ @Override
+ public String toString() {
+ return string().toString();
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public Builder toBuilder() {
+ return new Builder().fromResumableUpload(this);
+ }
+
+ public static final class Builder {
+
+ protected String uploadId;
+ protected Integer statusCode;
+ protected String contentLength;
+ protected Long rangeUpperValue;
+ protected Long rangeLowerValue;
+
+ public Builder uploadId(String uploadId) {
+ this.uploadId = uploadId;
+ return this;
+ }
+
+ public Builder statusCode(Integer statusCode) {
+ this.statusCode = statusCode;
+ return this;
+ }
+
+ public Builder contentLength(String contentLength) {
+ this.contentLength = contentLength;
+ return this;
+ }
+
+ public Builder rangeUpperValue(Long rangeUpperValue) {
+ this.rangeUpperValue = rangeUpperValue;
+ return this;
+ }
+
+ public Builder rangeLowerValue(Long rangeLowerValue) {
+ this.rangeLowerValue = rangeLowerValue;
+ return this;
+ }
+
+ public ResumableUpload build() {
+ return new ResumableUpload(statusCode, uploadId, contentLength, rangeLowerValue, rangeUpperValue);
+ }
+
+ public Builder fromResumableUpload(ResumableUpload in) {
+ return this.statusCode(in.getStatusCode()).uploadId(in.getUploadId()).contentLength(in.getContentLength())
+ .rangeUpperValue(in.getRangeUpperValue()).rangeLowerValue(in.getRangeLowerValue());
+ }
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/98c28a6b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/features/ResumableUploadApi.java
----------------------------------------------------------------------
diff --git a/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/features/ResumableUploadApi.java b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/features/ResumableUploadApi.java
new file mode 100644
index 0000000..91d822c
--- /dev/null
+++ b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/features/ResumableUploadApi.java
@@ -0,0 +1,197 @@
+/*
+ * 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.features;
+
+import static org.jclouds.googlecloudstorage.reference.GoogleCloudStorageConstants.STORAGE_FULLCONTROL_SCOPE;
+
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.googlecloudstorage.binders.ResumableUploadBinder;
+import org.jclouds.googlecloudstorage.binders.UploadBinder;
+import org.jclouds.googlecloudstorage.domain.ResumableUpload;
+import org.jclouds.googlecloudstorage.domain.templates.ObjectTemplate;
+import org.jclouds.googlecloudstorage.options.InsertObjectOptions;
+import org.jclouds.googlecloudstorage.parser.ParseToResumableUpload;
+import org.jclouds.io.Payload;
+import org.jclouds.oauth.v2.config.OAuthScopes;
+import org.jclouds.oauth.v2.filters.OAuthAuthenticator;
+import org.jclouds.rest.annotations.MapBinder;
+import org.jclouds.rest.annotations.PayloadParam;
+import org.jclouds.rest.annotations.QueryParams;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.SkipEncoding;
+
+/**
+ * Provides Resumable Upload support via Rest API
+ *
+ * @see <a href="https://developers.google.com/storage/docs/json_api/v1/objects"/>
+ * @see <a href="https://developers.google.com/storage/docs/json_api/v1/how-tos/upload#resumable"/>
+ */
+
+@SkipEncoding({ '/', '=' })
+@RequestFilters(OAuthAuthenticator.class)
+public interface ResumableUploadApi {
+
+ /**
+ * initiate a Resumable Upload Session
+ *
+ * @see https://developers.google.com/storage/docs/json_api/v1/how-tos/upload#resumable
+ *
+ * @param bucketName
+ * Name of the bucket in which the object to be stored
+ * @param objectName
+ * Name of the object to upload
+ * @param contentType
+ * Content type of the uploaded data
+ * @param contentLength
+ * ContentLength of the uploaded object (Media part)
+ *
+ * @return a {@link ResumableUpload}
+ */
+ @Named("Object:initResumableUpload")
+ @POST
+ @QueryParams(keys = "uploadType", values = "resumable")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Path("/upload/storage/v1/b/{bucket}/o")
+ @OAuthScopes(STORAGE_FULLCONTROL_SCOPE)
+ @ResponseParser(ParseToResumableUpload.class)
+ ResumableUpload initResumableUpload(@PathParam("bucket") String bucketName, @QueryParam("name") String objectName,
+ @HeaderParam("X-Upload-Content-Type") String contentType,
+ @HeaderParam("X-Upload-Content-Length") String contentLength);
+
+ /**
+ * initiate a Resumable Upload Session
+ *
+ * @see https://developers.google.com/storage/docs/json_api/v1/how-tos/upload#simple
+ *
+ * @param bucketName
+ * Name of the bucket in which the object to be stored
+ * @param contentType
+ * Content type of the uploaded data (Media part)
+ * @param contentLength
+ * Content length of the uploaded data (Media part)
+ * @param metada
+ * Supply an {@link ObjectTemplate}
+ *
+ * @return a {@link ResumableUpload}
+ */
+ @Named("Object:resumableUpload")
+ @POST
+ @QueryParams(keys = "uploadType", values = "resumable")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Path("/upload/storage/v1/b/{bucket}/o")
+ @OAuthScopes(STORAGE_FULLCONTROL_SCOPE)
+ @MapBinder(ResumableUploadBinder.class)
+ @ResponseParser(ParseToResumableUpload.class)
+ ResumableUpload initResumableUpload(@PathParam("bucket") String bucketName,
+ @HeaderParam("X-Upload-Content-Type") String contentType,
+ @HeaderParam("X-Upload-Content-Length") Long contentLength,
+ @PayloadParam("template") ObjectTemplate metadata);
+
+ /**
+ * Stores a new object
+ *
+ * @see https://developers.google.com/storage/docs/json_api/v1/how-tos/upload#resumable
+ *
+ * @param bucketName
+ * Name of the bucket in which the object to be stored
+ * @param options
+ * Supply {@link InsertObjectOptions} with optional query parameters. 'name' is mandatory.
+ *
+ * @return If successful, this method returns a {@link GCSObject} resource.
+ */
+ @Named("Object:resumableUpload")
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ @QueryParams(keys = "uploadType", values = "resumable")
+ @Path("/upload/storage/v1/b/{bucket}/o")
+ @OAuthScopes(STORAGE_FULLCONTROL_SCOPE)
+ @MapBinder(UploadBinder.class)
+ @ResponseParser(ParseToResumableUpload.class)
+ ResumableUpload upload(@PathParam("bucket") String bucketName, @QueryParam("upload_id") String uploadId,
+ @HeaderParam("Content-Type") String contentType, @HeaderParam("Content-Length") String contentLength,
+ @PayloadParam("payload") Payload payload);
+
+ /**
+ * Facilitate to use resumable upload operation to upload files in chunks
+ *
+ * @see https://developers.google.com/storage/docs/json_api/v1/how-tos/upload#resumable
+ *
+ * @param bucketName
+ * Name of the bucket in which the object to be stored
+ * @param uploadId
+ * uploadId returned from initResumableUpload operation
+ * @param contentType
+ * Content type of the uploaded data
+ * @param contentLength
+ * Content length of the uploaded data
+ * @param contentRange
+ * Range in {bytes StartingByte - Endingbyte/Totalsize } format ex: bytes 0 - 1213/2000
+ * @param payload
+ * a {@link Payload} with actual data to upload
+ *
+ * @return a {@link ResumableUpload}
+ */
+ @Named("Object:Upload")
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ @QueryParams(keys = "uploadType", values = "resumable")
+ @Path("/upload/storage/v1/b/{bucket}/o")
+ @OAuthScopes(STORAGE_FULLCONTROL_SCOPE)
+ @MapBinder(UploadBinder.class)
+ @ResponseParser(ParseToResumableUpload.class)
+ ResumableUpload chunkUpload(@PathParam("bucket") String bucketName, @QueryParam("upload_id") String uploadId,
+ @HeaderParam("Content-Type") String contentType, @HeaderParam("Content-Length") Long contentLength,
+ @HeaderParam("Content-Range") String contentRange, @PayloadParam("payload") Payload payload);
+
+ /**
+ * Check the status of a resumable upload
+ *
+ * @see https://developers.google.com/storage/docs/json_api/v1/how-tos/upload#resumable
+ *
+ * @param bucketName
+ * Name of the bucket in which the object to be stored
+ * @param uploadId
+ * uploadId returned from initResumableUpload operation
+ * @param contentRange
+ * Range in {bytes StartingByte - Endingbyte/Totalsize } format ex: bytes 0 - 1213/2000
+ *
+ * @return a {@link ResumableUpload}
+ */
+
+ @Named("Object:Upload")
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ @DefaultValue("0")
+ @QueryParams(keys = "uploadType", values = "resumable")
+ @Path("/upload/storage/v1/b/{bucket}/o")
+ @OAuthScopes(STORAGE_FULLCONTROL_SCOPE)
+ @ResponseParser(ParseToResumableUpload.class)
+ ResumableUpload checkStatus(@PathParam("bucket") String bucketName, @QueryParam("upload_id") String uploadId,
+ @HeaderParam("Content-Range") String contentRange);
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/98c28a6b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/handlers/GoogleCloudStorageErrorHandler.java
----------------------------------------------------------------------
diff --git a/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/handlers/GoogleCloudStorageErrorHandler.java b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/handlers/GoogleCloudStorageErrorHandler.java
index f7e24e9..9c6840e 100644
--- a/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/handlers/GoogleCloudStorageErrorHandler.java
+++ b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/handlers/GoogleCloudStorageErrorHandler.java
@@ -46,6 +46,8 @@ public class GoogleCloudStorageErrorHandler implements HttpErrorHandler {
String message412 = "PreconditionFailed: At least one of the pre-conditions you specified did not hold.\n";
switch (response.getStatusCode()) {
+ case 308:
+ return;
case 401:
case 403:
exception = new AuthorizationException(message, exception);
http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/98c28a6b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/handlers/GoogleCloudStorageRedirectRetryHandler.java
----------------------------------------------------------------------
diff --git a/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/handlers/GoogleCloudStorageRedirectRetryHandler.java b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/handlers/GoogleCloudStorageRedirectRetryHandler.java
new file mode 100644
index 0000000..45e6924
--- /dev/null
+++ b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/handlers/GoogleCloudStorageRedirectRetryHandler.java
@@ -0,0 +1,47 @@
+/*
+ * 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.handlers;
+
+import javax.inject.Singleton;
+
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
+import org.jclouds.http.handlers.RedirectionRetryHandler;
+import com.google.inject.Inject;
+
+/**
+ * This will parse and set an appropriate exception on the command object.
+ */
+@Singleton
+public class GoogleCloudStorageRedirectRetryHandler extends RedirectionRetryHandler {
+
+ @Inject
+ protected GoogleCloudStorageRedirectRetryHandler(BackoffLimitedRetryHandler backoffHandler) {
+ super(backoffHandler);
+ }
+
+ @Override
+ public boolean shouldRetryRequest(HttpCommand command, HttpResponse response) {
+ if (response.getStatusCode() == 308) {
+ return false;
+ } else {
+ return super.shouldRetryRequest(command, response);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/98c28a6b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/parser/ParseToResumableUpload.java
----------------------------------------------------------------------
diff --git a/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/parser/ParseToResumableUpload.java b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/parser/ParseToResumableUpload.java
new file mode 100644
index 0000000..fa3c318
--- /dev/null
+++ b/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/parser/ParseToResumableUpload.java
@@ -0,0 +1,66 @@
+/*
+ * 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.parser;
+
+import java.util.regex.Pattern;
+
+import org.jclouds.googlecloudstorage.domain.ResumableUpload;
+import org.jclouds.http.HttpResponse;
+
+import com.google.common.base.Function;
+import com.google.common.base.Splitter;
+
+public class ParseToResumableUpload implements Function<HttpResponse, ResumableUpload> {
+
+ @Override
+ public ResumableUpload apply(HttpResponse response) {
+
+ String contentLength = response.getFirstHeaderOrNull("Content-Length");
+ String sessionUri = response.getFirstHeaderOrNull("Location");
+ String uploadId = null;
+ if (sessionUri != null) {
+ uploadId = getUploadId(sessionUri);
+ }
+ String range = response.getFirstHeaderOrNull("Range");
+ Long upperLimit = null;
+ Long lowerLimit = null;
+ if (range != null) {
+ upperLimit = getUpperLimitFromRange(range);
+ lowerLimit = getLowerLimitFromRange(range);
+ }
+
+ return ResumableUpload.builder().statusCode(response.getStatusCode()).contentLength(contentLength)
+ .uploadId(uploadId).rangeUpperValue(upperLimit).rangeLowerValue(lowerLimit).build();
+ }
+
+ // Return the Id of the Upload
+ private String getUploadId(String sessionUri) {
+ return Splitter.on(Pattern.compile("\\&")).trimResults().omitEmptyStrings().withKeyValueSeparator("=")
+ .split(sessionUri).get("upload_id");
+ }
+
+ private long getUpperLimitFromRange(String range) {
+ String upperLimit = range.split("-")[1];
+ return Long.parseLong(upperLimit);
+ }
+
+ private long getLowerLimitFromRange(String range) {
+ String removeByte = range.split("=")[1];
+ String lowerLimit = removeByte.split("-")[0];
+ return Long.parseLong(lowerLimit);
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds-labs-google/blob/98c28a6b/google-cloud-storage/src/test/java/org/jclouds/googlecloudstorage/features/ResumableUploadApiLiveTest.java
----------------------------------------------------------------------
diff --git a/google-cloud-storage/src/test/java/org/jclouds/googlecloudstorage/features/ResumableUploadApiLiveTest.java b/google-cloud-storage/src/test/java/org/jclouds/googlecloudstorage/features/ResumableUploadApiLiveTest.java
new file mode 100644
index 0000000..a802032
--- /dev/null
+++ b/google-cloud-storage/src/test/java/org/jclouds/googlecloudstorage/features/ResumableUploadApiLiveTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.features;
+
+import static javax.ws.rs.core.Response.Status.OK;
+import static javax.ws.rs.core.Response.Status.CREATED;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNotEquals;
+import static org.assertj.core.api.Assertions.assertThat;
+import java.io.IOException;
+import java.util.UUID;
+
+import org.jclouds.googlecloudstorage.domain.DomainResourceRefferences.ObjectRole;
+import org.jclouds.googlecloudstorage.domain.Bucket;
+import org.jclouds.googlecloudstorage.domain.DomainUtils;
+import org.jclouds.googlecloudstorage.domain.ResumableUpload;
+import org.jclouds.googlecloudstorage.domain.templates.BucketTemplate;
+import org.jclouds.googlecloudstorage.domain.templates.ObjectTemplate;
+import org.jclouds.googlecloudstorage.domain.ObjectAccessControls;
+import org.jclouds.googlecloudstorage.internal.BaseGoogleCloudStorageApiLiveTest;
+import org.jclouds.io.Payloads;
+import org.jclouds.io.payloads.ByteSourcePayload;
+import org.jclouds.utils.TestUtils;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.io.ByteSource;
+
+public class ResumableUploadApiLiveTest extends BaseGoogleCloudStorageApiLiveTest {
+
+ private static final String BUCKET_NAME = "resumableuploadbucket" + UUID.randomUUID();
+ private static final String UPLOAD_OBJECT_NAME = "jcloudslogo.jpg";
+ private static final String CHUNKED_OBJECT_NAME = "jclouds.pdf";
+ private static final int INCOMPLETE = 308;
+ private static final long MIN_CHUNK_SIZE = 256 * 1024; // Min allowed size for a chunk
+
+ private ResumableUploadApi api() {
+ return api.getResumableUploadApi();
+ }
+
+ @BeforeClass
+ private void createBucket() {
+ BucketTemplate template = new BucketTemplate().name(BUCKET_NAME);
+ Bucket bucket = api.getBucketApi().createBucket(PROJECT_NUMBER, template);
+ assertNotNull(bucket);
+ }
+
+ @Test(groups = "live")
+ public void testResumableJpegUpload() throws IOException {
+
+ // Read Object
+ long contentLength = MIN_CHUNK_SIZE * 4;
+ ByteSource byteSource = TestUtils.randomByteSource().slice(0, contentLength);
+
+ // Initialize resumableUpload with metadata. ObjectTemaplete must provide the name
+ ObjectAccessControls oacl = ObjectAccessControls.builder().bucket(BUCKET_NAME).entity("allUsers")
+ .role(ObjectRole.OWNER).build();
+
+ ObjectTemplate template = new ObjectTemplate();
+ template.contentType("image/jpeg").addAcl(oacl).size(contentLength).name(UPLOAD_OBJECT_NAME)
+ .contentLanguage("en").contentDisposition("attachment");
+
+ ResumableUpload initResponse = api().initResumableUpload(BUCKET_NAME, "image/jpeg", contentLength, template);
+
+ assertNotNull(initResponse);
+ assertEquals(initResponse.getStatusCode().intValue(), OK.getStatusCode());
+ assertNotNull(initResponse.getUploadId());
+
+ String uploadId = initResponse.getUploadId();
+
+ // Upload the payload
+ ByteSourcePayload payload = Payloads.newByteSourcePayload(byteSource);
+ ResumableUpload uploadResponse = api().upload(BUCKET_NAME, uploadId, "image/jpeg", byteSource.read().length + "",
+ payload);
+
+ assertEquals(uploadResponse.getStatusCode().intValue(), OK.getStatusCode());
+
+ // CheckStatus
+ ResumableUpload status = api().checkStatus(BUCKET_NAME, uploadId, "bytes */*");
+
+ int code = status.getStatusCode();
+ assertNotEquals(code, INCOMPLETE);
+ }
+
+ @Test(groups = "live")
+ public void testResumableChunkedUpload() throws IOException, InterruptedException {
+
+ // Read Object
+ long contentLength = MIN_CHUNK_SIZE * 3;
+ ByteSource byteSource = TestUtils.randomByteSource().slice(0, contentLength);
+
+ // Initialize resumableUpload with metadata. ObjectTemaplete must provide the name
+ ObjectAccessControls oacl = ObjectAccessControls.builder().bucket(BUCKET_NAME).entity("allUsers")
+ .role(ObjectRole.OWNER).build();
+
+ ObjectTemplate template = new ObjectTemplate();
+ template.contentType("application/pdf").addAcl(oacl).size(contentLength).name(CHUNKED_OBJECT_NAME)
+ .contentLanguage("en").contentDisposition("attachment");
+
+ ResumableUpload initResponse = api().initResumableUpload(BUCKET_NAME, "application/pdf", contentLength, template);
+
+ assertNotNull(initResponse);
+ assertEquals(initResponse.getStatusCode().intValue(), OK.getStatusCode());
+ assertNotNull(initResponse.getUploadId());
+
+ // Get the upload_id for the session
+ String uploadId = initResponse.getUploadId();
+
+ // Check the status first
+ ResumableUpload status = api().checkStatus(BUCKET_NAME, uploadId, "bytes */*");
+ int code = status.getStatusCode();
+ assertEquals(code, INCOMPLETE);
+
+ // Uploads in 2 chunks.
+ long totalSize = byteSource.read().length;
+ long offset = 0;
+ // Size of the first chunk
+ long chunkSize = MIN_CHUNK_SIZE * 2;
+
+ // Uploading First chunk
+ ByteSourcePayload payload = Payloads.newByteSourcePayload(byteSource.slice(offset, chunkSize));
+ long length = byteSource.slice(offset, chunkSize).size();
+ String Content_Range = DomainUtils.generateContentRange(0L, length, totalSize);
+ ResumableUpload uploadResponse = api().chunkUpload(BUCKET_NAME, uploadId, "application/pdf", length,
+ Content_Range, payload);
+
+ int code2 = uploadResponse.getStatusCode();
+ assertEquals(code2, INCOMPLETE);
+
+ // Read uploaded length
+ long lowerValue = uploadResponse.getRangeLowerValue();
+ long uploaded = uploadResponse.getRangeUpperValue();
+
+ assertThat(lowerValue).isEqualTo(0);
+ assertThat(uploaded).isEqualTo(chunkSize - 1); // confirms chunk is totally uploaded
+
+ long resumeLength = totalSize - (uploaded + 1);
+
+ // 2nd chunk
+ ByteSourcePayload payload2 = Payloads.newByteSourcePayload(byteSource.slice(uploaded + 1,
+ byteSource.read().length - uploaded - 1));
+ // Upload the 2nd chunk
+ String Content_Range2 = DomainUtils.generateContentRange(uploaded + 1, totalSize - 1, totalSize);
+ ResumableUpload resumeResponse = api().chunkUpload(BUCKET_NAME, uploadId, "application/pdf", resumeLength,
+ Content_Range2, payload2);
+
+ int code3 = resumeResponse.getStatusCode();
+ assertThat(code3).isIn(OK.getStatusCode(), CREATED.getStatusCode()); // 200 or 201 if upload succeeded
+ }
+
+ @AfterClass
+ private void deleteObjectsandBucket() {
+ api.getObjectApi().deleteObject(BUCKET_NAME, UPLOAD_OBJECT_NAME);
+ api.getObjectApi().deleteObject(BUCKET_NAME, CHUNKED_OBJECT_NAME);
+ api.getBucketApi().deleteBucket(BUCKET_NAME);
+ }
+}