Skip to content

Commit 77ea501

Browse files
committed
feat(gax): implement queryStatus in HttpJsonResumableUploadClient
1 parent 3e9ab99 commit 77ea501

5 files changed

Lines changed: 477 additions & 16 deletions

File tree

sdk-platform-java/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonResumableUploadClient.java

Lines changed: 139 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
import com.google.api.core.SettableApiFuture;
3939
import com.google.api.gax.resumable.ChunkUploadRequest;
4040
import com.google.api.gax.resumable.ChunkUploadResponse;
41+
import com.google.api.gax.resumable.QueryStatusRequest;
42+
import com.google.api.gax.resumable.QueryStatusResponse;
4143
import com.google.api.gax.resumable.ResumableUploadClient;
4244
import com.google.api.gax.resumable.ResumableUploadSession;
4345
import com.google.api.gax.resumable.StartUploadRequest;
@@ -86,6 +88,9 @@ public final class HttpJsonResumableUploadClient implements ResumableUploadClien
8688

8789
private static final PathTemplate PATH_TEMPLATE = PathTemplate.create("{+path}");
8890

91+
private static final Map<String, List<String>> QUERY_STATUS_HEADERS =
92+
ImmutableMap.of(UPLOAD_COMMAND_HEADER, ImmutableList.of("query"));
93+
8994
private static final ApiMethodDescriptor<StartUploadRequest, String> START_UPLOAD_DESCRIPTOR =
9095
ApiMethodDescriptor.<StartUploadRequest, String>newBuilder()
9196
.setFullMethodName("ResumableUpload/StartUpload")
@@ -150,6 +155,36 @@ public PathTemplate getPathTemplate() {
150155
.setResponseParser(StringHttpResponseParser.create())
151156
.build();
152157

158+
private static final ApiMethodDescriptor<QueryStatusRequest, String> QUERY_STATUS_DESCRIPTOR =
159+
ApiMethodDescriptor.<QueryStatusRequest, String>newBuilder()
160+
.setFullMethodName("ResumableUpload/QueryStatus")
161+
.setHttpMethod(HttpMethods.POST)
162+
.setType(ApiMethodDescriptor.MethodType.UNARY)
163+
.setRequestFormatter(
164+
new HttpRequestFormatter<QueryStatusRequest>() {
165+
@Override
166+
public Map<String, List<String>> getQueryParamNames(QueryStatusRequest request) {
167+
return Collections.emptyMap();
168+
}
169+
170+
@Override
171+
public String getRequestBody(QueryStatusRequest request) {
172+
return "";
173+
}
174+
175+
@Override
176+
public String getPath(QueryStatusRequest request) {
177+
return request.getUploadUrl();
178+
}
179+
180+
@Override
181+
public PathTemplate getPathTemplate() {
182+
return PATH_TEMPLATE;
183+
}
184+
})
185+
.setResponseParser(StringHttpResponseParser.create())
186+
.build();
187+
153188
private final ClientContext clientContext;
154189

155190
public static HttpJsonResumableUploadClient create(ClientContext clientContext) {
@@ -225,6 +260,32 @@ public ApiFuture<ChunkUploadResponse> futureCall(
225260
};
226261
}
227262

263+
@Override
264+
public UnaryCallable<QueryStatusRequest, QueryStatusResponse> queryStatusCallable() {
265+
return new UnaryCallable<QueryStatusRequest, QueryStatusResponse>() {
266+
@Override
267+
public ApiFuture<QueryStatusResponse> futureCall(
268+
QueryStatusRequest request, @Nullable ApiCallContext inputContext) {
269+
Preconditions.checkNotNull(request);
270+
HttpJsonCallContext context =
271+
(HttpJsonCallContext)
272+
HttpJsonCallContext.createDefault()
273+
.nullToSelf(clientContext.getDefaultCallContext())
274+
.merge(inputContext)
275+
.withExtraHeaders(QUERY_STATUS_HEADERS);
276+
277+
HttpJsonClientCall<QueryStatusRequest, String> clientCall =
278+
HttpJsonClientCalls.newCall(QUERY_STATUS_DESCRIPTOR, context);
279+
280+
SettableApiFuture<QueryStatusResponse> future = SettableApiFuture.create();
281+
HttpJsonClientCalls.startUnaryCall(
282+
clientCall, request, context, new QueryStatusResponseListener(future));
283+
284+
return future;
285+
}
286+
};
287+
}
288+
228289
private static class StartUploadResponseListener extends HttpJsonClientCall.Listener<String> {
229290

230291
private final SettableApiFuture<ResumableUploadSession> future;
@@ -304,25 +365,15 @@ private static class ChunkUploadResponseListener extends HttpJsonClientCall.List
304365

305366
@Override
306367
public void onHeaders(HttpJsonMetadata responseHeaders) {
307-
Map<String, Object> headers = responseHeaders.getHeaders();
308-
309-
String statusStr = HttpHeadersUtils.getFirstHeader(headers, UPLOAD_STATUS_HEADER);
368+
String statusStr =
369+
HttpHeadersUtils.getFirstHeader(responseHeaders.getHeaders(), UPLOAD_STATUS_HEADER);
310370
if (statusStr != null) {
311371
this.hasUploadStatusHeader = true;
312-
if (STATUS_FINAL.equalsIgnoreCase(statusStr)) {
313-
this.isComplete = true;
314-
}
372+
this.isComplete = STATUS_FINAL.equalsIgnoreCase(statusStr);
315373
}
316-
317-
String sizeReceivedStr =
318-
HttpHeadersUtils.getFirstHeader(headers, UPLOAD_SIZE_RECEIVED_HEADER);
319-
if (!Strings.isNullOrEmpty(sizeReceivedStr)) {
320-
try {
321-
this.committedOffset = Long.parseLong(sizeReceivedStr);
322-
} catch (NumberFormatException ignored) {
323-
// Ignore invalid/malformed size received header and fall back to local offset
324-
// calculation.
325-
}
374+
Long sizeReceived = parseSizeReceived(responseHeaders);
375+
if (sizeReceived != null) {
376+
this.committedOffset = sizeReceived;
326377
}
327378
}
328379

@@ -367,12 +418,84 @@ public void onClose(int statusCode, HttpJsonMetadata trailers) {
367418
}
368419
}
369420

421+
private static class QueryStatusResponseListener extends HttpJsonClientCall.Listener<String> {
422+
423+
private final SettableApiFuture<QueryStatusResponse> future;
424+
private boolean isComplete = false;
425+
@Nullable private Long committedOffset = null;
426+
private String responseBody = "";
427+
428+
QueryStatusResponseListener(SettableApiFuture<QueryStatusResponse> future) {
429+
this.future = future;
430+
}
431+
432+
@Override
433+
public void onHeaders(HttpJsonMetadata responseHeaders) {
434+
this.isComplete = isUploadFinal(responseHeaders);
435+
this.committedOffset = parseSizeReceived(responseHeaders);
436+
}
437+
438+
@Override
439+
public void onMessage(@Nullable String message) {
440+
if (message != null) {
441+
this.responseBody = message;
442+
}
443+
}
444+
445+
@Override
446+
public void onClose(int statusCode, HttpJsonMetadata trailers) {
447+
try {
448+
if (statusCode >= 200 && statusCode < 300) {
449+
if (isComplete || committedOffset != null) {
450+
future.set(
451+
QueryStatusResponse.create(
452+
committedOffset != null ? committedOffset : 0L,
453+
isComplete,
454+
isComplete ? responseBody : ""));
455+
} else {
456+
future.setException(
457+
ApiExceptionFactory.createException(
458+
"Query status response did not contain valid X-Goog-Upload-Size-Received"
459+
+ " header",
460+
/* cause= */ null,
461+
HttpJsonStatusCode.of(StatusCode.Code.INTERNAL),
462+
/* retryable= */ false));
463+
}
464+
} else {
465+
future.setException(
466+
createApiException(statusCode, trailers, "Failed to query upload status"));
467+
}
468+
} catch (Throwable t) {
469+
future.setException(
470+
ApiExceptionFactory.createException(
471+
"Internal error processing query status response",
472+
t,
473+
HttpJsonStatusCode.of(StatusCode.Code.INTERNAL),
474+
/* retryable= */ false));
475+
}
476+
}
477+
}
478+
370479
private static boolean isUploadFinal(HttpJsonMetadata responseHeaders) {
371480
String statusStr =
372481
HttpHeadersUtils.getFirstHeader(responseHeaders.getHeaders(), UPLOAD_STATUS_HEADER);
373482
return STATUS_FINAL.equalsIgnoreCase(statusStr);
374483
}
375484

485+
@Nullable
486+
private static Long parseSizeReceived(HttpJsonMetadata responseHeaders) {
487+
String sizeReceivedStr =
488+
HttpHeadersUtils.getFirstHeader(responseHeaders.getHeaders(), UPLOAD_SIZE_RECEIVED_HEADER);
489+
if (!Strings.isNullOrEmpty(sizeReceivedStr)) {
490+
try {
491+
return Long.parseLong(sizeReceivedStr);
492+
} catch (NumberFormatException ignored) {
493+
// Unparseable header; return null and let the listener decide how to handle it.
494+
}
495+
}
496+
return null;
497+
}
498+
376499
private static ApiException createApiException(
377500
int statusCode, @Nullable HttpJsonMetadata trailers, String actionDescription) {
378501
Throwable cause = trailers != null ? trailers.getException() : null;

0 commit comments

Comments
 (0)