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..69043e639819 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 the settings configured here apply strictly to the initial session initiation" + + " request, and not to the subsequent data transfer requests."; + + 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..1e36b0cd6ee5 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,52 @@ 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 = + serviceConfig.getAllGapicRetrySettings(service).get(retryParamsName); + 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 +2147,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 +2165,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 +2308,7 @@ private static TypeStore createStaticTypes() { PagedListResponseFactory.class, PartitionKey.class, RequestBuilder.class, + ResumableUploadCallSettings.class, RetrySettings.class, ServerStreamingCallSettings.class, StatusCode.class, @@ -2341,6 +2428,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 +2442,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/common/AbstractTransportServiceStubClassComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java index b3f45f38d301..c025c3c207f8 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java @@ -49,8 +49,6 @@ import com.google.api.generator.engine.ast.Statement; import com.google.api.generator.engine.ast.StringObjectValue; import com.google.api.generator.engine.ast.ThisObjectValue; -import com.google.api.generator.engine.ast.ThrowExpr; -import com.google.api.generator.engine.ast.TryCatchStatement; import com.google.api.generator.engine.ast.TypeNode; import com.google.api.generator.engine.ast.ValueExpr; import com.google.api.generator.engine.ast.VaporReference; @@ -1096,141 +1094,13 @@ private static List createCallableGetterMethods( private List createStubOverrideMethods( VariableExpr backgroundResourcesVarExpr, Service service) { - Function methodMakerStarterFn = - methodName -> - MethodDefinition.builder() - .setIsOverride(true) - .setScope(ScopeNode.PUBLIC) - .setName(methodName); - - Function voidMethodMakerFn = - methodName -> - methodMakerStarterFn - .apply(methodName) - .setReturnType(TypeNode.VOID) - .setBody( - Arrays.asList( - ExprStatement.withExpr( - MethodInvocationExpr.builder() - .setExprReferenceExpr(backgroundResourcesVarExpr) - .setMethodName(methodName) - .build()))) - .build(); - - Function booleanMethodMakerFn = - methodName -> - methodMakerStarterFn - .apply(methodName) - .setReturnType(TypeNode.BOOLEAN) - .setReturnExpr( - MethodInvocationExpr.builder() - .setExprReferenceExpr(backgroundResourcesVarExpr) - .setMethodName(methodName) - .setReturnType(TypeNode.BOOLEAN) - .build()) - .build(); - - // Generate the close() method: - // @Override - // public final void close() { - // try { - // backgroundResources.close(); - // } catch (RuntimeException e) { - // throw e; - // } catch (Exception e) { - // throw new IllegalStateException("Failed to close resource", e); - // } - // } - - VariableExpr catchRuntimeExceptionVarExpr = - VariableExpr.builder() - .setVariable( - Variable.builder() - .setType(TypeNode.withExceptionClazz(RuntimeException.class)) - .setName("e") - .build()) - .build(); - VariableExpr catchExceptionVarExpr = - VariableExpr.builder() - .setVariable( - Variable.builder() - .setType(TypeNode.withExceptionClazz(Exception.class)) - .setName("e") - .build()) - .build(); List javaMethods = new ArrayList<>(); if (service.operationPollingMethod() != null) { javaMethods.addAll(createLongRunningClientGetters()); } - javaMethods.add( - methodMakerStarterFn - .apply("close") - .setIsFinal(true) - .setReturnType(TypeNode.VOID) - .setBody( - Arrays.asList( - TryCatchStatement.builder() - .setTryBody( - Arrays.asList( - ExprStatement.withExpr( - MethodInvocationExpr.builder() - .setExprReferenceExpr(backgroundResourcesVarExpr) - .setMethodName("close") - .build()))) - .addCatch( - catchRuntimeExceptionVarExpr.toBuilder().setIsDecl(true).build(), - Arrays.asList( - ExprStatement.withExpr( - ThrowExpr.builder() - .setThrowExpr(catchRuntimeExceptionVarExpr) - .build()))) - .addCatch( - catchExceptionVarExpr.toBuilder().setIsDecl(true).build(), - Arrays.asList( - ExprStatement.withExpr( - ThrowExpr.builder() - .setType( - TypeNode.withExceptionClazz( - IllegalStateException.class)) - .setMessageExpr("Failed to close resource") - .setCauseExpr(catchExceptionVarExpr) - .build()))) - .build())) - .build()); - javaMethods.add(voidMethodMakerFn.apply("shutdown")); - javaMethods.add(booleanMethodMakerFn.apply("isShutdown")); - javaMethods.add(booleanMethodMakerFn.apply("isTerminated")); - javaMethods.add(voidMethodMakerFn.apply("shutdownNow")); - - List awaitTerminationArgs = - Arrays.asList( - VariableExpr.withVariable( - Variable.builder().setName("duration").setType(TypeNode.LONG).build()), - VariableExpr.withVariable( - Variable.builder() - .setName("unit") - .setType(FIXED_TYPESTORE.get("TimeUnit")) - .build())); - javaMethods.add( - methodMakerStarterFn - .apply("awaitTermination") - .setReturnType(TypeNode.BOOLEAN) - .setArguments( - awaitTerminationArgs.stream() - .map(v -> v.toBuilder().setIsDecl(true).build()) - .collect(Collectors.toList())) - .setThrowsExceptions(Arrays.asList(FIXED_TYPESTORE.get("InterruptedException"))) - .setReturnExpr( - MethodInvocationExpr.builder() - .setExprReferenceExpr(backgroundResourcesVarExpr) - .setMethodName("awaitTermination") - .setArguments( - awaitTerminationArgs.stream() - .map(v -> (Expr) v) - .collect(Collectors.toList())) - .setReturnType(TypeNode.BOOLEAN) - .build()) - .build()); + javaMethods.addAll( + BackgroundResourceMethodComposer.createBackgroundResourceMethods( + backgroundResourcesVarExpr)); return javaMethods; } diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/BackgroundResourceMethodComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/BackgroundResourceMethodComposer.java new file mode 100644 index 000000000000..a187158b30b7 --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/BackgroundResourceMethodComposer.java @@ -0,0 +1,188 @@ +// 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 com.google.api.generator.engine.ast.ConcreteReference; +import com.google.api.generator.engine.ast.Expr; +import com.google.api.generator.engine.ast.ExprStatement; +import com.google.api.generator.engine.ast.MethodDefinition; +import com.google.api.generator.engine.ast.MethodInvocationExpr; +import com.google.api.generator.engine.ast.ScopeNode; +import com.google.api.generator.engine.ast.ThrowExpr; +import com.google.api.generator.engine.ast.TryCatchStatement; +import com.google.api.generator.engine.ast.TypeNode; +import com.google.api.generator.engine.ast.Variable; +import com.google.api.generator.engine.ast.VariableExpr; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.jspecify.annotations.NullMarked; + +/** + * Generates the {@code BackgroundResource} method overrides that a generated stub delegates to its + * {@code backgroundResources} member. + */ +@NullMarked +public final class BackgroundResourceMethodComposer { + + private static final TypeNode TIME_UNIT_TYPE = + TypeNode.withReference(ConcreteReference.withClazz(TimeUnit.class)); + private static final TypeNode INTERRUPTED_EXCEPTION_TYPE = + TypeNode.withReference(ConcreteReference.withClazz(InterruptedException.class)); + + private BackgroundResourceMethodComposer() {} + + /** + * Returns the {@code BackgroundResource} overrides, in declaration order, each delegating to the + * given {@code backgroundResources} member variable. + */ + public static List createBackgroundResourceMethods( + VariableExpr backgroundResourcesVarExpr) { + Function methodMakerStarterFn = + methodName -> + MethodDefinition.builder() + .setIsOverride(true) + .setScope(ScopeNode.PUBLIC) + .setName(methodName); + + Function voidMethodMakerFn = + methodName -> + methodMakerStarterFn + .apply(methodName) + .setReturnType(TypeNode.VOID) + .setBody( + Arrays.asList( + ExprStatement.withExpr( + MethodInvocationExpr.builder() + .setExprReferenceExpr(backgroundResourcesVarExpr) + .setMethodName(methodName) + .build()))) + .build(); + + Function booleanMethodMakerFn = + methodName -> + methodMakerStarterFn + .apply(methodName) + .setReturnType(TypeNode.BOOLEAN) + .setReturnExpr( + MethodInvocationExpr.builder() + .setExprReferenceExpr(backgroundResourcesVarExpr) + .setMethodName(methodName) + .setReturnType(TypeNode.BOOLEAN) + .build()) + .build(); + + // Generate the close() method: + // @Override + // public final void close() { + // try { + // backgroundResources.close(); + // } catch (RuntimeException e) { + // throw e; + // } catch (Exception e) { + // throw new IllegalStateException("Failed to close resource", e); + // } + // } + + VariableExpr catchRuntimeExceptionVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder() + .setType(TypeNode.withExceptionClazz(RuntimeException.class)) + .setName("e") + .build()) + .build(); + VariableExpr catchExceptionVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder() + .setType(TypeNode.withExceptionClazz(Exception.class)) + .setName("e") + .build()) + .build(); + + List javaMethods = new ArrayList<>(); + javaMethods.add( + methodMakerStarterFn + .apply("close") + .setIsFinal(true) + .setReturnType(TypeNode.VOID) + .setBody( + Arrays.asList( + TryCatchStatement.builder() + .setTryBody( + Arrays.asList( + ExprStatement.withExpr( + MethodInvocationExpr.builder() + .setExprReferenceExpr(backgroundResourcesVarExpr) + .setMethodName("close") + .build()))) + .addCatch( + catchRuntimeExceptionVarExpr.toBuilder().setIsDecl(true).build(), + Arrays.asList( + ExprStatement.withExpr( + ThrowExpr.builder() + .setThrowExpr(catchRuntimeExceptionVarExpr) + .build()))) + .addCatch( + catchExceptionVarExpr.toBuilder().setIsDecl(true).build(), + Arrays.asList( + ExprStatement.withExpr( + ThrowExpr.builder() + .setType( + TypeNode.withExceptionClazz( + IllegalStateException.class)) + .setMessageExpr("Failed to close resource") + .setCauseExpr(catchExceptionVarExpr) + .build()))) + .build())) + .build()); + javaMethods.add(voidMethodMakerFn.apply("shutdown")); + javaMethods.add(booleanMethodMakerFn.apply("isShutdown")); + javaMethods.add(booleanMethodMakerFn.apply("isTerminated")); + javaMethods.add(voidMethodMakerFn.apply("shutdownNow")); + + List awaitTerminationArgs = + Arrays.asList( + VariableExpr.withVariable( + Variable.builder().setName("duration").setType(TypeNode.LONG).build()), + VariableExpr.withVariable( + Variable.builder().setName("unit").setType(TIME_UNIT_TYPE).build())); + javaMethods.add( + methodMakerStarterFn + .apply("awaitTermination") + .setReturnType(TypeNode.BOOLEAN) + .setArguments( + awaitTerminationArgs.stream() + .map(v -> v.toBuilder().setIsDecl(true).build()) + .collect(Collectors.toList())) + .setThrowsExceptions(Arrays.asList(INTERRUPTED_EXCEPTION_TYPE)) + .setReturnExpr( + MethodInvocationExpr.builder() + .setExprReferenceExpr(backgroundResourcesVarExpr) + .setMethodName("awaitTermination") + .setArguments( + awaitTerminationArgs.stream() + .map(v -> (Expr) v) + .collect(Collectors.toList())) + .setReturnType(TypeNode.BOOLEAN) + .build()) + .build()); + return javaMethods; + } +} diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonDescriptorComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonDescriptorComposer.java new file mode 100644 index 000000000000..71e38aa5829b --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonDescriptorComposer.java @@ -0,0 +1,675 @@ +// 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.rest; + +import com.google.api.core.InternalApi; +import com.google.api.gax.httpjson.ApiMethodDescriptor; +import com.google.api.gax.httpjson.ApiMethodDescriptor.MethodType; +import com.google.api.gax.httpjson.FieldsExtractor; +import com.google.api.gax.httpjson.ProtoMessageRequestFormatter; +import com.google.api.gax.httpjson.ProtoMessageResponseParser; +import com.google.api.gax.httpjson.ProtoRestSerializer; +import com.google.api.generator.engine.ast.AnnotationNode; +import com.google.api.generator.engine.ast.AssignmentExpr; +import com.google.api.generator.engine.ast.ConcreteReference; +import com.google.api.generator.engine.ast.EnumRefExpr; +import com.google.api.generator.engine.ast.Expr; +import com.google.api.generator.engine.ast.ExprStatement; +import com.google.api.generator.engine.ast.IfStatement; +import com.google.api.generator.engine.ast.LambdaExpr; +import com.google.api.generator.engine.ast.MethodDefinition; +import com.google.api.generator.engine.ast.MethodInvocationExpr; +import com.google.api.generator.engine.ast.NewObjectExpr; +import com.google.api.generator.engine.ast.PrimitiveValue; +import com.google.api.generator.engine.ast.ScopeNode; +import com.google.api.generator.engine.ast.Statement; +import com.google.api.generator.engine.ast.StringObjectValue; +import com.google.api.generator.engine.ast.TypeNode; +import com.google.api.generator.engine.ast.ValueExpr; +import com.google.api.generator.engine.ast.Variable; +import com.google.api.generator.engine.ast.VariableExpr; +import com.google.api.generator.gapic.composer.store.TypeStore; +import com.google.api.generator.gapic.model.HttpBindings.HttpBinding; +import com.google.api.generator.gapic.model.Method; +import com.google.api.generator.gapic.model.Service; +import com.google.api.generator.gapic.utils.JavaStyle; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.TypeRegistry; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +final class HttpJsonDescriptorComposer { + + private static final String BUILD_METHOD_NAME = "build"; + private static final String NEW_BUILDER_METHOD_NAME = "newBuilder"; + private static final String SLASH = "/"; + + private static final TypeStore FIXED_REST_TYPESTORE = createStaticTypes(); + + private static final VariableExpr TYPE_REGISTRY_VAR_EXPR = + VariableExpr.builder() + .setVariable( + Variable.builder() + .setName("typeRegistry") + .setType(FIXED_REST_TYPESTORE.get(TypeRegistry.class.getSimpleName())) + .build()) + .build(); + + private HttpJsonDescriptorComposer() {} + + private static TypeStore createStaticTypes() { + return new TypeStore( + Arrays.asList( + ApiMethodDescriptor.class, + ArrayList.class, + FieldsExtractor.class, + HashMap.class, + InternalApi.class, + List.class, + ProtoMessageRequestFormatter.class, + ProtoMessageResponseParser.class, + ProtoRestSerializer.class, + TypeRegistry.class)); + } + + static BiFunction, Function> + getMethodMaker() { + return (mName, argExpr) -> + m -> + MethodInvocationExpr.builder() + .setMethodName(mName) + .setArguments(argExpr) + .setExprReferenceExpr(m) + .build(); + } + + static String prependPathPrefix(String path, @Nullable String pathPrefix) { + if (pathPrefix == null) { + return path; + } + String normalizedPrefix = pathPrefix.startsWith(SLASH) ? pathPrefix : SLASH + pathPrefix; + if (normalizedPrefix.endsWith(SLASH)) { + normalizedPrefix = normalizedPrefix.substring(0, normalizedPrefix.length() - 1); + } + return normalizedPrefix + (path.startsWith(SLASH) ? path : SLASH + path); + } + + static String getProtoRpcFullMethodName(Service protoService, Method protoMethod) { + if (protoMethod.isMixin()) { + return String.format("%s/%s", protoMethod.mixedInApiName(), protoMethod.name()); + } + return String.format( + "%s.%s/%s", protoService.protoPakkage(), protoService.name(), protoMethod.name()); + } + + static List getHttpMethodTypeExpr(Method protoMethod) { + return Collections.singletonList( + ValueExpr.withValue( + StringObjectValue.withValue(protoMethod.httpBindings().httpVerb().toString()))); + } + + static List getMethodTypeExpr(Method protoMethod) { + MethodType methodType; + switch (protoMethod.stream()) { + case NONE: + methodType = MethodType.UNARY; + break; + case SERVER: + methodType = MethodType.SERVER_STREAMING; + break; + case CLIENT: + case BIDI: + default: + throw new UnsupportedOperationException( + String.format( + "Methods of type %s are not supported by REST transport", protoMethod.stream())); + } + EnumRefExpr expr = + EnumRefExpr.builder() + .setName(methodType.toString()) + .setType( + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(ApiMethodDescriptor.MethodType.class) + .build())) + .build(); + return Collections.singletonList(expr); + } + + static List getRequestFormatterExpr(Method protoMethod, boolean restNumericEnumsEnabled) { + return getRequestFormatterExpr(protoMethod, restNumericEnumsEnabled, null); + } + + static List getRequestFormatterExpr( + Method protoMethod, boolean restNumericEnumsEnabled, @Nullable String pathPrefix) { + BiFunction, Function> + methodMaker = getMethodMaker(); + + MethodInvocationExpr expr = + MethodInvocationExpr.builder() + .setStaticReferenceType( + FIXED_REST_TYPESTORE.get(ProtoMessageRequestFormatter.class.getSimpleName())) + .setMethodName(NEW_BUILDER_METHOD_NAME) + .setGenerics(Collections.singletonList(protoMethod.inputType().reference())) + .build(); + + TypeNode extractorVarType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(Map.class) + .setGenerics(TypeNode.STRING.reference(), TypeNode.STRING.reference()) + .build()); + + String pathPattern = + prependPathPrefix(protoMethod.httpBindings().lowerCamelPattern(), pathPrefix); + + expr = + methodMaker + .apply( + "setPath", + Arrays.asList( + ValueExpr.withValue(StringObjectValue.withValue(pathPattern)), + createFieldsExtractorClassInstance( + protoMethod, + extractorVarType, + protoMethod.httpBindings().pathParameters(), + "putPathParam", + restNumericEnumsEnabled))) + .apply(expr); + + if (!protoMethod.httpBindings().lowerCamelAdditionalPatterns().isEmpty()) { + expr = + methodMaker + .apply( + "setAdditionalPaths", + protoMethod.httpBindings().lowerCamelAdditionalPatterns().stream() + .map( + a -> + ValueExpr.withValue( + StringObjectValue.withValue(prependPathPrefix(a, pathPrefix)))) + .collect(Collectors.toList())) + .apply(expr); + } + TypeNode fieldsVarGenericType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(List.class) + .setGenerics(TypeNode.STRING.reference()) + .build()); + + extractorVarType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(Map.class) + .setGenerics(TypeNode.STRING.reference(), fieldsVarGenericType.reference()) + .build()); + + expr = + methodMaker + .apply( + "setQueryParamsExtractor", + Arrays.asList( + createFieldsExtractorClassInstance( + protoMethod, + extractorVarType, + protoMethod.httpBindings().queryParameters(), + "putQueryParam", + restNumericEnumsEnabled))) + .apply(expr); + + extractorVarType = TypeNode.STRING; + boolean asteriskBody = protoMethod.httpBindings().isAsteriskBody(); + expr = + methodMaker + .apply( + "setRequestBodyExtractor", + Arrays.asList( + createBodyFieldsExtractorClassInstance( + protoMethod, + extractorVarType, + asteriskBody + ? protoMethod.httpBindings().pathParameters() + : protoMethod.httpBindings().bodyParameters(), + "toBody", + asteriskBody, + restNumericEnumsEnabled))) + .apply(expr); + + expr = methodMaker.apply(BUILD_METHOD_NAME, Collections.emptyList()).apply(expr); + + return Collections.singletonList(expr); + } + + static List setResponseParserExpr(Method protoMethod) { + BiFunction, Function> + methodMaker = getMethodMaker(); + + MethodInvocationExpr expr = + MethodInvocationExpr.builder() + .setStaticReferenceType( + FIXED_REST_TYPESTORE.get(ProtoMessageResponseParser.class.getSimpleName())) + .setMethodName(NEW_BUILDER_METHOD_NAME) + .setGenerics(Collections.singletonList(protoMethod.outputType().reference())) + .build(); + + expr = + methodMaker + .apply( + "setDefaultInstance", + Arrays.asList( + MethodInvocationExpr.builder() + .setStaticReferenceType(protoMethod.outputType()) + .setMethodName("getDefaultInstance") + .setReturnType(protoMethod.outputType()) + .build())) + .apply(expr); + + expr = + methodMaker + .apply("setDefaultTypeRegistry", Arrays.asList(TYPE_REGISTRY_VAR_EXPR)) + .apply(expr); + expr = methodMaker.apply(BUILD_METHOD_NAME, Collections.emptyList()).apply(expr); + + return Collections.singletonList(expr); + } + + static MethodInvocationExpr createMethodDescriptorExpr( + Service service, + Method protoMethod, + VariableExpr methodDescriptorVarExpr, + boolean restNumericEnumsEnabled, + @Nullable String pathPrefix) { + MethodInvocationExpr expr = + MethodInvocationExpr.builder() + .setMethodName(NEW_BUILDER_METHOD_NAME) + .setStaticReferenceType( + FIXED_REST_TYPESTORE.get(ApiMethodDescriptor.class.getSimpleName())) + .setGenerics(methodDescriptorVarExpr.variable().type().reference().generics()) + .build(); + + BiFunction, Function> + methodMaker = getMethodMaker(); + + String codeMethodArgName = getProtoRpcFullMethodName(service, protoMethod); + expr = + methodMaker + .apply( + "setFullMethodName", + Arrays.asList(ValueExpr.withValue(StringObjectValue.withValue(codeMethodArgName)))) + .apply(expr); + + expr = methodMaker.apply("setHttpMethod", getHttpMethodTypeExpr(protoMethod)).apply(expr); + expr = methodMaker.apply("setType", getMethodTypeExpr(protoMethod)).apply(expr); + expr = + methodMaker + .apply( + "setRequestFormatter", + getRequestFormatterExpr(protoMethod, restNumericEnumsEnabled, pathPrefix)) + .apply(expr); + expr = methodMaker.apply("setResponseParser", setResponseParserExpr(protoMethod)).apply(expr); + + return expr; + } + + static Statement createMethodDescriptorVariableDecl( + Service service, + Method protoMethod, + VariableExpr methodDescriptorVarExpr, + boolean restNumericEnumsEnabled, + @Nullable String pathPrefix) { + MethodInvocationExpr expr = + createMethodDescriptorExpr( + service, protoMethod, methodDescriptorVarExpr, restNumericEnumsEnabled, pathPrefix); + + expr = + MethodInvocationExpr.builder() + .setMethodName(BUILD_METHOD_NAME) + .setExprReferenceExpr(expr) + .setReturnType(methodDescriptorVarExpr.type()) + .build(); + + return ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr( + methodDescriptorVarExpr.toBuilder() + .setIsDecl(true) + .setScope(ScopeNode.PRIVATE) + .setIsStatic(true) + .setIsFinal(true) + .build()) + .setValueExpr(expr) + .build()); + } + + static MethodDefinition createGetMethodDescriptorsMethod( + Collection methodDescriptorVarExprs) { + List bodyExprs = new ArrayList<>(); + + VariableExpr methodDescriptorsVarExpr = + VariableExpr.withVariable( + Variable.builder() + .setType( + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(List.class) + .setGenerics( + Arrays.asList( + FIXED_REST_TYPESTORE.get("ApiMethodDescriptor").reference())) + .build())) + .setName("methodDescriptors") + .build()); + + bodyExprs.add( + AssignmentExpr.builder() + .setVariableExpr(methodDescriptorsVarExpr.toBuilder().setIsDecl(true).build()) + .setValueExpr( + NewObjectExpr.builder() + .setType(FIXED_REST_TYPESTORE.get("ArrayList")) + .setIsGeneric(true) + .build()) + .build()); + + for (VariableExpr methodDescriptorVarExpr : methodDescriptorVarExprs) { + bodyExprs.add( + MethodInvocationExpr.builder() + .setExprReferenceExpr(methodDescriptorsVarExpr) + .setMethodName("add") + .setArguments(methodDescriptorVarExpr) + .build()); + } + + return MethodDefinition.builder() + .setScope(ScopeNode.PUBLIC) + .setIsStatic(true) + .setReturnType(methodDescriptorsVarExpr.type()) + .setReturnExpr(methodDescriptorsVarExpr) + .setAnnotations( + Arrays.asList( + AnnotationNode.withType( + TypeNode.withReference(ConcreteReference.withClazz(InternalApi.class))))) + .setName("getMethodDescriptors") + .setBody(bodyExprs.stream().map(ExprStatement::withExpr).collect(Collectors.toList())) + .build(); + } + + private static Expr createBodyFieldsExtractorClassInstance( + Method method, + TypeNode extractorReturnType, + Set httpBindingFieldNames, + String serializerMethodName, + boolean asteriskBody, + boolean restNumericEnumEnabled) { + List bodyStatements = new ArrayList<>(); + + Expr returnExpr = null; + Expr serializerExpr = + MethodInvocationExpr.builder() + .setMethodName("create") + .setStaticReferenceType( + FIXED_REST_TYPESTORE.get(ProtoRestSerializer.class.getSimpleName())) + .build(); + + VariableExpr requestVarExpr = + VariableExpr.withVariable( + Variable.builder().setType(method.inputType()).setName("request").build()); + Expr bodyRequestExpr = requestVarExpr; + String requestMethodPrefix = "get"; + String bodyParamName = null; + + if (asteriskBody) { + bodyRequestExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(requestVarExpr) + .setMethodName("toBuilder") + .build(); + requestMethodPrefix = "clear"; + } + + Expr prevExpr = bodyRequestExpr; + for (HttpBinding httpBindingFieldName : httpBindingFieldNames) { + MethodInvocationExpr.Builder requestFieldMethodExprBuilder = + MethodInvocationExpr.builder().setExprReferenceExpr(prevExpr); + bodyParamName = + !Strings.isNullOrEmpty(httpBindingFieldName.jsonName()) + ? httpBindingFieldName.jsonName() + : JavaStyle.toLowerCamelCase(httpBindingFieldName.name()); + String[] descendantFields = httpBindingFieldName.name().split("\\."); + if (asteriskBody && descendantFields.length > 1) { + continue; + } + + for (int i = 0; i < descendantFields.length; i++) { + String currFieldName = descendantFields[i]; + String bindingFieldMethodName = + String.format("%s%s", requestMethodPrefix, JavaStyle.toUpperCamelCase(currFieldName)); + requestFieldMethodExprBuilder = + requestFieldMethodExprBuilder.setMethodName(bindingFieldMethodName); + + if (i < descendantFields.length - 1) { + requestFieldMethodExprBuilder = + MethodInvocationExpr.builder() + .setExprReferenceExpr(requestFieldMethodExprBuilder.build()); + } + } + prevExpr = requestFieldMethodExprBuilder.build(); + } + + if (httpBindingFieldNames.isEmpty() && !asteriskBody) { + returnExpr = ValueExpr.createNullExpr(); + } else { + ImmutableList.Builder paramsPutArgs = ImmutableList.builder(); + if (asteriskBody) { + prevExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(prevExpr) + .setMethodName(BUILD_METHOD_NAME) + .build(); + bodyParamName = "*"; + } + paramsPutArgs.add(ValueExpr.withValue(StringObjectValue.withValue(bodyParamName))); + paramsPutArgs.add(prevExpr); + + PrimitiveValue primitiveValue = + PrimitiveValue.builder() + .setType(TypeNode.BOOLEAN) + .setValue(String.valueOf(restNumericEnumEnabled)) + .build(); + paramsPutArgs.add(ValueExpr.withValue(primitiveValue)); + + returnExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(serializerExpr) + .setMethodName(serializerMethodName) + .setArguments(paramsPutArgs.build()) + .setReturnType(extractorReturnType) + .build(); + } + + return LambdaExpr.builder() + .setArguments(requestVarExpr.toBuilder().setIsDecl(true).build()) + .setBody(bodyStatements) + .setReturnExpr(returnExpr) + .build(); + } + + private static Expr createFieldsExtractorClassInstance( + Method method, + TypeNode extractorReturnType, + Set httpBindingFieldNames, + String serializerMethodName, + boolean restNumericEnumsEnabled) { + List bodyStatements = new ArrayList<>(); + + VariableExpr fieldsVarExpr = + VariableExpr.withVariable( + Variable.builder().setName("fields").setType(extractorReturnType).build()); + Expr fieldsAssignExpr = + AssignmentExpr.builder() + .setVariableExpr(fieldsVarExpr.toBuilder().setIsDecl(true).build()) + .setValueExpr( + NewObjectExpr.builder() + .setType(FIXED_REST_TYPESTORE.get(HashMap.class.getSimpleName())) + .setIsGeneric(true) + .build()) + .build(); + + bodyStatements.add(ExprStatement.withExpr(fieldsAssignExpr)); + + TypeNode serializerVarType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(ProtoRestSerializer.class) + .setGenerics(method.inputType().reference()) + .build()); + + VariableExpr serializerVarExpr = + VariableExpr.withVariable( + Variable.builder().setName("serializer").setType(serializerVarType).build()); + + Expr serializerAssignExpr = + AssignmentExpr.builder() + .setVariableExpr(serializerVarExpr.toBuilder().setIsDecl(true).build()) + .setValueExpr( + MethodInvocationExpr.builder() + .setStaticReferenceType( + FIXED_REST_TYPESTORE.get(ProtoRestSerializer.class.getSimpleName())) + .setMethodName("create") + .setReturnType(serializerVarType) + .build()) + .build(); + + bodyStatements.add(ExprStatement.withExpr(serializerAssignExpr)); + + VariableExpr requestVarExpr = + VariableExpr.withVariable( + Variable.builder().setType(method.inputType()).setName("request").build()); + + for (HttpBinding httpBindingFieldName : httpBindingFieldNames) { + MethodInvocationExpr.Builder requestFieldGetterExprBuilder = + MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr); + MethodInvocationExpr.Builder requestFieldHasExprBuilder = + MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr); + String[] descendantFields = httpBindingFieldName.name().split("\\."); + for (int i = 0; i < descendantFields.length; i++) { + String currFieldName = descendantFields[i]; + String bindingFieldMethodName = + getBindingFieldMethodName( + httpBindingFieldName, + descendantFields.length, + i, + JavaStyle.toUpperCamelCase(currFieldName)); + requestFieldGetterExprBuilder = + requestFieldGetterExprBuilder.setMethodName(bindingFieldMethodName); + + String bindingFieldHasMethodName = + (i < descendantFields.length - 1) + ? bindingFieldMethodName + : String.format("has%s", JavaStyle.toUpperCamelCase(currFieldName)); + requestFieldHasExprBuilder = + requestFieldHasExprBuilder + .setMethodName(bindingFieldHasMethodName) + .setReturnType(TypeNode.BOOLEAN); + + if (i < descendantFields.length - 1) { + requestFieldGetterExprBuilder = + MethodInvocationExpr.builder() + .setExprReferenceExpr(requestFieldGetterExprBuilder.build()); + requestFieldHasExprBuilder = + MethodInvocationExpr.builder() + .setExprReferenceExpr(requestFieldHasExprBuilder.build()); + } + } + + MethodInvocationExpr requestBuilderExpr = requestFieldGetterExprBuilder.build(); + MethodInvocationExpr requestHasExpr = requestFieldHasExprBuilder.build(); + + ImmutableList.Builder paramsPutArgs = ImmutableList.builder(); + paramsPutArgs.add(fieldsVarExpr); + paramsPutArgs.add( + ValueExpr.withValue( + StringObjectValue.withValue( + (httpBindingFieldName.jsonName() != null) + ? httpBindingFieldName.jsonName() + : JavaStyle.toLowerCamelCase(httpBindingFieldName.name())))); + paramsPutArgs.add(requestBuilderExpr); + + Expr paramsPutExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(serializerVarExpr) + .setMethodName(serializerMethodName) + .setArguments(paramsPutArgs.build()) + .setReturnType(extractorReturnType) + .build(); + + if (httpBindingFieldName.isOptional()) { + bodyStatements.add( + IfStatement.builder() + .setConditionExpr(requestHasExpr) + .setBody(Arrays.asList(ExprStatement.withExpr(paramsPutExpr))) + .build()); + } else { + bodyStatements.add(ExprStatement.withExpr(paramsPutExpr)); + } + } + + if (restNumericEnumsEnabled && serializerMethodName.equals("putQueryParam")) { + ImmutableList.Builder paramsPutArgs = ImmutableList.builder(); + paramsPutArgs.add(fieldsVarExpr); + paramsPutArgs.add(ValueExpr.withValue(StringObjectValue.withValue("$alt"))); + paramsPutArgs.add(ValueExpr.withValue(StringObjectValue.withValue("json;enum-encoding=int"))); + + Expr paramsPutExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(serializerVarExpr) + .setMethodName(serializerMethodName) + .setArguments(paramsPutArgs.build()) + .setReturnType(extractorReturnType) + .build(); + bodyStatements.add(ExprStatement.withExpr(paramsPutExpr)); + } + + return LambdaExpr.builder() + .setArguments(requestVarExpr.toBuilder().setIsDecl(true).build()) + .setBody(bodyStatements) + .setReturnExpr(fieldsVarExpr) + .build(); + } + + static String getBindingFieldMethodName( + HttpBinding httpBindingField, int descendantFieldsLengths, int index, String currFieldName) { + if (index == descendantFieldsLengths - 1) { + if (httpBindingField.isRepeated()) { + return String.format("get%sList", currFieldName); + } + if (httpBindingField.isEnum()) { + return String.format("get%sValue", currFieldName); + } + } + return String.format("get%s", currFieldName); + } +} diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceResumableUploadStubClassComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceResumableUploadStubClassComposer.java new file mode 100644 index 000000000000..9d1fc2bd1808 --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceResumableUploadStubClassComposer.java @@ -0,0 +1,963 @@ +// 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.rest; + +import com.google.api.core.BetaApi; +import com.google.api.core.InternalApi; +import com.google.api.gax.core.BackgroundResource; +import com.google.api.gax.core.BackgroundResourceAggregation; +import com.google.api.gax.core.GaxProperties; +import com.google.api.gax.httpjson.ApiMethodDescriptor; +import com.google.api.gax.httpjson.GaxHttpJsonProperties; +import com.google.api.gax.httpjson.HttpJsonCallContext; +import com.google.api.gax.httpjson.HttpJsonCallSettings; +import com.google.api.gax.httpjson.HttpJsonCallableFactory; +import com.google.api.gax.httpjson.HttpJsonTransportChannel; +import com.google.api.gax.httpjson.ManagedHttpJsonChannel; +import com.google.api.gax.httpjson.ProtoMessageRequestFormatter; +import com.google.api.gax.httpjson.ProtoMessageResponseParser; +import com.google.api.gax.httpjson.ProtoRestSerializer; +import com.google.api.gax.rpc.ApiClientHeaderProvider; +import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.ResumableUploadCallable; +import com.google.api.generator.engine.ast.AnnotationNode; +import com.google.api.generator.engine.ast.AssignmentExpr; +import com.google.api.generator.engine.ast.ClassDefinition; +import com.google.api.generator.engine.ast.CommentStatement; +import com.google.api.generator.engine.ast.ConcreteReference; +import com.google.api.generator.engine.ast.EmptyLineStatement; +import com.google.api.generator.engine.ast.Expr; +import com.google.api.generator.engine.ast.ExprStatement; +import com.google.api.generator.engine.ast.IfStatement; +import com.google.api.generator.engine.ast.InstanceofExpr; +import com.google.api.generator.engine.ast.JavaDocComment; +import com.google.api.generator.engine.ast.LambdaExpr; +import com.google.api.generator.engine.ast.MethodDefinition; +import com.google.api.generator.engine.ast.MethodInvocationExpr; +import com.google.api.generator.engine.ast.NewObjectExpr; +import com.google.api.generator.engine.ast.RelationalOperationExpr; +import com.google.api.generator.engine.ast.ReturnExpr; +import com.google.api.generator.engine.ast.ScopeNode; +import com.google.api.generator.engine.ast.Statement; +import com.google.api.generator.engine.ast.StringObjectValue; +import com.google.api.generator.engine.ast.TernaryExpr; +import com.google.api.generator.engine.ast.ThisObjectValue; +import com.google.api.generator.engine.ast.TypeNode; +import com.google.api.generator.engine.ast.ValueExpr; +import com.google.api.generator.engine.ast.Variable; +import com.google.api.generator.engine.ast.VariableExpr; +import com.google.api.generator.gapic.composer.comment.CommentComposer; +import com.google.api.generator.gapic.composer.common.BackgroundResourceMethodComposer; +import com.google.api.generator.gapic.composer.common.ClassComposer; +import com.google.api.generator.gapic.composer.store.TypeStore; +import com.google.api.generator.gapic.composer.utils.PackageChecker; +import com.google.api.generator.gapic.model.GapicClass; +import com.google.api.generator.gapic.model.GapicClass.Kind; +import com.google.api.generator.gapic.model.GapicContext; +import com.google.api.generator.gapic.model.Method; +import com.google.api.generator.gapic.model.Service; +import com.google.api.generator.gapic.utils.JavaStyle; +import com.google.common.collect.Maps; +import com.google.protobuf.TypeRegistry; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import javax.annotation.Generated; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class HttpJsonServiceResumableUploadStubClassComposer implements ClassComposer { + private static final HttpJsonServiceResumableUploadStubClassComposer INSTANCE = + new HttpJsonServiceResumableUploadStubClassComposer(); + + private static final Statement EMPTY_LINE_STATEMENT = EmptyLineStatement.create(); + + /** + * The canonical path prefix defined by the Unified Resumable Upload protocol, prepended to the + * start URI path of every resumable upload RPC. Not service-specific: GFE routes {@code + * /resumable/upload} to the upload service for every API that enables resumable uploads. Making + * this configurable is future work. + */ + private static final String RESUMABLE_UPLOAD_PATH_PREFIX = "resumable/upload"; + + private static final List> STATIC_TYPES = + Arrays.asList( + ApiClientHeaderProvider.class, + ApiMethodDescriptor.class, + Arrays.class, + BackgroundResource.class, + BackgroundResourceAggregation.class, + BetaApi.class, + ClientContext.class, + Collections.class, + GaxHttpJsonProperties.class, + GaxProperties.class, + Generated.class, + HashMap.class, + HttpJsonCallContext.class, + HttpJsonCallSettings.class, + HttpJsonCallableFactory.class, + HttpJsonTransportChannel.class, + InternalApi.class, + InterruptedException.class, + List.class, + ManagedHttpJsonChannel.class, + Maps.class, + NullMarked.class, + ProtoMessageRequestFormatter.class, + ProtoMessageResponseParser.class, + ProtoRestSerializer.class, + ResumableUploadCallable.class, + TimeUnit.class, + TypeRegistry.class); + + private static final TypeStore FIXED_TYPESTORE = new TypeStore(STATIC_TYPES); + + private static final String BUILD_METHOD_NAME = "build"; + private static final String NEW_BUILDER_METHOD_NAME = "newBuilder"; + + private static final String DERIVE_CLIENT_CONTEXT_METHOD_NAME = "deriveHttpJsonClientContext"; + private static final String STUB_SETTINGS_PATTERN = "%sStubSettings"; + private static final String RESUMABLE_UPLOAD_STUB_PATTERN = "HttpJson%sResumableUploadStub"; + + protected HttpJsonServiceResumableUploadStubClassComposer() {} + + public static HttpJsonServiceResumableUploadStubClassComposer instance() { + return INSTANCE; + } + + @Override + public GapicClass generate(GapicContext context, Service service) { + List resumableUploadMethods = + service.methods().stream().filter(Method::isResumableUpload).collect(Collectors.toList()); + if (resumableUploadMethods.isEmpty()) { + return GapicClass.createNonGeneratedGapicClass(); + } + + String pakkage = service.pakkage() + ".stub"; + String className = String.format(RESUMABLE_UPLOAD_STUB_PATTERN, service.name()); + TypeStore typeStore = createDynamicTypes(service, pakkage, resumableUploadMethods); + + // Member: typeRegistry + VariableExpr typeRegistryVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder() + .setName("typeRegistry") + .setType(FIXED_TYPESTORE.get(TypeRegistry.class.getSimpleName())) + .build()) + .build(); + + Statement typeRegistryDecl = + ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr( + typeRegistryVarExpr.toBuilder() + .setIsDecl(true) + .setScope(ScopeNode.PRIVATE) + .setIsStatic(true) + .setIsFinal(true) + .build()) + .setValueExpr( + MethodInvocationExpr.builder() + .setExprReferenceExpr( + MethodInvocationExpr.builder() + .setStaticReferenceType( + FIXED_TYPESTORE.get(TypeRegistry.class.getSimpleName())) + .setMethodName(NEW_BUILDER_METHOD_NAME) + .build()) + .setMethodName(BUILD_METHOD_NAME) + .setReturnType(FIXED_TYPESTORE.get(TypeRegistry.class.getSimpleName())) + .build()) + .build()); + + // Method descriptors + Map protoMethodNameToDescriptorVarExprs = new LinkedHashMap<>(); + List methodDescriptorDecls = new ArrayList<>(); + for (Method method : resumableUploadMethods) { + String javaStyleName = JavaStyle.toLowerCamelCase(method.name()); + VariableExpr descriptorVarExpr = + VariableExpr.withVariable( + Variable.builder() + .setName(String.format("%sMethodDescriptor", javaStyleName)) + .setType( + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(ApiMethodDescriptor.class) + .setGenerics( + Arrays.asList( + method.inputType().reference(), + method.outputType().reference())) + .build())) + .build()); + protoMethodNameToDescriptorVarExprs.put(method.name(), descriptorVarExpr); + methodDescriptorDecls.add( + createMethodDescriptorVariableDecl( + service, method, descriptorVarExpr, context.restNumericEnumsEnabled())); + } + + // Member: backgroundResources + VariableExpr backgroundResourcesVarExpr = + VariableExpr.withVariable( + Variable.builder() + .setName("backgroundResources") + .setType(FIXED_TYPESTORE.get(BackgroundResource.class.getSimpleName())) + .build()); + + // Members: callables + Map callableClassMembers = new LinkedHashMap<>(); + for (Method method : resumableUploadMethods) { + String javaStyleName = JavaStyle.toLowerCamelCase(method.name()); + String callableName = String.format("%sCallable", javaStyleName); + TypeNode callableType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(ResumableUploadCallable.class) + .setGenerics( + Arrays.asList( + method.inputType().reference(), method.outputType().reference())) + .build()); + callableClassMembers.put( + callableName, + VariableExpr.withVariable( + Variable.builder().setName(callableName).setType(callableType).build())); + } + + // Class statements + List classStatements = new ArrayList<>(); + classStatements.add(typeRegistryDecl); + classStatements.add(EMPTY_LINE_STATEMENT); + classStatements.addAll(methodDescriptorDecls); + classStatements.add(EMPTY_LINE_STATEMENT); + classStatements.add( + ExprStatement.withExpr( + backgroundResourcesVarExpr.toBuilder() + .setIsDecl(true) + .setScope(ScopeNode.PRIVATE) + .setIsFinal(true) + .build())); + for (VariableExpr callableVar : callableClassMembers.values()) { + classStatements.add( + ExprStatement.withExpr( + callableVar.toBuilder() + .setIsDecl(true) + .setScope(ScopeNode.PRIVATE) + .setIsFinal(true) + .build())); + } + + // Methods + List methodDefinitions = new ArrayList<>(); + methodDefinitions.addAll(createStaticCreatorMethods(service, typeStore, className)); + methodDefinitions.addAll( + createConstructorMethods( + service, + typeStore, + className, + backgroundResourcesVarExpr, + callableClassMembers, + protoMethodNameToDescriptorVarExprs, + resumableUploadMethods)); + methodDefinitions.addAll(createCallableGetterMethods(callableClassMembers)); + methodDefinitions.addAll(createBackgroundResourceMethods(backgroundResourcesVarExpr)); + + ClassDefinition classDef = + ClassDefinition.builder() + .setPackageString(pakkage) + .setHeaderCommentStatements(createClassHeaderComments(service)) + .setAnnotations(createClassAnnotations(service)) + .setScope(ScopeNode.PUBLIC) + .setName(className) + .setImplementsTypes( + Arrays.asList(FIXED_TYPESTORE.get(BackgroundResource.class.getSimpleName()))) + .setMethods(methodDefinitions) + .setStatements(classStatements) + .build(); + + return GapicClass.create(Kind.STUB, classDef); + } + + private static List createStaticCreatorMethods( + Service service, TypeStore typeStore, String className) { + TypeNode thisClassType = typeStore.get(className); + TypeNode clientContextType = FIXED_TYPESTORE.get(ClientContext.class.getSimpleName()); + TypeNode transportChannelType = + FIXED_TYPESTORE.get(HttpJsonTransportChannel.class.getSimpleName()); + TypeNode stubSettingsType = typeStore.get(String.format(STUB_SETTINGS_PATTERN, service.name())); + + VariableExpr clientContextVarExpr = + VariableExpr.withVariable( + Variable.builder().setName("clientContext").setType(clientContextType).build()); + VariableExpr settingsVarExpr = + VariableExpr.withVariable( + Variable.builder().setName("settings").setType(stubSettingsType).build()); + + Statement reuseHttpJsonContextStmt = + IfStatement.builder() + .setConditionExpr( + InstanceofExpr.builder() + .setExpr( + MethodInvocationExpr.builder() + .setExprReferenceExpr(clientContextVarExpr) + .setMethodName("getTransportChannel") + .build()) + .setCheckType(transportChannelType) + .build()) + .setBody( + Arrays.asList( + ExprStatement.withExpr( + ReturnExpr.withExpr( + NewObjectExpr.builder() + .setType(thisClassType) + .setArguments(Arrays.asList(clientContextVarExpr, settingsVarExpr)) + .build())))) + .build(); + + CommentStatement methodComment = + CommentStatement.withComment( + JavaDocComment.builder() + .addComment( + "Constructs an instance from the given client context. An HTTP/JSON context is" + + " used as is. A context built for any other transport is replaced by one" + + " carrying a dedicated HTTP/JSON transport for the upload, which is" + + " registered as a background resource and closed with the caller.") + .build()); + + MethodDefinition createMethod = + MethodDefinition.builder() + .setHeaderCommentStatements(Arrays.asList(methodComment)) + .setScope(ScopeNode.PUBLIC) + .setIsStatic(true) + .setReturnType(thisClassType) + .setName("create") + .setArguments( + Arrays.asList( + clientContextVarExpr.toBuilder().setIsDecl(true).build(), + settingsVarExpr.toBuilder().setIsDecl(true).build())) + .setBody(Arrays.asList(reuseHttpJsonContextStmt)) + .setReturnExpr( + NewObjectExpr.builder() + .setType(thisClassType) + .setArguments( + Arrays.asList( + MethodInvocationExpr.builder() + .setMethodName(DERIVE_CLIENT_CONTEXT_METHOD_NAME) + .setArguments(Arrays.asList(clientContextVarExpr, settingsVarExpr)) + .setReturnType(clientContextType) + .build(), + settingsVarExpr)) + .build()) + .build(); + + return Arrays.asList( + createMethod, + createDeriveHttpJsonClientContextMethod( + service, stubSettingsType, clientContextVarExpr, settingsVarExpr)); + } + + private static MethodDefinition createDeriveHttpJsonClientContextMethod( + Service service, + TypeNode stubSettingsType, + VariableExpr clientContextVarExpr, + VariableExpr settingsVarExpr) { + TypeNode clientContextType = FIXED_TYPESTORE.get(ClientContext.class.getSimpleName()); + TypeNode managedChannelType = FIXED_TYPESTORE.get(ManagedHttpJsonChannel.class.getSimpleName()); + TypeNode transportChannelType = + FIXED_TYPESTORE.get(HttpJsonTransportChannel.class.getSimpleName()); + TypeNode callContextType = FIXED_TYPESTORE.get(HttpJsonCallContext.class.getSimpleName()); + + MethodInvocationExpr getCredentialsExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(clientContextVarExpr) + .setMethodName("getCredentials") + .setReturnType(TypeNode.OBJECT) + .build(); + Expr getExecutorExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(clientContextVarExpr) + .setMethodName("getExecutor") + .build(); + Expr getHeadersExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(clientContextVarExpr) + .setMethodName("getHeaders") + .build(); + Expr getClockExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(clientContextVarExpr) + .setMethodName("getClock") + .build(); + + // The context's endpoint wins: on the create(ClientContext) path the settings are defaults. + Expr contextEndpointExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(clientContextVarExpr) + .setMethodName("getEndpoint") + .setReturnType(TypeNode.STRING) + .build(); + Expr settingsEndpointExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(settingsVarExpr) + .setMethodName("getEndpoint") + .setReturnType(TypeNode.STRING) + .build(); + VariableExpr uploadEndpointVarExpr = + VariableExpr.withVariable( + Variable.builder().setName("uploadEndpoint").setType(TypeNode.STRING).build()); + Statement uploadEndpointDeclStmt = + ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr(uploadEndpointVarExpr.toBuilder().setIsDecl(true).build()) + .setValueExpr( + TernaryExpr.builder() + .setConditionExpr( + RelationalOperationExpr.notEqualToWithExprs( + contextEndpointExpr, ValueExpr.createNullExpr())) + .setThenExpr(contextEndpointExpr) + .setElseExpr(settingsEndpointExpr) + .build()) + .build()); + + VariableExpr httpJsonManagedChannelVarExpr = + VariableExpr.withVariable( + Variable.builder() + .setName("httpJsonManagedChannel") + .setType(managedChannelType) + .build()); + Expr channelBuilderExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(managedChannelType) + .setMethodName(NEW_BUILDER_METHOD_NAME) + .build(); + channelBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(channelBuilderExpr) + .setMethodName("setEndpoint") + .setArguments(Arrays.asList(uploadEndpointVarExpr)) + .build(); + channelBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(channelBuilderExpr) + .setMethodName("setExecutor") + .setArguments(Arrays.asList(getExecutorExpr)) + .build(); + channelBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(channelBuilderExpr) + .setMethodName(BUILD_METHOD_NAME) + .setReturnType(managedChannelType) + .build(); + Statement httpJsonManagedChannelDeclStmt = + ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr(httpJsonManagedChannelVarExpr.toBuilder().setIsDecl(true).build()) + .setValueExpr(channelBuilderExpr) + .build()); + + VariableExpr httpJsonTransportChannelVarExpr = + VariableExpr.withVariable( + Variable.builder() + .setName("httpJsonTransportChannel") + .setType(transportChannelType) + .build()); + Statement httpJsonTransportChannelDeclStmt = + ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr( + httpJsonTransportChannelVarExpr.toBuilder().setIsDecl(true).build()) + .setValueExpr( + MethodInvocationExpr.builder() + .setStaticReferenceType(transportChannelType) + .setMethodName("create") + .setArguments(Arrays.asList(httpJsonManagedChannelVarExpr)) + .setReturnType(transportChannelType) + .build()) + .build()); + + // Internal headers are rebuilt for HTTP/JSON rather than forwarded: the caller's + // x-goog-api-client may report a different transport. External headers take precedence. + // Built inline rather than read off the settings class, which exposes no HTTP/JSON header + // provider when the library is generated for gRPC only. + TypeNode gaxHttpJsonPropertiesType = + FIXED_TYPESTORE.get(GaxHttpJsonProperties.class.getSimpleName()); + Expr internalHeadersExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType( + FIXED_TYPESTORE.get(ApiClientHeaderProvider.class.getSimpleName())) + .setMethodName(NEW_BUILDER_METHOD_NAME) + .build(); + internalHeadersExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(internalHeadersExpr) + .setMethodName("setGeneratedLibToken") + .setArguments( + ValueExpr.withValue(StringObjectValue.withValue("gapic")), + MethodInvocationExpr.builder() + .setStaticReferenceType( + FIXED_TYPESTORE.get(GaxProperties.class.getSimpleName())) + .setMethodName("getLibraryVersion") + .setArguments( + VariableExpr.builder() + .setVariable( + Variable.builder() + .setType(TypeNode.CLASS_OBJECT) + .setName("class") + .build()) + .setStaticReferenceType(stubSettingsType) + .build()) + .build()) + .build(); + internalHeadersExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(internalHeadersExpr) + .setMethodName("setTransportToken") + .setArguments( + MethodInvocationExpr.builder() + .setStaticReferenceType(gaxHttpJsonPropertiesType) + .setMethodName("getHttpJsonTokenName") + .build(), + MethodInvocationExpr.builder() + .setStaticReferenceType(gaxHttpJsonPropertiesType) + .setMethodName("getHttpJsonVersion") + .build()) + .build(); + internalHeadersExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(internalHeadersExpr) + .setMethodName("setQuotaProjectIdToken") + .setArguments( + MethodInvocationExpr.builder() + .setExprReferenceExpr(clientContextVarExpr) + .setMethodName("getQuotaProjectId") + .setReturnType(TypeNode.STRING) + .build()) + .build(); + if (service.hasApiVersion()) { + internalHeadersExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(internalHeadersExpr) + .setMethodName("setApiVersionToken") + .setArguments(ValueExpr.withValue(StringObjectValue.withValue(service.apiVersion()))) + .build(); + } + internalHeadersExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(internalHeadersExpr) + .setMethodName(BUILD_METHOD_NAME) + .build(); + internalHeadersExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(internalHeadersExpr) + .setMethodName("getHeaders") + .build(); + + TypeNode stringToStringMapType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(Map.class) + .setGenerics( + Arrays.asList(TypeNode.STRING.reference(), TypeNode.STRING.reference())) + .build()); + VariableExpr uploadHeadersVarExpr = + VariableExpr.withVariable( + Variable.builder().setName("uploadHeaders").setType(stringToStringMapType).build()); + Statement uploadHeadersDeclStmt = + ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr(uploadHeadersVarExpr.toBuilder().setIsDecl(true).build()) + .setValueExpr( + NewObjectExpr.builder() + .setType(FIXED_TYPESTORE.get(HashMap.class.getSimpleName())) + .setIsGeneric(true) + .setArguments(Arrays.asList(internalHeadersExpr)) + .build()) + .build()); + Statement uploadHeadersPutAllStmt = + ExprStatement.withExpr( + MethodInvocationExpr.builder() + .setExprReferenceExpr(uploadHeadersVarExpr) + .setMethodName("putAll") + .setArguments(Arrays.asList(getHeadersExpr)) + .build()); + + VariableExpr headerValueVarExpr = + VariableExpr.withVariable( + Variable.builder().setName("value").setType(TypeNode.STRING).build()); + Expr extraHeadersExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(FIXED_TYPESTORE.get(Maps.class.getSimpleName())) + .setMethodName("transformValues") + .setArguments( + Arrays.asList( + uploadHeadersVarExpr, + LambdaExpr.builder() + .setArguments(headerValueVarExpr.toBuilder().setIsDecl(true).build()) + .setReturnExpr( + MethodInvocationExpr.builder() + .setStaticReferenceType( + FIXED_TYPESTORE.get(Collections.class.getSimpleName())) + .setMethodName("singletonList") + .setArguments(Arrays.asList(headerValueVarExpr)) + .build()) + .build())) + .build(); + + // The resumable upload callables read credentials and headers exclusively from the default call + // context, so they must be attached here rather than to the ClientContext itself. + Expr defaultCallContextExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(callContextType) + .setMethodName("createDefault") + .build(); + defaultCallContextExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(defaultCallContextExpr) + .setMethodName("withTransportChannel") + .setArguments(Arrays.asList(httpJsonTransportChannelVarExpr)) + .setReturnType(callContextType) + .build(); + defaultCallContextExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(defaultCallContextExpr) + .setMethodName("withCredentials") + .setArguments(Arrays.asList(getCredentialsExpr)) + .setReturnType(callContextType) + .build(); + defaultCallContextExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(defaultCallContextExpr) + .setMethodName("withExtraHeaders") + .setArguments(Arrays.asList(extraHeadersExpr)) + .setReturnType(callContextType) + .build(); + + Expr backgroundResourcesExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(FIXED_TYPESTORE.get(Arrays.class.getSimpleName())) + .setMethodName("asList") + .setArguments(Arrays.asList(httpJsonTransportChannelVarExpr)) + .build(); + + Expr contextBuilderExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(clientContextType) + .setMethodName(NEW_BUILDER_METHOD_NAME) + .build(); + contextBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(contextBuilderExpr) + .setMethodName("setCredentials") + .setArguments(Arrays.asList(getCredentialsExpr)) + .build(); + contextBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(contextBuilderExpr) + .setMethodName("setEndpoint") + .setArguments(Arrays.asList(uploadEndpointVarExpr)) + .build(); + contextBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(contextBuilderExpr) + .setMethodName("setExecutor") + .setArguments(Arrays.asList(getExecutorExpr)) + .build(); + contextBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(contextBuilderExpr) + .setMethodName("setClock") + .setArguments(Arrays.asList(getClockExpr)) + .build(); + contextBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(contextBuilderExpr) + .setMethodName("setTransportChannel") + .setArguments(Arrays.asList(httpJsonTransportChannelVarExpr)) + .build(); + contextBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(contextBuilderExpr) + .setMethodName("setDefaultCallContext") + .setArguments(Arrays.asList(defaultCallContextExpr)) + .build(); + contextBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(contextBuilderExpr) + .setMethodName("setBackgroundResources") + .setArguments(Arrays.asList(backgroundResourcesExpr)) + .build(); + contextBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(contextBuilderExpr) + .setMethodName(BUILD_METHOD_NAME) + .setReturnType(clientContextType) + .build(); + + return MethodDefinition.builder() + .setScope(ScopeNode.PRIVATE) + .setIsStatic(true) + .setReturnType(clientContextType) + .setName(DERIVE_CLIENT_CONTEXT_METHOD_NAME) + .setArguments( + Arrays.asList( + clientContextVarExpr.toBuilder().setIsDecl(true).build(), + settingsVarExpr.toBuilder().setIsDecl(true).build())) + .setBody( + Arrays.asList( + uploadEndpointDeclStmt, + httpJsonManagedChannelDeclStmt, + httpJsonTransportChannelDeclStmt, + uploadHeadersDeclStmt, + uploadHeadersPutAllStmt)) + .setReturnExpr(contextBuilderExpr) + .build(); + } + + private static List createConstructorMethods( + Service service, + TypeStore typeStore, + String className, + VariableExpr backgroundResourcesVarExpr, + Map callableClassMembers, + Map protoMethodNameToDescriptorVarExprs, + List resumableUploadMethods) { + TypeNode thisClassType = typeStore.get(className); + TypeNode clientContextType = FIXED_TYPESTORE.get(ClientContext.class.getSimpleName()); + TypeNode stubSettingsType = typeStore.get(String.format(STUB_SETTINGS_PATTERN, service.name())); + + VariableExpr clientContextVarExpr = + VariableExpr.withVariable( + Variable.builder().setName("clientContext").setType(clientContextType).build()); + VariableExpr settingsVarExpr = + VariableExpr.withVariable( + Variable.builder().setName("settings").setType(stubSettingsType).build()); + + Expr thisExpr = ValueExpr.withValue(ThisObjectValue.withType(thisClassType)); + List ctorStatements = new ArrayList<>(); + + for (Method method : resumableUploadMethods) { + String javaStyleName = JavaStyle.toLowerCamelCase(method.name()); + VariableExpr descriptorVarExpr = protoMethodNameToDescriptorVarExprs.get(method.name()); + VariableExpr callableVarExpr = + callableClassMembers.get(String.format("%sCallable", javaStyleName)); + + TypeNode httpJsonCallSettingsType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(HttpJsonCallSettings.class) + .setGenerics( + Arrays.asList( + method.inputType().reference(), method.outputType().reference())) + .build()); + + VariableExpr transportSettingsVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder() + .setName(String.format("%sTransportSettings", javaStyleName)) + .setType(httpJsonCallSettingsType) + .build()) + .setIsDecl(true) + .build(); + + MethodInvocationExpr callSettingsBuilderExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType( + FIXED_TYPESTORE.get(HttpJsonCallSettings.class.getSimpleName())) + .setGenerics( + Arrays.asList(method.inputType().reference(), method.outputType().reference())) + .setMethodName(NEW_BUILDER_METHOD_NAME) + .build(); + callSettingsBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(callSettingsBuilderExpr) + .setMethodName("setMethodDescriptor") + .setArguments(Arrays.asList(descriptorVarExpr)) + .build(); + callSettingsBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(callSettingsBuilderExpr) + .setMethodName("setTypeRegistry") + .setArguments( + Arrays.asList( + VariableExpr.builder() + .setVariable( + Variable.builder() + .setName("typeRegistry") + .setType(FIXED_TYPESTORE.get(TypeRegistry.class.getSimpleName())) + .build()) + .build())) + .build(); + callSettingsBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(callSettingsBuilderExpr) + .setMethodName(BUILD_METHOD_NAME) + .setReturnType(httpJsonCallSettingsType) + .build(); + + ctorStatements.add( + ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr(transportSettingsVarExpr) + .setValueExpr(callSettingsBuilderExpr) + .build())); + + MethodInvocationExpr getSettingsExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(settingsVarExpr) + .setMethodName(String.format("%sSettings", javaStyleName)) + .build(); + + MethodInvocationExpr createCallableExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType( + FIXED_TYPESTORE.get(HttpJsonCallableFactory.class.getSimpleName())) + .setMethodName("createResumableUploadCallable") + .setArguments( + Arrays.asList( + transportSettingsVarExpr.toBuilder().setIsDecl(false).build(), + getSettingsExpr, + clientContextVarExpr)) + .setReturnType(callableVarExpr.type()) + .build(); + + ctorStatements.add( + ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr( + callableVarExpr.toBuilder().setExprReferenceExpr(thisExpr).build()) + .setValueExpr(createCallableExpr) + .build())); + ctorStatements.add(EMPTY_LINE_STATEMENT); + } + + MethodInvocationExpr getBackgroundResourcesExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(clientContextVarExpr) + .setMethodName("getBackgroundResources") + .build(); + + Expr newBackgroundResourceExpr = + NewObjectExpr.builder() + .setType(FIXED_TYPESTORE.get(BackgroundResourceAggregation.class.getSimpleName())) + .setArguments(Arrays.asList(getBackgroundResourcesExpr)) + .build(); + + ctorStatements.add( + ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr( + backgroundResourcesVarExpr.toBuilder().setExprReferenceExpr(thisExpr).build()) + .setValueExpr(newBackgroundResourceExpr) + .build())); + + return Arrays.asList( + MethodDefinition.constructorBuilder() + .setScope(ScopeNode.PRIVATE) + .setReturnType(thisClassType) + .setArguments( + Arrays.asList( + clientContextVarExpr.toBuilder().setIsDecl(true).build(), + settingsVarExpr.toBuilder().setIsDecl(true).build())) + .setBody(ctorStatements) + .build()); + } + + private static Statement createMethodDescriptorVariableDecl( + Service service, + Method protoMethod, + VariableExpr methodDescriptorVarExpr, + boolean restNumericEnumsEnabled) { + return HttpJsonDescriptorComposer.createMethodDescriptorVariableDecl( + service, + protoMethod, + methodDescriptorVarExpr, + restNumericEnumsEnabled, + RESUMABLE_UPLOAD_PATH_PREFIX); + } + + private static List createCallableGetterMethods( + Map callableClassMembers) { + return callableClassMembers.entrySet().stream() + .map( + e -> + MethodDefinition.builder() + .setScope(ScopeNode.PUBLIC) + .setReturnType(e.getValue().type()) + .setName(e.getKey()) + .setReturnExpr(e.getValue()) + .build()) + .collect(Collectors.toList()); + } + + private static List createBackgroundResourceMethods( + VariableExpr backgroundResourcesVarExpr) { + return BackgroundResourceMethodComposer.createBackgroundResourceMethods( + backgroundResourcesVarExpr); + } + + private static List createClassAnnotations(Service service) { + List annotations = new ArrayList<>(); + annotations.add( + AnnotationNode.builder() + .setType(FIXED_TYPESTORE.get(Generated.class.getSimpleName())) + .setDescription("by gapic-generator-java") + .build()); + annotations.add( + AnnotationNode.withType(FIXED_TYPESTORE.get(InternalApi.class.getSimpleName()))); + annotations.add(AnnotationNode.withType(FIXED_TYPESTORE.get(NullMarked.class.getSimpleName()))); + if (!PackageChecker.isGaApi(service.pakkage())) { + annotations.add(AnnotationNode.withType(FIXED_TYPESTORE.get(BetaApi.class.getSimpleName()))); + } + if (service.isDeprecated()) { + annotations.add(AnnotationNode.withType(TypeNode.DEPRECATED)); + } + return annotations; + } + + private static List createClassHeaderComments(Service service) { + JavaDocComment.Builder javaDocBuilder = JavaDocComment.builder(); + return Arrays.asList( + CommentComposer.AUTO_GENERATED_CLASS_COMMENT, + CommentStatement.withComment( + javaDocBuilder + .addComment( + String.format( + "REST stub transport for the resumable upload methods of %s.", + service.name())) + .addParagraph( + "This class is for advanced usage and reflects the underlying API directly.") + .build())); + } + + private static TypeStore createDynamicTypes( + Service service, String pakkage, List resumableUploadMethods) { + TypeStore typeStore = new TypeStore(STATIC_TYPES); + typeStore.put(pakkage, String.format(RESUMABLE_UPLOAD_STUB_PATTERN, service.name())); + typeStore.put(pakkage, String.format(STUB_SETTINGS_PATTERN, service.name())); + for (Method method : resumableUploadMethods) { + typeStore.put( + method.inputType().reference().pakkage(), method.inputType().reference().name()); + typeStore.put( + method.outputType().reference().pakkage(), method.outputType().reference().name()); + } + return typeStore; + } +} diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java index f3ca00f9db0f..685f3784bc8d 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposer.java @@ -17,7 +17,6 @@ import com.google.api.HttpRule; import com.google.api.core.InternalApi; import com.google.api.gax.httpjson.ApiMethodDescriptor; -import com.google.api.gax.httpjson.ApiMethodDescriptor.MethodType; import com.google.api.gax.httpjson.FieldsExtractor; import com.google.api.gax.httpjson.HttpJsonCallSettings; import com.google.api.gax.httpjson.HttpJsonLongRunningClient; @@ -30,13 +29,10 @@ import com.google.api.gax.longrunning.OperationSnapshot; import com.google.api.gax.rpc.LongRunningClient; import com.google.api.gax.rpc.UnaryCallable; -import com.google.api.generator.engine.ast.AnnotationNode; import com.google.api.generator.engine.ast.AssignmentExpr; import com.google.api.generator.engine.ast.ConcreteReference; -import com.google.api.generator.engine.ast.EnumRefExpr; import com.google.api.generator.engine.ast.Expr; import com.google.api.generator.engine.ast.ExprStatement; -import com.google.api.generator.engine.ast.IfStatement; import com.google.api.generator.engine.ast.LambdaExpr; import com.google.api.generator.engine.ast.MethodDefinition; import com.google.api.generator.engine.ast.MethodInvocationExpr; @@ -55,16 +51,12 @@ import com.google.api.generator.gapic.composer.common.AbstractTransportServiceStubClassComposer; import com.google.api.generator.gapic.composer.store.TypeStore; import com.google.api.generator.gapic.model.GapicContext; -import com.google.api.generator.gapic.model.HttpBindings.HttpBinding; import com.google.api.generator.gapic.model.Message; import com.google.api.generator.gapic.model.Method; import com.google.api.generator.gapic.model.OperationResponse; import com.google.api.generator.gapic.model.Service; import com.google.api.generator.gapic.utils.JavaStyle; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Strings; import com.google.common.collect.BiMap; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.protobuf.TypeRegistry; import java.util.ArrayList; @@ -85,8 +77,8 @@ public class HttpJsonServiceStubClassComposer extends AbstractTransportServiceSt private static final HttpJsonServiceStubClassComposer INSTANCE = new HttpJsonServiceStubClassComposer(); - private static final TypeStore FIXED_REST_TYPESTORE = createStaticTypes(); - private static final VariableExpr TYPE_REGISTRY_VAR_EXPR = + static final TypeStore FIXED_REST_TYPESTORE = createStaticTypes(); + static final VariableExpr TYPE_REGISTRY_VAR_EXPR = VariableExpr.builder() .setVariable( Variable.builder() @@ -137,33 +129,11 @@ protected Statement createMethodDescriptorVariableDecl( Map messageTypes, boolean restNumericEnumsEnabled) { MethodInvocationExpr expr = - MethodInvocationExpr.builder() - .setMethodName("newBuilder") - .setStaticReferenceType( - FIXED_REST_TYPESTORE.get(ApiMethodDescriptor.class.getSimpleName())) - .setGenerics(methodDescriptorVarExpr.variable().type().reference().generics()) - .build(); + HttpJsonDescriptorComposer.createMethodDescriptorExpr( + service, protoMethod, methodDescriptorVarExpr, restNumericEnumsEnabled, null); BiFunction, Function> - methodMaker = getMethodMaker(); - - String codeMethodArgName = getProtoRpcFullMethodName(service, protoMethod); - expr = - methodMaker - .apply( - "setFullMethodName", - Arrays.asList(ValueExpr.withValue(StringObjectValue.withValue(codeMethodArgName)))) - .apply(expr); - - expr = methodMaker.apply("setHttpMethod", getHttpMethodTypeExpr(protoMethod)).apply(expr); - expr = methodMaker.apply("setType", getMethodTypeExpr(protoMethod)).apply(expr); - expr = - methodMaker - .apply( - "setRequestFormatter", - getRequestFormatterExpr(protoMethod, restNumericEnumsEnabled)) - .apply(expr); - expr = methodMaker.apply("setResponseParser", setResponseParserExpr(protoMethod)).apply(expr); + methodMaker = HttpJsonDescriptorComposer.getMethodMaker(); if (protoMethod.isOperationPollingMethod() || protoMethod.hasLro()) { expr = @@ -217,192 +187,14 @@ protected List createGetMethodDescriptorsMethod( Service service, TypeStore typeStore, Map protoMethodNameToDescriptorVarExprs) { - - List bodyExprs = new ArrayList<>(); - - VariableExpr methodDescriptorsVarExpr = - VariableExpr.withVariable( - Variable.builder() - .setType( - TypeNode.withReference( - ConcreteReference.builder() - .setClazz(List.class) - .setGenerics( - Arrays.asList( - FIXED_REST_TYPESTORE.get("ApiMethodDescriptor").reference())) - .build())) - .setName("methodDescriptors") - .build()); - - bodyExprs.add( - AssignmentExpr.builder() - .setVariableExpr(methodDescriptorsVarExpr.toBuilder().setIsDecl(true).build()) - .setValueExpr( - NewObjectExpr.builder() - .setType(FIXED_REST_TYPESTORE.get("ArrayList")) - .setIsGeneric(true) - .build()) - .build()); - - for (VariableExpr methodDescriptorVarExpr : protoMethodNameToDescriptorVarExprs.values()) { - bodyExprs.add( - MethodInvocationExpr.builder() - .setExprReferenceExpr(methodDescriptorsVarExpr) - .setMethodName("add") - .setArguments(methodDescriptorVarExpr) - .build()); - } - - return Arrays.asList( - MethodDefinition.builder() - .setScope(ScopeNode.PUBLIC) - .setIsStatic(true) - .setReturnType(methodDescriptorsVarExpr.type()) - .setReturnExpr(methodDescriptorsVarExpr) - .setAnnotations( - Arrays.asList(AnnotationNode.withType(FIXED_REST_TYPESTORE.get("InternalApi")))) - .setName("getMethodDescriptors") - .setBody(bodyExprs.stream().map(ExprStatement::withExpr).collect(Collectors.toList())) - .build()); + return Collections.singletonList( + HttpJsonDescriptorComposer.createGetMethodDescriptorsMethod( + protoMethodNameToDescriptorVarExprs.values())); } - private BiFunction, Function> + static BiFunction, Function> getMethodMaker() { - return (mName, argExpr) -> - (m) -> - MethodInvocationExpr.builder() - .setMethodName(mName) - .setArguments(argExpr) - .setExprReferenceExpr(m) - .build(); - } - - private List getRequestFormatterExpr(Method protoMethod, boolean restNumericEnumsEnabled) { - BiFunction, Function> - methodMaker = getMethodMaker(); - - MethodInvocationExpr expr = - MethodInvocationExpr.builder() - .setStaticReferenceType( - FIXED_REST_TYPESTORE.get(ProtoMessageRequestFormatter.class.getSimpleName())) - .setMethodName("newBuilder") - .setGenerics(Collections.singletonList(protoMethod.inputType().reference())) - .build(); - - TypeNode extractorVarType = - TypeNode.withReference( - ConcreteReference.builder() - .setClazz(Map.class) - .setGenerics(TypeNode.STRING.reference(), TypeNode.STRING.reference()) - .build()); - - expr = - methodMaker - .apply( - "setPath", - Arrays.asList( - ValueExpr.withValue( - StringObjectValue.withValue( - protoMethod.httpBindings().lowerCamelPattern())), - createFieldsExtractorClassInstance( - protoMethod, - extractorVarType, - protoMethod.httpBindings().pathParameters(), - "putPathParam", - restNumericEnumsEnabled))) - .apply(expr); - - if (!protoMethod.httpBindings().lowerCamelAdditionalPatterns().isEmpty()) { - expr = - methodMaker - .apply( - "setAdditionalPaths", - protoMethod.httpBindings().lowerCamelAdditionalPatterns().stream() - .map(a -> ValueExpr.withValue(StringObjectValue.withValue(a))) - .collect(Collectors.toList())) - .apply(expr); - } - TypeNode fieldsVarGenericType = - TypeNode.withReference( - ConcreteReference.builder() - .setClazz(List.class) - .setGenerics(TypeNode.STRING.reference()) - .build()); - - extractorVarType = - TypeNode.withReference( - ConcreteReference.builder() - .setClazz(Map.class) - .setGenerics(TypeNode.STRING.reference(), fieldsVarGenericType.reference()) - .build()); - - expr = - methodMaker - .apply( - "setQueryParamsExtractor", - Arrays.asList( - createFieldsExtractorClassInstance( - protoMethod, - extractorVarType, - protoMethod.httpBindings().queryParameters(), - "putQueryParam", - restNumericEnumsEnabled))) - .apply(expr); - - extractorVarType = TypeNode.STRING; - boolean asteriskBody = protoMethod.httpBindings().isAsteriskBody(); - expr = - methodMaker - .apply( - "setRequestBodyExtractor", - Arrays.asList( - createBodyFieldsExtractorClassInstance( - protoMethod, - extractorVarType, - asteriskBody - ? protoMethod.httpBindings().pathParameters() - : protoMethod.httpBindings().bodyParameters(), - "toBody", - asteriskBody, - restNumericEnumsEnabled))) - .apply(expr); - expr = methodMaker.apply("build", Collections.emptyList()).apply(expr); - - return Collections.singletonList(expr); - } - - private List setResponseParserExpr(Method protoMethod) { - BiFunction, Function> - methodMaker = getMethodMaker(); - - MethodInvocationExpr expr = - MethodInvocationExpr.builder() - .setStaticReferenceType( - FIXED_REST_TYPESTORE.get(ProtoMessageResponseParser.class.getSimpleName())) - .setMethodName("newBuilder") - .setGenerics(Collections.singletonList(protoMethod.outputType().reference())) - // .setArguments(Arrays.asList(m)) - .build(); - - expr = - methodMaker - .apply( - "setDefaultInstance", - Arrays.asList( - MethodInvocationExpr.builder() - .setStaticReferenceType(protoMethod.outputType()) - .setMethodName("getDefaultInstance") - .setReturnType(protoMethod.outputType()) - .build())) - .apply(expr); - - expr = - methodMaker - .apply("setDefaultTypeRegistry", Arrays.asList(TYPE_REGISTRY_VAR_EXPR)) - .apply(expr); - expr = methodMaker.apply("build", Collections.emptyList()).apply(expr); - - return Collections.singletonList(expr); + return HttpJsonDescriptorComposer.getMethodMaker(); } // Generates get[camelCase(fieldName)] @@ -726,320 +518,8 @@ private List setPollingRequestFactoryExpr( .build()); } - private Expr createBodyFieldsExtractorClassInstance( - Method method, - TypeNode extractorReturnType, - Set httpBindingFieldNames, - String serializerMethodName, - boolean asteriskBody, - boolean restNumericEnumEnabled) { - List bodyStatements = new ArrayList<>(); - - Expr returnExpr = null; - Expr serializerExpr = - MethodInvocationExpr.builder() - .setMethodName("create") - .setStaticReferenceType( - FIXED_REST_TYPESTORE.get(ProtoRestSerializer.class.getSimpleName())) - .build(); - - VariableExpr requestVarExpr = - VariableExpr.withVariable( - Variable.builder().setType(method.inputType()).setName("request").build()); - Expr bodyRequestExpr = requestVarExpr; - String requestMethodPrefix = "get"; - String bodyParamName = null; - - if (asteriskBody) { - bodyRequestExpr = - MethodInvocationExpr.builder() - .setExprReferenceExpr(requestVarExpr) - .setMethodName("toBuilder") - .build(); - // In case of `body: "*"` case we send the whole request message as a body, minus the fields - // in the path, therefore the "clear" prefix here. - requestMethodPrefix = "clear"; - } - - Expr prevExpr = bodyRequestExpr; - for (HttpBinding httpBindingFieldName : httpBindingFieldNames) { - // Handle foo.bar cases by descending into the subfields. - MethodInvocationExpr.Builder requestFieldMethodExprBuilder = - MethodInvocationExpr.builder().setExprReferenceExpr(prevExpr); - // Use explicit json_name if defined in the proto, prioritizing the actual wire name - // over Java-escaped identifiers. Note that trailing underscores (e.g., 'case_') result from: - // 1. protoc-gen-java: - // https://github.com/protocolbuffers/protobuf/blob/cecbbf41e43634c7c5b940dd336aa81b31fd4e5d/src/google/protobuf/compiler/java/names.cc#L189-L195 - // 2. gapic-generator-java Keyword implementation: - // com/google/api/generator/engine/lexicon/Keyword.java#L92-L94 - bodyParamName = - !Strings.isNullOrEmpty(httpBindingFieldName.jsonName()) - ? httpBindingFieldName.jsonName() - : JavaStyle.toLowerCamelCase(httpBindingFieldName.name()); - String[] descendantFields = httpBindingFieldName.name().split("\\."); - if (asteriskBody && descendantFields.length > 1) { - // This is the `body: "*"` case, do not clean nested body fields as it a very rare, not - // well-defined case, and it is generally safer to send more than less in such case. - continue; - } - - for (int i = 0; i < descendantFields.length; i++) { - String currFieldName = descendantFields[i]; - String bindingFieldMethodName = - String.format("%s%s", requestMethodPrefix, JavaStyle.toUpperCamelCase(currFieldName)); - requestFieldMethodExprBuilder = - requestFieldMethodExprBuilder.setMethodName(bindingFieldMethodName); - - if (i < descendantFields.length - 1) { - requestFieldMethodExprBuilder = - MethodInvocationExpr.builder() - .setExprReferenceExpr(requestFieldMethodExprBuilder.build()); - } - } - prevExpr = requestFieldMethodExprBuilder.build(); - } - - if (httpBindingFieldNames.isEmpty() && !asteriskBody) { - returnExpr = ValueExpr.createNullExpr(); - } else { - ImmutableList.Builder paramsPutArgs = ImmutableList.builder(); - if (asteriskBody) { - prevExpr = - MethodInvocationExpr.builder() - .setExprReferenceExpr(prevExpr) - .setMethodName("build") - .build(); - bodyParamName = "*"; - } - paramsPutArgs.add(ValueExpr.withValue(StringObjectValue.withValue(bodyParamName))); - paramsPutArgs.add(prevExpr); - - PrimitiveValue primitiveValue = - PrimitiveValue.builder() - .setType(TypeNode.BOOLEAN) - .setValue(String.valueOf(restNumericEnumEnabled)) - .build(); - paramsPutArgs.add(ValueExpr.withValue(primitiveValue)); - - returnExpr = - MethodInvocationExpr.builder() - .setExprReferenceExpr(serializerExpr) - .setMethodName(serializerMethodName) - .setArguments(paramsPutArgs.build()) - .setReturnType(extractorReturnType) - .build(); - } - - // Overrides FieldsExtractor - // (https://github.com/googleapis/gax-java/blob/12b18ee255d3fabe13bb3969df40753b29f830d5/gax-httpjson/src/main/java/com/google/api/gax/httpjson/FieldsExtractor.java). - return LambdaExpr.builder() - .setArguments(requestVarExpr.toBuilder().setIsDecl(true).build()) - .setBody(bodyStatements) - .setReturnExpr(returnExpr) - .build(); - } - - private Expr createFieldsExtractorClassInstance( - Method method, - TypeNode extractorReturnType, - Set httpBindingFieldNames, - String serializerMethodName, - boolean restNumericEnumsEnabled) { - List bodyStatements = new ArrayList<>(); - - VariableExpr fieldsVarExpr = - VariableExpr.withVariable( - Variable.builder().setName("fields").setType(extractorReturnType).build()); - Expr fieldsAssignExpr = - AssignmentExpr.builder() - .setVariableExpr(fieldsVarExpr.toBuilder().setIsDecl(true).build()) - .setValueExpr( - NewObjectExpr.builder() - .setType(FIXED_REST_TYPESTORE.get(HashMap.class.getSimpleName())) - .setIsGeneric(true) - .build()) - .build(); - - bodyStatements.add(ExprStatement.withExpr(fieldsAssignExpr)); - - TypeNode serializerVarType = - TypeNode.withReference( - ConcreteReference.builder() - .setClazz(ProtoRestSerializer.class) - .setGenerics(method.inputType().reference()) - .build()); - - VariableExpr serializerVarExpr = - VariableExpr.withVariable( - Variable.builder().setName("serializer").setType(serializerVarType).build()); - - Expr serializerAssignExpr = - AssignmentExpr.builder() - .setVariableExpr(serializerVarExpr.toBuilder().setIsDecl(true).build()) - .setValueExpr( - MethodInvocationExpr.builder() - .setStaticReferenceType( - FIXED_REST_TYPESTORE.get(ProtoRestSerializer.class.getSimpleName())) - .setMethodName("create") - .setReturnType(serializerVarType) - .build()) - .build(); - - bodyStatements.add(ExprStatement.withExpr(serializerAssignExpr)); - - VariableExpr requestVarExpr = - VariableExpr.withVariable( - Variable.builder().setType(method.inputType()).setName("request").build()); - - for (HttpBinding httpBindingFieldName : httpBindingFieldNames) { - // Handle foo.bar cases by descending into the subfields. - MethodInvocationExpr.Builder requestFieldGetterExprBuilder = - MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr); - MethodInvocationExpr.Builder requestFieldHasExprBuilder = - MethodInvocationExpr.builder().setExprReferenceExpr(requestVarExpr); - String[] descendantFields = httpBindingFieldName.name().split("\\."); - for (int i = 0; i < descendantFields.length; i++) { - String currFieldName = descendantFields[i]; - String bindingFieldMethodName = - getBindingFieldMethodName( - httpBindingFieldName, - descendantFields.length, - i, - JavaStyle.toUpperCamelCase(currFieldName)); - requestFieldGetterExprBuilder = - requestFieldGetterExprBuilder.setMethodName(bindingFieldMethodName); - - String bindingFieldHasMethodName = - (i < descendantFields.length - 1) - ? bindingFieldMethodName - : String.format("has%s", JavaStyle.toUpperCamelCase(currFieldName)); - requestFieldHasExprBuilder = - requestFieldHasExprBuilder - .setMethodName(bindingFieldHasMethodName) - .setReturnType(TypeNode.BOOLEAN); - - if (i < descendantFields.length - 1) { - requestFieldGetterExprBuilder = - MethodInvocationExpr.builder() - .setExprReferenceExpr(requestFieldGetterExprBuilder.build()); - requestFieldHasExprBuilder = - MethodInvocationExpr.builder() - .setExprReferenceExpr(requestFieldHasExprBuilder.build()); - } - } - - MethodInvocationExpr requestBuilderExpr = requestFieldGetterExprBuilder.build(); - MethodInvocationExpr requestHasExpr = requestFieldHasExprBuilder.build(); - - ImmutableList.Builder paramsPutArgs = ImmutableList.builder(); - - paramsPutArgs.add(fieldsVarExpr); - - paramsPutArgs.add( - ValueExpr.withValue( - StringObjectValue.withValue( - // Use explicit json_name if defined in the proto, prioritizing the actual wire - // name over Java-escaped identifiers (e.g., avoiding 'case_' generated to prevent - // keywords conflict). - (httpBindingFieldName.jsonName() != null) - ? httpBindingFieldName.jsonName() - : JavaStyle.toLowerCamelCase(httpBindingFieldName.name())))); - paramsPutArgs.add(requestBuilderExpr); - - Expr paramsPutExpr = - MethodInvocationExpr.builder() - .setExprReferenceExpr(serializerVarExpr) - .setMethodName(serializerMethodName) - .setArguments(paramsPutArgs.build()) - .setReturnType(extractorReturnType) - .build(); - - if (httpBindingFieldName.isOptional()) { - bodyStatements.add( - IfStatement.builder() - .setConditionExpr(requestHasExpr) - .setBody(Arrays.asList(ExprStatement.withExpr(paramsPutExpr))) - .build()); - } else { - bodyStatements.add(ExprStatement.withExpr(paramsPutExpr)); - } - } - - // Add a fixed query param for numeric enum, see b/232457244 for details - if (restNumericEnumsEnabled && serializerMethodName.equals("putQueryParam")) { - ImmutableList.Builder paramsPutArgs = ImmutableList.builder(); - - paramsPutArgs.add(fieldsVarExpr); - paramsPutArgs.add(ValueExpr.withValue(StringObjectValue.withValue("$alt"))); - paramsPutArgs.add(ValueExpr.withValue(StringObjectValue.withValue("json;enum-encoding=int"))); - - Expr paramsPutExpr = - MethodInvocationExpr.builder() - .setExprReferenceExpr(serializerVarExpr) - .setMethodName(serializerMethodName) - .setArguments(paramsPutArgs.build()) - .setReturnType(extractorReturnType) - .build(); - bodyStatements.add(ExprStatement.withExpr(paramsPutExpr)); - } - - // Overrides FieldsExtractor - // (https://github.com/googleapis/gax-java/blob/12b18ee255d3fabe13bb3969df40753b29f830d5/gax-httpjson/src/main/java/com/google/api/gax/httpjson/FieldsExtractor.java). - return LambdaExpr.builder() - .setArguments(requestVarExpr.toBuilder().setIsDecl(true).build()) - .setBody(bodyStatements) - .setReturnExpr(fieldsVarExpr) - .build(); - } - - @VisibleForTesting - String getBindingFieldMethodName( - HttpBinding httpBindingField, int descendantFieldsLengths, int index, String currFieldName) { - if (index == descendantFieldsLengths - 1) { - if (httpBindingField.isRepeated()) { - return String.format("get%sList", currFieldName); - } - if (httpBindingField.isEnum()) { - return String.format("get%sValue", currFieldName); - } - } - return String.format("get%s", currFieldName); - } - - private List getHttpMethodTypeExpr(Method protoMethod) { - return Collections.singletonList( - ValueExpr.withValue( - StringObjectValue.withValue(protoMethod.httpBindings().httpVerb().toString()))); - } - - private List getMethodTypeExpr(Method protoMethod) { - MethodType methodType; - switch (protoMethod.stream()) { - case NONE: - methodType = MethodType.UNARY; - break; - case SERVER: - methodType = MethodType.SERVER_STREAMING; - break; - case CLIENT: - // Not feasible to suppor in REST - case BIDI: - // Not feasible to suppor in REST - default: - throw new UnsupportedOperationException( - String.format( - "Methods of type %s are not supported by REST transport", protoMethod.stream())); - } - EnumRefExpr expr = - EnumRefExpr.builder() - .setName(methodType.toString()) - .setType( - TypeNode.withReference( - ConcreteReference.builder() - .setClazz(ApiMethodDescriptor.MethodType.class) - .build())) - .build(); - return Collections.singletonList(expr); + static String getProtoRpcName(Service protoService, Method protoMethod) { + return HttpJsonDescriptorComposer.getProtoRpcFullMethodName(protoService, protoMethod); } @Override @@ -1365,7 +845,8 @@ private List createInvalidClassMethods(Service service) { .setType(FIXED_TYPESTORE.get("UnsupportedOperationException")) .setMessageExpr( String.format( - "Not implemented: %s(). %s transport is not implemented for this method yet.", + "Not implemented: %s(). %s transport is not implemented for" + + " this method yet.", callableName, getTransportContext().transport())) .build()))) .build()); 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/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..8e214566f386 --- /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 the settings configured here apply strictly to the initial session initiation + * request, and not to the subsequent data transfer requests. + */ + 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 the settings configured here apply strictly to the initial session initiation + * request, and not to the subsequent data transfer requests. + */ + 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..1950164a0127 --- /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 the settings configured here apply strictly to the initial session initiation + * request, and not to the subsequent data transfer requests. + */ + 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 the settings configured here apply strictly to the initial session initiation + * request, and not to the subsequent data transfer requests. + */ + public ResumableUploadCallSettings.Builder uploadMediaSettings() { + return uploadMediaSettings; + } + + @Override + public ResumableUploadServiceStubSettings build() throws IOException { + return new ResumableUploadServiceStubSettings(this); + } + } +} diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceResumableUploadStubClassComposerTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceResumableUploadStubClassComposerTest.java new file mode 100644 index 000000000000..c1e1bd7536ad --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceResumableUploadStubClassComposerTest.java @@ -0,0 +1,37 @@ +// 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.rest; + +import com.google.api.generator.gapic.model.GapicClass; +import com.google.api.generator.gapic.model.GapicContext; +import com.google.api.generator.gapic.model.Service; +import com.google.api.generator.test.framework.Assert; +import com.google.api.generator.test.protoloader.RestTestProtoLoader; +import org.junit.jupiter.api.Test; + +class HttpJsonServiceResumableUploadStubClassComposerTest { + + @Test + void generate_matchesGolden() { + GapicContext context = RestTestProtoLoader.instance().parseShowcaseResumableUpload(); + Service service = context.services().get(0); + + GapicClass clazz = + HttpJsonServiceResumableUploadStubClassComposer.instance().generate(context, service); + Assert.assertGoldenClass( + this.getClass(), clazz, "HttpJsonResumableUploadServiceResumableUploadStub.golden"); + Assert.assertEmptySamples(clazz.samples()); + } +} diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposerTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposerTest.java index 0e78c76c3646..76c07f0e9181 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposerTest.java +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/HttpJsonServiceStubClassComposerTest.java @@ -86,7 +86,8 @@ void getBindingFieldMethodName_shouldReturnGetFieldListIfTheFieldIsInLastPositio .build(); HttpBinding httpBinding = HttpBinding.builder().setField(field).setName("doesNotMatter").build(); - String actual = composer.getBindingFieldMethodName(httpBinding, 4, 3, "Values"); + String actual = + HttpJsonDescriptorComposer.getBindingFieldMethodName(httpBinding, 4, 3, "Values"); Truth.assertThat(actual).isEqualTo("getValuesList"); } @@ -96,7 +97,8 @@ void getBindingFieldMethodName_shouldReturnGetFieldValueIfTheFieldIsInLastPositi Field.builder().setIsEnum(true).setName("doesNotMatter").setType(TypeNode.OBJECT).build(); HttpBinding httpBinding = HttpBinding.builder().setField(field).setName("doesNotMatter").build(); - String actual = composer.getBindingFieldMethodName(httpBinding, 4, 3, "Enums"); + String actual = + HttpJsonDescriptorComposer.getBindingFieldMethodName(httpBinding, 4, 3, "Enums"); Truth.assertThat(actual).isEqualTo("getEnumsValue"); } @@ -106,7 +108,8 @@ void getBindingFieldMethodName_shouldReturnGetFieldValueIfTheFieldIsInLastPositi Field field = Field.builder().setName("doesNotMatter").setType(TypeNode.OBJECT).build(); HttpBinding httpBinding = HttpBinding.builder().setField(field).setName("doesNotMatter").build(); - String actual = composer.getBindingFieldMethodName(httpBinding, 4, 3, "Value"); + String actual = + HttpJsonDescriptorComposer.getBindingFieldMethodName(httpBinding, 4, 3, "Value"); Truth.assertThat(actual).isEqualTo("getValue"); } @@ -115,7 +118,8 @@ void getBindingFieldMethodName_shouldReturnGetFieldIfTheFieldIsNotInLastPosition Field field = Field.builder().setName("doesNotMatter").setType(TypeNode.OBJECT).build(); HttpBinding httpBinding = HttpBinding.builder().setField(field).setName("doesNotMatter").build(); - String actual = composer.getBindingFieldMethodName(httpBinding, 4, 1, "Value"); + String actual = + HttpJsonDescriptorComposer.getBindingFieldMethodName(httpBinding, 4, 1, "Value"); Truth.assertThat(actual).isEqualTo("getValue"); } diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonResumableUploadServiceResumableUploadStub.golden b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonResumableUploadServiceResumableUploadStub.golden new file mode 100644 index 000000000000..229798286ab7 --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonResumableUploadServiceResumableUploadStub.golden @@ -0,0 +1,195 @@ +package com.google.showcase.v1beta1.stub; + +import com.google.api.core.BetaApi; +import com.google.api.core.InternalApi; +import com.google.api.gax.core.BackgroundResource; +import com.google.api.gax.core.BackgroundResourceAggregation; +import com.google.api.gax.core.GaxProperties; +import com.google.api.gax.httpjson.ApiMethodDescriptor; +import com.google.api.gax.httpjson.GaxHttpJsonProperties; +import com.google.api.gax.httpjson.HttpJsonCallContext; +import com.google.api.gax.httpjson.HttpJsonCallSettings; +import com.google.api.gax.httpjson.HttpJsonCallableFactory; +import com.google.api.gax.httpjson.HttpJsonTransportChannel; +import com.google.api.gax.httpjson.ManagedHttpJsonChannel; +import com.google.api.gax.httpjson.ProtoMessageRequestFormatter; +import com.google.api.gax.httpjson.ProtoMessageResponseParser; +import com.google.api.gax.httpjson.ProtoRestSerializer; +import com.google.api.gax.rpc.ApiClientHeaderProvider; +import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.ResumableUploadCallable; +import com.google.common.collect.Maps; +import com.google.protobuf.TypeRegistry; +import com.google.showcase.v1beta1.UploadMediaRequest; +import com.google.showcase.v1beta1.UploadMediaResponse; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.annotation.Generated; +import org.jspecify.annotations.NullMarked; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** + * REST stub transport for the resumable upload methods of ResumableUploadService. + * + *

