Skip to content

Commit 6fa5b21

Browse files
committed
Added Abort API
1 parent 08ac9bb commit 6fa5b21

17 files changed

+390
-117
lines changed

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@
2626
import com.google.api.gax.rpc.HeaderProvider;
2727
import com.google.api.gax.tracing.ApiTracerFactory;
2828
import com.google.auth.Credentials;
29-
import com.google.cloud.storage.Retrying.DefaultRetrier;
30-
import com.google.cloud.storage.Retrying.HttpRetrier;
3129
import com.google.cloud.storage.Retrying.RetryingDependencies;
3230
import com.google.cloud.ServiceFactory;
3331
import com.google.cloud.ServiceRpc;

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

Lines changed: 0 additions & 38 deletions
This file was deleted.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.storage;
18+
19+
import com.google.api.core.BetaApi;
20+
import com.google.api.core.InternalExtensionOnly;
21+
import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadRequest;
22+
import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadResponse;
23+
import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadRequest;
24+
import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadResponse;
25+
import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadRequest;
26+
import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadResponse;
27+
import com.google.cloud.storage.multipartupload.model.UploadPartRequest;
28+
import com.google.cloud.storage.multipartupload.model.UploadPartResponse;
29+
import java.io.IOException;
30+
import java.net.URI;
31+
import java.security.NoSuchAlgorithmException;
32+
33+
@BetaApi
34+
@InternalExtensionOnly
35+
public abstract class MultipartUploadClient {
36+
37+
MultipartUploadClient() {}
38+
39+
public abstract CreateMultipartUploadResponse createMultipartUpload(CreateMultipartUploadRequest request)
40+
throws IOException;
41+
42+
public abstract UploadPartResponse uploadPart(UploadPartRequest request, RequestBody requestBody)
43+
throws IOException;
44+
45+
public abstract CompleteMultipartUploadResponse completeMultipartUpload(
46+
CompleteMultipartUploadRequest request)
47+
throws NoSuchAlgorithmException, IOException;
48+
49+
public abstract AbortMultipartUploadResponse abortMultipartUpload(AbortMultipartUploadRequest request)
50+
throws IOException;
51+
52+
public static MultipartUploadClient create(MultipartUploadSettings config) {
53+
HttpStorageOptions options = config.getOptions();
54+
return new MultipartUploadClientImpl(
55+
URI.create(options.getHost()),
56+
options.getStorageRpcV1().getStorage().getRequestFactory(),
57+
options.createRetrier());
58+
}
59+
}

google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadImpl.java renamed to google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClientImpl.java

Lines changed: 119 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,36 +19,49 @@
1919
import static com.google.cloud.storage.MultipartUploadUtility.readStream;
2020
import static com.google.cloud.storage.MultipartUploadUtility.signRequest;
2121

22-
import com.google.cloud.storage.multipartupload.model.CompleteMultipartRequest;
23-
import com.google.cloud.storage.multipartupload.model.CompleteMultipartResponse;
22+
import com.google.api.client.http.AbstractHttpContent;
23+
import com.google.api.client.http.HttpRequestFactory;
24+
import com.google.api.client.util.ObjectParser;
25+
import com.google.api.gax.grpc.GrpcStatusCode;
26+
import com.google.api.gax.rpc.UnimplementedException;
27+
import com.google.cloud.storage.Retrying.Retrier;
28+
import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadRequest;
29+
import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadResponse;
30+
import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadRequest;
31+
import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadResponse;
2432
import com.google.cloud.storage.multipartupload.model.CompletedPart;
2533
import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadRequest;
2634
import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadResponse;
2735
import com.google.cloud.storage.multipartupload.model.UploadPartRequest;
2836
import com.google.cloud.storage.multipartupload.model.UploadPartResponse;
37+
import io.grpc.Status;
2938
import java.io.IOException;
39+
import java.io.InputStream;
3040
import java.io.OutputStream;
41+
import java.io.Reader;
42+
import java.lang.reflect.Type;
3143
import java.net.HttpURLConnection;
44+
import java.net.URI;
3245
import java.net.URL;
46+
import java.nio.charset.Charset;
3347
import java.nio.charset.StandardCharsets;
3448
import java.security.MessageDigest;
3549
import java.security.NoSuchAlgorithmException;
3650
import java.util.Base64;
3751

