Skip to content

Commit f6b31d3

Browse files
author
Dhriti Chopra
committed
chore: Adding fixes and tests for ListMultipartUpload
1 parent fa539c0 commit f6b31d3

File tree

4 files changed

+168
-41
lines changed

4 files changed

+168
-41
lines changed

google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadHttpRequestManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ ListMultipartUploadsResponse sendListMultipartUploadsRequest(
140140
String listUri =
141141
UriTemplate.expand(
142142
uri.toString() + "{bucket}?uploads{delimiter,encoding-type,key-marker,max-uploads,prefix,upload-id-marker}",
143-
params,
143+
params.build(),
144144
false);
145145
System.out.println(listUri);
146146
HttpRequest httpRequest = requestFactory.buildGetRequest(new GenericUrl(listUri));

google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/ListMultipartUploadsResponse.java

Lines changed: 67 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
77
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
8+
* http://www.apache.org/licenses/LICENSE-2.0
99
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -16,35 +16,69 @@
1616

1717
package com.google.cloud.storage.multipartupload.model;
1818

19+
import com.fasterxml.jackson.annotation.JsonAlias;
20+
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
21+
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
1922
import com.google.api.core.BetaApi;
2023
import com.google.common.collect.ImmutableList;
24+
import java.util.ArrayList;
25+
import java.util.List;
2126
import java.util.Objects;
2227

2328
/**
2429
* A response from listing all multipart uploads in a bucket.
2530
*
2631
* @see <a href="https://cloud.google.com/storage/docs/multipart-uploads#listing-uploads">Listing
27-
* multipart uploads</a>
28-
* @since 2.60.0 This new api is in preview and is subject to breaking changes.
32+
* multipart uploads</a>
33+
* @since 2.60.1 This new api is in preview and is subject to breaking changes.
2934
*/
3035
@BetaApi
3136
public final class ListMultipartUploadsResponse {
3237

33-
private ImmutableList<MultipartUpload> uploads;
38+
@JacksonXmlElementWrapper(useWrapping = false)
39+
@JacksonXmlProperty(localName = "Upload")
40+
private List<MultipartUpload> uploads;
41+
42+
@JacksonXmlProperty(localName = "Bucket")
3443
private String bucket;
44+
45+
@JacksonXmlProperty(localName = "Delimiter")
3546
private String delimiter;
47+
48+
@JacksonXmlProperty(localName = "EncodingType")
3649
private String encodingType;
50+
51+
@JacksonXmlProperty(localName = "KeyMarker")
3752
private String keyMarker;
53+
54+
@JacksonXmlProperty(localName = "UploadIdMarker")
3855
private String uploadIdMarker;
56+
57+
@JacksonXmlProperty(localName = "NextKeyMarker")
3958
private String nextKeyMarker;
59+
60+
@JacksonXmlProperty(localName = "NextUploadIdMarker")
4061
private String nextUploadIdMarker;
62+
63+
@JacksonXmlProperty(localName = "MaxUploads")
4164
private int maxUploads;
65+
66+
@JacksonXmlProperty(localName = "Prefix")
4267
private String prefix;
68+
69+
@JsonAlias("truncated")
70+
@JacksonXmlProperty(localName = "IsTruncated")
4371
private boolean isTruncated;
44-
private ImmutableList<String> commonPrefixes;
72+
73+
@JacksonXmlElementWrapper(useWrapping = false)
74+
@JacksonXmlProperty(localName = "CommonPrefixes")
75+
private List<CommonPrefixHelper> commonPrefixes;
76+
77+
// Jackson requires a no-arg constructor
78+
private ListMultipartUploadsResponse() {}
4579

4680
private ListMultipartUploadsResponse(
47-
ImmutableList<MultipartUpload> uploads,
81+
List<MultipartUpload> uploads,
4882
String bucket,
4983
String delimiter,
5084
String encodingType,
@@ -55,7 +89,7 @@ private ListMultipartUploadsResponse(
5589
int maxUploads,
5690
String prefix,
5791
boolean isTruncated,
58-
ImmutableList<String> commonPrefixes) {
92+
List<String> commonPrefixes) {
5993
this.uploads = uploads;
6094
this.bucket = bucket;
6195
this.delimiter = delimiter;
@@ -67,28 +101,21 @@ private ListMultipartUploadsResponse(
67101
this.maxUploads = maxUploads;
68102
this.prefix = prefix;
69103
this.isTruncated = isTruncated;
70-
this.commonPrefixes = commonPrefixes;
104+
if (commonPrefixes != null) {
105+
this.commonPrefixes = new ArrayList<>();
106+
for (String p : commonPrefixes) {
107+
CommonPrefixHelper h = new CommonPrefixHelper();
108+
h.prefix = p;
109+
this.commonPrefixes.add(h);
110+
}
111+
}
71112
}
72113

73-
private ListMultipartUploadsResponse() {}
74-
75-
/**
76-
* The list of multipart uploads.
77-
*
78-
* @return The list of multipart uploads.
79-
* @since 2.60.0 This new api is in preview and is subject to breaking changes.
80-
*/
81114
@BetaApi
82115
public ImmutableList<MultipartUpload> getUploads() {
83-
return uploads;
116+
return uploads == null ? ImmutableList.of() : ImmutableList.copyOf(uploads);
84117
}
85118

86-
/**
87-
* The bucket that contains the multipart uploads.
88-
*
89-
* @return The bucket name.
90-
* @since 2.60.0 This new api is in preview and is subject to breaking changes.
91-
*/
92119
@BetaApi
93120
public String getBucket() {
94121
return bucket;
@@ -201,7 +228,12 @@ public boolean isTruncated() {
201228
*/
202229
@BetaApi
203230
public ImmutableList<String> getCommonPrefixes() {
204-
return commonPrefixes;
231+
if (commonPrefixes == null) {
232+
return ImmutableList.of();
233+
}
234+
return commonPrefixes.stream()
235+
.map(h -> h.prefix)
236+
.collect(ImmutableList.toImmutableList());
205237
}
206238

207239
@Override
@@ -215,7 +247,7 @@ public boolean equals(Object o) {
215247
ListMultipartUploadsResponse that = (ListMultipartUploadsResponse) o;
216248
return isTruncated == that.isTruncated
217249
&& maxUploads == that.maxUploads
218-
&& Objects.equals(uploads, that.uploads)
250+
&& Objects.equals(getUploads(), that.getUploads())
219251
&& Objects.equals(bucket, that.bucket)
220252
&& Objects.equals(delimiter, that.delimiter)
221253
&& Objects.equals(encodingType, that.encodingType)
@@ -224,13 +256,13 @@ public boolean equals(Object o) {
224256
&& Objects.equals(nextKeyMarker, that.nextKeyMarker)
225257
&& Objects.equals(nextUploadIdMarker, that.nextUploadIdMarker)
226258
&& Objects.equals(prefix, that.prefix)
227-
&& Objects.equals(commonPrefixes, that.commonPrefixes);
259+
&& Objects.equals(getCommonPrefixes(), that.getCommonPrefixes());
228260
}
229261

230262
@Override
231263
public int hashCode() {
232264
return Objects.hash(
233-
uploads,
265+
getUploads(),
234266
bucket,
235267
delimiter,
236268
encodingType,
@@ -241,13 +273,13 @@ public int hashCode() {
241273
maxUploads,
242274
prefix,
243275
isTruncated,
244-
commonPrefixes);
276+
getCommonPrefixes());
245277
}
246278

247279
@Override
248280
public String toString() {
249281
return "ListMultipartUploadsResponse{" +
250-
"uploads=" + uploads +
282+
"uploads=" + getUploads() +
251283
", bucket='" + bucket + "'" +
252284
", delimiter='" + delimiter + "'" +
253285
", encodingType='" + encodingType + "'" +
@@ -258,7 +290,7 @@ public String toString() {
258290
", maxUploads=" + maxUploads +
259291
", prefix='" + prefix + "'" +
260292
", isTruncated=" + isTruncated +
261-
", commonPrefixes=" + commonPrefixes +
293+
", commonPrefixes=" + getCommonPrefixes() +
262294
'}';
263295
}
264296

@@ -273,11 +305,11 @@ public static Builder builder() {
273305
return new Builder();
274306
}
275307

276-
/**
277-
* A builder for {@link ListMultipartUploadsResponse}.
278-
*
279-
* @since 2.60.0 This new api is in preview and is subject to breaking changes.
280-
*/
308+
public static class CommonPrefixHelper {
309+
@JacksonXmlProperty(localName = "Prefix")
310+
public String prefix;
311+
}
312+
281313
@BetaApi
282314
public static final class Builder {
283315
private ImmutableList<MultipartUpload> uploads;

google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/MultipartUpload.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
77
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
8+
* http://www.apache.org/licenses/LICENSE-2.0
99
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -16,6 +16,7 @@
1616

1717
package com.google.cloud.storage.multipartupload.model;
1818

19+
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
1920
import com.google.api.core.BetaApi;
2021
import com.google.cloud.storage.StorageClass;
2122
import java.time.OffsetDateTime;
@@ -29,10 +30,19 @@
2930
@BetaApi
3031
public final class MultipartUpload {
3132

32-
private final String key;
33-
private final String uploadId;
34-
private final StorageClass storageClass;
35-
private final OffsetDateTime initiated;
33+
@JacksonXmlProperty(localName = "Key")
34+
private String key;
35+
36+
@JacksonXmlProperty(localName = "UploadId")
37+
private String uploadId;
38+
39+
@JacksonXmlProperty(localName = "StorageClass")
40+
private StorageClass storageClass;
41+
42+
@JacksonXmlProperty(localName = "Initiated")
43+
private OffsetDateTime initiated;
44+
45+
private MultipartUpload() {}
3646

3747
private MultipartUpload(
3848
String key, String uploadId, StorageClass storageClass, OffsetDateTime initiated) {

google-cloud-storage/src/test/java/com/google/cloud/storage/ITMultipartUploadHttpRequestManagerTest.java

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@
3838
import com.google.cloud.storage.multipartupload.model.CompletedPart;
3939
import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadRequest;
4040
import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadResponse;
41+
import com.google.cloud.storage.multipartupload.model.ListMultipartUploadsRequest;
42+
import com.google.cloud.storage.multipartupload.model.ListMultipartUploadsResponse;
4143
import com.google.cloud.storage.multipartupload.model.ListPartsRequest;
4244
import com.google.cloud.storage.multipartupload.model.ListPartsResponse;
45+
import com.google.cloud.storage.multipartupload.model.MultipartUpload;
4346
import com.google.cloud.storage.multipartupload.model.ObjectLockMode;
4447
import com.google.cloud.storage.multipartupload.model.Part;
4548
import com.google.cloud.storage.multipartupload.model.UploadPartRequest;
@@ -840,4 +843,86 @@ public void sendUploadPartRequest_error() throws Exception {
840843
endpoint, request, RewindableContent.empty()));
841844
}
842845
}
846+
847+
@Test
848+
public void sendListMultipartUploadsRequest_success() throws Exception {
849+
String mockXmlResponse =
850+
"<ListMultipartUploadsResult>" +
851+
" <Bucket>test-bucket</Bucket>" +
852+
" <KeyMarker>key-marker</KeyMarker>" +
853+
" <UploadIdMarker>upload-id-marker</UploadIdMarker>" +
854+
" <NextKeyMarker>next-key-marker</NextKeyMarker>" +
855+
" <NextUploadIdMarker>next-upload-id-marker</NextUploadIdMarker>" +
856+
" <MaxUploads>1</MaxUploads>" +
857+
" <IsTruncated>false</IsTruncated>" +
858+
" <Upload>" +
859+
" <Key>test-key</Key>" +
860+
" <UploadId>test-upload-id</UploadId>" +
861+
" <StorageClass>STANDARD</StorageClass>" +
862+
" <Initiated>2025-11-11T00:00:00Z</Initiated>" +
863+
" </Upload>" +
864+
"</ListMultipartUploadsResult>";
865+
866+
HttpRequestHandler handler =
867+
req -> {
868+
ByteBuf buf = Unpooled.wrappedBuffer(mockXmlResponse.getBytes(StandardCharsets.UTF_8));
869+
870+
DefaultFullHttpResponse resp =
871+
new DefaultFullHttpResponse(req.protocolVersion(), OK, buf);
872+
873+
resp.headers().set("Content-Type", "application/xml; charset=utf-8");
874+
resp.headers().set("Content-Length", resp.content().readableBytes());
875+
return resp;
876+
};
877+
878+
try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) {
879+
URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/");
880+
881+
ListMultipartUploadsRequest request =
882+
ListMultipartUploadsRequest.builder()
883+
.bucket("test-bucket")
884+
.maxUploads(1)
885+
.keyMarker("key-marker")
886+
.uploadIdMarker("upload-id-marker")
887+
.build();
888+
889+
ListMultipartUploadsResponse response =
890+
multipartUploadHttpRequestManager.sendListMultipartUploadsRequest(endpoint, request);
891+
892+
assertThat(response).isNotNull();
893+
assertThat(response.getBucket()).isEqualTo("test-bucket");
894+
assertThat(response.getUploads()).hasSize(1);
895+
896+
MultipartUpload upload = response.getUploads().get(0);
897+
assertThat(upload.getKey()).isEqualTo("test-key");
898+
assertThat(upload.getStorageClass()).isEqualTo(StorageClass.STANDARD);
899+
assertThat(upload.getInitiated())
900+
.isEqualTo(OffsetDateTime.of(2025, 11, 11, 0, 0, 0, 0, ZoneOffset.UTC));
901+
}
902+
}
903+
904+
@Test
905+
public void sendListMultipartUploadsRequest_error() throws Exception {
906+
HttpRequestHandler handler =
907+
req -> {
908+
FullHttpResponse resp =
909+
new DefaultFullHttpResponse(req.protocolVersion(), HttpResponseStatus.BAD_REQUEST);
910+
resp.headers().set(CONTENT_TYPE, "text/plain; charset=utf-8");
911+
return resp;
912+
};
913+
914+
try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) {
915+
URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/");
916+
ListMultipartUploadsRequest request =
917+
ListMultipartUploadsRequest.builder()
918+
.bucket("test-bucket")
919+
.build();
920+
921+
assertThrows(
922+
HttpResponseException.class,
923+
() ->
924+
multipartUploadHttpRequestManager.sendListMultipartUploadsRequest(
925+
endpoint, request));
926+
}
927+
}
843928
}

0 commit comments

Comments
 (0)