diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ChecksumResponseParser.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ChecksumResponseParser.java index 4d32cb42e..dd430a2f5 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ChecksumResponseParser.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ChecksumResponseParser.java @@ -22,19 +22,21 @@ import java.io.IOException; import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.stream.Collectors; -/** A utility class to parse {@link HttpResponse} and create a {@link UploadPartResponse}. */ +/** A utility class to parse checksums from an {@link HttpResponse}. */ final class ChecksumResponseParser { + private static final String X_GOOG_HASH = "x-goog-hash"; + private ChecksumResponseParser() {} static UploadPartResponse parseUploadResponse(HttpResponse response) { String eTag = response.getHeaders().getETag(); Map hashes = extractHashesFromHeader(response); - return UploadPartResponse.builder().eTag(eTag).md5(hashes.get("md5")).build(); + return UploadPartResponse.builder().eTag(eTag).md5(hashes.get("md5")).crc32c(hashes.get("crc32c")).build(); } static CompleteMultipartUploadResponse parseCompleteResponse(HttpResponse response) @@ -52,14 +54,18 @@ static CompleteMultipartUploadResponse parseCompleteResponse(HttpResponse respon } static Map extractHashesFromHeader(HttpResponse response) { - return Optional.ofNullable(response.getHeaders().getFirstHeaderStringValue("x-goog-hash")) - .map( - h -> - Arrays.stream(h.split(",")) - .map(s -> s.trim().split("=", 2)) - .filter(a -> a.length == 2) - .filter(a -> "crc32c".equalsIgnoreCase(a[0]) || "md5".equalsIgnoreCase(a[0])) - .collect(Collectors.toMap(a -> a[0].toLowerCase(), a -> a[1], (v1, v2) -> v1))) - .orElse(Collections.emptyMap()); + List hashHeaders = response.getHeaders().getHeaderStringValues(X_GOOG_HASH); + if (hashHeaders == null || hashHeaders.isEmpty()) { + return Collections.emptyMap(); + } + + return hashHeaders.stream() + .flatMap(h -> Arrays.stream(h.split(","))) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .map(s -> s.split("=", 2)) + .filter(a -> a.length == 2) + .filter(a -> "crc32c".equalsIgnoreCase(a[0]) || "md5".equalsIgnoreCase(a[0])) + .collect(Collectors.toMap(a -> a[0].toLowerCase(), a -> a[1], (v1, v2) -> v1)); } } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClient.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClient.java index 09a3af01c..3044c6765 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClient.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClient.java @@ -24,6 +24,8 @@ 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.ListMultipartUploadsRequest; +import com.google.cloud.storage.multipartupload.model.ListMultipartUploadsResponse; import com.google.cloud.storage.multipartupload.model.ListPartsRequest; import com.google.cloud.storage.multipartupload.model.ListPartsResponse; import com.google.cloud.storage.multipartupload.model.UploadPartRequest; @@ -100,6 +102,17 @@ public abstract CompleteMultipartUploadResponse completeMultipartUpload( @BetaApi public abstract UploadPartResponse uploadPart(UploadPartRequest request, RequestBody requestBody); + /** + * Lists all multipart uploads in a bucket. + * + * @param request The request object containing the details for listing the multipart uploads. + * @return A {@link ListMultipartUploadsResponse} object containing the list of multipart uploads. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public abstract ListMultipartUploadsResponse listMultipartUploads( + ListMultipartUploadsRequest request); + /** * Creates a new instance of {@link MultipartUploadClient}. * @@ -116,4 +129,4 @@ public static MultipartUploadClient create(MultipartUploadSettings config) { MultipartUploadHttpRequestManager.createFrom(options), options.getRetryAlgorithmManager()); } -} +} \ No newline at end of file diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClientImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClientImpl.java index 00b43c6ba..5c19f8832 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClientImpl.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClientImpl.java @@ -23,6 +23,8 @@ 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.ListMultipartUploadsRequest; +import com.google.cloud.storage.multipartupload.model.ListMultipartUploadsResponse; import com.google.cloud.storage.multipartupload.model.ListPartsRequest; import com.google.cloud.storage.multipartupload.model.ListPartsResponse; import com.google.cloud.storage.multipartupload.model.UploadPartRequest; @@ -100,4 +102,12 @@ public UploadPartResponse uploadPart(UploadPartRequest request, RequestBody requ }, Decoder.identity()); } + + @Override + public ListMultipartUploadsResponse listMultipartUploads(ListMultipartUploadsRequest request) { + return retrier.run( + retryAlgorithmManager.idempotent(), + () -> httpRequestManager.sendListMultipartUploadsRequest(uri, request), + Decoder.identity()); + } } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadHttpRequestManager.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadHttpRequestManager.java index be3a06730..b5f61e1f7 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadHttpRequestManager.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadHttpRequestManager.java @@ -36,6 +36,8 @@ 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.ListMultipartUploadsRequest; +import com.google.cloud.storage.multipartupload.model.ListMultipartUploadsResponse; import com.google.cloud.storage.multipartupload.model.ListPartsRequest; import com.google.cloud.storage.multipartupload.model.ListPartsResponse; import com.google.cloud.storage.multipartupload.model.UploadPartRequest; @@ -111,6 +113,43 @@ ListPartsResponse sendListPartsRequest(URI uri, ListPartsRequest request) throws return httpRequest.execute().parseAs(ListPartsResponse.class); } + ListMultipartUploadsResponse sendListMultipartUploadsRequest( + URI uri, ListMultipartUploadsRequest request) throws IOException { + + ImmutableMap.Builder params = + ImmutableMap.builder() + .put("bucket", request.bucket()); + if(request.delimiter() != null){ + params.put("delimiter", request.delimiter()); + } + if(request.encodingType() != null){ + params.put("encoding-type", request.encodingType()); + } + if(request.keyMarker() != null){ + params.put("key-marker", request.keyMarker()); + } + if(request.maxUploads() != null){ + params.put("max-uploads", request.maxUploads()); + } + if(request.prefix() != null){ + params.put("prefix", request.prefix()); + } + if(request.uploadIdMarker() != null){ + params.put("upload-id-marker", request.uploadIdMarker()); + } + String listUri = + UriTemplate.expand( + uri.toString() + "{bucket}?uploads{delimiter,encoding-type,key-marker,max-uploads,prefix,upload-id-marker}", + params, + false); + System.out.println(listUri); + HttpRequest httpRequest = requestFactory.buildGetRequest(new GenericUrl(listUri)); + httpRequest.getHeaders().putAll(headerProvider.getHeaders()); + httpRequest.setParser(objectParser); + httpRequest.setThrowExceptionOnExecuteError(true); + return httpRequest.execute().parseAs(ListMultipartUploadsResponse.class); + } + AbortMultipartUploadResponse sendAbortMultipartUploadRequest( URI uri, AbortMultipartUploadRequest request) throws IOException { @@ -248,7 +287,7 @@ private static String urlEncode(String value) { */ private static String formatName(String name) { // Only lowercase letters, digits, and "-" are allowed - return name.toLowerCase().replaceAll("[^\\w\\d\\-]", "-"); + return name.toLowerCase().replaceAll("[^\\w-]", "-"); } private static String formatSemver(String version) { diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/ListMultipartUploadsRequest.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/ListMultipartUploadsRequest.java new file mode 100644 index 000000000..51c814507 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/ListMultipartUploadsRequest.java @@ -0,0 +1,304 @@ +/* + * 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.api.core.BetaApi; +import java.util.Objects; + +/** + * A request to list all multipart uploads in a bucket. + * + * @see Listing + * multipart uploads + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ +@BetaApi +public final class ListMultipartUploadsRequest { + + private final String bucket; + private final String delimiter; + private final String encodingType; + private final String keyMarker; + private final Integer maxUploads; + private final String prefix; + private final String uploadIdMarker; + + private ListMultipartUploadsRequest( + String bucket, + String delimiter, + String encodingType, + String keyMarker, + Integer maxUploads, + String prefix, + String uploadIdMarker) { + this.bucket = bucket; + this.delimiter = delimiter; + this.encodingType = encodingType; + this.keyMarker = keyMarker; + this.maxUploads = maxUploads; + this.prefix = prefix; + this.uploadIdMarker = uploadIdMarker; + } + + /** + * The bucket to list multipart uploads from. + * + * @return The bucket name. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String bucket() { + return bucket; + } + + /** + * Character used to group keys. + * + * @return The delimiter. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String delimiter() { + return delimiter; + } + + /** + * The encoding type used by Cloud Storage to encode object names in the response. + * + * @return The encoding type. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String encodingType() { + return encodingType; + } + + /** + * Together with {@code upload-id-marker}, specifies the multipart upload after which listing + * should begin. + * + * @return The key marker. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String keyMarker() { + return keyMarker; + } + + /** + * The maximum number of multipart uploads to return. + * + * @return The maximum number of uploads. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Integer maxUploads() { + return maxUploads; + } + + /** + * Filters results to multipart uploads whose keys begin with this prefix. + * + * @return The prefix. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String prefix() { + return prefix; + } + + /** + * Together with {@code key-marker}, specifies the multipart upload after which listing should + * begin. + * + * @return The upload ID marker. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String uploadIdMarker() { + return uploadIdMarker; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ListMultipartUploadsRequest that = (ListMultipartUploadsRequest) o; + return Objects.equals(bucket, that.bucket) + && Objects.equals(delimiter, that.delimiter) + && Objects.equals(encodingType, that.encodingType) + && Objects.equals(keyMarker, that.keyMarker) + && Objects.equals(maxUploads, that.maxUploads) + && Objects.equals(prefix, that.prefix) + && Objects.equals(uploadIdMarker, that.uploadIdMarker); + } + + @Override + public int hashCode() { + return Objects.hash(bucket, delimiter, encodingType, keyMarker, maxUploads, prefix, uploadIdMarker); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("ListMultipartUploadsRequest{"); + sb.append("bucket='").append(bucket).append("'"); + sb.append(", delimiter='").append(delimiter).append("'"); + sb.append(", encodingType='").append(encodingType).append(","); + sb.append(", keyMarker='").append(keyMarker).append(","); + sb.append(", maxUploads=").append(maxUploads); + sb.append(", prefix='").append(prefix).append(","); + sb.append(", uploadIdMarker='").append(uploadIdMarker).append(","); + sb.append('}'); + return sb.toString(); + } + + /** + * Returns a new builder for this request. + * + * @return A new builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for {@link ListMultipartUploadsRequest}. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static final class Builder { + private String bucket; + private String delimiter; + private String encodingType; + private String keyMarker; + private Integer maxUploads; + private String prefix; + private String uploadIdMarker; + + private Builder() {} + + /** + * Sets the bucket to list multipart uploads from. + * + * @param bucket The bucket name. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder bucket(String bucket) { + this.bucket = bucket; + return this; + } + + /** + * Sets the delimiter used to group keys. + * + * @param delimiter The delimiter. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder delimiter(String delimiter) { + this.delimiter = delimiter; + return this; + } + + /** + * Sets the encoding type used by Cloud Storage to encode object names in the response. + * + * @param encodingType The encoding type. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder encodingType(String encodingType) { + this.encodingType = encodingType; + return this; + } + + /** + * Sets the key marker. + * + * @param keyMarker The key marker. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder keyMarker(String keyMarker) { + this.keyMarker = keyMarker; + return this; + } + + /** + * Sets the maximum number of multipart uploads to return. + * + * @param maxUploads The maximum number of uploads. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder maxUploads(Integer maxUploads) { + this.maxUploads = maxUploads; + return this; + } + + /** + * Sets the prefix to filter results. + * + * @param prefix The prefix. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder prefix(String prefix) { + this.prefix = prefix; + return this; + } + + /** + * Sets the upload ID marker. + * + * @param uploadIdMarker The upload ID marker. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder uploadIdMarker(String uploadIdMarker) { + this.uploadIdMarker = uploadIdMarker; + return this; + } + + /** + * Builds the request. + * + * @return The built request. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public ListMultipartUploadsRequest build() { + return new ListMultipartUploadsRequest( + bucket, delimiter, encodingType, keyMarker, maxUploads, prefix, uploadIdMarker); + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/ListMultipartUploadsResponse.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/ListMultipartUploadsResponse.java new file mode 100644 index 000000000..9e151c5df --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/ListMultipartUploadsResponse.java @@ -0,0 +1,477 @@ +/* + * 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.api.core.BetaApi; +import com.google.common.collect.ImmutableList; +import java.util.Objects; + +/** + * A response from listing all multipart uploads in a bucket. + * + * @see Listing + * multipart uploads + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ +@BetaApi +public final class ListMultipartUploadsResponse { + + private ImmutableList uploads; + private String bucket; + private String delimiter; + private String encodingType; + private String keyMarker; + private String uploadIdMarker; + private String nextKeyMarker; + private String nextUploadIdMarker; + private int maxUploads; + private String prefix; + private boolean isTruncated; + private ImmutableList commonPrefixes; + + private ListMultipartUploadsResponse( + ImmutableList uploads, + String bucket, + String delimiter, + String encodingType, + String keyMarker, + String uploadIdMarker, + String nextKeyMarker, + String nextUploadIdMarker, + int maxUploads, + String prefix, + boolean isTruncated, + ImmutableList commonPrefixes) { + this.uploads = uploads; + this.bucket = bucket; + this.delimiter = delimiter; + this.encodingType = encodingType; + this.keyMarker = keyMarker; + this.uploadIdMarker = uploadIdMarker; + this.nextKeyMarker = nextKeyMarker; + this.nextUploadIdMarker = nextUploadIdMarker; + this.maxUploads = maxUploads; + this.prefix = prefix; + this.isTruncated = isTruncated; + this.commonPrefixes = commonPrefixes; + } + + private ListMultipartUploadsResponse() {} + + /** + * The list of multipart uploads. + * + * @return The list of multipart uploads. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public ImmutableList getUploads() { + return uploads; + } + + /** + * The bucket that contains the multipart uploads. + * + * @return The bucket name. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String getBucket() { + return bucket; + } + + /** + * The delimiter applied to the request. + * + * @return The delimiter applied to the request. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String getDelimiter() { + return delimiter; + } + + /** + * The encoding type used by Cloud Storage to encode object names in the response. + * + * @return The encoding type. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String getEncodingType() { + return encodingType; + } + + /** + * The key at or after which the listing began. + * + * @return The key marker. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String getKeyMarker() { + return keyMarker; + } + + /** + * The upload ID at or after which the listing began. + * + * @return The upload ID marker. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String getUploadIdMarker() { + return uploadIdMarker; + } + + /** + * The key after which listing should begin. + * + * @return The key after which listing should begin. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String getNextKeyMarker() { + return nextKeyMarker; + } + + /** + * The upload ID after which listing should begin. + * + * @return The upload ID after which listing should begin. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String getNextUploadIdMarker() { + return nextUploadIdMarker; + } + + /** + * The maximum number of uploads to return. + * + * @return The maximum number of uploads. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public int getMaxUploads() { + return maxUploads; + } + + /** + * The prefix applied to the request. + * + * @return The prefix applied to the request. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String getPrefix() { + return prefix; + } + + /** + * A flag indicating whether or not the returned results are truncated. + * + * @return A flag indicating whether or not the returned results are truncated. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public boolean isTruncated() { + return isTruncated; + } + + /** + * If you specify a delimiter in the request, this element is returned. + * + * @return The common prefixes. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public ImmutableList getCommonPrefixes() { + return commonPrefixes; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ListMultipartUploadsResponse that = (ListMultipartUploadsResponse) o; + return isTruncated == that.isTruncated + && maxUploads == that.maxUploads + && Objects.equals(uploads, that.uploads) + && Objects.equals(bucket, that.bucket) + && Objects.equals(delimiter, that.delimiter) + && Objects.equals(encodingType, that.encodingType) + && Objects.equals(keyMarker, that.keyMarker) + && Objects.equals(uploadIdMarker, that.uploadIdMarker) + && Objects.equals(nextKeyMarker, that.nextKeyMarker) + && Objects.equals(nextUploadIdMarker, that.nextUploadIdMarker) + && Objects.equals(prefix, that.prefix) + && Objects.equals(commonPrefixes, that.commonPrefixes); + } + + @Override + public int hashCode() { + return Objects.hash( + uploads, + bucket, + delimiter, + encodingType, + keyMarker, + uploadIdMarker, + nextKeyMarker, + nextUploadIdMarker, + maxUploads, + prefix, + isTruncated, + commonPrefixes); + } + + @Override + public String toString() { + return "ListMultipartUploadsResponse{" + + "uploads=" + uploads + + ", bucket='" + bucket + "'" + + ", delimiter='" + delimiter + "'" + + ", encodingType='" + encodingType + "'" + + ", keyMarker='" + keyMarker + "'" + + ", uploadIdMarker='" + uploadIdMarker + "'" + + ", nextKeyMarker='" + nextKeyMarker + "'" + + ", nextUploadIdMarker='" + nextUploadIdMarker + "'" + + ", maxUploads=" + maxUploads + + ", prefix='" + prefix + "'" + + ", isTruncated=" + isTruncated + + ", commonPrefixes=" + commonPrefixes + + '}'; + } + + /** + * Returns a new builder for this response. + * + * @return A new builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for {@link ListMultipartUploadsResponse}. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static final class Builder { + private ImmutableList uploads; + private String bucket; + private String delimiter; + private String encodingType; + private String keyMarker; + private String uploadIdMarker; + private String nextKeyMarker; + private String nextUploadIdMarker; + private int maxUploads; + private String prefix; + private boolean isTruncated; + private ImmutableList commonPrefixes; + + private Builder() {} + + /** + * Sets the list of multipart uploads. + * + * @param uploads The list of multipart uploads. + * @return This builder. + * @since 2.60.0 This new api is in preview. + */ + @BetaApi + public Builder setUploads(ImmutableList uploads) { + this.uploads = uploads; + return this; + } + + /** + * Sets the bucket that contains the multipart uploads. + * + * @param bucket The bucket name. + * @return This builder. + * @since 2.60.0 This new api is in preview. + */ + @BetaApi + public Builder setBucket(String bucket) { + this.bucket = bucket; + return this; + } + + /** + * Sets the delimiter applied to the request. + * + * @param delimiter The delimiter applied to the request. + * @return This builder. + * @since 2.60.0 This new api is in preview. + */ + @BetaApi + public Builder setDelimiter(String delimiter) { + this.delimiter = delimiter; + return this; + } + + /** + * Sets the encoding type used by Cloud Storage to encode object names in the response. + * + * @param encodingType The encoding type. + * @return This builder. + * @since 2.60.0 This new api is in preview. + */ + @BetaApi + public Builder setEncodingType(String encodingType) { + this.encodingType = encodingType; + return this; + } + + /** + * Sets the key at or after which the listing began. + * + * @param keyMarker The key marker. + * @return This builder. + * @since 2.60.0 This new api is in preview. + */ + @BetaApi + public Builder setKeyMarker(String keyMarker) { + this.keyMarker = keyMarker; + return this; + } + + /** + * Sets the upload ID at or after which the listing began. + * + * @param uploadIdMarker The upload ID marker. + * @return This builder. + * @since 2.60.0 This new api is in preview. + */ + @BetaApi + public Builder setUploadIdMarker(String uploadIdMarker) { + this.uploadIdMarker = uploadIdMarker; + return this; + } + + /** + * Sets the key after which listing should begin. + * + * @param nextKeyMarker The key after which listing should begin. + * @return This builder. + * @since 2.60.0 This new api is in preview. + */ + @BetaApi + public Builder setNextKeyMarker(String nextKeyMarker) { + this.nextKeyMarker = nextKeyMarker; + return this; + } + + /** + * Sets the upload ID after which listing should begin. + * + * @param nextUploadIdMarker The upload ID after which listing should begin. + * @return This builder. + * @since 2.60.0 This new api is in preview. + */ + @BetaApi + public Builder setNextUploadIdMarker(String nextUploadIdMarker) { + this.nextUploadIdMarker = nextUploadIdMarker; + return this; + } + + /** + * Sets the maximum number of uploads to return. + * + * @param maxUploads The maximum number of uploads. + * @return This builder. + * @since 2.60.0 This new api is in preview. + */ + @BetaApi + public Builder setMaxUploads(int maxUploads) { + this.maxUploads = maxUploads; + return this; + } + + /** + * Sets the prefix applied to the request. + * + * @param prefix The prefix applied to the request. + * @return This builder. + * @since 2.60.0 This new api is in preview. + */ + @BetaApi + public Builder setPrefix(String prefix) { + this.prefix = prefix; + return this; + } + + /** + * Sets the flag indicating whether or not the returned results are truncated. + * + * @param isTruncated The flag indicating whether or not the returned results are truncated. + * @return This builder. + * @since 2.60.0 This new api is in preview. + */ + @BetaApi + public Builder setTruncated(boolean isTruncated) { + this.isTruncated = isTruncated; + return this; + } + + /** + * If you specify a delimiter in the request, this element is returned. + * + * @param commonPrefixes The common prefixes. + * @return This builder. + * @since 2.60.0 This new api is in preview. + */ + @BetaApi + public Builder setCommonPrefixes(ImmutableList commonPrefixes) { + this.commonPrefixes = commonPrefixes; + return this; + } + + /** + * Builds the response. + * + * @return The built response. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public ListMultipartUploadsResponse build() { + return new ListMultipartUploadsResponse( + uploads, + bucket, + delimiter, + encodingType, + keyMarker, + uploadIdMarker, + nextKeyMarker, + nextUploadIdMarker, + maxUploads, + prefix, + isTruncated, + commonPrefixes); + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/MultipartUpload.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/MultipartUpload.java new file mode 100644 index 000000000..a32619f9a --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/MultipartUpload.java @@ -0,0 +1,207 @@ +/* + * 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.api.core.BetaApi; +import com.google.cloud.storage.StorageClass; +import java.time.OffsetDateTime; +import java.util.Objects; + +/** + * Represents a multipart upload that is in progress. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ +@BetaApi +public final class MultipartUpload { + + private final String key; + private final String uploadId; + private final StorageClass storageClass; + private final OffsetDateTime initiated; + + private MultipartUpload( + String key, String uploadId, StorageClass storageClass, OffsetDateTime initiated) { + this.key = key; + this.uploadId = uploadId; + this.storageClass = storageClass; + this.initiated = initiated; + } + + /** + * The object name for which the multipart upload was initiated. + * + * @return The object name. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String getKey() { + return key; + } + + /** + * The ID of the multipart upload. + * + * @return The upload ID. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String getUploadId() { + return uploadId; + } + + /** + * The storage class of the object. + * + * @return The storage class. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public StorageClass getStorageClass() { + return storageClass; + } + + /** + * The date and time at which the multipart upload was initiated. + * + * @return The initiation date and time. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public OffsetDateTime getInitiated() { + return initiated; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MultipartUpload that = (MultipartUpload) o; + return Objects.equals(key, that.key) + && Objects.equals(uploadId, that.uploadId) + && Objects.equals(storageClass, that.storageClass) + && Objects.equals(initiated, that.initiated); + } + + @Override + public int hashCode() { + return Objects.hash(key, uploadId, storageClass, initiated); + } + + @Override + public String toString() { + return "MultipartUpload{" + + "key='" + key + '\'' + + ", uploadId='" + uploadId + '\'' + + ", storageClass=" + storageClass + + ", initiated=" + initiated + + "}"; + } + + /** + * Returns a new builder for this multipart upload. + * + * @return A new builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static Builder newBuilder() { + return new Builder(); + } + + /** + * A builder for {@link MultipartUpload}. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static final class Builder { + private String key; + private String uploadId; + private StorageClass storageClass; + private OffsetDateTime initiated; + + private Builder() {} + + /** + * Sets the object name for which the multipart upload was initiated. + * + * @param key The object name. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder setKey(String key) { + this.key = key; + return this; + } + + /** + * Sets the ID of the multipart upload. + * + * @param uploadId The upload ID. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder setUploadId(String uploadId) { + this.uploadId = uploadId; + return this; + } + + /** + * Sets the storage class of the object. + * + * @param storageClass The storage class. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder setStorageClass(StorageClass storageClass) { + this.storageClass = storageClass; + return this; + } + + /** + * Sets the date and time at which the multipart upload was initiated. + * + * @param initiated The initiation date and time. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder setInitiated(OffsetDateTime initiated) { + this.initiated = initiated; + return this; + } + + /** + * Builds the multipart upload. + * + * @return The built multipart upload. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public MultipartUpload build() { + return new MultipartUpload(key, uploadId, storageClass, initiated); + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/UploadPartResponse.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/UploadPartResponse.java index 30dc72b0f..3ea9c0d85 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/UploadPartResponse.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/UploadPartResponse.java @@ -31,10 +31,12 @@ public final class UploadPartResponse { private final String eTag; private final String md5; + private final String crc32c; private UploadPartResponse(Builder builder) { this.eTag = builder.etag; this.md5 = builder.md5; + this.crc32c = builder.crc32c; } /** @@ -59,6 +61,17 @@ public String md5() { return md5; } + /** + * Returns the CRC32C checksum of the uploaded part. + * + * @return The CRC32C checksum. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String crc32c() { + return crc32c; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -68,17 +81,23 @@ public boolean equals(Object o) { return false; } UploadPartResponse that = (UploadPartResponse) o; - return Objects.equals(eTag, that.eTag) && Objects.equals(md5, that.md5); + return Objects.equals(eTag, that.eTag) + && Objects.equals(md5, that.md5) + && Objects.equals(crc32c, that.crc32c); } @Override public int hashCode() { - return Objects.hash(eTag, md5); + return Objects.hash(eTag, md5, crc32c); } @Override public String toString() { - return MoreObjects.toStringHelper(this).add("etag", eTag).add("md5", md5).toString(); + return MoreObjects.toStringHelper(this) + .add("etag", eTag) + .add("md5", md5) + .add("crc32c", crc32c) + .toString(); } /** @@ -101,6 +120,7 @@ public static Builder builder() { public static class Builder { private String etag; private String md5; + private String crc32c; private Builder() {} @@ -130,6 +150,19 @@ public Builder md5(String md5) { return this; } + /** + * Sets the CRC32C checksum for the uploaded part. + * + * @param crc32c The CRC32C checksum. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder crc32c(String crc32c) { + this.crc32c = crc32c; + return this; + } + /** * Builds the {@code UploadPartResponse} object. *