diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/Composer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/Composer.java index edb180676c82..eaf34fff9f70 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/Composer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/Composer.java @@ -29,10 +29,12 @@ import com.google.api.generator.gapic.composer.grpcrest.HttpJsonServiceClientTestClassComposer; import com.google.api.generator.gapic.composer.resourcename.ResourceNameHelperClassComposer; import com.google.api.generator.gapic.composer.rest.HttpJsonServiceCallableFactoryClassComposer; +import com.google.api.generator.gapic.composer.rest.HttpJsonServiceResumableUploadStubClassComposer; import com.google.api.generator.gapic.composer.rest.HttpJsonServiceStubClassComposer; import com.google.api.generator.gapic.model.GapicClass; import com.google.api.generator.gapic.model.GapicContext; import com.google.api.generator.gapic.model.GapicPackageInfo; +import com.google.api.generator.gapic.model.Method; import com.google.api.generator.gapic.model.ReflectConfig; import com.google.api.generator.gapic.model.Sample; import com.google.api.generator.gapic.model.Service; @@ -91,6 +93,11 @@ public static List generateStubClasses(GapicContext context) { .services() .forEach( s -> { + if (s.methods().stream().anyMatch(Method::isResumableUpload)) { + clazzes.add( + HttpJsonServiceResumableUploadStubClassComposer.instance() + .generate(context, s)); + } if (context.transport() == Transport.REST) { clazzes.add( com.google.api.generator.gapic.composer.rest.ServiceStubClassComposer.instance() 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..9fa3efdfe7c8 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 @@ -25,6 +25,7 @@ import com.google.api.gax.rpc.OperationCallable; import com.google.api.gax.rpc.RequestParamsBuilder; import com.google.api.gax.rpc.RequestParamsExtractor; +import com.google.api.gax.rpc.ResumableUploadCallable; import com.google.api.gax.rpc.ServerStreamingCallable; import com.google.api.gax.rpc.UnaryCallable; import com.google.api.generator.engine.ast.AnnotationNode; @@ -106,7 +107,7 @@ public abstract class AbstractTransportServiceStubClassComposer implements Class private static final List AIP_STANDARDS_METHODS = ImmutableList.of( "Get", "List", "Create", "Delete", "Update", "Patch", "Insert", "AggregatedList"); - private static final Statement EMPTY_LINE_STATEMENT = EmptyLineStatement.create(); + protected static final Statement EMPTY_LINE_STATEMENT = EmptyLineStatement.create(); private static final String METHOD_DESCRIPTOR_NAME_PATTERN = "%sMethodDescriptor"; private static final String PAGED_CALLABLE_CLASS_MEMBER_PATTERN = "%sPagedCallable"; @@ -115,6 +116,8 @@ public abstract class AbstractTransportServiceStubClassComposer implements Class private static final String CALLABLE_FACTORY_MEMBER_NAME = "callableFactory"; protected static final String CALLABLE_CLASS_MEMBER_PATTERN = "%sCallable"; private static final String OPERATION_CALLABLE_CLASS_MEMBER_PATTERN = "%sOperationCallable"; + protected static final String RESUMABLE_UPLOAD_STUB_MEMBER_NAME = "resumableUploadStub"; + protected static final String RESUMABLE_UPLOAD_STUB_PATTERN = "HttpJson%sResumableUploadStub"; private static final ImmutableList HEURISTIC_ENABLED_PACKAGES = ImmutableList.of("google.cloud.compute", "google.cloud.sql", "google.cloud.bigquery"); @@ -141,6 +144,7 @@ private static TypeStore createStaticTypes() { ClientContext.class, ClientStreamingCallable.class, Generated.class, + ImmutableList.class, ImmutableMap.class, InterruptedException.class, IOException.class, @@ -153,6 +157,7 @@ private static TypeStore createStaticTypes() { TimeUnit.class, TypeRegistry.class, UnaryCallable.class, + ResumableUploadCallable.class, UnsupportedOperationException.class, NullMarked.class); return new TypeStore(concreteClazzes); @@ -213,6 +218,22 @@ public GapicClass generate(GapicContext context, Service service) { } } + if (service.methods().stream().anyMatch(Method::isResumableUpload)) { + TypeNode resumableUploadStubType = + typeStore.get(String.format(RESUMABLE_UPLOAD_STUB_PATTERN, service.name())); + if (isResumableUploadStubNullable()) { + resumableUploadStubType = + TypeNode.withReference(resumableUploadStubType.reference().copyAndSetNullable(true)); + } + classMemberVarExprs.put( + RESUMABLE_UPLOAD_STUB_MEMBER_NAME, + VariableExpr.withVariable( + Variable.builder() + .setName(RESUMABLE_UPLOAD_STUB_MEMBER_NAME) + .setType(resumableUploadStubType) + .build())); + } + classMemberVarExprs.put( CALLABLE_FACTORY_MEMBER_NAME, VariableExpr.withVariable( @@ -240,9 +261,7 @@ public GapicClass generate(GapicContext context, Service service) { callableClassMemberVarExprs, protoMethodNameToDescriptorVarExprs, classStatements); - methodDefinitions.addAll( - createStubOverrideMethods( - classMemberVarExprs.get(BACKGROUND_RESOURCES_MEMBER_NAME), service)); + methodDefinitions.addAll(createStubOverrideMethods(classMemberVarExprs, service)); StubCommentComposer commentComposer = new StubCommentComposer(getTransportContext().transportNames().get(0)); @@ -425,6 +444,7 @@ protected List createMethodDescriptorVariableDecls( boolean restNumericEnumsEnabled) { return service.methods().stream() .filter(x -> x.isSupportedByTransport(getTransportContext().transport())) + .filter(x -> !x.isResumableUpload()) .map( m -> createMethodDescriptorVariableDecl( @@ -454,6 +474,7 @@ protected Map createProtoMethodNameToDescriptorClassMember Service service, Class descriptorClass) { return service.methods().stream() .filter(x -> x.isSupportedByTransport(getTransportContext().transport())) + .filter(x -> !x.isResumableUpload()) .collect( Collectors.toMap( Method::name, @@ -488,6 +509,9 @@ private Map createCallableClassMembers( if (!protoMethod.isSupportedByTransport(getTransportContext().transport())) { continue; } + if (protoMethod.isResumableUpload()) { + continue; + } String javaStyleProtoMethodName = JavaStyle.toLowerCamelCase(protoMethod.name()); String callableName = String.format(CALLABLE_CLASS_MEMBER_PATTERN, javaStyleProtoMethodName); callableClassMembers.put(callableName, getCallableExpr(protoMethod, callableName)); @@ -592,6 +616,9 @@ protected List createClassMethods( service, classMemberVarExprs.get(getTransportContext().transportOperationsStubNames().get(0)))); javaMethods.addAll(createCallableGetterMethods(callableClassMemberVarExprs)); + if (service.methods().stream().anyMatch(Method::isResumableUpload)) { + javaMethods.addAll(createResumableUploadCallableGetterMethods(service, classMemberVarExprs)); + } return javaMethods; } @@ -778,6 +805,7 @@ protected List createConstructorMethods( Map javaStyleMethodNameToTransportSettingsVarExprs = service.methods().stream() .filter(x -> x.isSupportedByTransport(getTransportContext().transport())) + .filter(x -> !x.isResumableUpload()) .collect( Collectors.toMap( m -> JavaStyle.toLowerCamelCase(m.name()), @@ -803,6 +831,7 @@ protected List createConstructorMethods( secondCtorExprs.addAll( service.methods().stream() .filter(x -> x.isSupportedByTransport(getTransportContext().transport())) + .filter(x -> !x.isResumableUpload()) .map( m -> createTransportSettingsInitExpr( @@ -835,6 +864,9 @@ protected List createConstructorMethods( if (!method.isSupportedByTransport(getTransportContext().transport())) { continue; } + if (method.isResumableUpload()) { + continue; + } secondCtorExprs.addAll( createCallableInitExprs( context, @@ -853,6 +885,17 @@ protected List createConstructorMethods( secondCtorExprs.clear(); secondCtorStatements.add(EMPTY_LINE_STATEMENT); + if (service.methods().stream().anyMatch(Method::isResumableUpload)) { + secondCtorStatements.addAll( + createResumableUploadStubInitStatements( + service, + typeStore, + thisExpr, + classMemberVarExprs.get(RESUMABLE_UPLOAD_STUB_MEMBER_NAME), + clientContextVarExpr, + settingsVarExpr)); + } + secondCtorStatements.addAll(createLongRunningClient(service, typeStore)); // Instantiate backgroundResources. @@ -862,20 +905,86 @@ protected List createConstructorMethods( .setMethodName("getBackgroundResources") .build(); - VariableExpr backgroundResourcesVarExpr = classMemberVarExprs.get("backgroundResources"); - secondCtorExprs.add( - AssignmentExpr.builder() - .setVariableExpr( - backgroundResourcesVarExpr.toBuilder().setExprReferenceExpr(thisExpr).build()) - .setValueExpr( - NewObjectExpr.builder() - .setType(FIXED_TYPESTORE.get("BackgroundResourceAggregation")) - .setArguments(Arrays.asList(getBackgroundResourcesMethodExpr)) - .build()) - .build()); - secondCtorStatements.addAll( - secondCtorExprs.stream().map(ExprStatement::withExpr).collect(Collectors.toList())); - secondCtorExprs.clear(); + VariableExpr backgroundResourcesVarExpr = + classMemberVarExprs.get(BACKGROUND_RESOURCES_MEMBER_NAME); + VariableExpr thisBackgroundResourcesVarExpr = + backgroundResourcesVarExpr.toBuilder().setExprReferenceExpr(thisExpr).build(); + + Expr standardBackgroundResourceAggregationExpr = + NewObjectExpr.builder() + .setType(FIXED_TYPESTORE.get("BackgroundResourceAggregation")) + .setArguments(Arrays.asList(getBackgroundResourcesMethodExpr)) + .build(); + + if (service.methods().stream().anyMatch(Method::isResumableUpload)) { + MethodInvocationExpr listBuilderExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(FIXED_TYPESTORE.get("ImmutableList")) + .setGenerics(Arrays.asList(FIXED_TYPESTORE.get("BackgroundResource").reference())) + .setMethodName("builder") + .build(); + listBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(listBuilderExpr) + .setMethodName("addAll") + .setArguments(Arrays.asList(getBackgroundResourcesMethodExpr)) + .build(); + listBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(listBuilderExpr) + .setMethodName("add") + .setArguments( + Arrays.asList(classMemberVarExprs.get(RESUMABLE_UPLOAD_STUB_MEMBER_NAME))) + .build(); + listBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(listBuilderExpr) + .setMethodName("build") + .build(); + Expr aggregatedBackgroundResourceExpr = + NewObjectExpr.builder() + .setType(FIXED_TYPESTORE.get("BackgroundResourceAggregation")) + .setArguments(Arrays.asList(listBuilderExpr)) + .build(); + + if (isResumableUploadStubNullable()) { + Statement nonNullBackgroundResourceStmt = + ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr(thisBackgroundResourcesVarExpr) + .setValueExpr(aggregatedBackgroundResourceExpr) + .build()); + Statement nullBackgroundResourceStmt = + ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr(thisBackgroundResourcesVarExpr) + .setValueExpr(standardBackgroundResourceAggregationExpr) + .build()); + secondCtorStatements.add( + IfStatement.builder() + .setConditionExpr( + RelationalOperationExpr.notEqualToWithExprs( + classMemberVarExprs.get(RESUMABLE_UPLOAD_STUB_MEMBER_NAME), + ValueExpr.createNullExpr())) + .setBody(Arrays.asList(nonNullBackgroundResourceStmt)) + .setElseBody(Arrays.asList(nullBackgroundResourceStmt)) + .build()); + } else { + secondCtorStatements.add( + ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr(thisBackgroundResourcesVarExpr) + .setValueExpr(aggregatedBackgroundResourceExpr) + .build())); + } + } else { + secondCtorStatements.add( + ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr(thisBackgroundResourcesVarExpr) + .setValueExpr(standardBackgroundResourceAggregationExpr) + .build())); + } // Second constructor method. MethodDefinition secondCtor = @@ -908,6 +1017,36 @@ protected List createOperationsStubInitExpr( .build()); } + protected List createResumableUploadStubInitStatements( + Service service, + TypeStore typeStore, + Expr thisExpr, + VariableExpr resumableUploadStubVarExpr, + VariableExpr clientContextVarExpr, + VariableExpr settingsVarExpr) { + TypeNode stubType = + typeStore.get(String.format(RESUMABLE_UPLOAD_STUB_PATTERN, service.name())); + Expr createStubExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(stubType) + .setMethodName("create") + .setArguments(Arrays.asList(clientContextVarExpr, settingsVarExpr)) + .setReturnType(stubType) + .build(); + return Arrays.asList( + ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr( + resumableUploadStubVarExpr.toBuilder().setExprReferenceExpr(thisExpr).build()) + .setValueExpr(createStubExpr) + .build()), + EMPTY_LINE_STATEMENT); + } + + protected boolean isResumableUploadStubNullable() { + return false; + } + protected List createLongRunningClient(Service service, TypeStore typeStore) { return ImmutableList.of(); } @@ -1094,8 +1233,39 @@ private static List createCallableGetterMethods( .collect(Collectors.toList()); } + protected List createResumableUploadCallableGetterMethods( + Service service, Map classMemberVarExprs) { + VariableExpr resumableUploadStubVarExpr = + classMemberVarExprs.get(RESUMABLE_UPLOAD_STUB_MEMBER_NAME); + return service.methods().stream() + .filter(Method::isResumableUpload) + .map( + m -> { + String javaStyleMethodName = JavaStyle.toLowerCamelCase(m.name()); + String callableMethodName = + String.format(CALLABLE_CLASS_MEMBER_PATTERN, javaStyleMethodName); + TypeNode callableType = getCallableType(m); + Expr returnExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(resumableUploadStubVarExpr) + .setMethodName(callableMethodName) + .setReturnType(callableType) + .build(); + return MethodDefinition.builder() + .setIsOverride(true) + .setScope(ScopeNode.PUBLIC) + .setReturnType(callableType) + .setName(callableMethodName) + .setReturnExpr(returnExpr) + .build(); + }) + .collect(Collectors.toList()); + } + private List createStubOverrideMethods( - VariableExpr backgroundResourcesVarExpr, Service service) { + Map classMemberVarExprs, Service service) { + VariableExpr backgroundResourcesVarExpr = + classMemberVarExprs.get(BACKGROUND_RESOURCES_MEMBER_NAME); Function methodMakerStarterFn = methodName -> MethodDefinition.builder() @@ -1162,6 +1332,15 @@ private List createStubOverrideMethods( if (service.operationPollingMethod() != null) { javaMethods.addAll(createLongRunningClientGetters()); } + + List tryBodyStatements = new ArrayList<>(); + tryBodyStatements.add( + ExprStatement.withExpr( + MethodInvocationExpr.builder() + .setExprReferenceExpr(backgroundResourcesVarExpr) + .setMethodName("close") + .build())); + javaMethods.add( methodMakerStarterFn .apply("close") @@ -1170,13 +1349,7 @@ private List createStubOverrideMethods( .setBody( Arrays.asList( TryCatchStatement.builder() - .setTryBody( - Arrays.asList( - ExprStatement.withExpr( - MethodInvocationExpr.builder() - .setExprReferenceExpr(backgroundResourcesVarExpr) - .setMethodName("close") - .build()))) + .setTryBody(tryBodyStatements) .addCatch( catchRuntimeExceptionVarExpr.toBuilder().setIsDecl(true).build(), Arrays.asList( @@ -1281,10 +1454,22 @@ private TypeStore createDynamicTypes(Service service, String stubPakkage) { .collect(Collectors.toList()), true, getTransportContext().classNames().getServiceClientClassName(service)); + if (service.methods().stream().anyMatch(Method::isResumableUpload)) { + typeStore.put(stubPakkage, String.format(RESUMABLE_UPLOAD_STUB_PATTERN, service.name())); + } return typeStore; } protected static TypeNode getCallableType(Method protoMethod) { + if (protoMethod.isResumableUpload()) { + return TypeNode.withReference( + ConcreteReference.builder() + .setClazz(ResumableUploadCallable.class) + .setGenerics( + Arrays.asList( + protoMethod.inputType().reference(), protoMethod.outputType().reference())) + .build()); + } TypeNode callableType = FIXED_TYPESTORE.get("UnaryCallable"); switch (protoMethod.stream()) { case CLIENT: diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java index 5cbff693c162..f56bf9a55e98 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposer.java @@ -15,27 +15,40 @@ package com.google.api.generator.gapic.composer.grpc; import com.google.api.gax.grpc.GrpcCallSettings; +import com.google.api.gax.grpc.GrpcStatusCode; import com.google.api.gax.grpc.GrpcStubCallableFactory; +import com.google.api.gax.httpjson.HttpJsonCallContext; +import com.google.api.gax.httpjson.HttpJsonTransportChannel; +import com.google.api.gax.httpjson.ManagedHttpJsonChannel; +import com.google.api.gax.rpc.FailedPreconditionException; 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.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.RelationalOperationExpr; 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.ThrowExpr; 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.common.AbstractTransportServiceStubClassComposer; import com.google.api.generator.gapic.composer.store.TypeStore; import com.google.api.generator.gapic.model.Message; 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.longrunning.stub.GrpcOperationsStub; import io.grpc.MethodDescriptor; +import io.grpc.Status; import io.grpc.protobuf.ProtoUtils; import java.util.Arrays; import java.util.HashSet; @@ -44,6 +57,7 @@ import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.stream.Collectors; import org.jspecify.annotations.NullMarked; @NullMarked @@ -58,6 +72,12 @@ public class GrpcServiceStubClassComposer extends AbstractTransportServiceStubCl private static final Set REROUTE_TO_GRPC_INTERFACE_IAM_METHOD_ALLOWLIST = new HashSet<>(Arrays.asList("SetIamPolicy", "GetIamPolicy", "TestIamPermissions")); + private static final String BUILD_METHOD_NAME = "build"; + private static final String NEW_BUILDER_METHOD_NAME = "newBuilder"; + private static final String MANAGED_HTTP_JSON_CHANNEL_TYPE_NAME = "ManagedHttpJsonChannel"; + private static final String HTTP_JSON_TRANSPORT_CHANNEL_TYPE_NAME = "HttpJsonTransportChannel"; + private static final String CLIENT_CONTEXT_TYPE_NAME = "ClientContext"; + private static final TypeStore FIXED_GRPC_TYPE_STORE = createStaticTypes(); protected GrpcServiceStubClassComposer() { @@ -71,11 +91,18 @@ public static GrpcServiceStubClassComposer instance() { private static TypeStore createStaticTypes() { List> concreteClazzes = Arrays.asList( + Arrays.class, + FailedPreconditionException.class, GrpcCallSettings.class, GrpcOperationsStub.class, + GrpcStatusCode.class, GrpcStubCallableFactory.class, + HttpJsonCallContext.class, + HttpJsonTransportChannel.class, + ManagedHttpJsonChannel.class, MethodDescriptor.class, - ProtoUtils.class); + ProtoUtils.class, + Status.class); return new TypeStore(concreteClazzes); } @@ -88,7 +115,7 @@ protected Statement createMethodDescriptorVariableDecl( boolean restNumericEnumsEnabled) { MethodInvocationExpr methodDescriptorMaker = MethodInvocationExpr.builder() - .setMethodName("newBuilder") + .setMethodName(NEW_BUILDER_METHOD_NAME) .setStaticReferenceType(FIXED_GRPC_TYPE_STORE.get("MethodDescriptor")) .setGenerics(methodDescriptorVarExpr.variable().type().reference().generics()) .build(); @@ -155,7 +182,7 @@ protected Statement createMethodDescriptorVariableDecl( methodDescriptorMaker = MethodInvocationExpr.builder() - .setMethodName("build") + .setMethodName(BUILD_METHOD_NAME) .setExprReferenceExpr(methodDescriptorMaker) .setReturnType(methodDescriptorVarExpr.type()) .build(); @@ -213,4 +240,359 @@ protected String getProtoRpcFullMethodName(Service protoService, Method protoMet // long-term solution. return String.format("google.iam.v1.IAMPolicy/%s", protoMethod.name()); } + + @Override + protected boolean isResumableUploadStubNullable() { + return true; + } + + @Override + protected List createResumableUploadStubInitStatements( + Service service, + TypeStore typeStore, + Expr thisExpr, + VariableExpr resumableUploadStubVarExpr, + VariableExpr clientContextVarExpr, + VariableExpr settingsVarExpr) { + MethodInvocationExpr getCredentialsExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(clientContextVarExpr) + .setMethodName("getCredentials") + .setReturnType(TypeNode.OBJECT) + .build(); + + MethodInvocationExpr getEndpointExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(settingsVarExpr) + .setMethodName("getEndpoint") + .build(); + + MethodInvocationExpr getExecutorExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(clientContextVarExpr) + .setMethodName("getExecutor") + .build(); + + Expr getHeadersExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(clientContextVarExpr) + .setMethodName("getHeaders") + .build(); + Expr getInternalHeadersExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(clientContextVarExpr) + .setMethodName("getInternalHeaders") + .build(); + Expr getClockExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(clientContextVarExpr) + .setMethodName("getClock") + .build(); + + VariableExpr httpJsonManagedChannelVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder() + .setName("httpJsonManagedChannel") + .setType(FIXED_GRPC_TYPE_STORE.get(MANAGED_HTTP_JSON_CHANNEL_TYPE_NAME)) + .build()) + .setIsDecl(true) + .build(); + + MethodInvocationExpr channelNewBuilderExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(FIXED_GRPC_TYPE_STORE.get(MANAGED_HTTP_JSON_CHANNEL_TYPE_NAME)) + .setMethodName(NEW_BUILDER_METHOD_NAME) + .build(); + channelNewBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(channelNewBuilderExpr) + .setMethodName("setEndpoint") + .setArguments(Arrays.asList(getEndpointExpr)) + .build(); + channelNewBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(channelNewBuilderExpr) + .setMethodName("setExecutor") + .setArguments(Arrays.asList(getExecutorExpr)) + .build(); + channelNewBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(channelNewBuilderExpr) + .setMethodName(BUILD_METHOD_NAME) + .setReturnType(FIXED_GRPC_TYPE_STORE.get(MANAGED_HTTP_JSON_CHANNEL_TYPE_NAME)) + .build(); + + Statement httpJsonManagedChannelDeclStmt = + ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr(httpJsonManagedChannelVarExpr) + .setValueExpr(channelNewBuilderExpr) + .build()); + + VariableExpr httpJsonTransportChannelVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder() + .setName("httpJsonTransportChannel") + .setType(FIXED_GRPC_TYPE_STORE.get(HTTP_JSON_TRANSPORT_CHANNEL_TYPE_NAME)) + .build()) + .setIsDecl(true) + .build(); + + MethodInvocationExpr createTransportChannelExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(FIXED_GRPC_TYPE_STORE.get(HTTP_JSON_TRANSPORT_CHANNEL_TYPE_NAME)) + .setMethodName("create") + .setArguments( + Arrays.asList(httpJsonManagedChannelVarExpr.toBuilder().setIsDecl(false).build())) + .setReturnType(FIXED_GRPC_TYPE_STORE.get(HTTP_JSON_TRANSPORT_CHANNEL_TYPE_NAME)) + .build(); + + Statement httpJsonTransportChannelDeclStmt = + ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr(httpJsonTransportChannelVarExpr) + .setValueExpr(createTransportChannelExpr) + .build()); + + VariableExpr transportChannelArgExpr = + httpJsonTransportChannelVarExpr.toBuilder().setIsDecl(false).build(); + + MethodInvocationExpr createDefaultCallContextExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(FIXED_GRPC_TYPE_STORE.get("HttpJsonCallContext")) + .setMethodName("createDefault") + .build(); + + MethodInvocationExpr withTransportChannelCallContextExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(createDefaultCallContextExpr) + .setMethodName("withTransportChannel") + .setArguments(Arrays.asList(transportChannelArgExpr)) + .setReturnType(FIXED_GRPC_TYPE_STORE.get("HttpJsonCallContext")) + .build(); + + MethodInvocationExpr asListBackgroundResourceExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(FIXED_GRPC_TYPE_STORE.get("Arrays")) + .setMethodName("asList") + .setArguments(Arrays.asList(transportChannelArgExpr)) + .build(); + + MethodInvocationExpr newBuilderExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(FIXED_TYPESTORE.get(CLIENT_CONTEXT_TYPE_NAME)) + .setMethodName(NEW_BUILDER_METHOD_NAME) + .build(); + newBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(newBuilderExpr) + .setMethodName("setCredentials") + .setArguments(Arrays.asList(getCredentialsExpr)) + .build(); + newBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(newBuilderExpr) + .setMethodName("setEndpoint") + .setArguments(Arrays.asList(getEndpointExpr)) + .build(); + newBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(newBuilderExpr) + .setMethodName("setExecutor") + .setArguments(Arrays.asList(getExecutorExpr)) + .build(); + newBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(newBuilderExpr) + .setMethodName("setHeaders") + .setArguments(Arrays.asList(getHeadersExpr)) + .build(); + newBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(newBuilderExpr) + .setMethodName("setInternalHeaders") + .setArguments(Arrays.asList(getInternalHeadersExpr)) + .build(); + newBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(newBuilderExpr) + .setMethodName("setClock") + .setArguments(Arrays.asList(getClockExpr)) + .build(); + newBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(newBuilderExpr) + .setMethodName("setTransportChannel") + .setArguments(Arrays.asList(transportChannelArgExpr)) + .build(); + newBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(newBuilderExpr) + .setMethodName("setDefaultCallContext") + .setArguments(Arrays.asList(withTransportChannelCallContextExpr)) + .build(); + newBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(newBuilderExpr) + .setMethodName("setBackgroundResources") + .setArguments(Arrays.asList(asListBackgroundResourceExpr)) + .build(); + newBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(newBuilderExpr) + .setMethodName(BUILD_METHOD_NAME) + .setReturnType(FIXED_TYPESTORE.get(CLIENT_CONTEXT_TYPE_NAME)) + .build(); + + VariableExpr httpJsonClientContextVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder() + .setName("httpJsonClientContext") + .setType(FIXED_TYPESTORE.get(CLIENT_CONTEXT_TYPE_NAME)) + .build()) + .setIsDecl(true) + .build(); + Statement clientContextDeclStmt = + ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr(httpJsonClientContextVarExpr) + .setValueExpr(newBuilderExpr) + .build()); + + TypeNode uploadStubType = + typeStore.get(String.format(RESUMABLE_UPLOAD_STUB_PATTERN, service.name())); + Expr createStubExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(uploadStubType) + .setMethodName("create") + .setArguments( + Arrays.asList( + httpJsonClientContextVarExpr.toBuilder().setIsDecl(false).build(), + settingsVarExpr)) + .setReturnType(uploadStubType) + .build(); + Statement assignResumableUploadStub = + ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr( + resumableUploadStubVarExpr.toBuilder().setExprReferenceExpr(thisExpr).build()) + .setValueExpr(createStubExpr) + .build()); + + Statement assignNullStmt = + ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr( + resumableUploadStubVarExpr.toBuilder().setExprReferenceExpr(thisExpr).build()) + .setValueExpr(ValueExpr.createNullExpr()) + .build()); + + Expr condExpr = + RelationalOperationExpr.notEqualToWithExprs(getCredentialsExpr, ValueExpr.createNullExpr()); + Statement ifStmt = + IfStatement.builder() + .setConditionExpr(condExpr) + .setBody( + Arrays.asList( + httpJsonManagedChannelDeclStmt, + httpJsonTransportChannelDeclStmt, + clientContextDeclStmt, + assignResumableUploadStub)) + .setElseBody(Arrays.asList(assignNullStmt)) + .build(); + return Arrays.asList(ifStmt, EMPTY_LINE_STATEMENT); + } + + @Override + protected List createResumableUploadCallableGetterMethods( + Service service, Map classMemberVarExprs) { + VariableExpr resumableUploadStubVarExpr = + classMemberVarExprs.get(RESUMABLE_UPLOAD_STUB_MEMBER_NAME); + return service.methods().stream() + .filter(Method::isResumableUpload) + .map( + m -> { + String javaStyleMethodName = JavaStyle.toLowerCamelCase(m.name()); + String callableMethodName = + String.format(CALLABLE_CLASS_MEMBER_PATTERN, javaStyleMethodName); + TypeNode callableType = getCallableType(m); + + Statement nullCheckIfStmt = + IfStatement.builder() + .setConditionExpr( + RelationalOperationExpr.equalToWithExprs( + resumableUploadStubVarExpr, ValueExpr.createNullExpr())) + .setBody( + Arrays.asList( + ExprStatement.withExpr( + ThrowExpr.builder() + .setThrowExpr( + NewObjectExpr.builder() + .setType( + FIXED_GRPC_TYPE_STORE.get( + "FailedPreconditionException")) + .setArguments( + Arrays.asList( + ValueExpr.withValue( + StringObjectValue.withValue( + "Resumable uploads execute over" + + " HTTP/REST and require" + + " credentials. The client was" + + " initialized with a" + + " pre-constructed gRPC Channel," + + " from which credentials cannot" + + " be extracted. Please" + + " configure a" + + " CredentialsProvider" + + " instead.")), + ValueExpr.createNullExpr(), + MethodInvocationExpr.builder() + .setStaticReferenceType( + FIXED_GRPC_TYPE_STORE.get( + "GrpcStatusCode")) + .setMethodName("of") + .setArguments( + Arrays.asList( + EnumRefExpr.builder() + .setName( + "FAILED_PRECONDITION") + .setType( + TypeNode.withReference( + ConcreteReference + .builder() + .setClazz( + Status.Code + .class) + .build())) + .build())) + .build(), + ValueExpr.withValue( + PrimitiveValue.builder() + .setType(TypeNode.BOOLEAN) + .setValue("false") + .build()))) + .build()) + .build()))) + .build(); + + Expr returnExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(resumableUploadStubVarExpr) + .setMethodName(callableMethodName) + .setReturnType(callableType) + .build(); + return MethodDefinition.builder() + .setIsOverride(true) + .setScope(ScopeNode.PUBLIC) + .setReturnType(callableType) + .setName(callableMethodName) + .setBody(Arrays.asList(nullCheckIfStmt)) + .setReturnExpr(returnExpr) + .build(); + }) + .collect(Collectors.toList()); + } } diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/common/ResumableUploadTransportStubComposerTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/common/ResumableUploadTransportStubComposerTest.java new file mode 100644 index 000000000000..977f5e616c48 --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/common/ResumableUploadTransportStubComposerTest.java @@ -0,0 +1,215 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.api.generator.gapic.composer.common; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.api.generator.engine.writer.JavaWriterVisitor; +import com.google.api.generator.gapic.composer.grpc.GrpcServiceStubClassComposer; +import com.google.api.generator.gapic.composer.rest.HttpJsonServiceStubClassComposer; +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.Service; +import com.google.api.generator.test.protoloader.GrpcTestProtoLoader; +import com.google.api.generator.test.protoloader.RestTestProtoLoader; +import org.junit.jupiter.api.Test; + +class ResumableUploadTransportStubComposerTest { + + private static String generateGrpcStubCode() { + GapicContext context = GrpcTestProtoLoader.instance().parseShowcaseResumableUpload(); + Service service = context.services().get(0); + + GapicClass clazz = GrpcServiceStubClassComposer.instance().generate(context, service); + assertThat(clazz.kind()).isEqualTo(Kind.STUB); + + JavaWriterVisitor visitor = new JavaWriterVisitor(); + clazz.classDefinition().accept(visitor); + return visitor.write(); + } + + private static String generateRestStubCode() { + GapicContext context = RestTestProtoLoader.instance().parseShowcaseResumableUpload(); + Service service = context.services().get(0); + + GapicClass clazz = HttpJsonServiceStubClassComposer.instance().generate(context, service); + assertThat(clazz.kind()).isEqualTo(Kind.STUB); + + JavaWriterVisitor visitor = new JavaWriterVisitor(); + clazz.classDefinition().accept(visitor); + return visitor.write(); + } + + @Test + void generate_grpcStub_withCredentials_initializesHttpStub() { + String code = generateGrpcStubCode(); + + // Verify key imports + assertThat(code).contains("import com.google.api.gax.grpc.GrpcStatusCode;"); + assertThat(code).contains("import com.google.api.gax.httpjson.HttpJsonCallContext;"); + assertThat(code).contains("import com.google.api.gax.httpjson.HttpJsonTransportChannel;"); + assertThat(code).contains("import com.google.api.gax.httpjson.ManagedHttpJsonChannel;"); + assertThat(code).contains("import com.google.api.gax.rpc.FailedPreconditionException;"); + assertThat(code).contains("import com.google.api.gax.rpc.ResumableUploadCallable;"); + assertThat(code).contains("import com.google.common.collect.ImmutableList;"); + assertThat(code).contains("import io.grpc.Status;"); + assertThat(code).contains("import java.util.Arrays;"); + + // Verify class declaration and nullable stub field + assertThat(code) + .contains("public class GrpcResumableUploadServiceStub extends ResumableUploadServiceStub"); + assertThat(code) + .contains( + "private final @Nullable HttpJsonResumableUploadServiceResumableUploadStub" + + " resumableUploadStub;"); + + // Verify constructor credentials check and HTTP channel instantiation + assertThat(code).contains("if (clientContext.getCredentials() != null) {"); + assertThat(code).contains("ManagedHttpJsonChannel httpJsonManagedChannel ="); + assertThat(code).contains("HttpJsonTransportChannel httpJsonTransportChannel ="); + assertThat(code).contains("ClientContext httpJsonClientContext ="); + assertThat(code).contains(".setCredentials(clientContext.getCredentials())"); + assertThat(code).contains(".setEndpoint(settings.getEndpoint())"); + assertThat(code).contains(".setExecutor(clientContext.getExecutor())"); + assertThat(code).contains(".setHeaders(clientContext.getHeaders())"); + assertThat(code).contains(".setInternalHeaders(clientContext.getInternalHeaders())"); + assertThat(code).contains(".setTransportChannel(httpJsonTransportChannel)"); + assertThat(code).contains(".withTransportChannel(httpJsonTransportChannel)"); + assertThat(code).contains(".setBackgroundResources(Arrays.asList(httpJsonTransportChannel))"); + assertThat(code).contains("this.resumableUploadStub ="); + assertThat(code) + .contains( + "HttpJsonResumableUploadServiceResumableUploadStub.create(httpJsonClientContext," + + " settings);"); + } + + @Test + void generate_grpcStub_withoutCredentials_assignsNull() { + String code = generateGrpcStubCode(); + + assertThat(code).contains("} else {"); + assertThat(code).contains("this.resumableUploadStub = null;"); + } + + @Test + void generate_grpcStub_uploadMediaCallable_guardsNullWithFailedPrecondition() { + String code = generateGrpcStubCode(); + + // Verify callable getter throwing FailedPreconditionException when resumableUploadStub == null + assertThat(code) + .contains( + "public ResumableUploadCallable" + + " uploadMediaCallable() {"); + assertThat(code).contains("if (resumableUploadStub == null) {"); + assertThat(code).contains("throw new FailedPreconditionException("); + assertThat(code) + .contains("\"Resumable uploads execute over HTTP/REST and require credentials."); + assertThat(code).contains("GrpcStatusCode.of(Status.Code.FAILED_PRECONDITION)"); + assertThat(code).contains("return resumableUploadStub.uploadMediaCallable();"); + } + + @Test + void generate_grpcStub_implementsBackgroundResource() { + String code = generateGrpcStubCode(); + + // Verify BackgroundResource aggregation with fallback + assertThat(code).contains("if (resumableUploadStub != null) {"); + assertThat(code).contains("this.backgroundResources ="); + assertThat(code).contains("new BackgroundResourceAggregation("); + assertThat(code).contains("ImmutableList.builder()"); + assertThat(code).contains(".addAll(clientContext.getBackgroundResources())"); + assertThat(code).contains(".add(resumableUploadStub)"); + assertThat(code).contains(".build());"); + assertThat(code) + .contains( + "this.backgroundResources =\n" + + " new" + + " BackgroundResourceAggregation(clientContext.getBackgroundResources());"); + + // Verify close() delegates only to backgroundResources + assertThat(code).contains("backgroundResources.close();"); + assertThat(code).doesNotContain("resumableUploadStub.close();"); + } + + @Test + void generate_restStub_delegatesToResumableUploadStub() { + String code = generateRestStubCode(); + + // Verify class declaration and non-null stub field + assertThat(code) + .contains( + "public class HttpJsonResumableUploadServiceStub extends ResumableUploadServiceStub"); + assertThat(code) + .contains( + "private final HttpJsonResumableUploadServiceResumableUploadStub resumableUploadStub;"); + assertThat(code) + .doesNotContain( + "private final @Nullable HttpJsonResumableUploadServiceResumableUploadStub" + + " resumableUploadStub;"); + + // Verify REST constructor directly creates the upload stub + assertThat(code).contains("this.resumableUploadStub ="); + assertThat(code) + .contains( + "HttpJsonResumableUploadServiceResumableUploadStub.create(clientContext, settings);"); + + // Verify callable getter delegates directly + assertThat(code) + .contains( + "public ResumableUploadCallable" + + " uploadMediaCallable() {"); + assertThat(code).contains("return resumableUploadStub.uploadMediaCallable();"); + + // Verify BackgroundResource aggregation + assertThat(code).contains("ImmutableList.builder()"); + assertThat(code).contains(".addAll(clientContext.getBackgroundResources())"); + assertThat(code).contains(".add(resumableUploadStub)"); + assertThat(code).contains("backgroundResources.close();"); + assertThat(code).doesNotContain("resumableUploadStub.close();"); + } + + @Test + void generateGrpcServiceStubClass_serviceWithoutResumableUpload_noUploadStub() { + GapicContext context = GrpcTestProtoLoader.instance().parseShowcaseEcho(); + Service service = context.services().get(0); + + GapicClass clazz = GrpcServiceStubClassComposer.instance().generate(context, service); + assertThat(clazz.kind()).isEqualTo(Kind.STUB); + + JavaWriterVisitor visitor = new JavaWriterVisitor(); + clazz.classDefinition().accept(visitor); + String code = visitor.write(); + + assertThat(code).doesNotContain("resumableUploadStub"); + assertThat(code).doesNotContain("HttpJsonCallContext"); + assertThat(code).doesNotContain("FailedPreconditionException"); + } + + @Test + void generateHttpJsonServiceStubClass_serviceWithoutResumableUpload_noUploadStub() { + GapicContext context = RestTestProtoLoader.instance().parseCompliance(); + Service service = context.services().get(0); + + GapicClass clazz = HttpJsonServiceStubClassComposer.instance().generate(context, service); + assertThat(clazz.kind()).isEqualTo(Kind.STUB); + + JavaWriterVisitor visitor = new JavaWriterVisitor(); + clazz.classDefinition().accept(visitor); + String code = visitor.write(); + + assertThat(code).doesNotContain("resumableUploadStub"); + } +} diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposerTest.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposerTest.java index eb54b80091b9..423eca5430a0 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposerTest.java +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/GrpcServiceStubClassComposerTest.java @@ -122,4 +122,14 @@ void generateGrpcServiceStubClass_clientResourceNameExtractor() { Assert.assertGoldenClass(this.getClass(), clazz, "GrpcResourceNameExtractorStub.golden"); Assert.assertEmptySamples(clazz.samples()); } + + @Test + void generateGrpcServiceStubClass_resumableUpload() { + GapicContext context = GrpcTestProtoLoader.instance().parseShowcaseResumableUpload(); + Service service = context.services().get(0); + GapicClass clazz = GrpcServiceStubClassComposer.instance().generate(context, service); + + Assert.assertGoldenClass(this.getClass(), clazz, "GrpcResumableUploadServiceStub.golden"); + Assert.assertEmptySamples(clazz.samples()); + } } diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcResumableUploadServiceStub.golden b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcResumableUploadServiceStub.golden new file mode 100644 index 000000000000..fe3c7c060a52 --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcResumableUploadServiceStub.golden @@ -0,0 +1,174 @@ +package com.google.showcase.v1beta1.stub; + +import com.google.api.core.BetaApi; +import com.google.api.gax.core.BackgroundResource; +import com.google.api.gax.core.BackgroundResourceAggregation; +import com.google.api.gax.grpc.GrpcStatusCode; +import com.google.api.gax.grpc.GrpcStubCallableFactory; +import com.google.api.gax.httpjson.HttpJsonCallContext; +import com.google.api.gax.httpjson.HttpJsonTransportChannel; +import com.google.api.gax.httpjson.ManagedHttpJsonChannel; +import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.FailedPreconditionException; +import com.google.api.gax.rpc.ResumableUploadCallable; +import com.google.common.collect.ImmutableList; +import com.google.longrunning.stub.GrpcOperationsStub; +import com.google.showcase.v1beta1.UploadMediaRequest; +import com.google.showcase.v1beta1.UploadMediaResponse; +import io.grpc.Status; +import java.io.IOException; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; +import javax.annotation.Generated; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** + * gRPC stub implementation for the ResumableUploadService service API. + * + *

This class is for advanced usage and reflects the underlying API directly. + */ +@NullMarked +@BetaApi +@Generated("by gapic-generator-java") +public class GrpcResumableUploadServiceStub extends ResumableUploadServiceStub { + + private final BackgroundResource backgroundResources; + private final GrpcOperationsStub operationsStub; + private final @Nullable HttpJsonResumableUploadServiceResumableUploadStub resumableUploadStub; + private final GrpcStubCallableFactory callableFactory; + + public static final GrpcResumableUploadServiceStub create( + ResumableUploadServiceStubSettings settings) throws IOException { + return new GrpcResumableUploadServiceStub(settings, ClientContext.create(settings)); + } + + public static final GrpcResumableUploadServiceStub create(ClientContext clientContext) + throws IOException { + return new GrpcResumableUploadServiceStub( + ResumableUploadServiceStubSettings.newBuilder().build(), clientContext); + } + + public static final GrpcResumableUploadServiceStub create( + ClientContext clientContext, GrpcStubCallableFactory callableFactory) throws IOException { + return new GrpcResumableUploadServiceStub( + ResumableUploadServiceStubSettings.newBuilder().build(), clientContext, callableFactory); + } + + /** + * Constructs an instance of GrpcResumableUploadServiceStub, using the given settings. This is + * protected so that it is easy to make a subclass, but otherwise, the static factory methods + * should be preferred. + */ + protected GrpcResumableUploadServiceStub( + ResumableUploadServiceStubSettings settings, ClientContext clientContext) throws IOException { + this(settings, clientContext, new GrpcResumableUploadServiceCallableFactory()); + } + + /** + * Constructs an instance of GrpcResumableUploadServiceStub, using the given settings. This is + * protected so that it is easy to make a subclass, but otherwise, the static factory methods + * should be preferred. + */ + protected GrpcResumableUploadServiceStub( + ResumableUploadServiceStubSettings settings, + ClientContext clientContext, + GrpcStubCallableFactory callableFactory) + throws IOException { + this.callableFactory = callableFactory; + this.operationsStub = GrpcOperationsStub.create(clientContext, callableFactory); + + if (clientContext.getCredentials() != null) { + ManagedHttpJsonChannel httpJsonManagedChannel = + ManagedHttpJsonChannel.newBuilder() + .setEndpoint(settings.getEndpoint()) + .setExecutor(clientContext.getExecutor()) + .build(); + HttpJsonTransportChannel httpJsonTransportChannel = + HttpJsonTransportChannel.create(httpJsonManagedChannel); + ClientContext httpJsonClientContext = + ClientContext.newBuilder() + .setCredentials(clientContext.getCredentials()) + .setEndpoint(settings.getEndpoint()) + .setExecutor(clientContext.getExecutor()) + .setHeaders(clientContext.getHeaders()) + .setInternalHeaders(clientContext.getInternalHeaders()) + .setClock(clientContext.getClock()) + .setTransportChannel(httpJsonTransportChannel) + .setDefaultCallContext( + HttpJsonCallContext.createDefault() + .withTransportChannel(httpJsonTransportChannel)) + .setBackgroundResources(Arrays.asList(httpJsonTransportChannel)) + .build(); + this.resumableUploadStub = + HttpJsonResumableUploadServiceResumableUploadStub.create(httpJsonClientContext, settings); + } else { + this.resumableUploadStub = null; + } + + if (resumableUploadStub != null) { + this.backgroundResources = + new BackgroundResourceAggregation( + ImmutableList.builder() + .addAll(clientContext.getBackgroundResources()) + .add(resumableUploadStub) + .build()); + } else { + this.backgroundResources = + new BackgroundResourceAggregation(clientContext.getBackgroundResources()); + } + } + + public GrpcOperationsStub getOperationsStub() { + return operationsStub; + } + + @Override + public ResumableUploadCallable uploadMediaCallable() { + if (resumableUploadStub == null) { + throw new FailedPreconditionException( + "Resumable uploads execute over HTTP/REST and require credentials. The client was initialized with a pre-constructed gRPC Channel, from which credentials cannot be extracted. Please configure a CredentialsProvider instead.", + null, + GrpcStatusCode.of(Status.Code.FAILED_PRECONDITION), + false); + } + return resumableUploadStub.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); + } +} 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 index 18bf6a022cab..fd9c15126e50 100644 --- 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 @@ -17,12 +17,14 @@ import static com.google.common.truth.Truth.assertThat; import com.google.api.generator.engine.writer.JavaWriterVisitor; +import com.google.api.generator.gapic.composer.Composer; 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.Service; import com.google.api.generator.test.framework.Assert; import com.google.api.generator.test.protoloader.RestTestProtoLoader; +import java.util.List; import org.junit.jupiter.api.Test; class HttpJsonServiceResumableUploadStubClassComposerTest { @@ -155,6 +157,21 @@ void generate_serviceWithoutResumableUpload_returnsNonGeneratedClass() { assertThat(clazz.kind()).isEqualTo(GapicClass.Kind.NON_GENERATED); } + @Test + void composer_generateStubClasses_registersUploadStubWhenResumableUploadPresent() { + GapicContext context = RestTestProtoLoader.instance().parseShowcaseResumableUpload(); + List stubClasses = Composer.generateStubClasses(context); + assertThat( + stubClasses.stream() + .anyMatch( + c -> + c.classDefinition() + .classIdentifier() + .name() + .equals("HttpJsonResumableUploadServiceResumableUploadStub"))) + .isTrue(); + } + @Test void generate_matchesGolden() { GapicContext context = RestTestProtoLoader.instance().parseShowcaseResumableUpload(); 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 25ee5da79ed0..4b372e73ccb7 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 @@ -178,5 +178,14 @@ void generateHttpJsonServiceStubClass_clientResourceNameExtractor() { Assert.assertGoldenClass(this.getClass(), clazz, "HttpJsonResourceNameExtractorStub.golden"); Assert.assertEmptySamples(clazz.samples()); } -} + @Test + void generateHttpJsonServiceStubClass_resumableUpload() { + GapicContext context = RestTestProtoLoader.instance().parseShowcaseResumableUpload(); + Service service = context.services().get(0); + GapicClass clazz = composer.generate(context, service); + + Assert.assertGoldenClass(this.getClass(), clazz, "HttpJsonResumableUploadServiceStub.golden"); + Assert.assertEmptySamples(clazz.samples()); + } +} diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonResumableUploadServiceStub.golden b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonResumableUploadServiceStub.golden new file mode 100644 index 000000000000..1a6b8b67ecd4 --- /dev/null +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonResumableUploadServiceStub.golden @@ -0,0 +1,134 @@ +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.httpjson.ApiMethodDescriptor; +import com.google.api.gax.httpjson.HttpJsonStubCallableFactory; +import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.ResumableUploadCallable; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.TypeRegistry; +import com.google.showcase.v1beta1.UploadMediaRequest; +import com.google.showcase.v1beta1.UploadMediaResponse; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import javax.annotation.Generated; +import org.jspecify.annotations.NullMarked; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** + * REST stub implementation for the ResumableUploadService service API. + * + *

This class is for advanced usage and reflects the underlying API directly. + */ +@NullMarked +@BetaApi +@Generated("by gapic-generator-java") +public class HttpJsonResumableUploadServiceStub extends ResumableUploadServiceStub { + private static final TypeRegistry typeRegistry = TypeRegistry.newBuilder().build(); + + private final BackgroundResource backgroundResources; + private final HttpJsonResumableUploadServiceResumableUploadStub resumableUploadStub; + private final HttpJsonStubCallableFactory callableFactory; + + public static final HttpJsonResumableUploadServiceStub create( + ResumableUploadServiceStubSettings settings) throws IOException { + return new HttpJsonResumableUploadServiceStub(settings, ClientContext.create(settings)); + } + + public static final HttpJsonResumableUploadServiceStub create(ClientContext clientContext) + throws IOException { + return new HttpJsonResumableUploadServiceStub( + ResumableUploadServiceStubSettings.newBuilder().build(), clientContext); + } + + public static final HttpJsonResumableUploadServiceStub create( + ClientContext clientContext, HttpJsonStubCallableFactory callableFactory) throws IOException { + return new HttpJsonResumableUploadServiceStub( + ResumableUploadServiceStubSettings.newBuilder().build(), clientContext, callableFactory); + } + + /** + * Constructs an instance of HttpJsonResumableUploadServiceStub, using the given settings. This is + * protected so that it is easy to make a subclass, but otherwise, the static factory methods + * should be preferred. + */ + protected HttpJsonResumableUploadServiceStub( + ResumableUploadServiceStubSettings settings, ClientContext clientContext) throws IOException { + this(settings, clientContext, new HttpJsonResumableUploadServiceCallableFactory()); + } + + /** + * Constructs an instance of HttpJsonResumableUploadServiceStub, using the given settings. This is + * protected so that it is easy to make a subclass, but otherwise, the static factory methods + * should be preferred. + */ + protected HttpJsonResumableUploadServiceStub( + ResumableUploadServiceStubSettings settings, + ClientContext clientContext, + HttpJsonStubCallableFactory callableFactory) + throws IOException { + this.callableFactory = callableFactory; + + this.resumableUploadStub = + HttpJsonResumableUploadServiceResumableUploadStub.create(clientContext, settings); + + this.backgroundResources = + new BackgroundResourceAggregation( + ImmutableList.builder() + .addAll(clientContext.getBackgroundResources()) + .add(resumableUploadStub) + .build()); + } + + @InternalApi + public static List getMethodDescriptors() { + List methodDescriptors = new ArrayList<>(); + return methodDescriptors; + } + + @Override + public ResumableUploadCallable uploadMediaCallable() { + return resumableUploadStub.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); + } +}