Skip to content

Commit 6e17945

Browse files
committed
feat(generator): emit callable getter and convenience method for resumable upload RPCs
- In AbstractServiceClientClassComposer: emit public ResumableUploadCallable<RequestT, ResponseT> [method]Callable() delegating to stub - In AbstractServiceClientClassComposer: emit synchronous convenience method [method](RequestT request, InputStream payload) calling ApiExceptions.callAndTranslateApiException - Suppress method variants (flattened methods) for resumable upload RPCs - In ServiceClientCommentComposer: emit Javadoc call context override warning on both callable method and synchronous convenience method - Cover the generated client and its samples with ResumableUploadServiceClient.golden and the resumableuploadserviceclient sample goldens
1 parent 627610b commit 6e17945

13 files changed

Lines changed: 573 additions & 65 deletions

sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/comment/ServiceClientCommentComposer.java

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,19 @@
3333
import java.util.Optional;
3434
import java.util.stream.Collectors;
3535
import org.jspecify.annotations.NullMarked;
36+
import org.jspecify.annotations.Nullable;
3637

3738
@NullMarked
3839
public class ServiceClientCommentComposer {
3940
// Tokens.
4041
private static final String EMPTY_STRING = "";
4142
private static final String API_EXCEPTION_TYPE_NAME = "com.google.api.gax.rpc.ApiException";
4243
private static final String EXCEPTION_CONDITION = "if the remote call fails";
44+
private static final String REQUEST_PARAM_NAME = "request";
45+
private static final String REQUEST_PARAM_DESCRIPTION =
46+
"The request object containing all of the parameters for the API call.";
47+
private static final String PAYLOAD_PARAM_NAME = "payload";
48+
private static final String PAYLOAD_PARAM_DESCRIPTION = "The payload data stream to upload.";
4349

4450
// Constants.
4551
private static final String SERVICE_DESCRIPTION_INTRO_STRING =
@@ -93,6 +99,11 @@ public class ServiceClientCommentComposer {
9399
+ " that it is easy to make a subclass, but otherwise, the static factory methods"
94100
+ " should be preferred.";
95101

102+
private static final String RESUMABLE_UPLOAD_CALL_CONTEXT_WARNING =
103+
"Call context overrides (such as withTimeout, withRetrySettings, or credentials) apply"
104+
+ " strictly to the start request (session initiation). Per-chunk PUT calls rely on"
105+
+ " the configured timeout and retry settings from ResumableUploadCallSettings.";
106+
96107
// Comments.
97108
public static final CommentStatement GET_OPERATIONS_CLIENT_METHOD_COMMENT =
98109
toSimpleComment(
@@ -105,9 +116,9 @@ public static List<CommentStatement> createClassHeaderComments(
105116
String classMethodSampleCode,
106117
String credentialsSampleCode,
107118
String endpointSampleCode,
108-
String transportSampleCode,
109-
String primaryTransport,
110-
String secondaryTransport) {
119+
@Nullable String transportSampleCode,
120+
@Nullable String primaryTransport,
121+
@Nullable String secondaryTransport) {
111122
JavaDocComment.Builder classHeaderJavadocBuilder = JavaDocComment.builder();
112123
if (service.hasDescription()) {
113124
String descriptionComment =
@@ -187,14 +198,17 @@ public static List<CommentStatement> createRpcMethodHeaderComment(
187198
methodJavadocBuilder = methodJavadocBuilder.addUnescapedComment(descriptionComment);
188199
}
189200

190-
if (sampleCodeOpt.isPresent()) {
201+
if (method.isResumableUpload()) {
202+
methodJavadocBuilder.addParagraph(RESUMABLE_UPLOAD_CALL_CONTEXT_WARNING);
203+
}
204+
205+
if (sampleCodeOpt.isPresent() && !method.isResumableUpload()) {
191206
methodJavadocBuilder.addParagraph(METHOD_DESCRIPTION_SAMPLE_CODE_SUMMARY_STRING);
192207
methodJavadocBuilder.addSampleCode(sampleCodeOpt.get());
193208
}
194209

195210
if (methodArguments.isEmpty()) {
196-
methodJavadocBuilder.addParam(
197-
"request", "The request object containing all of the parameters for the API call.");
211+
methodJavadocBuilder.addParam(REQUEST_PARAM_NAME, REQUEST_PARAM_DESCRIPTION);
198212
} else {
199213
for (MethodArgument argument : methodArguments) {
200214
// TODO(miraleung): Remove the newline replacement when we support CommonMark.
@@ -204,6 +218,10 @@ public static List<CommentStatement> createRpcMethodHeaderComment(
204218
}
205219
}
206220

221+
if (method.isResumableUpload()) {
222+
methodJavadocBuilder.addParam(PAYLOAD_PARAM_NAME, PAYLOAD_PARAM_DESCRIPTION);
223+
}
224+
207225
methodJavadocBuilder.setThrows(API_EXCEPTION_TYPE_NAME, EXCEPTION_CONDITION);
208226

209227
if (method.isDeprecated()) {
@@ -348,8 +366,12 @@ public static List<CommentStatement> createRpcCallableMethodHeaderComment(
348366
methodJavadocBuilder = methodJavadocBuilder.addUnescapedComment(descriptionComment);
349367
}
350368

351-
methodJavadocBuilder.addParagraph(METHOD_DESCRIPTION_SAMPLE_CODE_SUMMARY_STRING);
352-
if (sampleCodeOpt.isPresent()) {
369+
if (method.isResumableUpload()) {
370+
methodJavadocBuilder.addParagraph(RESUMABLE_UPLOAD_CALL_CONTEXT_WARNING);
371+
}
372+
373+
if (sampleCodeOpt.isPresent() && !method.isResumableUpload()) {
374+
methodJavadocBuilder.addParagraph(METHOD_DESCRIPTION_SAMPLE_CODE_SUMMARY_STRING);
353375
methodJavadocBuilder.addSampleCode(sampleCodeOpt.get());
354376
}
355377

sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceClientClassComposer.java

Lines changed: 136 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@
2424
import com.google.api.gax.paging.AbstractFixedSizeCollection;
2525
import com.google.api.gax.paging.AbstractPage;
2626
import com.google.api.gax.paging.AbstractPagedListResponse;
27+
import com.google.api.gax.rpc.ApiExceptions;
2728
import com.google.api.gax.rpc.BidiStreamingCallable;
2829
import com.google.api.gax.rpc.ClientStreamingCallable;
2930
import com.google.api.gax.rpc.OperationCallable;
3031
import com.google.api.gax.rpc.PageContext;
32+
import com.google.api.gax.rpc.ResumableUploadCallSettings;
33+
import com.google.api.gax.rpc.ResumableUploadCallable;
3134
import com.google.api.gax.rpc.ServerStreamingCallable;
3235
import com.google.api.gax.rpc.UnaryCallable;
3336
import com.google.api.generator.engine.ast.AnnotationNode;
@@ -88,6 +91,7 @@
8891
import com.google.longrunning.Operation;
8992
import com.google.rpc.Status;
9093
import java.io.IOException;
94+
import java.io.InputStream;
9195
import java.util.ArrayList;
9296
import java.util.Arrays;
9397
import java.util.Collections;
@@ -109,6 +113,7 @@ public abstract class AbstractServiceClientClassComposer implements ClassCompose
109113
private static final String CALLABLE_NAME_PATTERN = "%sCallable";
110114
private static final String PAGED_CALLABLE_NAME_PATTERN = "%sPagedCallable";
111115
private static final String OPERATION_CALLABLE_NAME_PATTERN = "%sOperationCallable";
116+
private static final String REQUEST_VAR_NAME = "request";
112117

113118
private static final Reference LIST_REFERENCE = ConcreteReference.withClazz(List.class);
114119
private static final Reference MAP_REFERENCE = ConcreteReference.withClazz(Map.class);
@@ -650,42 +655,46 @@ private static List<MethodDefinition> createServiceMethods(
650655
methodVariantsForClientHeader.put(method.name(), new ArrayList<>());
651656
}
652657
if (method.stream().equals(Stream.NONE)) {
653-
List<MethodDefinition> generatedMethods =
654-
createMethodVariants(
655-
method,
656-
ClassNames.getServiceClientClassName(service),
657-
messageTypes,
658-
typeStore,
659-
resourceNames,
660-
samples,
661-
service);
662-
663-
// Collect data for gapic_metadata.json.
664-
grpcRpcToJavaMethodMetadata
665-
.get(method.name())
666-
.addAll(
667-
generatedMethods.stream()
668-
.map(m -> javaMethodNameFn.apply(m))
669-
.collect(Collectors.toList()));
670-
671-
// Collect data for Client header
672-
methodVariantsForClientHeader
673-
.get(method.name())
674-
.addAll(
675-
generatedMethods.stream()
676-
.map(AbstractServiceClientClassComposer::getJavaMethod)
677-
.collect(Collectors.toList()));
678-
javaMethods.addAll(generatedMethods);
658+
if (!method.isResumableUpload()) {
659+
List<MethodDefinition> generatedMethods =
660+
createMethodVariants(
661+
method,
662+
ClassNames.getServiceClientClassName(service),
663+
messageTypes,
664+
typeStore,
665+
resourceNames,
666+
samples,
667+
service);
668+
669+
// Collect data for gapic_metadata.json.
670+
grpcRpcToJavaMethodMetadata
671+
.get(method.name())
672+
.addAll(
673+
generatedMethods.stream()
674+
.map(m -> javaMethodNameFn.apply(m))
675+
.collect(Collectors.toList()));
676+
677+
// Collect data for Client header
678+
methodVariantsForClientHeader
679+
.get(method.name())
680+
.addAll(
681+
generatedMethods.stream()
682+
.map(AbstractServiceClientClassComposer::getJavaMethod)
683+
.collect(Collectors.toList()));
684+
javaMethods.addAll(generatedMethods);
685+
}
679686

680687
MethodDefinition generatedMethod =
681-
createMethodDefaultMethod(
682-
method,
683-
ClassNames.getServiceClientClassName(service),
684-
messageTypes,
685-
typeStore,
686-
resourceNames,
687-
samples,
688-
service);
688+
method.isResumableUpload()
689+
? createResumableUploadDefaultMethod(method, typeStore)
690+
: createMethodDefaultMethod(
691+
method,
692+
ClassNames.getServiceClientClassName(service),
693+
messageTypes,
694+
typeStore,
695+
resourceNames,
696+
samples,
697+
service);
689698

690699
// Collect data for gapic_metadata.json and client header.
691700
grpcRpcToJavaMethodMetadata.get(method.name()).add(javaMethodNameFn.apply(generatedMethod));
@@ -778,7 +787,8 @@ private static List<MethodDefinition> createMethodVariants(
778787
// Request proto builder.
779788
VariableExpr requestVarExpr =
780789
VariableExpr.builder()
781-
.setVariable(Variable.builder().setName("request").setType(methodInputType).build())
790+
.setVariable(
791+
Variable.builder().setName(REQUEST_VAR_NAME).setType(methodInputType).build())
782792
.setIsDecl(true)
783793
.build();
784794

@@ -873,7 +883,8 @@ private static MethodDefinition createMethodDefaultMethod(
873883
// Construct the method that accepts a request proto.
874884
VariableExpr requestArgVarExpr =
875885
VariableExpr.builder()
876-
.setVariable(Variable.builder().setName("request").setType(methodInputType).build())
886+
.setVariable(
887+
Variable.builder().setName(REQUEST_VAR_NAME).setType(methodInputType).build())
877888
.setIsDecl(true)
878889
.build();
879890
String callableMethodName =
@@ -885,9 +896,8 @@ private static MethodDefinition createMethodDefaultMethod(
885896
}
886897

887898
Optional<Sample> defaultMethodSample =
888-
Optional.of(
889-
ServiceClientMethodSampleComposer.composeCanonicalSample(
890-
method, typeStore.get(clientName), resourceNames, messageTypes, service));
899+
ServiceClientMethodSampleComposer.composeCanonicalSample(
900+
method, typeStore.get(clientName), resourceNames, messageTypes, service);
891901
Optional<String> defaultMethodDocSample = Optional.empty();
892902
if (defaultMethodSample.isPresent()) {
893903
samples.add(defaultMethodSample.get());
@@ -914,14 +924,86 @@ private static MethodDefinition createMethodDefaultMethod(
914924
.setName(String.format(method.hasLro() ? "%sAsync" : "%s", methodName))
915925
.setArguments(Arrays.asList(requestArgVarExpr));
916926

927+
if (method.hasLro()) {
928+
methodBuilder =
929+
methodBuilder.setReturnExpr(callableMethodExpr).setReturnType(methodOutputType);
930+
} else {
931+
if (isProtoEmptyType(methodOutputType)) {
932+
methodBuilder =
933+
methodBuilder
934+
.setBody(Arrays.asList(ExprStatement.withExpr(callableMethodExpr)))
935+
.setReturnType(TypeNode.VOID);
936+
} else {
937+
methodBuilder =
938+
methodBuilder.setReturnExpr(callableMethodExpr).setReturnType(methodOutputType);
939+
}
940+
}
941+
942+
methodBuilder.setAnnotations(createMethodAnnotations(method, typeStore));
943+
return methodBuilder.build();
944+
}
945+
946+
private static MethodDefinition createResumableUploadDefaultMethod(
947+
Method method, TypeStore typeStore) {
948+
String methodName = JavaStyle.toLowerCamelCase(method.name());
949+
TypeNode methodInputType = method.inputType();
950+
TypeNode methodOutputType = method.outputType();
951+
952+
VariableExpr requestArgVarExpr =
953+
VariableExpr.builder()
954+
.setVariable(
955+
Variable.builder().setName(REQUEST_VAR_NAME).setType(methodInputType).build())
956+
.setIsDecl(true)
957+
.build();
958+
VariableExpr payloadArgVarExpr =
959+
VariableExpr.builder()
960+
.setVariable(
961+
Variable.builder().setName("payload").setType(typeStore.get("InputStream")).build())
962+
.setIsDecl(true)
963+
.build();
964+
965+
String callableMethodName = String.format(CALLABLE_NAME_PATTERN, methodName);
966+
MethodInvocationExpr callableMethodExpr =
967+
MethodInvocationExpr.builder().setMethodName(callableMethodName).build();
968+
MethodInvocationExpr futureCallExpr =
969+
MethodInvocationExpr.builder()
970+
.setExprReferenceExpr(callableMethodExpr)
971+
.setMethodName("futureCall")
972+
.setArguments(
973+
Arrays.asList(
974+
requestArgVarExpr.toBuilder().setIsDecl(false).build(),
975+
payloadArgVarExpr.toBuilder().setIsDecl(false).build(),
976+
CastExpr.builder()
977+
.setType(typeStore.get("ResumableUploadCallSettings"))
978+
.setExpr(ValueExpr.createNullExpr())
979+
.build()))
980+
.build();
981+
982+
MethodInvocationExpr callAndTranslateExpr =
983+
MethodInvocationExpr.builder()
984+
.setStaticReferenceType(typeStore.get("ApiExceptions"))
985+
.setMethodName("callAndTranslateApiException")
986+
.setArguments(Arrays.asList(futureCallExpr))
987+
.setReturnType(methodOutputType)
988+
.build();
989+
990+
MethodDefinition.Builder methodBuilder =
991+
MethodDefinition.builder()
992+
.setHeaderCommentStatements(
993+
ServiceClientCommentComposer.createRpcMethodHeaderComment(method, Optional.empty()))
994+
.setScope(ScopeNode.PUBLIC)
995+
.setIsFinal(true)
996+
.setName(methodName)
997+
.setArguments(Arrays.asList(requestArgVarExpr, payloadArgVarExpr));
998+
917999
if (isProtoEmptyType(methodOutputType)) {
9181000
methodBuilder =
9191001
methodBuilder
920-
.setBody(Arrays.asList(ExprStatement.withExpr(callableMethodExpr)))
1002+
.setBody(Arrays.asList(ExprStatement.withExpr(callAndTranslateExpr)))
9211003
.setReturnType(TypeNode.VOID);
9221004
} else {
9231005
methodBuilder =
924-
methodBuilder.setReturnExpr(callableMethodExpr).setReturnType(methodOutputType);
1006+
methodBuilder.setReturnExpr(callAndTranslateExpr).setReturnType(methodOutputType);
9251007
}
9261008

9271009
methodBuilder.setAnnotations(createMethodAnnotations(method, typeStore));
@@ -992,7 +1074,9 @@ private static MethodDefinition createCallableMethod(
9921074
case NONE:
9931075
// Fall through.
9941076
default:
995-
rawCallableReturnType = typeStore.get("UnaryCallable");
1077+
rawCallableReturnType =
1078+
typeStore.get(
1079+
method.isResumableUpload() ? "ResumableUploadCallable" : "UnaryCallable");
9961080
}
9971081
}
9981082

@@ -1038,13 +1122,12 @@ private static MethodDefinition createCallableMethod(
10381122
} else if (callableMethodKind.equals(CallableMethodKind.REGULAR)) {
10391123
if (method.stream().equals(Stream.NONE)) {
10401124
sampleCode =
1041-
Optional.of(
1042-
ServiceClientCallableMethodSampleComposer.composeRegularCallableMethod(
1043-
method,
1044-
typeStore.get(ClassNames.getServiceClientClassName(service)),
1045-
resourceNames,
1046-
messageTypes,
1047-
service));
1125+
ServiceClientCallableMethodSampleComposer.composeRegularCallableMethod(
1126+
method,
1127+
typeStore.get(ClassNames.getServiceClientClassName(service)),
1128+
resourceNames,
1129+
messageTypes,
1130+
service);
10481131
} else {
10491132
sampleCode =
10501133
Optional.of(
@@ -1797,6 +1880,7 @@ private static TypeStore createTypes(Service service, Map<String, Message> messa
17971880
List<Class<?>> concreteClazzes =
17981881
Arrays.asList(
17991882
AbstractPagedListResponse.class,
1883+
ApiExceptions.class,
18001884
ApiFunction.class,
18011885
ApiFuture.class,
18021886
ApiFutures.class,
@@ -1806,6 +1890,7 @@ private static TypeStore createTypes(Service service, Map<String, Message> messa
18061890
BidiStreamingCallable.class,
18071891
ClientStreamingCallable.class,
18081892
Generated.class,
1893+
InputStream.class,
18091894
InterruptedException.class,
18101895
IOException.class,
18111896
MoreExecutors.class,
@@ -1814,6 +1899,8 @@ private static TypeStore createTypes(Service service, Map<String, Message> messa
18141899
Operation.class,
18151900
OperationFuture.class,
18161901
OperationCallable.class,
1902+
ResumableUploadCallSettings.class,
1903+
ResumableUploadCallable.class,
18171904
ServerStreamingCallable.class,
18181905
Status.class,
18191906
Strings.class,

0 commit comments

Comments
 (0)