Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import com.google.api.core.InternalExtensionOnly;
import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadRequest;
import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadResponse;
import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadRequest;
import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadResponse;
import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadRequest;
import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadResponse;
import com.google.cloud.storage.multipartupload.model.ListPartsRequest;
Expand Down Expand Up @@ -73,6 +75,17 @@ public abstract CreateMultipartUploadResponse createMultipartUpload(
public abstract AbortMultipartUploadResponse abortMultipartUpload(
AbortMultipartUploadRequest request);

/**
* Completes a multipart upload.
*
* @param request The request object containing the details for completing the multipart upload.
* @return A {@link CompleteMultipartUploadResponse} object containing information about the
* completed upload.
*/
@BetaApi
public abstract CompleteMultipartUploadResponse completeMultipartUpload(
CompleteMultipartUploadRequest request);

/**
* Uploads a part in a multipart upload.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import com.google.cloud.storage.Retrying.Retrier;
import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadRequest;
import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadResponse;
import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadRequest;
import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadResponse;
import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadRequest;
import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadResponse;
import com.google.cloud.storage.multipartupload.model.ListPartsRequest;
Expand Down Expand Up @@ -79,6 +81,16 @@ public AbortMultipartUploadResponse abortMultipartUpload(AbortMultipartUploadReq
Decoder.identity());
}

@Override
@BetaApi
public CompleteMultipartUploadResponse completeMultipartUpload(
CompleteMultipartUploadRequest request) {
return retrier.run(
retryAlgorithmManager.idempotent(),
() -> httpRequestManager.sendCompleteMultipartUploadRequest(uri, request),
Decoder.identity());
}

@Override
@BetaApi
public UploadPartResponse uploadPart(UploadPartRequest request, RequestBody requestBody) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import com.google.cloud.storage.Crc32cValue.Crc32cLengthKnown;
import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadRequest;
import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadResponse;
import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadRequest;
import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadResponse;
import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadRequest;
import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadResponse;
import com.google.cloud.storage.multipartupload.model.ListPartsRequest;
Expand All @@ -44,6 +46,7 @@
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.regex.Matcher;
Expand Down Expand Up @@ -111,11 +114,33 @@ AbortMultipartUploadResponse sendAbortMultipartUploadRequest(
String abortUri = uri.toString() + resourcePath + queryString;

HttpRequest httpRequest = requestFactory.buildDeleteRequest(new GenericUrl(abortUri));
httpRequest.getHeaders().putAll(headerProvider.getHeaders());
httpRequest.setParser(objectParser);
httpRequest.setThrowExceptionOnExecuteError(true);
return httpRequest.execute().parseAs(AbortMultipartUploadResponse.class);
}

CompleteMultipartUploadResponse sendCompleteMultipartUploadRequest(
URI uri, CompleteMultipartUploadRequest request) throws IOException {
String encodedBucket = urlEncode(request.bucket());
String encodedKey = urlEncode(request.key());
String resourcePath = "/" + encodedBucket + "/" + encodedKey;
String queryString = "?uploadId=" + request.uploadId();
String completeUri = uri.toString() + resourcePath + queryString;
byte[] bytes = new XmlMapper().writeValueAsBytes(request.multipartUpload());
HttpRequest httpRequest =
requestFactory.buildPostRequest(
new GenericUrl(completeUri), new ByteArrayContent("application/xml", bytes));
httpRequest.getHeaders().putAll(headerProvider.getHeaders());
@Nullable Crc32cLengthKnown crc32cValue = Hasher.defaultHasher().hash(ByteBuffer.wrap(bytes));
if (crc32cValue != null) {
addChecksumHeader(crc32cValue, httpRequest.getHeaders());
}
httpRequest.setParser(objectParser);
httpRequest.setThrowExceptionOnExecuteError(true);
return httpRequest.execute().parseAs(CompleteMultipartUploadResponse.class);
}

UploadPartResponse sendUploadPartRequest(
URI uri, UploadPartRequest request, RewindableContent rewindableContent) throws IOException {
String encodedBucket = urlEncode(request.bucket());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
* Copyright 2025 Google LLC
*
* Licensed 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 com.google.cloud.storage.multipartupload.model;

import com.google.common.base.MoreObjects;
import java.util.Objects;

/** Represents a request to complete a multipart upload. */
public final class CompleteMultipartUploadRequest {

private final String bucket;
private final String key;
private final String uploadId;
private final CompletedMultipartUpload multipartUpload;

private CompleteMultipartUploadRequest(Builder builder) {
this.bucket = builder.bucket;
this.key = builder.key;
this.uploadId = builder.uploadId;
this.multipartUpload = builder.multipartUpload;
}

/**
* Returns the bucket name.
*
* @return The bucket name.
*/
public String bucket() {
return bucket;
}

/**
* Returns the object name.
*
* @return The object name.
*/
public String key() {
return key;
}

/**
* Returns the upload ID of the multipart upload.
*
* @return The upload ID.
*/
public String uploadId() {
return uploadId;
}

/**
* Returns the {@link CompletedMultipartUpload} payload for this request.
*
* @return The {@link CompletedMultipartUpload} payload.
*/
public CompletedMultipartUpload multipartUpload() {
return multipartUpload;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CompleteMultipartUploadRequest)) {
return false;
}
CompleteMultipartUploadRequest that = (CompleteMultipartUploadRequest) o;
return Objects.equals(bucket, that.bucket)
&& Objects.equals(key, that.key)
&& Objects.equals(uploadId, that.uploadId)
&& Objects.equals(multipartUpload, that.multipartUpload);
}

@Override
public int hashCode() {
return Objects.hash(bucket, key, uploadId, multipartUpload);
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("bucket", bucket)
.add("key", key)
.add("uploadId", uploadId)
.add("completedMultipartUpload", multipartUpload)
.toString();
}

/**
* Creates a new builder for {@link CompleteMultipartUploadRequest}.
*
* @return A new builder.
*/
public static Builder builder() {
return new Builder();
}

/** Builder for {@link CompleteMultipartUploadRequest}. */
public static class Builder {
private String bucket;
private String key;
private String uploadId;
private CompletedMultipartUpload multipartUpload;

private Builder() {}

/**
* Sets the bucket name.
*
* @param bucket The bucket name.
* @return This builder.
*/
public Builder bucket(String bucket) {
this.bucket = bucket;
return this;
}

/**
* Sets the object name.
*
* @param key The object name.
* @return This builder.
*/
public Builder key(String key) {
this.key = key;
return this;
}

/**
* Sets the upload ID of the multipart upload.
*
* @param uploadId The upload ID.
* @return This builder.
*/
public Builder uploadId(String uploadId) {
this.uploadId = uploadId;
return this;
}

/**
* Sets the {@link CompletedMultipartUpload} payload for this request.
*
* @param completedMultipartUpload The {@link CompletedMultipartUpload} payload.
* @return This builder.
*/
public Builder multipartUpload(CompletedMultipartUpload completedMultipartUpload) {
this.multipartUpload = completedMultipartUpload;
return this;
}

/**
* Builds the {@link CompleteMultipartUploadRequest} object.
*
* @return The new {@link CompleteMultipartUploadRequest} object.
*/
public CompleteMultipartUploadRequest build() {
return new CompleteMultipartUploadRequest(this);
}
}
}
Loading
Loading