diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/comment/SettingsCommentComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/comment/SettingsCommentComposer.java index fee47355b40e..ea379a222302 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/comment/SettingsCommentComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/comment/SettingsCommentComposer.java @@ -130,6 +130,16 @@ public static CommentStatement createCallSettingsGetterComment( isMethodInternal); } + private static final String RESUMABLE_UPLOAD_CALL_SETTINGS_DOC_NOTE = + "Note that custom retry settings and headers configured via ApiCallContext" + + " apply strictly to the initial session initiation request."; + + public static CommentStatement createResumableUploadCallSettingsGetterComment( + String javaMethodName, boolean isMethodDeprecated, boolean isMethodInternal) { + return createResumableUploadCallSettingsComment( + CALL_SETTINGS_METHOD_DOC_PATTERN, javaMethodName, isMethodDeprecated, isMethodInternal); + } + public static CommentStatement createBuilderClassComment(String outerClassName) { return toCommentStatement(String.format(BUILDER_CLASS_DOC_PATTERN, outerClassName)); } @@ -140,6 +150,30 @@ public static CommentStatement createCallSettingsBuilderGetterComment( return toCommentStatement(methodComment, isMethodDeprecated, isMethodInternal); } + public static CommentStatement createResumableUploadCallSettingsBuilderGetterComment( + String javaMethodName, boolean isMethodDeprecated, boolean isMethodInternal) { + return createResumableUploadCallSettingsComment( + CALL_SETTINGS_BUILDER_METHOD_DOC_PATTERN, + javaMethodName, + isMethodDeprecated, + isMethodInternal); + } + + private static CommentStatement createResumableUploadCallSettingsComment( + String pattern, String javaMethodName, boolean isMethodDeprecated, boolean isMethodInternal) { + JavaDocComment.Builder docBuilder = + JavaDocComment.builder() + .addComment(String.format(pattern, javaMethodName)) + .addParagraph(RESUMABLE_UPLOAD_CALL_SETTINGS_DOC_NOTE); + if (isMethodDeprecated) { + docBuilder.setDeprecated(CommentComposer.DEPRECATED_METHOD_STRING); + } + if (isMethodInternal) { + docBuilder.setInternalOnly(CommentComposer.INTERNAL_ONLY_METHOD_STRING); + } + return CommentStatement.withComment(docBuilder.build()); + } + public static List createClassHeaderComments( String configuredClassName, String defaultHost, diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceSettingsClassComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceSettingsClassComposer.java index e2d10d6c454c..c9930932d55e 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceSettingsClassComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceSettingsClassComposer.java @@ -25,6 +25,7 @@ import com.google.api.gax.rpc.ClientSettings; import com.google.api.gax.rpc.OperationCallSettings; import com.google.api.gax.rpc.PagedCallSettings; +import com.google.api.gax.rpc.ResumableUploadCallSettings; import com.google.api.gax.rpc.ServerStreamingCallSettings; import com.google.api.gax.rpc.StreamingCallSettings; import com.google.api.gax.rpc.StubSettings; @@ -153,7 +154,7 @@ private static List createClassHeaderComments( // list. List publicMethods = service.methods().stream() - .filter(m -> m.isInternalApi() == false) + .filter(m -> !m.isInternalApi() && !m.isResumableUpload()) .collect(Collectors.toList()); Optional methodOpt = publicMethods.isEmpty() @@ -311,12 +312,18 @@ private static List createSettingsGetterMethods( // Add method header comment statements and annotations. private static MethodDefinition methodBuilderHelper( Method protoMethod, MethodDefinition.Builder methodBuilder, String javaMethodName) { - return methodBuilder - .setHeaderCommentStatements( - SettingsCommentComposer.createCallSettingsGetterComment( + CommentStatement commentStatement = + protoMethod.isResumableUpload() + ? SettingsCommentComposer.createResumableUploadCallSettingsGetterComment( + getMethodNameFromSettingsVarName(javaMethodName), + protoMethod.isDeprecated(), + protoMethod.isInternalApi()) + : SettingsCommentComposer.createCallSettingsGetterComment( getMethodNameFromSettingsVarName(javaMethodName), protoMethod.isDeprecated(), - protoMethod.isInternalApi())) + protoMethod.isInternalApi()); + return methodBuilder + .setHeaderCommentStatements(commentStatement) .setAnnotations(createMethodAnnotations(protoMethod)) .build(); } @@ -794,13 +801,19 @@ private static List createNestedBuilderSettingsGetterMethods( String javaMethodName = String.format("%sSettings", javaStyleName); MethodDefinition.Builder methodBuilder = methodMakerFn.apply(getCallSettingsBuilderType(protoMethod, typeStore), javaMethodName); + CommentStatement commentStatement = + protoMethod.isResumableUpload() + ? SettingsCommentComposer.createResumableUploadCallSettingsBuilderGetterComment( + getMethodNameFromSettingsVarName(javaMethodName), + protoMethod.isDeprecated(), + protoMethod.isInternalApi()) + : SettingsCommentComposer.createCallSettingsBuilderGetterComment( + getMethodNameFromSettingsVarName(javaMethodName), + protoMethod.isDeprecated(), + protoMethod.isInternalApi()); javaMethods.add( methodBuilder - .setHeaderCommentStatements( - SettingsCommentComposer.createCallSettingsBuilderGetterComment( - getMethodNameFromSettingsVarName(javaMethodName), - protoMethod.isDeprecated(), - protoMethod.isInternalApi())) + .setHeaderCommentStatements(commentStatement) .setAnnotations(createMethodAnnotations(protoMethod)) .build()); @@ -856,6 +869,7 @@ private static TypeStore createStaticTypes() { Operation.class, OperationCallSettings.class, PagedCallSettings.class, + ResumableUploadCallSettings.class, ServerStreamingCallSettings.class, StreamingCallSettings.class, StubSettings.class, @@ -933,7 +947,13 @@ private static TypeNode getCallSettingsTypeHelper( Method protoMethod, TypeStore typeStore, boolean isBuilder) { Class callSettingsClazz = isBuilder ? UnaryCallSettings.Builder.class : UnaryCallSettings.class; - if (protoMethod.isPaged()) { + if (protoMethod.isResumableUpload()) { + return TypeNode.withReference( + ConcreteReference.withClazz( + isBuilder + ? ResumableUploadCallSettings.Builder.class + : ResumableUploadCallSettings.class)); + } else if (protoMethod.isPaged()) { callSettingsClazz = isBuilder ? PagedCallSettings.Builder.class : PagedCallSettings.class; } else if (protoMethod.isBatching()) { callSettingsClazz = diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceStubSettingsClassComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceStubSettingsClassComposer.java index d713a5e1b891..10c13902ab35 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceStubSettingsClassComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceStubSettingsClassComposer.java @@ -43,6 +43,7 @@ import com.google.api.gax.rpc.PagedCallSettings; import com.google.api.gax.rpc.PagedListDescriptor; import com.google.api.gax.rpc.PagedListResponseFactory; +import com.google.api.gax.rpc.ResumableUploadCallSettings; import com.google.api.gax.rpc.ServerStreamingCallSettings; import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.rpc.StreamingCallSettings; @@ -91,6 +92,7 @@ import com.google.api.generator.gapic.model.GapicBatchingSettings; import com.google.api.generator.gapic.model.GapicClass; import com.google.api.generator.gapic.model.GapicContext; +import com.google.api.generator.gapic.model.GapicRetrySettings; import com.google.api.generator.gapic.model.GapicServiceConfig; import com.google.api.generator.gapic.model.Message; import com.google.api.generator.gapic.model.Method; @@ -106,6 +108,7 @@ import com.google.common.collect.Lists; import com.google.longrunning.Operation; import com.google.protobuf.Empty; +import com.google.protobuf.util.Durations; import java.io.IOException; import java.time.Duration; import java.util.ArrayList; @@ -132,6 +135,7 @@ public abstract class AbstractServiceStubSettingsClassComposer implements ClassC private static final String BATCHING_DESC_PATTERN = "%s_BATCHING_DESC"; private static final String PAGE_STR_DESC_PATTERN = "%s_PAGE_STR_DESC"; private static final String PAGED_RESPONSE_FACTORY_PATTERN = "%s_PAGE_STR_FACT"; + private static final String SETTINGS_METHOD_NAME_PATTERN = "%sSettings"; private static final String NESTED_BUILDER_CLASS_NAME = "Builder"; private static final String NESTED_UNARY_METHOD_SETTINGS_BUILDERS_VAR_NAME = "unaryMethodSettingsBuilders"; @@ -188,6 +192,7 @@ public GapicClass generate(GapicContext context, Service service) { List samples = new ArrayList<>(); Set deprecatedSettingVarNames = new HashSet<>(); Set internalSettingVarNames = new HashSet<>(); + Set resumableUploadSettingVarNames = new HashSet<>(); Map methodSettingsMemberVarExprs = createMethodSettingsClassMemberVarExprs( service, @@ -195,7 +200,8 @@ public GapicClass generate(GapicContext context, Service service) { typeStore, /* isNestedClass= */ false, deprecatedSettingVarNames, - internalSettingVarNames); + internalSettingVarNames, + resumableUploadSettingVarNames); String className = ClassNames.getServiceStubSettingsClassName(service); List classHeaderComments = createClassHeaderComments(service, typeStore.get(className), samples); @@ -217,6 +223,7 @@ public GapicClass generate(GapicContext context, Service service) { methodSettingsMemberVarExprs, deprecatedSettingVarNames, internalSettingVarNames, + resumableUploadSettingVarNames, typeStore)) .setNestedClasses( Arrays.asList(createNestedBuilderClass(service, serviceConfig, typeStore))) @@ -444,7 +451,7 @@ private static List createClassHeaderComments( // list. List publicMethods = service.methods().stream() - .filter(m -> m.isInternalApi() == false) + .filter(m -> !m.isInternalApi() && !m.isResumableUpload()) .collect(Collectors.toList()); Optional methodOpt = publicMethods.isEmpty() @@ -510,7 +517,8 @@ private static Map createMethodSettingsClassMemberVarExprs TypeStore typeStore, boolean isNestedClass, Set deprecatedSettingVarNames, - Set internalSettingVarNames) { + Set internalSettingVarNames, + Set resumableUploadSettingVarNames) { Map varExprs = new LinkedHashMap<>(); // Creates class variables Settings, e.g. echoSettings. @@ -519,13 +527,17 @@ private static Map createMethodSettingsClassMemberVarExprs !Objects.isNull(serviceConfig) && serviceConfig.hasBatchingSetting(service, method); TypeNode settingsType = getCallSettingsType(method, typeStore, hasBatchingSettings, isNestedClass); - String varName = String.format("%sSettings", JavaStyle.toLowerCamelCase(method.name())); + String varName = + String.format(SETTINGS_METHOD_NAME_PATTERN, JavaStyle.toLowerCamelCase(method.name())); if (method.isDeprecated()) { deprecatedSettingVarNames.add(varName); } if (method.isInternalApi()) { internalSettingVarNames.add(varName); } + if (method.isResumableUpload()) { + resumableUploadSettingVarNames.add(varName); + } varExprs.put( varName, VariableExpr.withVariable( @@ -1050,11 +1062,15 @@ private List createClassMethods( Map methodSettingsMemberVarExprs, Set deprecatedSettingVarNames, Set internalSettingVarNames, + Set resumableUploadSettingVarNames, TypeStore typeStore) { List javaMethods = new ArrayList<>(); javaMethods.addAll( createMethodSettingsGetterMethods( - methodSettingsMemberVarExprs, deprecatedSettingVarNames, internalSettingVarNames)); + methodSettingsMemberVarExprs, + deprecatedSettingVarNames, + internalSettingVarNames, + resumableUploadSettingVarNames)); javaMethods.add(createCreateStubMethod(service, typeStore)); javaMethods.addAll(createDefaultHelperAndGetterMethods(service, typeStore)); javaMethods.addAll( @@ -1087,15 +1103,21 @@ private static List createMethodAnnotation( private static List createMethodSettingsGetterMethods( Map methodSettingsMemberVarExprs, final Set deprecatedSettingVarNames, - final Set internalSettingVarNames) { + final Set internalSettingVarNames, + final Set resumableUploadSettingVarNames) { Function, MethodDefinition> varToMethodFn = e -> { boolean isDeprecated = deprecatedSettingVarNames.contains(e.getKey()); boolean isInternal = internalSettingVarNames.contains(e.getKey()); + boolean isResumableUpload = resumableUploadSettingVarNames.contains(e.getKey()); + CommentStatement commentStatement = + isResumableUpload + ? SettingsCommentComposer.createResumableUploadCallSettingsGetterComment( + getMethodNameFromSettingsVarName(e.getKey()), isDeprecated, isInternal) + : SettingsCommentComposer.createCallSettingsGetterComment( + getMethodNameFromSettingsVarName(e.getKey()), isDeprecated, isInternal); return MethodDefinition.builder() - .setHeaderCommentStatements( - SettingsCommentComposer.createCallSettingsGetterComment( - getMethodNameFromSettingsVarName(e.getKey()), isDeprecated, isInternal)) + .setHeaderCommentStatements(commentStatement) .setAnnotations(createMethodAnnotation(isDeprecated, isInternal)) .setScope(ScopeNode.PUBLIC) .setReturnType(e.getValue().type()) @@ -1441,6 +1463,7 @@ private ClassDefinition createNestedBuilderClass( Set nestedDeprecatedSettingVarNames = new HashSet<>(); Set nestedInternalSettingVarNames = new HashSet<>(); + Set nestedResumableUploadSettingVarNames = new HashSet<>(); Map nestedMethodSettingsMemberVarExprs = createMethodSettingsClassMemberVarExprs( service, @@ -1448,7 +1471,8 @@ private ClassDefinition createNestedBuilderClass( typeStore, /* isNestedClass= */ true, nestedDeprecatedSettingVarNames, - nestedInternalSettingVarNames); + nestedInternalSettingVarNames, + nestedResumableUploadSettingVarNames); // TODO(miraleung): Fill this out. return ClassDefinition.builder() @@ -1470,6 +1494,7 @@ private ClassDefinition createNestedBuilderClass( nestedMethodSettingsMemberVarExprs, nestedDeprecatedSettingVarNames, nestedInternalSettingVarNames, + nestedResumableUploadSettingVarNames, typeStore)) .build(); } @@ -1531,6 +1556,7 @@ private List createNestedClassMethods( Map nestedMethodSettingsMemberVarExprs, Set nestedDeprecatedSettingVarNames, Set nestedInternalSettingVarNames, + Set nestedResumableUploadSettingVarNames, TypeStore typeStore) { List nestedClassMethods = new ArrayList<>(); nestedClassMethods.addAll( @@ -1544,7 +1570,8 @@ private List createNestedClassMethods( createNestedClassSettingsBuilderGetterMethods( nestedMethodSettingsMemberVarExprs, nestedDeprecatedSettingVarNames, - nestedInternalSettingVarNames)); + nestedInternalSettingVarNames, + nestedResumableUploadSettingVarNames)); nestedClassMethods.add(createNestedClassBuildMethod(service, typeStore)); return nestedClassMethods; } @@ -1573,7 +1600,7 @@ private MethodDefinition createNestedClassInitDefaultsMethod( "No batching setting found for service %s, method %s", service.name(), method.name())); String settingsGetterMethodName = - String.format("%sSettings", JavaStyle.toLowerCamelCase(method.name())); + String.format(SETTINGS_METHOD_NAME_PATTERN, JavaStyle.toLowerCamelCase(method.name())); bodyStatements.add( ExprStatement.withExpr( RetrySettingsComposer.createBatchingBuilderSettingsExpr( @@ -1581,6 +1608,12 @@ private MethodDefinition createNestedClassInitDefaultsMethod( bodyStatements.add(EMPTY_LINE_STATEMENT); } + if (method.isResumableUpload()) { + addResumableUploadGlobalTimeoutStatements( + service, serviceConfig, method, builderVarExpr, bodyStatements); + continue; + } + bodyStatements.add( ExprStatement.withExpr( RetrySettingsComposer.createSimpleBuilderSettingsExpr( @@ -1621,6 +1654,54 @@ private MethodDefinition createNestedClassInitDefaultsMethod( .build(); } + private static void addResumableUploadGlobalTimeoutStatements( + Service service, + @Nullable GapicServiceConfig serviceConfig, + Method method, + VariableExpr builderVarExpr, + List bodyStatements) { + if (serviceConfig == null) { + return; + } + String retryParamsName = serviceConfig.getRetryParamsName(service, method); + GapicRetrySettings retrySettings = + retryParamsName != null + ? serviceConfig.getAllGapicRetrySettings(service).get(retryParamsName) + : null; + if (retrySettings != null + && retrySettings.timeout() != null + && Durations.toMillis(retrySettings.timeout()) > 0) { + String settingsGetterMethodName = + String.format(SETTINGS_METHOD_NAME_PATTERN, JavaStyle.toLowerCamelCase(method.name())); + Expr settingsExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(builderVarExpr) + .setMethodName(settingsGetterMethodName) + .build(); + long timeoutMillis = Durations.toMillis(retrySettings.timeout()); + ValueExpr timeoutMillisExpr = + ValueExpr.withValue( + PrimitiveValue.builder() + .setType(TypeNode.LONG) + .setValue(String.format("%dL", timeoutMillis)) + .build()); + Expr durationOfMillisExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(FIXED_TYPESTORE.get("Duration")) + .setMethodName("ofMillis") + .setArguments(timeoutMillisExpr) + .build(); + settingsExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(settingsExpr) + .setMethodName("setGlobalTimeout") + .setArguments(durationOfMillisExpr) + .build(); + bodyStatements.add(ExprStatement.withExpr(settingsExpr)); + bodyStatements.add(EMPTY_LINE_STATEMENT); + } + } + private static List createNestedClassConstructorMethods( Service service, GapicServiceConfig serviceConfig, @@ -2068,7 +2149,8 @@ private static MethodDefinition createNestedClassUnaryMethodSettingsBuilderGette private static List createNestedClassSettingsBuilderGetterMethods( Map nestedMethodSettingsMemberVarExprs, Set nestedDeprecatedSettingVarNames, - Set nestedInternalSettingVarNames) { + Set nestedInternalSettingVarNames, + Set nestedResumableUploadSettingVarNames) { Reference operationCallSettingsBuilderRef = ConcreteReference.withClazz(OperationCallSettings.Builder.class); Function isOperationCallSettingsBuilderFn = @@ -2085,12 +2167,18 @@ private static List createNestedClassSettingsBuilderGetterMeth boolean isDeprecated = nestedDeprecatedSettingVarNames.contains(varName); boolean isInternal = nestedInternalSettingVarNames.contains(varName); + boolean isResumableUpload = nestedResumableUploadSettingVarNames.contains(varName); + + CommentStatement commentStatement = + isResumableUpload + ? SettingsCommentComposer.createResumableUploadCallSettingsBuilderGetterComment( + getMethodNameFromSettingsVarName(varName), isDeprecated, isInternal) + : SettingsCommentComposer.createCallSettingsBuilderGetterComment( + getMethodNameFromSettingsVarName(varName), isDeprecated, isInternal); javaMethods.add( MethodDefinition.builder() - .setHeaderCommentStatements( - SettingsCommentComposer.createCallSettingsBuilderGetterComment( - getMethodNameFromSettingsVarName(varName), isDeprecated, isInternal)) + .setHeaderCommentStatements(commentStatement) .setAnnotations(createMethodAnnotation(isDeprecated, isInternal)) .setScope(ScopeNode.PUBLIC) .setReturnType(settingsVarExpr.type()) @@ -2222,6 +2310,7 @@ private static TypeStore createStaticTypes() { PagedListResponseFactory.class, PartitionKey.class, RequestBuilder.class, + ResumableUploadCallSettings.class, RetrySettings.class, ServerStreamingCallSettings.class, StatusCode.class, @@ -2341,6 +2430,13 @@ private static String getPagedResponseTypeName(String methodName) { CommonStrings.PAGED_RESPONSE_TYPE_NAME_PATTERN, JavaStyle.toUpperCamelCase(methodName)); } + private static TypeNode getResumableUploadCallSettingsType( + Function, TypeNode> typeMakerFn, boolean isSettingsBuilder) { + return isSettingsBuilder + ? typeMakerFn.apply(ResumableUploadCallSettings.Builder.class) + : typeMakerFn.apply(ResumableUploadCallSettings.class); + } + private static TypeNode getCallSettingsType( Method method, TypeStore typeStore, @@ -2348,6 +2444,9 @@ private static TypeNode getCallSettingsType( final boolean isSettingsBuilder) { Function, TypeNode> typeMakerFn = clz -> TypeNode.withReference(ConcreteReference.withClazz(clz)); + if (method.isResumableUpload()) { + return getResumableUploadCallSettingsType(typeMakerFn, isSettingsBuilder); + } // Default: No streaming. TypeNode callSettingsType = method.isPaged() diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/samplecode/SettingsSampleComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/samplecode/SettingsSampleComposer.java index d8c796c83be3..153f300c041e 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/samplecode/SettingsSampleComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/samplecode/SettingsSampleComposer.java @@ -25,6 +25,7 @@ import com.google.api.generator.engine.ast.VaporReference; import com.google.api.generator.engine.ast.Variable; import com.google.api.generator.engine.ast.VariableExpr; +import com.google.api.generator.gapic.model.Method; import com.google.api.generator.gapic.model.RegionTag; import com.google.api.generator.gapic.model.Sample; import com.google.api.generator.gapic.utils.JavaStyle; @@ -38,6 +39,14 @@ @NullMarked public final class SettingsSampleComposer { + public static Optional composeSettingsSample( + Method method, String settingsClassName, TypeNode classType) { + if (method.isResumableUpload()) { + return Optional.empty(); + } + return composeSettingsSample(Optional.of(method.name()), settingsClassName, classType); + } + public static Optional composeSettingsSample( Optional methodNameOpt, String settingsClassName, TypeNode classType) { if (!methodNameOpt.isPresent()) { diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/common/ResumableUploadSettingsComposerTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/common/ResumableUploadSettingsComposerTest.java new file mode 100644 index 000000000000..d9f9675c4332 --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/common/ResumableUploadSettingsComposerTest.java @@ -0,0 +1,152 @@ +// Copyright 2026 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.api.generator.gapic.composer.common; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.api.generator.engine.writer.JavaWriterVisitor; +import com.google.api.generator.gapic.composer.rest.ServiceSettingsClassComposer; +import com.google.api.generator.gapic.composer.rest.ServiceStubSettingsClassComposer; +import com.google.api.generator.gapic.model.GapicClass; +import com.google.api.generator.gapic.model.GapicContext; +import com.google.api.generator.gapic.model.GapicServiceConfig; +import com.google.api.generator.gapic.model.Service; +import com.google.api.generator.test.protoloader.RestTestProtoLoader; +import com.google.protobuf.Duration; +import io.grpc.serviceconfig.MethodConfig; +import io.grpc.serviceconfig.ServiceConfig; +import java.util.Optional; +import org.junit.jupiter.api.Test; + +class ResumableUploadSettingsComposerTest { + + @Test + void generateServiceStubSettings_resumableUploadCallSettings() { + GapicContext context = RestTestProtoLoader.instance().parseShowcaseResumableUpload(); + Service service = context.services().get(0); + + GapicClass clazz = ServiceStubSettingsClassComposer.instance().generate(context, service); + JavaWriterVisitor visitor = new JavaWriterVisitor(); + clazz.classDefinition().accept(visitor); + String code = visitor.write(); + + // Verify import of ResumableUploadCallSettings + assertThat(code).contains("import com.google.api.gax.rpc.ResumableUploadCallSettings;"); + + // Verify field in outer class + assertThat(code).contains("private final ResumableUploadCallSettings uploadMediaSettings;"); + + // Verify getter in outer class + assertThat(code) + .contains( + "public ResumableUploadCallSettings uploadMediaSettings() {\n" + + " return uploadMediaSettings;\n" + + " }"); + + // Verify field in Builder class + assertThat(code) + .contains("private final ResumableUploadCallSettings.Builder uploadMediaSettings;"); + + // Verify getter in Builder class + assertThat(code) + .contains( + "public ResumableUploadCallSettings.Builder uploadMediaSettings() {\n" + + " return uploadMediaSettings;\n" + + " }"); + + // Verify Builder constructor instantiates with ResumableUploadCallSettings.newBuilder() + assertThat(code).contains("uploadMediaSettings = ResumableUploadCallSettings.newBuilder();"); + + // Verify outer constructor builds the settings + assertThat(code) + .contains("uploadMediaSettings = settingsBuilder.uploadMediaSettings().build();"); + + // Verify unaryMethodSettingsBuilders does NOT contain uploadMediaSettings + assertThat(code).doesNotContain("unaryMethodSettingsBuilders.add(uploadMediaSettings);"); + assertThat(code) + .doesNotContain("unaryMethodSettingsBuilders.add(builder.uploadMediaSettings());"); + + // Verify Javadoc on settings getters + assertThat(code) + .contains("Note that custom retry settings and headers configured via ApiCallContext"); + assertThat(code).contains("the initial session initiation request."); + } + + @Test + void generateServiceSettings_resumableUploadCallSettings() { + GapicContext context = RestTestProtoLoader.instance().parseShowcaseResumableUpload(); + Service service = context.services().get(0); + + GapicClass clazz = ServiceSettingsClassComposer.instance().generate(context, service); + JavaWriterVisitor visitor = new JavaWriterVisitor(); + clazz.classDefinition().accept(visitor); + String code = visitor.write(); + + // Verify import of ResumableUploadCallSettings + assertThat(code).contains("import com.google.api.gax.rpc.ResumableUploadCallSettings;"); + + // Verify getter in outer ServiceSettings class delegating to stub settings + assertThat(code) + .contains( + "public ResumableUploadCallSettings uploadMediaSettings() {\n" + + " return ((ResumableUploadServiceStubSettings)" + + " getStubSettings()).uploadMediaSettings();\n" + + " }"); + + // Verify getter in Builder class delegating to stub settings builder + assertThat(code) + .contains( + "public ResumableUploadCallSettings.Builder uploadMediaSettings() {\n" + + " return getStubSettingsBuilder().uploadMediaSettings();\n" + + " }"); + + // Verify Javadoc on settings getters + assertThat(code) + .contains("Note that custom retry settings and headers configured via ApiCallContext"); + assertThat(code).contains("the initial session initiation request."); + } + + @Test + void generateServiceStubSettings_withTimeoutConfig() { + GapicContext baseContext = RestTestProtoLoader.instance().parseShowcaseResumableUpload(); + Service service = baseContext.services().get(0); + + ServiceConfig serviceConfig = + ServiceConfig.newBuilder() + .addMethodConfig( + MethodConfig.newBuilder() + .addName( + MethodConfig.Name.newBuilder() + .setService("google.showcase.v1beta1.ResumableUploadService") + .setMethod("UploadMedia")) + .setTimeout(Duration.newBuilder().setSeconds(300))) + .build(); + GapicServiceConfig gapicServiceConfig = GapicServiceConfig.create(Optional.of(serviceConfig)); + GapicContext context = baseContext.toBuilder().setServiceConfig(gapicServiceConfig).build(); + + GapicClass clazz = ServiceStubSettingsClassComposer.instance().generate(context, service); + JavaWriterVisitor visitor = new JavaWriterVisitor(); + clazz.classDefinition().accept(visitor); + String code = visitor.write(); + + // Verify global timeout is configured on builder.uploadMediaSettings() + assertThat(code) + .contains("builder.uploadMediaSettings().setGlobalTimeout(Duration.ofMillis(300000L));"); + + // Verify retryable codes and retry settings are NOT attempted on ResumableUploadCallSettings + assertThat(code).doesNotContain("builder.uploadMediaSettings().setRetryableCodes"); + assertThat(code).doesNotContain("builder.uploadMediaSettings().setRetrySettings"); + } +} diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceSettingsClassComposerTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceSettingsClassComposerTest.java index da503cd09f01..d70b43bb3b9f 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceSettingsClassComposerTest.java +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceSettingsClassComposerTest.java @@ -45,7 +45,13 @@ static Stream data() { TestProtoLoader.instance().parseSelectiveGenerationTesting(), "localhost:7469", "v1beta1", - 1)); + 1), + Arguments.of( + "ResumableUploadServiceSettings", + TestProtoLoader.instance().parseShowcaseResumableUpload(), + "localhost:7469", + "v1beta1", + 0)); } @ParameterizedTest diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceStubSettingsClassComposerTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceStubSettingsClassComposerTest.java index 3287309ee457..3e7a017403f3 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceStubSettingsClassComposerTest.java +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/ServiceStubSettingsClassComposerTest.java @@ -68,6 +68,12 @@ static Stream data() { GrpcTestProtoLoader.instance().parseBigqueryService(), "bigquery", "v2", + 0), + Arguments.of( + "ResumableUploadServiceStubSettings", + GrpcTestProtoLoader.instance().parseShowcaseResumableUpload(), + "localhost:7469", + "v1beta1", 0)); } diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/ResumableUploadServiceSettings.golden b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/ResumableUploadServiceSettings.golden new file mode 100644 index 000000000000..8d6fbc1100b2 --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/ResumableUploadServiceSettings.golden @@ -0,0 +1,164 @@ +package com.google.showcase.v1beta1; + +import com.google.api.core.ApiFunction; +import com.google.api.core.BetaApi; +import com.google.api.gax.core.GoogleCredentialsProvider; +import com.google.api.gax.core.InstantiatingExecutorProvider; +import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; +import com.google.api.gax.rpc.ApiClientHeaderProvider; +import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.ClientSettings; +import com.google.api.gax.rpc.ResumableUploadCallSettings; +import com.google.api.gax.rpc.StubSettings; +import com.google.api.gax.rpc.TransportChannelProvider; +import com.google.api.gax.rpc.UnaryCallSettings; +import com.google.showcase.v1beta1.stub.ResumableUploadServiceStubSettings; +import java.io.IOException; +import java.util.List; +import javax.annotation.Generated; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** + * Settings class to configure an instance of {@link ResumableUploadServiceClient}. + * + *

The default instance has everything set to sensible defaults: + * + *

    + *
  • The default service address (localhost) and default port (7469) are used. + *
  • Credentials are acquired automatically through Application Default Credentials. + *
  • Retries are configured for idempotent methods but not for non-idempotent methods. + *
+ * + *

The builder of this class is recursive, so contained classes are themselves builders. When + * build() is called, the tree of builders is called to create the complete settings object. + */ +@NullMarked +@BetaApi +@Generated("by gapic-generator-java") +public class ResumableUploadServiceSettings extends ClientSettings { + + /** + * Returns the object with the settings used for calls to uploadMedia. + * + *

Note that custom retry settings and headers configured via ApiCallContext apply strictly to + * the initial session initiation request. + */ + public ResumableUploadCallSettings uploadMediaSettings() { + return ((ResumableUploadServiceStubSettings) getStubSettings()).uploadMediaSettings(); + } + + public static final ResumableUploadServiceSettings create(ResumableUploadServiceStubSettings stub) + throws IOException { + return new ResumableUploadServiceSettings.Builder(stub.toBuilder()).build(); + } + + /** Returns a builder for the default ExecutorProvider for this service. */ + public static InstantiatingExecutorProvider.Builder defaultExecutorProviderBuilder() { + return ResumableUploadServiceStubSettings.defaultExecutorProviderBuilder(); + } + + /** Returns the default service endpoint. */ + public static String getDefaultEndpoint() { + return ResumableUploadServiceStubSettings.getDefaultEndpoint(); + } + + /** Returns the default service scopes. */ + public static List getDefaultServiceScopes() { + return ResumableUploadServiceStubSettings.getDefaultServiceScopes(); + } + + /** Returns a builder for the default credentials for this service. */ + public static GoogleCredentialsProvider.Builder defaultCredentialsProviderBuilder() { + return ResumableUploadServiceStubSettings.defaultCredentialsProviderBuilder(); + } + + /** Returns a builder for the default ChannelProvider for this service. */ + public static InstantiatingGrpcChannelProvider.Builder defaultGrpcTransportProviderBuilder() { + return ResumableUploadServiceStubSettings.defaultGrpcTransportProviderBuilder(); + } + + public static TransportChannelProvider defaultTransportChannelProvider() { + return ResumableUploadServiceStubSettings.defaultTransportChannelProvider(); + } + + public static ApiClientHeaderProvider.Builder defaultApiClientHeaderProviderBuilder() { + return ResumableUploadServiceStubSettings.defaultApiClientHeaderProviderBuilder(); + } + + /** Returns a new builder for this class. */ + public static Builder newBuilder() { + return Builder.createDefault(); + } + + /** Returns a new builder for this class. */ + public static Builder newBuilder(@Nullable ClientContext clientContext) { + return new Builder(clientContext); + } + + /** Returns a builder containing all the values of this settings class. */ + public Builder toBuilder() { + return new Builder(this); + } + + protected ResumableUploadServiceSettings(Builder settingsBuilder) throws IOException { + super(settingsBuilder); + } + + /** Builder for ResumableUploadServiceSettings. */ + public static class Builder + extends ClientSettings.Builder { + + protected Builder() throws IOException { + this(((ClientContext) null)); + } + + protected Builder(@Nullable ClientContext clientContext) { + super(ResumableUploadServiceStubSettings.newBuilder(clientContext)); + } + + protected Builder(ResumableUploadServiceSettings settings) { + super(settings.getStubSettings().toBuilder()); + } + + protected Builder(ResumableUploadServiceStubSettings.Builder stubSettings) { + super(stubSettings); + } + + private static Builder createDefault() { + return new Builder(ResumableUploadServiceStubSettings.newBuilder()); + } + + public ResumableUploadServiceStubSettings.Builder getStubSettingsBuilder() { + return ((ResumableUploadServiceStubSettings.Builder) getStubSettings()); + } + + /** + * Applies the given settings updater function to all of the unary API methods in this service. + * + *

Note: This method does not support applying settings to streaming methods. + */ + public Builder applyToAllUnaryMethods( + ApiFunction, Void> settingsUpdater) { + super.applyToAllUnaryMethods( + getStubSettingsBuilder().unaryMethodSettingsBuilders(), settingsUpdater); + return this; + } + + /** + * Returns the builder for the settings used for calls to uploadMedia. + * + *

Note that custom retry settings and headers configured via ApiCallContext apply strictly + * to the initial session initiation request. + */ + public ResumableUploadCallSettings.Builder uploadMediaSettings() { + return getStubSettingsBuilder().uploadMediaSettings(); + } + + @Override + public ResumableUploadServiceSettings build() throws IOException { + return new ResumableUploadServiceSettings(this); + } + } +} diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/ResumableUploadServiceStubSettings.golden b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/ResumableUploadServiceStubSettings.golden new file mode 100644 index 000000000000..59ba0aead96c --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/ResumableUploadServiceStubSettings.golden @@ -0,0 +1,246 @@ +package com.google.showcase.v1beta1.stub; + +import com.google.api.core.ApiFunction; +import com.google.api.core.BetaApi; +import com.google.api.core.ObsoleteApi; +import com.google.api.gax.core.GaxProperties; +import com.google.api.gax.core.GoogleCredentialsProvider; +import com.google.api.gax.core.InstantiatingExecutorProvider; +import com.google.api.gax.grpc.GaxGrpcProperties; +import com.google.api.gax.grpc.GrpcTransportChannel; +import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.ApiClientHeaderProvider; +import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.LibraryMetadata; +import com.google.api.gax.rpc.ResumableUploadCallSettings; +import com.google.api.gax.rpc.StatusCode; +import com.google.api.gax.rpc.StubSettings; +import com.google.api.gax.rpc.TransportChannelProvider; +import com.google.api.gax.rpc.UnaryCallSettings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import java.io.IOException; +import java.time.Duration; +import java.util.List; +import javax.annotation.Generated; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** + * Settings class to configure an instance of {@link ResumableUploadServiceStub}. + * + *

The default instance has everything set to sensible defaults: + * + *

    + *
  • The default service address (localhost) and default port (7469) are used. + *
  • Credentials are acquired automatically through Application Default Credentials. + *
  • Retries are configured for idempotent methods but not for non-idempotent methods. + *
+ * + *

The builder of this class is recursive, so contained classes are themselves builders. When + * build() is called, the tree of builders is called to create the complete settings object. + */ +@NullMarked +@BetaApi +@Generated("by gapic-generator-java") +@SuppressWarnings("CanonicalDuration") +public class ResumableUploadServiceStubSettings + extends StubSettings { + /** The default scopes of the service. */ + private static final ImmutableList DEFAULT_SERVICE_SCOPES = + ImmutableList.builder().build(); + + private final ResumableUploadCallSettings uploadMediaSettings; + + /** + * Returns the object with the settings used for calls to uploadMedia. + * + *

Note that custom retry settings and headers configured via ApiCallContext apply strictly to + * the initial session initiation request. + */ + public ResumableUploadCallSettings uploadMediaSettings() { + return uploadMediaSettings; + } + + public ResumableUploadServiceStub createStub() throws IOException { + if (getTransportChannelProvider() + .getTransportName() + .equals(GrpcTransportChannel.getGrpcTransportName())) { + return GrpcResumableUploadServiceStub.create(this); + } + throw new UnsupportedOperationException( + String.format( + "Transport not supported: %s", getTransportChannelProvider().getTransportName())); + } + + /** Returns a builder for the default ExecutorProvider for this service. */ + public static InstantiatingExecutorProvider.Builder defaultExecutorProviderBuilder() { + return InstantiatingExecutorProvider.newBuilder(); + } + + /** Returns the default service endpoint. */ + @ObsoleteApi("Use getEndpoint() instead") + public static String getDefaultEndpoint() { + return "localhost:7469"; + } + + /** Returns the default mTLS service endpoint. */ + public static String getDefaultMtlsEndpoint() { + return "localhost:7469"; + } + + /** Returns the default service scopes. */ + public static List getDefaultServiceScopes() { + return DEFAULT_SERVICE_SCOPES; + } + + /** Returns a builder for the default credentials for this service. */ + public static GoogleCredentialsProvider.Builder defaultCredentialsProviderBuilder() { + return GoogleCredentialsProvider.newBuilder() + .setScopesToApply(DEFAULT_SERVICE_SCOPES) + .setUseJwtAccessWithScope(true); + } + + /** Returns a builder for the default ChannelProvider for this service. */ + public static InstantiatingGrpcChannelProvider.Builder defaultGrpcTransportProviderBuilder() { + return InstantiatingGrpcChannelProvider.newBuilder() + .setMaxInboundMessageSize(Integer.MAX_VALUE); + } + + public static TransportChannelProvider defaultTransportChannelProvider() { + return defaultGrpcTransportProviderBuilder().build(); + } + + public static ApiClientHeaderProvider.Builder defaultApiClientHeaderProviderBuilder() { + return ApiClientHeaderProvider.newBuilder() + .setGeneratedLibToken( + "gapic", GaxProperties.getLibraryVersion(ResumableUploadServiceStubSettings.class)) + .setTransportToken( + GaxGrpcProperties.getGrpcTokenName(), GaxGrpcProperties.getGrpcVersion()); + } + + /** Returns a new builder for this class. */ + public static Builder newBuilder() { + return Builder.createDefault(); + } + + /** Returns a new builder for this class. */ + public static Builder newBuilder(@Nullable ClientContext clientContext) { + return new Builder(clientContext); + } + + /** Returns a builder containing all the values of this settings class. */ + public Builder toBuilder() { + return new Builder(this); + } + + protected ResumableUploadServiceStubSettings(Builder settingsBuilder) throws IOException { + super(settingsBuilder); + + uploadMediaSettings = settingsBuilder.uploadMediaSettings().build(); + } + + @Override + protected LibraryMetadata getLibraryMetadata() { + return LibraryMetadata.newBuilder().build(); + } + + /** Builder for ResumableUploadServiceStubSettings. */ + public static class Builder + extends StubSettings.Builder { + private final ImmutableList> unaryMethodSettingsBuilders; + private final ResumableUploadCallSettings.Builder uploadMediaSettings; + private static final ImmutableMap> + RETRYABLE_CODE_DEFINITIONS; + + static { + ImmutableMap.Builder> definitions = + ImmutableMap.builder(); + definitions.put("no_retry_codes", ImmutableSet.copyOf(Lists.newArrayList())); + RETRYABLE_CODE_DEFINITIONS = definitions.build(); + } + + private static final ImmutableMap RETRY_PARAM_DEFINITIONS; + + static { + ImmutableMap.Builder definitions = ImmutableMap.builder(); + RetrySettings settings = null; + settings = RetrySettings.newBuilder().setRpcTimeoutMultiplier(1.0).build(); + definitions.put("no_retry_params", settings); + RETRY_PARAM_DEFINITIONS = definitions.build(); + } + + protected Builder() { + this(((ClientContext) null)); + } + + protected Builder(@Nullable ClientContext clientContext) { + super(clientContext); + + uploadMediaSettings = ResumableUploadCallSettings.newBuilder(); + + unaryMethodSettingsBuilders = ImmutableList.>of(); + initDefaults(this); + } + + protected Builder(ResumableUploadServiceStubSettings settings) { + super(settings); + + uploadMediaSettings = settings.uploadMediaSettings.toBuilder(); + + unaryMethodSettingsBuilders = ImmutableList.>of(); + } + + private static Builder createDefault() { + Builder builder = new Builder(((ClientContext) null)); + + builder.setTransportChannelProvider(defaultTransportChannelProvider()); + builder.setCredentialsProvider(defaultCredentialsProviderBuilder().build()); + builder.setInternalHeaderProvider(defaultApiClientHeaderProviderBuilder().build()); + builder.setMtlsEndpoint(getDefaultMtlsEndpoint()); + builder.setSwitchToMtlsEndpointAllowed(true); + + return initDefaults(builder); + } + + private static Builder initDefaults(Builder builder) { + builder.uploadMediaSettings().setGlobalTimeout(Duration.ofMillis(5000L)); + + return builder; + } + + /** + * Applies the given settings updater function to all of the unary API methods in this service. + * + *

Note: This method does not support applying settings to streaming methods. + */ + public Builder applyToAllUnaryMethods( + ApiFunction, Void> settingsUpdater) { + super.applyToAllUnaryMethods(unaryMethodSettingsBuilders, settingsUpdater); + return this; + } + + public ImmutableList> unaryMethodSettingsBuilders() { + return unaryMethodSettingsBuilders; + } + + /** + * Returns the builder for the settings used for calls to uploadMedia. + * + *

Note that custom retry settings and headers configured via ApiCallContext apply strictly + * to the initial session initiation request. + */ + public ResumableUploadCallSettings.Builder uploadMediaSettings() { + return uploadMediaSettings; + } + + @Override + public ResumableUploadServiceStubSettings build() throws IOException { + return new ResumableUploadServiceStubSettings(this); + } + } +}