This class is for advanced usage and reflects the underlying API directly. + */ +@Generated("by gapic-generator-java") +@InternalApi +@NullMarked +@BetaApi +public class HttpJsonResumableUploadServiceResumableUploadStub implements BackgroundResource { + private static final TypeRegistry typeRegistry = TypeRegistry.newBuilder().build(); + + private static final ApiMethodDescriptor + uploadMediaMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName("google.showcase.v1beta1.ResumableUploadService/UploadMedia") + .setHttpMethod("POST") + .setType(ApiMethodDescriptor.MethodType.UNARY) + .setRequestFormatter( + ProtoMessageRequestFormatter.newBuilder() + .setPath( + "/resumable/upload/v1beta1/files:upload", + request -> { + Map fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + return fields; + }) + .setQueryParamsExtractor( + request -> { + Map> fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + return fields; + }) + .setRequestBodyExtractor( + request -> + ProtoRestSerializer.create() + .toBody("*", request.toBuilder().build(), false)) + .build()) + .setResponseParser( + ProtoMessageResponseParser.newBuilder() + .setDefaultInstance(UploadMediaResponse.getDefaultInstance()) + .setDefaultTypeRegistry(typeRegistry) + .build()) + .build(); + + private final BackgroundResource backgroundResources; + private final ResumableUploadCallable + uploadMediaCallable; + + /** + * Constructs an instance from the given client context. An HTTP/JSON context is used as is. A + * context built for any other transport is replaced by one carrying a dedicated HTTP/JSON + * transport for the upload, which is registered as a background resource and closed with the + * caller. + */ + public static HttpJsonResumableUploadServiceResumableUploadStub create( + ClientContext clientContext, ResumableUploadServiceStubSettings settings) { + if (clientContext.getTransportChannel() instanceof HttpJsonTransportChannel) { + return new HttpJsonResumableUploadServiceResumableUploadStub(clientContext, settings); + } + return new HttpJsonResumableUploadServiceResumableUploadStub( + deriveHttpJsonClientContext(clientContext, settings), settings); + } + + private static ClientContext deriveHttpJsonClientContext( + ClientContext clientContext, ResumableUploadServiceStubSettings settings) { + String uploadEndpoint = + clientContext.getEndpoint() != null ? clientContext.getEndpoint() : settings.getEndpoint(); + ManagedHttpJsonChannel httpJsonManagedChannel = + ManagedHttpJsonChannel.newBuilder() + .setEndpoint(uploadEndpoint) + .setExecutor(clientContext.getExecutor()) + .build(); + HttpJsonTransportChannel httpJsonTransportChannel = + HttpJsonTransportChannel.create(httpJsonManagedChannel); + Map uploadHeaders = + new HashMap<>( + ApiClientHeaderProvider.newBuilder() + .setGeneratedLibToken( + "gapic", + GaxProperties.getLibraryVersion(ResumableUploadServiceStubSettings.class)) + .setTransportToken( + GaxHttpJsonProperties.getHttpJsonTokenName(), + GaxHttpJsonProperties.getHttpJsonVersion()) + .setQuotaProjectIdToken(clientContext.getQuotaProjectId()) + .build() + .getHeaders()); + uploadHeaders.putAll(clientContext.getHeaders()); + return ClientContext.newBuilder() + .setCredentials(clientContext.getCredentials()) + .setEndpoint(uploadEndpoint) + .setExecutor(clientContext.getExecutor()) + .setClock(clientContext.getClock()) + .setTransportChannel(httpJsonTransportChannel) + .setDefaultCallContext( + HttpJsonCallContext.createDefault() + .withTransportChannel(httpJsonTransportChannel) + .withCredentials(clientContext.getCredentials()) + .withExtraHeaders( + Maps.transformValues(uploadHeaders, value -> Collections.singletonList(value)))) + .setBackgroundResources(Arrays.asList(httpJsonTransportChannel)) + .build(); + } + + private HttpJsonResumableUploadServiceResumableUploadStub( + ClientContext clientContext, ResumableUploadServiceStubSettings settings) { + HttpJsonCallSettings uploadMediaTransportSettings = + HttpJsonCallSettings.newBuilder() + .setMethodDescriptor(uploadMediaMethodDescriptor) + .setTypeRegistry(typeRegistry) + .build(); + this.uploadMediaCallable = + HttpJsonCallableFactory.createResumableUploadCallable( + uploadMediaTransportSettings, settings.uploadMediaSettings(), clientContext); + + this.backgroundResources = + new BackgroundResourceAggregation(clientContext.getBackgroundResources()); + } + + public ResumableUploadCallable uploadMediaCallable() { + return uploadMediaCallable; + } + + @Override + public final void close() { + try { + backgroundResources.close(); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new IllegalStateException("Failed to close resource", e); + } + } + + @Override + public void shutdown() { + backgroundResources.shutdown(); + } + + @Override + public boolean isShutdown() { + return backgroundResources.isShutdown(); + } + + @Override + public boolean isTerminated() { + return backgroundResources.isTerminated(); + } + + @Override + public void shutdownNow() { + backgroundResources.shutdownNow(); + } + + @Override + public boolean awaitTermination(long duration, TimeUnit unit) throws InterruptedException { + return backgroundResources.awaitTermination(duration, unit); + } +}