38-
public class MultipartUploadImpl implements MultipartUpload {
39-
40-
private static final String BUCKET_NAME = "shreyassinha";
41-
private static final String OBJECT_NAME = "5mb"; // The name for the object in GCS
42-
private static final String FILE_PATH = "5mb-examplefile-com.txt"; // The local file to upload
52+
public class MultipartUploadClientImpl extends MultipartUploadClient {
4353

4454
// Add HMAC keys from GCS Settings > Interoperability
4555

4656
// --- End Configuration ---
4757
private static final String GCS_ENDPOINT = "https://storage.googleapis.com";
4858

59+
public MultipartUploadClientImpl(URI uri, HttpRequestFactory requestFactory, Retrier retrier) {
60+
}
61+
4962
public CreateMultipartUploadResponse createMultipartUpload(CreateMultipartUploadRequest request)
5063
throws IOException {
51-
String resourcePath = "/" + request.getBucket() + "/" + request.getKey();
64+
String resourcePath = "/" + request.bucket() + "/" + request.key();
5265
String uri = GCS_ENDPOINT + resourcePath + "?uploads";
5366
String date = getRfc1123Date();
5467
String contentType = "application/x-www-form-urlencoded";
@@ -74,13 +87,13 @@ public CreateMultipartUploadResponse createMultipartUpload(CreateMultipartUpload
7487
int start = responseBody.indexOf(uploadIdTag) + uploadIdTag.length();
7588
int end = responseBody.indexOf("</UploadId>");
7689
String uploadId = responseBody.substring(start, end);
77-
return CreateMultipartUploadResponse.newBuilder().setUploadId(uploadId).build();
90+
return CreateMultipartUploadResponse.builder().uploadId(uploadId).build();
7891
}
7992

8093
public UploadPartResponse uploadPart(UploadPartRequest request, RequestBody requestBody)
8194
throws IOException {
82-
String resourcePath = "/" + request.getBucket() + "/" + request.getKey();
83-
String queryString = "?partNumber=" + request.getPartNumber() + "&uploadId=" + request.getUploadId();
95+
String resourcePath = "/" + request.bucket() + "/" + request.key();
96+
String queryString = "?partNumber=" + request.partNumber() + "&uploadId=" + request.uploadId();
8497
String uri = GCS_ENDPOINT + resourcePath + queryString;
8598
String date = getRfc1123Date();
8699
String contentType = "application/octet-stream";
@@ -94,32 +107,32 @@ public UploadPartResponse uploadPart(UploadPartRequest request, RequestBody requ
94107
connection.setRequestProperty("Date", date);
95108
connection.setRequestProperty("Authorization", authHeader);
96109
connection.setRequestProperty("Content-Type", contentType);
97-
connection.setFixedLengthStreamingMode(requestBody.getPartDate().length);
110+
connection.setFixedLengthStreamingMode(requestBody.getPartData().length);
98111
connection.setDoOutput(true);
99112

100113
try (OutputStream os = connection.getOutputStream()) {
101-
os.write(requestBody.getPartDate());
114+
os.write(requestBody.getPartData());
102115
}
103116

104117
if (connection.getResponseCode() != 200) {
105118
String error = readStream(connection.getErrorStream());
106-
throw new RuntimeException("Failed to upload part " + request.getPartNumber() + ": " + connection.getResponseCode() + " " + error);
119+
throw new RuntimeException("Failed to upload part " + request.partNumber() + ": " + connection.getResponseCode() + " " + error);
107120
}
108121
String eTag = connection.getHeaderField("ETag");
109-
return UploadPartResponse.newBuilder().setEtag(eTag).build();
122+
return UploadPartResponse.builder().etag(eTag).build();
110123
}
111124

112-
public CompleteMultipartResponse completeMultipartUpload(CompleteMultipartRequest request)
125+
public CompleteMultipartUploadResponse completeMultipartUpload(CompleteMultipartUploadRequest request)
113126
throws NoSuchAlgorithmException, IOException {
114-
String resourcePath = "/" + request.getBucket() + "/" + request.getKey();
115-
String queryString = "?uploadId=" + request.getUploadId();
127+
String resourcePath = "/" + request.bucket() + "/" + request.key();
128+
String queryString = "?uploadId=" + request.uploadId();
116129
String uri = GCS_ENDPOINT + resourcePath + queryString;
117130

118131
StringBuilder xmlBodyBuilder = new StringBuilder("<CompleteMultipartUpload>\n");
119-
for (CompletedPart part : request.getCompletedMultipartUpload().getCompletedPartList()) {
132+
for (CompletedPart part : request.completedMultipartUpload().completedPartList()) {
120133
xmlBodyBuilder.append(" <Part>\n");
121-
xmlBodyBuilder.append(" <PartNumber>").append(part.getPartNumber()).append("</PartNumber>\n");
122-
xmlBodyBuilder.append(" <ETag>").append(part.getEtag()).append("</ETag>\n");
134+
xmlBodyBuilder.append(" <PartNumber>").append(part.partNumber()).append("</PartNumber>\n");
135+
xmlBodyBuilder.append(" <ETag>").append(part.etag()).append("</ETag>\n");
123136
xmlBodyBuilder.append(" </Part>\n");
124137
}
125138
xmlBodyBuilder.append("</CompleteMultipartUpload>");
@@ -153,4 +166,89 @@ public CompleteMultipartResponse completeMultipartUpload(CompleteMultipartReques
153166
}
154167
return null;
155168
}
169+
170+
@Override
171+
public AbortMultipartUploadResponse abortMultipartUpload(AbortMultipartUploadRequest request) throws IOException{
172+
String resourcePath = "/" + request.bucket() + "/" + request.key();
173+
String queryString = "?uploadId=" + request.uploadId();
174+
String uri = GCS_ENDPOINT + resourcePath + queryString;
175+
String date = getRfc1123Date();
176+
177+
// GCS Signature Rule #4: The query string IS NOT included for the DELETE abort request.
178+
String signature = signRequest("DELETE", "", "", date, resourcePath, GOOGLE_SECRET_KEY);
179+
180+
String authHeader = "GOOG1 " + GOOGLE_ACCESS_KEY + ":" + signature;
181+
182+
HttpURLConnection connection = (HttpURLConnection) new URL(uri).openConnection();
183+
connection.setRequestMethod("DELETE");
184+
connection.setRequestProperty("Date", date);
185+
connection.setRequestProperty("Authorization", authHeader);
186+
187+
if (connection.getResponseCode() != 204) {
188+
String error = readStream(connection.getErrorStream());
189+
throw new RuntimeException("Failed to abort upload: " + connection.getResponseCode() + " " + error);
190+
}
191+
return new AbortMultipartUploadResponse();
192+
}
193+
194+
private static final class Utf8StringRequestContent extends AbstractHttpContent {
195+
196+
private final byte[] xml;
197+
198+
private Utf8StringRequestContent(byte[] xml) {
199+
// https://www.ietf.org/rfc/rfc2376.txt#:~:text=6.1%20text/xml%20with%20UTF%2D8%20Charset
200+
super("text/xml;charset=utf-8");
201+
this.xml = xml;
202+
}
203+
204+
@Override
205+
public long getLength() throws IOException {
206+
return super.getLength();
207+
}
208+
209+
@Override
210+
public void writeTo(OutputStream out) throws IOException {
211+
out.write(xml);
212+
}
213+
214+
public static Utf8StringRequestContent of(String xml) {
215+
return new Utf8StringRequestContent(xml.getBytes(StandardCharsets.UTF_8));
216+
}
217+
}
218+
219+
private static class XmlObjectParser implements ObjectParser {
220+
221+
@Override
222+
public <T> T parseAndClose(InputStream in, Charset charset, Class<T> dataClass)
223+
throws IOException {
224+
try (InputStream is = in) {
225+
return todo();
226+
}
227+
}
228+
229+
@Override
230+
public Object parseAndClose(InputStream in, Charset charset, Type dataType) throws IOException {
231+
try (InputStream is = in) {
232+
return todo();
233+
}
234+
}
235+
236+
@Override
237+
public <T> T parseAndClose(Reader reader, Class<T> dataClass) throws IOException {
238+
try (Reader r = reader) {
239+
return todo();
240+
}
241+
}
242+
243+
@Override
244+
public Object parseAndClose(Reader reader, Type dataType) throws IOException {
245+
try (Reader r = reader) {
246+
return todo();
247+
}
248+
}
249+
250+
private static <T> T todo() {
251+
throw new UnimplementedException("todo", null, GrpcStatusCode.of(Status.Code.UNIMPLEMENTED), false);
252+
}
253+
}
156254
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.cloud.storage;
17+
18+
public final class MultipartUploadSettings {
19+
private final HttpStorageOptions options;
20+
21+
private MultipartUploadSettings(HttpStorageOptions options) {
22+
this.options = options;
23+
}
24+
25+
public HttpStorageOptions getOptions() {
26+
return options;
27+
}
28+
29+
public static MultipartUploadSettings of(HttpStorageOptions options) {
30+
return new MultipartUploadSettings(options);
31+
}
32+
}

0 commit comments

Comments
 (0)