diff --git a/.bazeliskrc b/.bazeliskrc new file mode 100644 index 0000000000..463b45379f --- /dev/null +++ b/.bazeliskrc @@ -0,0 +1,2 @@ +# See https://github.com/bazelbuild/bazelisk +USE_BAZEL_VERSION=4.2.2 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e99dba0136..0c24ac9b4c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -7,8 +7,8 @@ name: ci jobs: build: runs-on: ubuntu-latest - strategy: + fail-fast: false matrix: java: [8, 11] steps: @@ -18,7 +18,7 @@ jobs: java-version: ${{ matrix.java }} distribution: temurin - run: java -version - + - run: bazelisk version - name: Bazel File Cache Setup id: cache-bazel uses: actions/cache@v3 @@ -41,31 +41,8 @@ jobs: echo "and it will start over with a clean cache." echo "The old one will disappear after 7 days." - - name: Unit Tests - run: bazel --batch test //:units --noshow_progress --test_output=errors - - name: Integration Tests - run: bazel --batch test //test/integration/... - - - name: Gradle Build Generated Storage Client Library - run: | - echo "Building Storage lib from generated source..." - mkdir /tmp/java-storage - bazel --batch build @com_google_googleapis//google/storage/v2:google-cloud-storage-v2-java - tar zxvf bazel-bin/external/com_google_googleapis/google/storage/v2/google-cloud-storage-v2-java.tar.gz -C /tmp/java-storage - pushd /tmp/java-storage/google-cloud-storage-v2-java - ./gradlew clean build publishToMavenLocal sourcesJar allJars - popd - - - name: Gradle Build Generated Compute Client Library - run: | - echo "Building Compute lib from generated source..." - mkdir /tmp/java-compute - bazel --batch build @com_google_googleapis//google/cloud/compute/v1small:google-cloud-compute-small-v1-java - tar zxvf bazel-bin/external/com_google_googleapis/google/cloud/compute/v1small/google-cloud-compute-small-v1-java.tar.gz -C /tmp/java-compute - pushd /tmp/java-compute/google-cloud-compute-small-v1-java - ./gradlew clean build publishToMavenLocal sourcesJar allJars - popd + run: bazelisk --batch test //test/integration/... - uses: actions/upload-artifact@v3 if: ${{ failure() }} @@ -74,21 +51,61 @@ jobs: path: ~/.cache/bazel/*/*/*/gapic_generator_java/bazel-out/*/testlogs/* retention-days: 5 - - name: Java Linter - run: bazel --batch run //:google_java_format_verification - - coverage: + generate-client-libraries: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + java: [ 8, 11 ] steps: - - uses: actions/checkout@v2 - - uses: actions/setup-go@v3 - with: - java-version: 8 - - run: java -version + - uses: actions/checkout@v2 + - uses: actions/setup-java@v3 + with: + java-version: ${{ matrix.java }} + distribution: temurin + - run: java -version + - run: bazelisk version + - name: Bazel File Cache Setup + id: cache-bazel + uses: actions/cache@v3 + with: + path: ~/.cache/bazel + key: ${{ runner.os }}-${{ secrets.CACHE_VERSION }} + + - name: Bazel Cache Not Found + if: steps.cache-bazel.outputs.cache-hit != 'true' + run: | + echo "No cache found." + - name: Bazel Cache Found + if: steps.cache-bazel.outputs.cache-hit == 'true' + run: | + echo -n "Cache found. Cache size: " + du -sh ~/.cache/bazel + echo "If the cache seems broken, update the CACHE_VERSION secret in" + echo "https://github.com/googleapis/googleapis-discovery/settings/secrets/actions" + echo "(use any random string, any GUID will work)" + echo "and it will start over with a clean cache." + echo "The old one will disappear after 7 days." + + - name: Gradle Build Generated Storage Client Library + run: | + echo "Building Storage lib from generated source..." + mkdir /tmp/java-storage + bazelisk --batch build @com_google_googleapis//google/storage/v2:google-cloud-storage-v2-java + tar zxvf bazel-bin/external/com_google_googleapis/google/storage/v2/google-cloud-storage-v2-java.tar.gz -C /tmp/java-storage + pushd /tmp/java-storage/google-cloud-storage-v2-java + ./gradlew clean build publishToMavenLocal sourcesJar allJars + popd - - name: Generate Code Coverage Report - # Run only test targets, and not golden_update targets. - run: bazel coverage //:units --combined_report=lcov + - name: Gradle Build Generated Compute Client Library + run: | + echo "Building Compute lib from generated source..." + mkdir /tmp/java-compute + bazelisk --batch build @com_google_googleapis//google/cloud/compute/v1small:google-cloud-compute-small-v1-java + tar zxvf bazel-bin/external/com_google_googleapis/google/cloud/compute/v1small/google-cloud-compute-small-v1-java.tar.gz -C /tmp/java-compute + pushd /tmp/java-compute/google-cloud-compute-small-v1-java + ./gradlew clean build publishToMavenLocal sourcesJar allJars + popd license-header: runs-on: ubuntu-latest diff --git a/BUILD.bazel b/BUILD.bazel index e8e17887a3..3ebbe83dc4 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -47,6 +47,17 @@ TEST_DEPS = [ "@io_github_java_diff_utils//jar", ] +SPRING_DEPS = [ + "@maven//:org_springframework_boot_spring_boot_starter", + "@maven//:org_springframework_boot_spring_boot_autoconfigure", + "@maven//:org_springframework_boot_spring_boot", + "@maven//:org_springframework_spring_context", + "@maven//:org_springframework_spring_beans", + "@maven//:com_google_cloud_spring_cloud_gcp_autoconfigure", + "@maven//:com_google_cloud_spring_cloud_gcp_core", + "@maven//:commons_logging_commons_logging", +] + proto_library( name = "service_config_proto", srcs = ["src/main/proto/service_config.proto"], @@ -100,13 +111,13 @@ java_library( name = "gapic_generator_java", srcs = glob(["src/main/java/**/*.java"]), plugins = [":autovalue_plugin"], - deps = MAIN_DEPS, + deps = MAIN_DEPS + SPRING_DEPS, ) java_library( name = "gapic_generator_java_test", srcs = glob(["src/test/java/**/*.java"]), - deps = [":gapic_generator_java"] + MAIN_DEPS + TEST_DEPS, + deps = [":gapic_generator_java"] + MAIN_DEPS + TEST_DEPS + SPRING_DEPS, ) java_binary( @@ -115,6 +126,12 @@ java_binary( runtime_deps = [":gapic_generator_java"] + MAIN_DEPS, ) +java_binary( + name = "protoc-gen-java_gapic_spring", + main_class = "com.google.api.generator.spring.Main", + runtime_deps = [":gapic_generator_java"] + MAIN_DEPS + SPRING_DEPS, +) + # Request dumper binary, which dumps the CodeGeneratorRequest to a file on disk # which will be identical to the one passed to the protoc-gen-java_gapic during # normal execution. The dumped file then can be used to run this gapic-generator diff --git a/WORKSPACE b/WORKSPACE index 32f311e27e..0168823bba 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -79,8 +79,14 @@ load("@com_google_protobuf//:protobuf_deps.bzl", "PROTOBUF_MAVEN_ARTIFACTS", "pr load("@rules_jvm_external//:defs.bzl", "maven_install") + +SPRING_MAVEN_ARTIFACTS = [ + "org.springframework.boot:spring-boot-starter:2.7.4", + "com.google.cloud:spring-cloud-gcp-autoconfigure:3.4.1", +] + maven_install( - artifacts = PROTOBUF_MAVEN_ARTIFACTS, + artifacts = PROTOBUF_MAVEN_ARTIFACTS + SPRING_MAVEN_ARTIFACTS, generate_compat_repositories = True, repositories = [ "https://repo.maven.apache.org/maven2/", diff --git a/pom.xml b/pom.xml index 76416d39da..64f732d00c 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ com.google.cloud google-cloud-shared-dependencies - 3.0.5 + 3.0.6 pom import @@ -27,7 +27,7 @@ kr.motd.maven os-maven-plugin - 1.7.0 + 1.7.1 @@ -324,6 +324,18 @@ 4.13.2 + + + org.springframework.boot + spring-boot-starter + 2.7.4 + + + com.google.cloud + spring-cloud-gcp-autoconfigure + 3.4.1 + + \n" + + " ../../spring-cloud-gcp-starters/pom.xml\n" + + " \n" + + " %s\n" + + " ${project.parent.version}-preview\n" + + " %s\n" + + " Spring Boot Starter with AutoConfiguration for %s\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " %s\n" + + " %s\n" + + " \n" + + "\n" + + " \n" + + " org.springframework.boot\n" + + " spring-boot-starter\n" + + " \n" + + "\n" + + " \n" + + " com.google.cloud\n" + + " spring-cloud-gcp-autoconfigure\n" + + " \n" + + "\n" + + "\n" + + "", + springParentVersion, + springStarterArtifactId, + springStarterName, + clientLibraryShortName, + clientLibraryGroupId, + clientLibraryName)); + + return sb.toString(); + } + + private static void writePom(GapicContext context, JarOutputStream jos) { + JarEntry jarEntry = new JarEntry("pom.xml"); + try { + jos.putNextEntry(jarEntry); + String result = buildPomString(context); + jos.write(result.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new GapicWriterException("Could not write pom.xml", e); + } + } + + private static String getPath(String pakkage, String className) { + String path = pakkage.replaceAll("\\.", "/"); + if (className.startsWith("Mock") || className.endsWith("Test")) { + path = "src/test/java/" + path; + } else { + path = "src/main/java/" + path; + } + + // Resource name helpers go into the protobuf package. Denote this with "proto/src/main/*". + if (className.endsWith("Name")) { + path = "proto/" + path; + } + return path; + } +} diff --git a/src/main/java/com/google/api/generator/spring/composer/SpringAutoConfigClassComposer.java b/src/main/java/com/google/api/generator/spring/composer/SpringAutoConfigClassComposer.java new file mode 100644 index 0000000000..1c83dd02d5 --- /dev/null +++ b/src/main/java/com/google/api/generator/spring/composer/SpringAutoConfigClassComposer.java @@ -0,0 +1,1144 @@ +// Copyright 2022 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.spring.composer; + +import com.google.api.gax.core.CredentialsProvider; +import com.google.api.gax.core.ExecutorProvider; +import com.google.api.gax.httpjson.InstantiatingHttpJsonChannelProvider; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.HeaderProvider; +import com.google.api.gax.rpc.TransportChannelProvider; +import com.google.api.generator.engine.ast.AnnotationNode; +import com.google.api.generator.engine.ast.ArithmeticOperationExpr; +import com.google.api.generator.engine.ast.AssignmentExpr; +import com.google.api.generator.engine.ast.CastExpr; +import com.google.api.generator.engine.ast.ClassDefinition; +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.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.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.ThisObjectValue; +import com.google.api.generator.engine.ast.TypeNode; +import com.google.api.generator.engine.ast.ValueExpr; +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.composer.common.ClassComposer; +import com.google.api.generator.gapic.composer.utils.ClassNames; +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.GapicServiceConfig; +import com.google.api.generator.gapic.model.Method; +import com.google.api.generator.gapic.model.Service; +import com.google.api.generator.gapic.model.Transport; +import com.google.api.generator.gapic.utils.JavaStyle; +import com.google.api.generator.spring.composer.comment.SpringAutoconfigCommentComposer; +import com.google.api.generator.spring.utils.ComposerUtils; +import com.google.api.generator.spring.utils.LoggerUtils; +import com.google.api.generator.spring.utils.Utils; +import com.google.cloud.spring.autoconfigure.core.GcpContextAutoConfiguration; +import com.google.cloud.spring.core.Credentials; +import com.google.cloud.spring.core.DefaultCredentialsProvider; +import com.google.common.base.CaseFormat; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +public class SpringAutoConfigClassComposer implements ClassComposer { + private static final SpringAutoConfigClassComposer INSTANCE = new SpringAutoConfigClassComposer(); + + private static final Map STATIC_TYPES = createStaticTypes(); + private static final Statement EMPTY_LINE_STATEMENT = EmptyLineStatement.create(); + + private SpringAutoConfigClassComposer() {} + + public static SpringAutoConfigClassComposer instance() { + return INSTANCE; + } + + @Override + public GapicClass generate(GapicContext context, Service service) { + String packageName = Utils.getSpringPackageName(service.pakkage()); + Map dynamicTypes = createDynamicTypes(service, packageName); + String serviceName = service.name(); + String serviceNameLowerCamel = JavaStyle.toLowerCamelCase(serviceName); + String serviceNameLowerHyphen = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_HYPHEN, serviceName); + String className = Utils.getServiceAutoConfigurationClassName(service); + String credentialsProviderName = serviceNameLowerCamel + "Credentials"; + String transportChannelProviderName = "default" + serviceName + "TransportChannelProvider"; + String clientName = serviceNameLowerCamel + "Client"; + GapicClass.Kind kind = Kind.MAIN; + + GapicServiceConfig gapicServiceConfig = context.serviceConfig(); + + Expr thisExpr = ValueExpr.withValue(ThisObjectValue.withType(dynamicTypes.get(className))); + Transport transport = context.transport(); + // TODO(emmwang): this condition is adapted from latest gapic-generator-java changes as part of + // https://github.com/googleapis/gapic-generator-java/issues/1117, but should be updated to use + // the gapic-implemented helpers once spring generator code is migrated. + boolean hasRestSupportedMethod = + service.methods().stream() + .anyMatch( + x -> + x.httpBindings() != null + && x.stream() != Method.Stream.BIDI + && x.stream() != Method.Stream.CLIENT); + boolean hasRestOption = transport.equals(Transport.GRPC_REST) && hasRestSupportedMethod; + String serviceSettingsMethodName = JavaStyle.toLowerCamelCase(service.name()) + "Settings"; + + ClassDefinition classDef = + ClassDefinition.builder() + .setPackageString(packageName) + .setName(className) + .setScope(ScopeNode.PUBLIC) + .setHeaderCommentStatements( + SpringAutoconfigCommentComposer.createClassHeaderComments(service)) + .setStatements( + createMemberVariables(service, packageName, dynamicTypes, gapicServiceConfig)) + .setAnnotations(createClassAnnotations(service, dynamicTypes)) + .setMethods( + Arrays.asList( + createConstructor(service, className, dynamicTypes, thisExpr), + createTransportChannelProviderBeanMethod( + service, + transportChannelProviderName, + dynamicTypes, + thisExpr, + hasRestOption), + createSettingsBeanMethod( + service, + transportChannelProviderName, + dynamicTypes, + thisExpr, + hasRestOption, + serviceSettingsMethodName), + createClientBeanMethod(dynamicTypes, service, serviceSettingsMethodName), + createUserAgentHeaderProviderMethod( + serviceNameLowerHyphen, className, dynamicTypes, thisExpr))) + .build(); + + return GapicClass.create(kind, classDef); + } + + private static List createMemberVariables( + Service service, + String packageName, + Map types, + GapicServiceConfig serviceConfig) { + + // private final LanguageProperties clientProperties; + ExprStatement clientPropertiesStatement = + ComposerUtils.createMemberVarStatement( + "clientProperties", + types.get(Utils.getServicePropertiesClassName(service)), + true, + null, + null); + Statement credentialProvider = + ComposerUtils.createMemberVarStatement( + "credentialsProvider", STATIC_TYPES.get("CredentialsProvider"), true, null, null); + + Statement loggerStatement = + LoggerUtils.getLoggerDeclarationExpr( + Utils.getServiceAutoConfigurationClassName(service), types); + return Arrays.asList(clientPropertiesStatement, credentialProvider, loggerStatement); + } + + private static MethodDefinition createConstructor( + Service service, String className, Map types, Expr thisExpr) { + VariableExpr clientPropertiesVarExpr = + VariableExpr.withVariable( + Variable.builder() + .setName("clientProperties") + .setType(types.get(Utils.getServicePropertiesClassName(service))) + .build()); + VariableExpr credentialsProviderVarExpr = + VariableExpr.withVariable( + Variable.builder() + .setName("credentialsProvider") + .setType(STATIC_TYPES.get("CredentialsProvider")) + .build()); + + // this.clientProperties = clientProperties; + AssignmentExpr thisClientPropertiesAssignmentExpr = + AssignmentExpr.builder() + .setVariableExpr( + clientPropertiesVarExpr.toBuilder().setExprReferenceExpr(thisExpr).build()) + .setValueExpr(clientPropertiesVarExpr) + .build(); + ExprStatement thisClientPropertiesAssignmentStatement = + ExprStatement.withExpr(thisClientPropertiesAssignmentExpr); + + // if (this.clientProperties.getCredentials().hasKey()) { + // this.credentialsProvider = new DefaultCredentialsProvider(this.clientProperties); + // } else { + // this.credentialsProvider = credentialsProvider; + // } + VariableExpr thisClientProperties = + clientPropertiesVarExpr.toBuilder().setExprReferenceExpr(thisExpr).build(); + AssignmentExpr.Builder thisCredentialsProviderAssignmentExprBuilder = + AssignmentExpr.builder() + .setVariableExpr( + credentialsProviderVarExpr.toBuilder().setExprReferenceExpr(thisExpr).build()); + ExprStatement thisCredentialsProviderToGlobalAssignmentStatement = + ExprStatement.withExpr( + thisCredentialsProviderAssignmentExprBuilder + .setValueExpr(credentialsProviderVarExpr) + .build()); + + CastExpr newCredentialsProviderExpr = + CastExpr.builder() + .setExpr( + NewObjectExpr.builder() + .setType(STATIC_TYPES.get("DefaultCredentialsProvider")) + .setArguments(thisClientProperties) + .build()) + .setType(STATIC_TYPES.get("CredentialsProvider")) + .build(); + ExprStatement thisCredentialsProviderAssignmentExprNewStatement = + ExprStatement.withExpr( + thisCredentialsProviderAssignmentExprBuilder + .setValueExpr(newCredentialsProviderExpr) + .build()); + + Expr clientPropertiesGetCredentials = + MethodInvocationExpr.builder() + .setExprReferenceExpr(thisClientProperties) + .setMethodName("getCredentials") + .setReturnType(STATIC_TYPES.get("Credentials")) + .build(); + Expr clientPropertiesCredentialsHasKey = + MethodInvocationExpr.builder() + .setExprReferenceExpr(clientPropertiesGetCredentials) + .setMethodName("hasKey") + .setReturnType(TypeNode.BOOLEAN) + .build(); + + Statement logClientCredentials = + LoggerUtils.createLoggerStatement( + ValueExpr.withValue( + StringObjectValue.withValue( + "Using credentials from " + service.name() + "-specific configuration")), + types); + IfStatement thisCredentialsProviderAssignmentStatement = + createIfStatement( + clientPropertiesCredentialsHasKey, + Arrays.asList(logClientCredentials, thisCredentialsProviderAssignmentExprNewStatement), + Arrays.asList(thisCredentialsProviderToGlobalAssignmentStatement)); + + return MethodDefinition.constructorBuilder() + .setScope(ScopeNode.PROTECTED) + .setThrowsExceptions(Arrays.asList(TypeNode.withExceptionClazz(IOException.class))) + .setReturnType(types.get(className)) + .setArguments( + Arrays.asList( + clientPropertiesVarExpr.toBuilder().setIsDecl(true).build(), + credentialsProviderVarExpr.toBuilder().setIsDecl(true).build())) + .setBody( + Arrays.asList( + thisClientPropertiesAssignmentStatement, + thisCredentialsProviderAssignmentStatement)) + .build(); + } + + private static List createClassAnnotations( + Service service, Map types) { + // @AutoConfiguration + // @AutoConfigureAfter(GcpContextAutoConfiguration.class) + // @ConditionalOnClass(LanguageServiceClient.class) + // @ConditionalOnProperty(value = + // "com.google.cloud.language.v1.spring.auto.language-service.enabled", matchIfMissing = true) + // @EnableConfigurationProperties(LanguageProperties.class) + + AssignmentExpr valueStringAssignmentExpr = + AssignmentExpr.builder() + .setVariableExpr( + VariableExpr.withVariable( + Variable.builder().setName("value").setType(TypeNode.STRING).build())) + .setValueExpr( + ValueExpr.withValue( + StringObjectValue.withValue( + Utils.getSpringPropertyPrefix(service.pakkage(), service.name()) + + ".enabled"))) + .build(); + AssignmentExpr matchIfMissingAssignmentExpr = + AssignmentExpr.builder() + .setVariableExpr( + VariableExpr.withVariable( + Variable.builder().setName("matchIfMissing").setType(TypeNode.BOOLEAN).build())) + .setValueExpr( + ValueExpr.withValue( + PrimitiveValue.builder().setValue("true").setType(TypeNode.BOOLEAN).build())) + .build(); + AnnotationNode conditionalOnPropertyNode = + AnnotationNode.builder() + .setType(STATIC_TYPES.get("ConditionalOnProperty")) + .addDescription(valueStringAssignmentExpr) + .addDescription(matchIfMissingAssignmentExpr) + .build(); + + AnnotationNode conditionalOnClassNode = + AnnotationNode.builder() + .setType(STATIC_TYPES.get("ConditionalOnClass")) + .setDescription( + VariableExpr.builder() + .setVariable( + Variable.builder().setType(TypeNode.CLASS_OBJECT).setName("class").build()) + .setStaticReferenceType(types.get("ServiceClient")) + .build()) + .build(); + AnnotationNode autoConfigureAfterNode = + AnnotationNode.builder() + .setType(STATIC_TYPES.get("AutoConfigureAfter")) + .setDescription( + VariableExpr.builder() + .setVariable( + Variable.builder().setType(TypeNode.CLASS_OBJECT).setName("class").build()) + .setStaticReferenceType(STATIC_TYPES.get("GcpContextAutoConfiguration")) + .build()) + .build(); + AnnotationNode configurationNode = + AnnotationNode.builder().setType(STATIC_TYPES.get("AutoConfiguration")).build(); + AnnotationNode enableConfigurationPropertiesNode = + AnnotationNode.builder() + .setType(STATIC_TYPES.get("EnableConfigurationProperties")) + .setDescription( + VariableExpr.builder() + .setVariable( + Variable.builder().setType(TypeNode.CLASS_OBJECT).setName("class").build()) + .setStaticReferenceType(types.get(Utils.getServicePropertiesClassName(service))) + .build()) + .build(); + + return Arrays.asList( + configurationNode, + autoConfigureAfterNode, + conditionalOnClassNode, + conditionalOnPropertyNode, + enableConfigurationPropertiesNode); + } + + private static MethodDefinition createTransportChannelProviderBeanMethod( + Service service, + String methodName, + Map types, + Expr thisExpr, + boolean hasRestOption) { + AssignmentExpr nameStringAssignmentExpr = + AssignmentExpr.builder() + .setVariableExpr( + VariableExpr.withVariable( + Variable.builder().setName("name").setType(TypeNode.STRING).build())) + .setValueExpr(ValueExpr.withValue(StringObjectValue.withValue(methodName))) + .build(); + AnnotationNode conditionalOnMissingBean = + AnnotationNode.builder() + .setType(STATIC_TYPES.get("ConditionalOnMissingBean")) + .addDescription(nameStringAssignmentExpr) + .build(); + + MethodDefinition.Builder beanMethodBuilder = + MethodDefinition.builder() + .setHeaderCommentStatements( + SpringAutoconfigCommentComposer.createTransportChannelProviderComment()) + .setName(methodName) + .setScope(ScopeNode.PUBLIC) + .setReturnType(STATIC_TYPES.get("TransportChannelProvider")) + .setAnnotations( + Arrays.asList( + AnnotationNode.withType(STATIC_TYPES.get("Bean")), conditionalOnMissingBean)); + + // LanguageServiceSettings.defaultTransportChannelProvider() + MethodInvocationExpr defaultTransportChannelProviderExpr = + MethodInvocationExpr.builder() + .setMethodName("defaultTransportChannelProvider") + .setStaticReferenceType(types.get("ServiceSettings")) + .setReturnType(STATIC_TYPES.get("TransportChannelProvider")) + .build(); + + if (hasRestOption) { + // if (this.clientProperties.isUseRest()) { + // return LanguageServiceSettings.defaultHttpJsonTransportProviderBuilder().build(); + // } + Variable clientPropertiesVar = + Variable.builder() + .setName("clientProperties") + .setType(types.get(Utils.getServicePropertiesClassName(service))) + .build(); + VariableExpr thisClientPropertiesVarExpr = + VariableExpr.withVariable(clientPropertiesVar) + .toBuilder() + .setExprReferenceExpr(thisExpr) + .build(); + + MethodInvocationExpr getUseRest = + MethodInvocationExpr.builder() + .setMethodName("getUseRest") + .setReturnType(TypeNode.BOOLEAN) + .setExprReferenceExpr(thisClientPropertiesVarExpr) + .build(); + + // LanguageServiceSettings.defaultHttpJsonTransportProviderBuilder().build() + Expr defaultTransportProviderExprChain = + MethodInvocationExpr.builder() + .setStaticReferenceType(types.get("ServiceSettings")) + .setMethodName("defaultHttpJsonTransportProviderBuilder") + .build(); + MethodInvocationExpr defaultHttpJsonTransportProviderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(defaultTransportProviderExprChain) + .setMethodName("build") + .setReturnType(STATIC_TYPES.get("InstantiatingHttpJsonChannelProvider")) + .build(); + + IfStatement returnHttpJsonTransportChannelProviderStatement = + createIfStatement( + getUseRest, + Arrays.asList( + ExprStatement.withExpr( + ReturnExpr.withExpr(defaultHttpJsonTransportProviderExpr))), + null); + + return beanMethodBuilder + .setBody(Arrays.asList(returnHttpJsonTransportChannelProviderStatement)) + .setReturnExpr(defaultTransportChannelProviderExpr) + .build(); + } + + return beanMethodBuilder.setReturnExpr(defaultTransportChannelProviderExpr).build(); + } + + private static IfStatement createIfStatement( + Expr conditionExpr, List ifBody, List elseBody) { + IfStatement.Builder credentialIfStatement = + IfStatement.builder().setConditionExpr(conditionExpr).setBody(ifBody); + if (elseBody != null) { + credentialIfStatement.setElseBody(elseBody); + } + return credentialIfStatement.build(); + } + + private static MethodDefinition createSettingsBeanMethod( + Service service, + String transportChannelProviderName, + Map types, + Expr thisExpr, + boolean hasRestOption, + String serviceSettingsMethodName) { + // argument variables: + VariableExpr credentialsProviderVariableExpr = + VariableExpr.withVariable( + Variable.builder() + .setName("credentialsProvider") + .setType(STATIC_TYPES.get("CredentialsProvider")) + .build()); + VariableExpr transportChannelProviderVariableExpr = + VariableExpr.withVariable( + Variable.builder() + .setName("defaultTransportChannelProvider") + .setType(STATIC_TYPES.get("TransportChannelProvider")) + .build()); + + List bodyStatements = new ArrayList<>(); + + Variable settingBuilderVariable = + Variable.builder() + .setName("clientSettingsBuilder") + .setType(types.get("ServiceSettingsBuilder")) + .build(); + + VariableExpr settingsVarExpr = + VariableExpr.withVariable(settingBuilderVariable).toBuilder().setIsDecl(true).build(); + + Expr newBuilderExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(types.get("ServiceSettings")) + .setMethodName("newBuilder") + .setReturnType(types.get("ServiceSettingsBuilder")) + .build(); + + Variable clientPropertiesVar = + Variable.builder() + .setName("clientProperties") + .setType(types.get(Utils.getServicePropertiesClassName(service))) + .build(); + VariableExpr thisClientPropertiesVarExpr = + VariableExpr.withVariable(clientPropertiesVar) + .toBuilder() + .setExprReferenceExpr(thisExpr) + .build(); + + if (hasRestOption) { + // For GRPC+REST libraries + // LanguageServiceSettings.Builder clientSettingsBuilder; + // if (this.clientProperties.isUseRest()) { + // clientSettingsBuilder = LanguageServiceSettings.newHttpJsonBuilder(); + // } else { + // clientSettingsBuilder = LanguageServiceSettings.newBuilder(); + // } + MethodInvocationExpr getUseRest = + MethodInvocationExpr.builder() + .setMethodName("getUseRest") + .setReturnType(TypeNode.BOOLEAN) + .setExprReferenceExpr(thisClientPropertiesVarExpr) + .build(); + + // clientSettingsBuilder = LanguageServiceSettings.newHttpJsonBuilder(); + Expr newHttpJsonBuilderExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(types.get("ServiceSettings")) + .setMethodName("newHttpJsonBuilder") + .setReturnType(types.get("ServiceSettingsBuilder")) + .build(); + + AssignmentExpr newHttpJsonBuilderAssignmentExpr = + AssignmentExpr.builder() + .setVariableExpr(VariableExpr.withVariable(settingBuilderVariable)) + .setValueExpr(newHttpJsonBuilderExpr) + .build(); + + // clientSettingsBuilder = LanguageServiceSettings.newBuilder(); + AssignmentExpr newBuilderAssignmentExpr = + AssignmentExpr.builder() + .setVariableExpr(VariableExpr.withVariable(settingBuilderVariable)) + .setValueExpr(newBuilderExpr) + .build(); + + ExprStatement newBuilderStatement = ExprStatement.withExpr(newBuilderAssignmentExpr); + ExprStatement newHttpJsonBuilderStatement = + ExprStatement.withExpr(newHttpJsonBuilderAssignmentExpr); + + IfStatement setClientSettingsBuilderStatement = + createIfStatement( + getUseRest, + Arrays.asList( + newHttpJsonBuilderStatement, + LoggerUtils.createLoggerStatement( + ValueExpr.withValue( + StringObjectValue.withValue("Using REST (HTTP/JSON) transport.")), + types)), + Arrays.asList(newBuilderStatement)); + + bodyStatements.add(ExprStatement.withExpr(settingsVarExpr)); + bodyStatements.add(setClientSettingsBuilderStatement); + + } else { + // For GRPC-only libraries + // LanguageServiceSettings.Builder clientSettingsBuilder = + // LanguageServiceSettings.newBuilder(); + + AssignmentExpr clientSettingsBuilderAssignmentExpr = + AssignmentExpr.builder() + .setVariableExpr(settingsVarExpr) + .setValueExpr(newBuilderExpr) + .build(); + + bodyStatements.add(ExprStatement.withExpr(clientSettingsBuilderAssignmentExpr)); + } + + VariableExpr thisCredentialsProvider = + VariableExpr.withVariable( + Variable.builder() + .setName("credentialsProvider") + .setType(STATIC_TYPES.get("CredentialsProvider")) + .build()) + .toBuilder() + .setExprReferenceExpr(thisExpr) + .build(); + + Expr settingsBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(VariableExpr.withVariable(settingBuilderVariable)) + .setMethodName("setCredentialsProvider") + .setArguments(thisCredentialsProvider) + .build(); + // clientSettingsBuilder + // .setTransportChannelProvider(defaultTransportChannelProvider) + settingsBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(settingsBuilderExpr) + .setMethodName("setTransportChannelProvider") + .setArguments(transportChannelProviderVariableExpr) + .build(); + // .setHeaderProvider(this.serAgentHeaderProvider()); + MethodInvocationExpr userAgentHeaderProviderInvocation = + MethodInvocationExpr.builder() + .setExprReferenceExpr(thisExpr) + .setMethodName("userAgentHeaderProvider") + .setReturnType(STATIC_TYPES.get("HeaderProvider")) + .build(); + settingsBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(settingsBuilderExpr) + .setMethodName("setHeaderProvider") + .setArguments(userAgentHeaderProviderInvocation) + .setReturnType(settingBuilderVariable.type()) + .build(); + + bodyStatements.add(ExprStatement.withExpr(settingsBuilderExpr)); + + // if (this.clientProperties.getQuotaProjectId() != null) { + // clientSettingsBuilder.setQuotaProjectId(clientProperties.getQuotaProjectId()); + // LOGGER.info("Quota project id set to: " + clientProperties.getQuotaProjectId() + // + ", this overrides project id from credentials."); + // } + + MethodInvocationExpr getQuotaProjectId = + MethodInvocationExpr.builder() + .setMethodName("getQuotaProjectId") + .setReturnType(TypeNode.STRING) + .setExprReferenceExpr(thisClientPropertiesVarExpr) + .build(); + RelationalOperationExpr projectIdIsNull = + RelationalOperationExpr.notEqualToWithExprs(getQuotaProjectId, ValueExpr.createNullExpr()); + + // clientSettingsBuilder.setQuotaProjectId(clientProperties.getQuotaProjectId()); + MethodInvocationExpr setQuotaProjectId = + MethodInvocationExpr.builder() + .setExprReferenceExpr(VariableExpr.withVariable(settingBuilderVariable)) + .setMethodName("setQuotaProjectId") + .setArguments(getQuotaProjectId) + .build(); + + Statement projectIdLoggerStatement = + LoggerUtils.createLoggerStatement( + LoggerUtils.concatManyWithExprs( + ValueExpr.withValue(StringObjectValue.withValue("Quota project id set to ")), + getQuotaProjectId, + ValueExpr.withValue( + StringObjectValue.withValue(", this overrides project id from credentials."))), + types); + + IfStatement setQuotaProjectIdStatement = + createIfStatement( + projectIdIsNull, + Arrays.asList(ExprStatement.withExpr(setQuotaProjectId), projectIdLoggerStatement), + null); + + bodyStatements.add(setQuotaProjectIdStatement); + + // if (this.clientProperties.getExecutorThreadCount() != null) { + // ExecutorProvider executorProvider = + // LanguageServiceSettings.defaultExecutorProviderBuilder() + // .setExecutorThreadCount(clientProperties.getExecutorThreadCount()).build(); + // clientSettingsBuilder + // .setBackgroundExecutorProvider(executorProvider); + // } + + MethodInvocationExpr getExecutorThreadCount = + MethodInvocationExpr.builder() + .setMethodName("getExecutorThreadCount") + .setReturnType(TypeNode.INT_OBJECT) + .setExprReferenceExpr(thisClientPropertiesVarExpr) + .build(); + RelationalOperationExpr executorThreadCountIsNull = + RelationalOperationExpr.notEqualToWithExprs( + getExecutorThreadCount, ValueExpr.createNullExpr()); + + VariableExpr executorProviderVarExpr = + VariableExpr.withVariable( + Variable.builder() + .setType(STATIC_TYPES.get("ExecutorProvider")) + .setName("executorProvider") + .build()); + + MethodInvocationExpr chainedMethodToSetExecutorProvider = + MethodInvocationExpr.builder() + .setStaticReferenceType(types.get("ServiceSettings")) + .setMethodName("defaultExecutorProviderBuilder") + .build(); + chainedMethodToSetExecutorProvider = + MethodInvocationExpr.builder() + .setExprReferenceExpr(chainedMethodToSetExecutorProvider) + .setMethodName("setExecutorThreadCount") + .setArguments(getExecutorThreadCount) + .build(); + chainedMethodToSetExecutorProvider = + MethodInvocationExpr.builder() + .setExprReferenceExpr(chainedMethodToSetExecutorProvider) + .setMethodName("build") + .setReturnType(STATIC_TYPES.get("ExecutorProvider")) + .build(); + AssignmentExpr executorProviderAssignExpr = + AssignmentExpr.builder() + .setVariableExpr(executorProviderVarExpr.toBuilder().setIsDecl(true).build()) + .setValueExpr(chainedMethodToSetExecutorProvider) + .build(); + MethodInvocationExpr setBackgroundExecutorProvider = + MethodInvocationExpr.builder() + .setExprReferenceExpr(VariableExpr.withVariable(settingBuilderVariable)) + .setMethodName("setBackgroundExecutorProvider") + .setArguments(executorProviderVarExpr) + .build(); + + Statement backgroundExecutorLoggerStatement = + LoggerUtils.createLoggerStatement( + ArithmeticOperationExpr.concatWithExprs( + ValueExpr.withValue( + StringObjectValue.withValue("Background executor thread count is ")), + getExecutorThreadCount), + types); + IfStatement setBackgroundExecutorProviderStatement = + createIfStatement( + executorThreadCountIsNull, + Arrays.asList( + ExprStatement.withExpr(executorProviderAssignExpr), + ExprStatement.withExpr(setBackgroundExecutorProvider), + backgroundExecutorLoggerStatement), + null); + + bodyStatements.add(setBackgroundExecutorProviderStatement); + + // If service-level properties configured, update retry settings for each method + + Variable serviceRetryPropertiesVar = + Variable.builder().setName("serviceRetry").setType(types.get("Retry")).build(); + + VariableExpr serviceRetryPropertiesVarExpr = + VariableExpr.builder().setVariable(serviceRetryPropertiesVar).setIsDecl(true).build(); + + // clientProperties.getRetry() + MethodInvocationExpr serviceRetryPropertiesExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(VariableExpr.withVariable(clientPropertiesVar)) + .setMethodName("getRetry") + .setReturnType(types.get("Retry")) + .build(); + + // Retry retrySettings = clientProperties.getRetrySettings(); + AssignmentExpr serviceRetrySettingsExpr = + AssignmentExpr.builder() + .setVariableExpr(serviceRetryPropertiesVarExpr) + .setValueExpr(serviceRetryPropertiesExpr) + .build(); + + bodyStatements.add(ExprStatement.withExpr(serviceRetrySettingsExpr)); + + RelationalOperationExpr serviceRetryPropertiesNotNull = + RelationalOperationExpr.notEqualToWithExprs( + VariableExpr.withVariable(serviceRetryPropertiesVar), ValueExpr.createNullExpr()); + + List updateRetrySettingsStatementBody = new ArrayList<>(); + + for (Method method : Utils.getMethodsForRetryConfiguration(service)) { + List updateMethodWithServiceRetryStatements = + createUpdateRetrySettingsStatements( + method.name(), settingBuilderVariable, serviceRetryPropertiesVar, types); + updateRetrySettingsStatementBody.addAll(updateMethodWithServiceRetryStatements); + updateRetrySettingsStatementBody.add(EMPTY_LINE_STATEMENT); + } + + updateRetrySettingsStatementBody.add( + LoggerUtils.createLoggerStatement( + ValueExpr.withValue( + StringObjectValue.withValue( + "Configured service-level retry settings from properties.")), + types)); + + IfStatement setRetrySettingsStatement = + createIfStatement(serviceRetryPropertiesNotNull, updateRetrySettingsStatementBody, null); + + bodyStatements.add(setRetrySettingsStatement); + + // If-blocks to update with method-level properties + for (Method method : Utils.getMethodsForRetryConfiguration(service)) { + String methodNameLowerCamel = JavaStyle.toLowerCamelCase(method.name()); + String methodNameUpperCamel = JavaStyle.toUpperCamelCase(method.name()); + + Variable methodRetryPropertiesVar = + Variable.builder() + .setName(methodNameLowerCamel + "Retry") + .setType(types.get("Retry")) + .build(); + + VariableExpr methodRetryPropertiesVarExpr = + VariableExpr.builder().setVariable(methodRetryPropertiesVar).setIsDecl(true).build(); + + // clientProperties.getRetry() + MethodInvocationExpr methodRetryPropertiesExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(VariableExpr.withVariable(clientPropertiesVar)) + .setMethodName(String.format("get%sRetry", methodNameUpperCamel)) + .setReturnType(types.get("Retry")) + .build(); + + AssignmentExpr methodRetrySettingsExpr = + AssignmentExpr.builder() + .setVariableExpr(methodRetryPropertiesVarExpr) + .setValueExpr(methodRetryPropertiesExpr) + .build(); + + bodyStatements.add(ExprStatement.withExpr(methodRetrySettingsExpr)); + + RelationalOperationExpr methodRetryPropertiesNotNull = + RelationalOperationExpr.notEqualToWithExprs( + VariableExpr.withVariable(methodRetryPropertiesVar), ValueExpr.createNullExpr()); + + List updateMethodRetrySettingsStatementBody = + createUpdateRetrySettingsStatements( + method.name(), settingBuilderVariable, methodRetryPropertiesVar, types); + + updateMethodRetrySettingsStatementBody.add( + LoggerUtils.createLoggerStatement( + ValueExpr.withValue( + StringObjectValue.withValue( + String.format( + "Configured method-level retry settings for %s from properties.", + methodNameLowerCamel))), + types)); + + IfStatement setMethodRetrySettingsStatement = + createIfStatement( + methodRetryPropertiesNotNull, updateMethodRetrySettingsStatementBody, null); + + bodyStatements.add(setMethodRetrySettingsStatement); + } + + // return expressions + MethodInvocationExpr returnExpr = + MethodInvocationExpr.builder() + .setMethodName("build") + .setExprReferenceExpr(settingsVarExpr.toBuilder().setIsDecl(false).build()) + .setReturnType(types.get("ServiceSettings")) + .build(); + List argumentsVariableExprs = + Arrays.asList( + transportChannelProviderVariableExpr + .toBuilder() + .setIsDecl(true) + .setAnnotations( + Arrays.asList( + AnnotationNode.builder() + .setType(STATIC_TYPES.get("Qualifier")) + .setDescription(transportChannelProviderName) + .build())) + .build()); + + return MethodDefinition.builder() + .setHeaderCommentStatements( + SpringAutoconfigCommentComposer.createSettingsBeanComment( + service, + Utils.getServicePropertiesClassName(service), + transportChannelProviderName)) + .setName(serviceSettingsMethodName) + .setScope(ScopeNode.PUBLIC) + .setReturnType(types.get("ServiceSettings")) + .setArguments(argumentsVariableExprs) + .setAnnotations( + Arrays.asList( + AnnotationNode.withType(STATIC_TYPES.get("Bean")), + AnnotationNode.withType(STATIC_TYPES.get("ConditionalOnMissingBean")))) + .setThrowsExceptions(Arrays.asList(TypeNode.withExceptionClazz(IOException.class))) + .setReturnExpr(returnExpr) + .setBody(bodyStatements) + .build(); + } + + private static List createUpdateRetrySettingsStatements( + String methodName, + Variable settingBuilderVariable, + Variable retryFromPropertiesVar, + Map types) { + + List results = new ArrayList<>(); + String methodNameLowerCamel = JavaStyle.toLowerCamelCase(methodName); + String settingsVarName = methodNameLowerCamel + "Settings"; + String retrySettingsVarName = methodNameLowerCamel + "RetrySettings"; + + Variable methodRetrySettingsVariable = + Variable.builder() + .setName(retrySettingsVarName) + .setType(STATIC_TYPES.get("RetrySettings")) + .build(); + + // clientSettingsBuilder.analyzeSentimentSettings() + MethodInvocationExpr methodSettingsExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(VariableExpr.withVariable(settingBuilderVariable)) + .setMethodName(settingsVarName) + .build(); + + // clientSettingsBuilder.analyzeSentimentSettings().getRetrySettings() + MethodInvocationExpr getRetrySettingsExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(methodSettingsExpr) + .setMethodName(String.format("getRetrySettings")) + .build(); + + MethodInvocationExpr updatedRetrySettingsExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(types.get("RetryUtil")) + .setMethodName("updateRetrySettings") + .setArguments( + Arrays.asList( + getRetrySettingsExpr, VariableExpr.withVariable(retryFromPropertiesVar))) + .setReturnType(STATIC_TYPES.get("RetrySettings")) + .build(); + + // clientSettingsBuilder.analyzeSentimentSettings().setRetrySettings(analyzeSentimentRetrySettings) + MethodInvocationExpr setRetrySettingsExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(methodSettingsExpr) + .setMethodName(String.format("setRetrySettings")) + .setArguments(VariableExpr.withVariable(methodRetrySettingsVariable)) + .build(); + + results.add( + ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr( + VariableExpr.builder() + .setVariable(methodRetrySettingsVariable) + .setIsDecl(true) + .build()) + .setValueExpr(updatedRetrySettingsExpr) + .build())); + results.add(ExprStatement.withExpr(setRetrySettingsExpr)); + + return results; + } + + private static MethodDefinition createClientBeanMethod( + Map types, Service service, String serviceSettingsMethodName) { + VariableExpr clientSettingsVariableExpr = + VariableExpr.withVariable( + Variable.builder() + .setName(serviceSettingsMethodName) + .setType(types.get("ServiceSettings")) + .build()); + MethodInvocationExpr returnExpr = + MethodInvocationExpr.builder() + .setMethodName("create") + .setStaticReferenceType(types.get("ServiceClient")) + .setReturnType(types.get("ServiceClient")) + .setArguments(clientSettingsVariableExpr) + .build(); + List argumentsVariableExprs = + Arrays.asList(clientSettingsVariableExpr.toBuilder().setIsDecl(true).build()); + String methodName = JavaStyle.toLowerCamelCase(service.name()) + "Client"; + return MethodDefinition.builder() + .setHeaderCommentStatements( + SpringAutoconfigCommentComposer.createClientBeanComment( + service, serviceSettingsMethodName)) + .setName(methodName) + .setScope(ScopeNode.PUBLIC) + .setReturnType(types.get("ServiceClient")) + .setArguments(argumentsVariableExprs) + .setAnnotations( + Arrays.asList( + AnnotationNode.withType(STATIC_TYPES.get("Bean")), + AnnotationNode.withType(STATIC_TYPES.get("ConditionalOnMissingBean")))) + .setThrowsExceptions(Arrays.asList(TypeNode.withExceptionClazz(IOException.class))) + .setReturnExpr(returnExpr) + .build(); + } + + private static MethodDefinition createUserAgentHeaderProviderMethod( + String serviceName, String className, Map types, Expr thisExpr) { + // private HeaderProvider userAgentHeaderProvider() { + // String springLibrary = "spring-autogen-language"; + // String version = this.getClass().getPackage().getImplementationVersion(); + // return () -> Collections.singletonMap("user-agent", springLibrary + "/" + version); + // } + List bodyStatements = new ArrayList<>(); + + VariableExpr springLibStringVariableExpr = + VariableExpr.builder() + .setVariable( + Variable.builder().setName("springLibrary").setType(TypeNode.STRING).build()) + .setIsDecl(true) + .build(); + Expr springLibStringValueExpr = + ValueExpr.withValue(StringObjectValue.withValue("spring-autogen-" + serviceName)); + + AssignmentExpr springLibStringAssignExpr = + AssignmentExpr.builder() + .setVariableExpr(springLibStringVariableExpr) + .setValueExpr(springLibStringValueExpr) + .build(); + bodyStatements.add(ExprStatement.withExpr(springLibStringAssignExpr)); + + VariableExpr versionStringVariableExpr = + VariableExpr.builder() + .setVariable(Variable.builder().setName("version").setType(TypeNode.STRING).build()) + .setIsDecl(true) + .build(); + Expr thisVersionExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(thisExpr) + .setMethodName("getClass") + .build(); + thisVersionExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(thisVersionExpr) + .setMethodName("getPackage") + .build(); + thisVersionExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(thisVersionExpr) + .setMethodName("getImplementationVersion") + .setReturnType(TypeNode.STRING) + .build(); + AssignmentExpr versionStringAssignExpr = + AssignmentExpr.builder() + .setVariableExpr(versionStringVariableExpr) + .setValueExpr(thisVersionExpr) + .build(); + bodyStatements.add(ExprStatement.withExpr(versionStringAssignExpr)); + + ValueExpr slash = ValueExpr.withValue(StringObjectValue.withValue("/")); + ArithmeticOperationExpr userAgentStringConcat = + ArithmeticOperationExpr.concatWithExprs( + springLibStringVariableExpr.toBuilder().setIsDecl(false).build(), slash); + userAgentStringConcat = + ArithmeticOperationExpr.concatWithExprs( + userAgentStringConcat, versionStringVariableExpr.toBuilder().setIsDecl(false).build()); + Expr collectionsExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(STATIC_TYPES.get("Collections")) + .setMethodName("singletonMap") + .setArguments( + ValueExpr.withValue(StringObjectValue.withValue("user-agent")), + userAgentStringConcat) + .setReturnType(STATIC_TYPES.get("HeaderProvider")) + .build(); + LambdaExpr returnExpr = LambdaExpr.builder().setReturnExpr(collectionsExpr).build(); + return MethodDefinition.builder() + .setName("userAgentHeaderProvider") + .setScope(ScopeNode.PRIVATE) + .setReturnType(STATIC_TYPES.get("HeaderProvider")) + .setReturnExpr(returnExpr) + .setBody(bodyStatements) + .build(); + } + + private static Map createStaticTypes() { + List> concreteClazzes = + Arrays.asList( + TransportChannelProvider.class, + InstantiatingHttpJsonChannelProvider.class, + ExecutorProvider.class, + ConditionalOnClass.class, + ConditionalOnProperty.class, + ConditionalOnMissingBean.class, + EnableConfigurationProperties.class, + CredentialsProvider.class, + GcpContextAutoConfiguration.class, + AutoConfiguration.class, + AutoConfigureAfter.class, + Bean.class, + Qualifier.class, + DefaultCredentialsProvider.class, + RetrySettings.class, + HeaderProvider.class, + Collections.class, + Credentials.class); + Map concreteClazzesMap = + concreteClazzes.stream() + .collect( + Collectors.toMap( + Class::getSimpleName, + c -> TypeNode.withReference(ConcreteReference.withClazz(c)))); + return concreteClazzesMap; + } + + private static Map createDynamicTypes(Service service, String packageName) { + Map typeMap = new HashMap<>(); + TypeNode clientAutoconfiguration = + TypeNode.withReference( + VaporReference.builder() + .setName(Utils.getServiceAutoConfigurationClassName(service)) + .setPakkage(packageName) + .build()); + + TypeNode clientProperties = + TypeNode.withReference( + VaporReference.builder() + .setName(Utils.getServicePropertiesClassName(service)) + .setPakkage(packageName) + .build()); + + // TODO: This should move to static types once class is added into spring-cloud-gcp-core + TypeNode retryProperties = + TypeNode.withReference( + VaporReference.builder() + .setName("Retry") + .setPakkage("com.google.cloud.spring.core") + .build()); + + // TODO: This should move to static types once class is added into spring-cloud-gcp-core + TypeNode retryUtil = + TypeNode.withReference( + VaporReference.builder() + .setName("RetryUtil") + .setPakkage("com.google.cloud.spring.core.util") + .build()); + + TypeNode serviceClient = + TypeNode.withReference( + VaporReference.builder() + .setName(ClassNames.getServiceClientClassName(service)) + .setPakkage(service.pakkage()) + .build()); + TypeNode serviceSettings = + TypeNode.withReference( + VaporReference.builder() + .setName(ClassNames.getServiceSettingsClassName(service)) + .setPakkage(service.pakkage()) + .build()); + TypeNode serviceSettingsBuilder = + TypeNode.withReference( + VaporReference.builder() + .setPakkage(service.pakkage()) + .setName("Builder") + .setEnclosingClassNames(ClassNames.getServiceSettingsClassName(service)) + .build()); + + typeMap.put(Utils.getServiceAutoConfigurationClassName(service), clientAutoconfiguration); + typeMap.put(Utils.getServicePropertiesClassName(service), clientProperties); + typeMap.put("ServiceClient", serviceClient); + typeMap.put("ServiceSettings", serviceSettings); + typeMap.put("ServiceSettingsBuilder", serviceSettingsBuilder); + typeMap.put("Retry", retryProperties); + typeMap.put("RetryUtil", retryUtil); + + return typeMap; + } +} diff --git a/src/main/java/com/google/api/generator/spring/composer/SpringComposer.java b/src/main/java/com/google/api/generator/spring/composer/SpringComposer.java new file mode 100644 index 0000000000..5a570b43db --- /dev/null +++ b/src/main/java/com/google/api/generator/spring/composer/SpringComposer.java @@ -0,0 +1,140 @@ +// Copyright 2022 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.spring.composer; + +import com.google.api.core.BetaApi; +import com.google.api.generator.engine.ast.AnnotationNode; +import com.google.api.generator.engine.ast.ClassDefinition; +import com.google.api.generator.gapic.composer.comment.CommentComposer; +import com.google.api.generator.gapic.composer.store.TypeStore; +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.Transport; +import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Generated; + +public class SpringComposer { + + private static final TypeStore FIXED_TYPESTORE = createStaticTypes(); + private static final AnnotationNode GENERATED_ANNOTATION = + AnnotationNode.builder() + .setType(FIXED_TYPESTORE.get("Generated")) + .setDescription("by google-cloud-spring-generator") + .build(); + private static final AnnotationNode BETA_ANNOTATION = + AnnotationNode.builder() + .setType(FIXED_TYPESTORE.get("BetaApi")) + .setDescription("Autogenerated Spring autoconfiguration is not yet stable") + .build(); + + public static List composeServiceAutoConfigClasses(GapicContext context) { + List clazzes = new ArrayList<>(); + + clazzes.addAll(generatePerServiceClasses(context)); + List clazzesWithHeader = addApacheLicense(clazzes); + return addExtraClassAnnotations(clazzesWithHeader); + } + + public static GapicPackageInfo composePackageInfo(GapicContext context) { + GapicPackageInfo gapicPackageInfo = + addExtraClassAnnotations(SpringPackageInfoComposer.generatePackageInfo(context)); + return addApacheLicense(gapicPackageInfo); + } + + protected static List generatePerServiceClasses(GapicContext context) { + List clazzes = new ArrayList<>(); + context + .services() + .forEach( + s -> { + // Transport.REST is out of scope for Spring composers. + if (context.transport() == Transport.GRPC + || context.transport() == Transport.GRPC_REST) { + clazzes.add(SpringAutoConfigClassComposer.instance().generate(context, s)); + clazzes.add(SpringPropertiesClassComposer.instance().generate(context, s)); + } + }); + return clazzes; + } + + protected static List addApacheLicense(List gapicClassList) { + return gapicClassList.stream() + .map( + gapicClass -> { + ClassDefinition classWithHeader = + gapicClass + .classDefinition() + .toBuilder() + .setFileHeader(CommentComposer.APACHE_LICENSE_COMMENT) + .build(); + return GapicClass.create(gapicClass.kind(), classWithHeader); + }) + .collect(Collectors.toList()); + } + + protected static List addExtraClassAnnotations(List gapicClassList) { + return gapicClassList.stream() + .map( + gapicClass -> { + ClassDefinition classWithUpdatedAnnotations = + gapicClass + .classDefinition() + .toBuilder() + .setAnnotations( + prependExtraAnnotations(gapicClass.classDefinition().annotations())) + .build(); + + return GapicClass.create(gapicClass.kind(), classWithUpdatedAnnotations); + }) + .collect(Collectors.toList()); + } + + protected static GapicPackageInfo addExtraClassAnnotations(GapicPackageInfo gapicPackageInfo) { + return GapicPackageInfo.with( + gapicPackageInfo + .packageInfo() + .toBuilder() + .setAnnotations(prependExtraAnnotations(gapicPackageInfo.packageInfo().annotations())) + .build()); + } + + private static ImmutableList prependExtraAnnotations( + ImmutableList annotationNodes) { + return ImmutableList.builder() + .add(GENERATED_ANNOTATION) + .add(BETA_ANNOTATION) + .addAll(annotationNodes) + .build(); + } + + private static GapicPackageInfo addApacheLicense(GapicPackageInfo gapicPackageInfo) { + return GapicPackageInfo.with( + gapicPackageInfo + .packageInfo() + .toBuilder() + .setFileHeader(CommentComposer.APACHE_LICENSE_COMMENT) + .build()); + } + + private static TypeStore createStaticTypes() { + List> concreteClazzes = Arrays.asList(BetaApi.class, Generated.class); + return new TypeStore(concreteClazzes); + } +} diff --git a/src/main/java/com/google/api/generator/spring/composer/SpringPackageInfoComposer.java b/src/main/java/com/google/api/generator/spring/composer/SpringPackageInfoComposer.java new file mode 100644 index 0000000000..c145f27c15 --- /dev/null +++ b/src/main/java/com/google/api/generator/spring/composer/SpringPackageInfoComposer.java @@ -0,0 +1,46 @@ +// Copyright 2022 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.spring.composer; + +import com.google.api.generator.engine.ast.CommentStatement; +import com.google.api.generator.engine.ast.JavaDocComment; +import com.google.api.generator.engine.ast.PackageInfoDefinition; +import com.google.api.generator.gapic.model.GapicContext; +import com.google.api.generator.gapic.model.GapicPackageInfo; +import com.google.api.generator.spring.utils.Utils; +import com.google.common.base.Preconditions; + +public class SpringPackageInfoComposer { + private static final String PACKAGE_INFO_TITLE_PATTERN = + "Spring Boot auto-configurations for %s."; + + public static GapicPackageInfo generatePackageInfo(GapicContext context) { + Preconditions.checkState(!context.services().isEmpty(), "No services found to generate"); + PackageInfoDefinition packageInfo = + PackageInfoDefinition.builder() + .setPakkage(Utils.getSpringPackageName(Utils.getPackageName(context))) + .setHeaderCommentStatements(createPackageInfoJavadoc(context)) + .build(); + return GapicPackageInfo.with(packageInfo); + } + + private static CommentStatement createPackageInfoJavadoc(GapicContext context) { + JavaDocComment.Builder javaDocCommentBuilder = JavaDocComment.builder(); + javaDocCommentBuilder = + javaDocCommentBuilder.addComment( + String.format(PACKAGE_INFO_TITLE_PATTERN, Utils.getLibName(context))); + return CommentStatement.withComment(javaDocCommentBuilder.build()); + } +} diff --git a/src/main/java/com/google/api/generator/spring/composer/SpringPropertiesClassComposer.java b/src/main/java/com/google/api/generator/spring/composer/SpringPropertiesClassComposer.java new file mode 100644 index 0000000000..34d67eb92e --- /dev/null +++ b/src/main/java/com/google/api/generator/spring/composer/SpringPropertiesClassComposer.java @@ -0,0 +1,364 @@ +// Copyright 2022 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.spring.composer; + +import static com.google.api.generator.engine.ast.NewObjectExpr.builder; + +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.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.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.ThisObjectValue; +import com.google.api.generator.engine.ast.TypeNode; +import com.google.api.generator.engine.ast.ValueExpr; +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.composer.common.ClassComposer; +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.GapicServiceConfig; +import com.google.api.generator.gapic.model.Method; +import com.google.api.generator.gapic.model.Service; +import com.google.api.generator.gapic.model.Transport; +import com.google.api.generator.gapic.utils.JavaStyle; +import com.google.api.generator.spring.composer.comment.SpringPropertiesCommentComposer; +import com.google.api.generator.spring.utils.ComposerUtils; +import com.google.api.generator.spring.utils.Utils; +import com.google.cloud.spring.core.Credentials; +import com.google.cloud.spring.core.CredentialsSupplier; +import com.google.common.base.CaseFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +public class SpringPropertiesClassComposer implements ClassComposer { + + private static final Map STATIC_TYPES = createStaticTypes(); + private static final SpringPropertiesClassComposer INSTANCE = new SpringPropertiesClassComposer(); + + public static SpringPropertiesClassComposer instance() { + return INSTANCE; + } + + @Override + public GapicClass generate(GapicContext context, Service service) { + String packageName = Utils.getSpringPackageName(service.pakkage()); + String className = Utils.getServicePropertiesClassName(service); + GapicServiceConfig gapicServiceConfig = context.serviceConfig(); + Map dynamicTypes = createDynamicTypes(service, packageName); + // TODO(emmwang): this condition is adapted from latest gapic-generator-java changes as part of + // https://github.com/googleapis/gapic-generator-java/issues/1117, but should be updated to use + // the gapic-implemented helpers once spring generator code is migrated. + boolean hasRestSupportedMethod = + service.methods().stream() + .anyMatch( + x -> + x.httpBindings() != null + && x.stream() != Method.Stream.BIDI + && x.stream() != Method.Stream.CLIENT); + boolean hasRestOption = + context.transport().equals(Transport.GRPC_REST) && hasRestSupportedMethod; + + // TODO: this is the prefix user will use to set properties, may need to change depending on + // branding. + AnnotationNode classAnnotationNode = + AnnotationNode.builder() + .setType(STATIC_TYPES.get("ConfigurationProperties")) + .setDescription(Utils.getSpringPropertyPrefix(service.pakkage(), service.name())) + .build(); + + ClassDefinition classDef = + ClassDefinition.builder() + .setHeaderCommentStatements( + SpringPropertiesCommentComposer.createClassHeaderComments( + className, service.name())) + .setPackageString(packageName) + .setName(className) + .setScope(ScopeNode.PUBLIC) + .setStatements( + createMemberVariables( + service, packageName, dynamicTypes, gapicServiceConfig, hasRestOption)) + .setMethods( + createGetterSetters(service, dynamicTypes, gapicServiceConfig, hasRestOption)) + .setAnnotations(Arrays.asList(classAnnotationNode)) + .setImplementsTypes(Arrays.asList(STATIC_TYPES.get("CredentialsSupplier"))) + .build(); + return GapicClass.create(Kind.MAIN, classDef); + // return null; + } + + private static List createMemberVariables( + Service service, + String packageName, + Map types, + GapicServiceConfig serviceConfig, + boolean hasRestOption) { + + String serviceName = service.name(); + List statements = new ArrayList<>(); + + // Note that the annotations are set on the VariableExpr rather than the ExprStatement. + // The single annotation works fine here, + // but multiple annotations would be written to the same line + List nestedPropertyAnnotations = + Arrays.asList(AnnotationNode.withType(STATIC_TYPES.get("NestedConfigurationProperty"))); + + // @NestedConfigurationProperty + // private final Credentials credentials = new + // Credentials("https://www.googleapis.com/auth/cloud-language"); + NewObjectExpr defaultCredentialScopes = + builder() + .setType(STATIC_TYPES.get("Credentials")) + .setArguments( + service.oauthScopes().stream() + .map(x -> ValueExpr.withValue(StringObjectValue.withValue(x))) + .collect(Collectors.toList())) + .build(); + ExprStatement credentialsStatement = + ComposerUtils.createMemberVarStatement( + "credentials", + STATIC_TYPES.get("Credentials"), + true, + defaultCredentialScopes, + nestedPropertyAnnotations); + statements.add(SpringPropertiesCommentComposer.createCredentialsPropertyComment()); + statements.add(credentialsStatement); + // private String quotaProjectId; + ExprStatement quotaProjectIdVarStatement = + ComposerUtils.createMemberVarStatement( + "quotaProjectId", TypeNode.STRING, false, null, null); + statements.add(SpringPropertiesCommentComposer.createQuotaProjectIdPropertyComment()); + statements.add(quotaProjectIdVarStatement); + // private Integer executorThreadCount; + ExprStatement executorThreadCountVarStatement = + ComposerUtils.createMemberVarStatement( + "executorThreadCount", TypeNode.INT_OBJECT, false, null, null); + statements.add(SpringPropertiesCommentComposer.createExecutorThreadCountPropertyComment()); + statements.add(executorThreadCountVarStatement); + if (hasRestOption) { + ExprStatement useRestVarStatement = + ComposerUtils.createMemberVarStatement( + "useRest", + TypeNode.BOOLEAN, + false, + ValueExpr.withValue( + PrimitiveValue.builder().setType(TypeNode.BOOLEAN).setValue("false").build()), + null); + statements.add(SpringPropertiesCommentComposer.createUseRestPropertyComment()); + statements.add(useRestVarStatement); + } + // private Retry retry; + ExprStatement retryPropertiesStatement = + ComposerUtils.createMemberVarStatement( + "retry", types.get("Retry"), false, null, nestedPropertyAnnotations); + statements.add(SpringPropertiesCommentComposer.createServiceRetryPropertyComment()); + statements.add(retryPropertiesStatement); + + for (Method method : Utils.getMethodsForRetryConfiguration(service)) { + String methodNameLowerCamel = JavaStyle.toLowerCamelCase(method.name()); + String methodPropertiesVarName = methodNameLowerCamel + "Retry"; + ExprStatement methodRetryPropertiesStatement = + ComposerUtils.createMemberVarStatement( + methodPropertiesVarName, types.get("Retry"), false, null, nestedPropertyAnnotations); + statements.add( + SpringPropertiesCommentComposer.createMethodRetryPropertyComment(methodNameLowerCamel)); + statements.add(methodRetryPropertiesStatement); + } + + return statements; + } + + private static List createGetterSetters( + Service service, + Map types, + GapicServiceConfig gapicServiceConfig, + boolean hasRestOption) { + + TypeNode thisClassType = types.get(Utils.getServicePropertiesClassName(service)); + List methodDefinitions = new ArrayList<>(); + + methodDefinitions.add( + createGetterMethod( + thisClassType, + "credentials", + STATIC_TYPES.get("Credentials"), + Arrays.asList(AnnotationNode.OVERRIDE))); + methodDefinitions.add( + createGetterMethod(thisClassType, "quotaProjectId", TypeNode.STRING, null)); + methodDefinitions.add(createSetterMethod(thisClassType, "quotaProjectId", TypeNode.STRING)); + if (hasRestOption) { + methodDefinitions.add(createGetterMethod(thisClassType, "useRest", TypeNode.BOOLEAN, null)); + methodDefinitions.add(createSetterMethod(thisClassType, "useRest", TypeNode.BOOLEAN)); + } + methodDefinitions.add( + createGetterMethod(thisClassType, "executorThreadCount", TypeNode.INT_OBJECT, null)); + methodDefinitions.add( + createSetterMethod(thisClassType, "executorThreadCount", TypeNode.INT_OBJECT)); + + methodDefinitions.add(createGetterMethod(thisClassType, "retry", types.get("Retry"), null)); + methodDefinitions.add(createSetterMethod(thisClassType, "retry", types.get("Retry"))); + + for (Method method : Utils.getMethodsForRetryConfiguration(service)) { + String methodPropertiesVarName = JavaStyle.toLowerCamelCase(method.name()) + "Retry"; + methodDefinitions.add( + createGetterMethod(thisClassType, methodPropertiesVarName, types.get("Retry"), null)); + methodDefinitions.add( + createSetterMethod(thisClassType, methodPropertiesVarName, types.get("Retry"))); + } + + return methodDefinitions; + } + + private static MethodDefinition createGetterMethod( + TypeNode thisClassType, + String propertyName, + TypeNode returnType, + List annotationNodes) { + + Variable propertyVar = Variable.builder().setName(propertyName).setType(returnType).build(); + Expr thisExpr = ValueExpr.withValue(ThisObjectValue.withType(thisClassType)); + + VariableExpr propertyVariableExpr = + VariableExpr.withVariable(propertyVar).toBuilder().setExprReferenceExpr(thisExpr).build(); + + String methodName = "get" + CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, propertyName); + + return MethodDefinition.builder() + .setName(methodName) + .setScope(ScopeNode.PUBLIC) + .setReturnType(returnType) + .setAnnotations(annotationNodes == null ? Collections.emptyList() : annotationNodes) + .setReturnExpr(propertyVariableExpr) + .build(); + } + + private static MethodDefinition createSetterMethod( + TypeNode thisClassType, String propertyName, TypeNode returnType) { + + // Common building blocks + Variable propertyVar = Variable.builder().setName(propertyName).setType(returnType).build(); + Expr thisExpr = ValueExpr.withValue(ThisObjectValue.withType(thisClassType)); + TypeNode threetenBpDurationType = STATIC_TYPES.get("org.threeten.bp.Duration"); + TypeNode javaTimeDurationType = STATIC_TYPES.get("java.time.Duration"); + + // Default building blocks - may be updated in Duration condition below + Variable argumentVar = propertyVar; + Expr propertyValueExpr = VariableExpr.withVariable(argumentVar); + + // Setter logic for Duration accepts different type and handles conversion + if (returnType.equals(threetenBpDurationType)) { + argumentVar = Variable.builder().setName(propertyName).setType(javaTimeDurationType).build(); + + MethodInvocationExpr durationToStringExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(VariableExpr.withVariable(argumentVar)) + .setMethodName("toString") + .setReturnType(TypeNode.STRING) + .build(); + + propertyValueExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(threetenBpDurationType) + .setMethodName("parse") + .setArguments(durationToStringExpr) + .setReturnType(threetenBpDurationType) + .build(); + } + + AssignmentExpr propertyVarExpr = + AssignmentExpr.builder() + .setVariableExpr( + VariableExpr.withVariable(propertyVar) + .toBuilder() + .setExprReferenceExpr(thisExpr) + .build()) + .setValueExpr(propertyValueExpr) + .build(); + + String methodName = "set" + CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, propertyName); + + return MethodDefinition.builder() + .setName(methodName) + .setScope(ScopeNode.PUBLIC) + .setReturnType(TypeNode.VOID) + .setArguments(VariableExpr.builder().setVariable(argumentVar).setIsDecl(true).build()) + .setBody(Arrays.asList(ExprStatement.withExpr(propertyVarExpr))) + .build(); + } + + private static Map createDynamicTypes(Service service, String packageName) { + Map typeMap = new HashMap<>(); + + TypeNode clientProperties = + TypeNode.withReference( + VaporReference.builder() + .setName(Utils.getServicePropertiesClassName(service)) + .setPakkage(packageName) + .build()); + + // TODO: This should move to static types once class is added into spring-cloud-gcp-core + TypeNode retryProperties = + TypeNode.withReference( + VaporReference.builder() + .setName("Retry") + .setPakkage("com.google.cloud.spring.core") + .build()); + + typeMap.put(Utils.getServicePropertiesClassName(service), clientProperties); + typeMap.put("Retry", retryProperties); + + return typeMap; + } + + private static Map createStaticTypes() { + List> concreteClazzes = + Arrays.asList( + ConfigurationProperties.class, + NestedConfigurationProperty.class, + CredentialsSupplier.class, + Credentials.class); + Map concreteClazzesMap = + concreteClazzes.stream() + .collect( + Collectors.toMap( + Class::getSimpleName, + c -> TypeNode.withReference(ConcreteReference.withClazz(c)))); + // Add Duration classes with full name + concreteClazzesMap.put( + "org.threeten.bp.Duration", + TypeNode.withReference(ConcreteReference.withClazz(org.threeten.bp.Duration.class))); + concreteClazzesMap.put( + "java.time.Duration", + TypeNode.withReference(ConcreteReference.withClazz(java.time.Duration.class))); + return concreteClazzesMap; + } +} diff --git a/src/main/java/com/google/api/generator/spring/composer/comment/SpringAutoconfigCommentComposer.java b/src/main/java/com/google/api/generator/spring/composer/comment/SpringAutoconfigCommentComposer.java new file mode 100644 index 0000000000..52be895586 --- /dev/null +++ b/src/main/java/com/google/api/generator/spring/composer/comment/SpringAutoconfigCommentComposer.java @@ -0,0 +1,139 @@ +// Copyright 2022 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.spring.composer.comment; + +import com.google.api.generator.engine.ast.CommentStatement; +import com.google.api.generator.engine.ast.JavaDocComment; +import com.google.api.generator.gapic.composer.comment.CommentComposer; +import com.google.api.generator.gapic.composer.utils.ClassNames; +import com.google.api.generator.gapic.model.Service; +import java.util.Arrays; +import java.util.List; + +public class SpringAutoconfigCommentComposer { + + private static final String CLASS_HEADER_SUMMARY_PATTERN = "Auto-configuration for {@link %s}."; + private static final String CLASS_HEADER_GENERAL_DESCRIPTION = + "Provides auto-configuration for Spring Boot"; + private static final String CLASS_HEADER_DEFAULTS_DESCRIPTION = + "The default instance has everything set to sensible defaults:"; + private static final String CLASS_HEADER_DEFAULTS_CREDENTIALS_DESCRIPTION = + "Credentials are acquired automatically through Application Default Credentials."; + private static final String CLASS_HEADER_DEFAULTS_TRANSPORT_DESCRIPTION = + "The default transport provider is used."; + private static final String CLASS_HEADER_DEFAULTS_RETRIES_DESCRIPTION = + "Retries are configured for idempotent methods but not for non-idempotent methods."; + + public static final String CREDENTIALS_PROVIDER_GENERAL_DESCRIPTION = + "Obtains the default credentials provider. The used key will be obtained from Spring Boot " + + "configuration data files."; + + public static final String TRANSPORT_CHANNEL_PROVIDER_GENERAL_DESCRIPTION = + "Provides a default transport channel provider bean. The default is gRPC and will default to it unless the " + + "useRest option is supported and provided to use HTTP transport instead"; + public static final String TRANSPORT_CHANNEL_PROVIDER_RETURN = + "a default transport channel provider."; + public static final String CLIENT_SETTINGS_BEAN_GENERAL_DESCRIPTION = + "Provides a %s bean configured to use a DefaultCredentialsProvider " + + "and the client library's default transport channel provider (%s()). " + + "It also configures the quota project ID and executor thread count, if provided through properties."; + + public static final String CLIENT_SETTINGS_BEAN_RETRY_SETTINGS_DESCRIPTION = + "Retry settings are also configured from service-level and method-level properties specified in %s. " + + "Method-level properties will take precedence over service-level properties if available, " + + "and client library defaults will be used if neither are specified."; + public static final String CLIENT_SETTINGS_BEAN_RETURN_STATEMENT = + "a {@link %s} bean configured with {@link TransportChannelProvider} bean."; + + public static final String CLIENT_BEAN_GENERAL_DESCRIPTION = + "Provides a %s bean configured with %s."; + public static final String CLIENT_BEAN_RETURN_STATEMENT = + "a {@link %s} bean configured with {@link %s}"; + + public SpringAutoconfigCommentComposer() {} + + public static List createClassHeaderComments(Service service) { + + JavaDocComment.Builder javaDocCommentBuilder = + JavaDocComment.builder() + .addUnescapedComment( + String.format( + CLASS_HEADER_SUMMARY_PATTERN, ClassNames.getServiceClientClassName(service))) + .addParagraph(CLASS_HEADER_GENERAL_DESCRIPTION) + .addParagraph(CLASS_HEADER_DEFAULTS_DESCRIPTION) + .addUnorderedList( + Arrays.asList( + CLASS_HEADER_DEFAULTS_TRANSPORT_DESCRIPTION, + CLASS_HEADER_DEFAULTS_CREDENTIALS_DESCRIPTION, + CLASS_HEADER_DEFAULTS_RETRIES_DESCRIPTION)); + + return Arrays.asList( + CommentComposer.AUTO_GENERATED_CLASS_COMMENT, + CommentStatement.withComment(javaDocCommentBuilder.build())); + } + + public static CommentStatement createCredentialsProviderBeanComment() { + return CommentStatement.withComment( + JavaDocComment.builder().addParagraph(CREDENTIALS_PROVIDER_GENERAL_DESCRIPTION).build()); + } + + public static CommentStatement createTransportChannelProviderComment() { + return CommentStatement.withComment( + JavaDocComment.builder() + .addParagraph(TRANSPORT_CHANNEL_PROVIDER_GENERAL_DESCRIPTION) + .setReturn(TRANSPORT_CHANNEL_PROVIDER_RETURN) + .build()); + } + + public static CommentStatement createSettingsBeanComment( + Service service, String propertiesClazzName, String channelProviderName) { + return CommentStatement.withComment( + JavaDocComment.builder() + .addParagraph( + String.format( + CLIENT_SETTINGS_BEAN_GENERAL_DESCRIPTION, + ClassNames.getServiceSettingsClassName(service), + channelProviderName)) + .addParagraph( + String.format(CLIENT_SETTINGS_BEAN_RETRY_SETTINGS_DESCRIPTION, propertiesClazzName)) + .addParam( + "defaultTransportChannelProvider", + "TransportChannelProvider to use in the settings.") + .setReturn( + String.format( + CLIENT_SETTINGS_BEAN_RETURN_STATEMENT, + ClassNames.getServiceSettingsClassName(service))) + .build()); + } + + public static CommentStatement createClientBeanComment( + Service service, String serviceSettingsMethodName) { + return CommentStatement.withComment( + JavaDocComment.builder() + .addParagraph( + String.format( + CLIENT_BEAN_GENERAL_DESCRIPTION, + ClassNames.getServiceClientClassName(service), + ClassNames.getServiceSettingsClassName(service))) + .addParam( + serviceSettingsMethodName, "settings to configure an instance of client bean.") + .setReturn( + String.format( + CLIENT_BEAN_RETURN_STATEMENT, + ClassNames.getServiceClientClassName(service), + ClassNames.getServiceSettingsClassName(service))) + .build()); + } +} diff --git a/src/main/java/com/google/api/generator/spring/composer/comment/SpringPropertiesCommentComposer.java b/src/main/java/com/google/api/generator/spring/composer/comment/SpringPropertiesCommentComposer.java new file mode 100644 index 0000000000..172d0d0da4 --- /dev/null +++ b/src/main/java/com/google/api/generator/spring/composer/comment/SpringPropertiesCommentComposer.java @@ -0,0 +1,78 @@ +// Copyright 2022 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.spring.composer.comment; + +import com.google.api.generator.engine.ast.CommentStatement; +import com.google.api.generator.engine.ast.JavaDocComment; +import com.google.api.generator.gapic.composer.comment.CommentComposer; +import java.util.Arrays; +import java.util.List; + +public class SpringPropertiesCommentComposer { + private static final String CLASS_HEADER_GENERAL_DESCRIPTION = + "Provides default property values for %s client bean"; + private static final String CREDENTIALS_DESCRIPTION = + "OAuth2 credentials to authenticate and authorize calls to Google Cloud Client Libraries."; + private static final String QUOTA_PROJECT_ID_DESCRIPTION = "Quota project to use for billing."; + private static final String EXECUTOR_THREAD_COUNT_DESCRIPTION = + "Number of threads used for executors."; + private static final String USE_REST_DESCRIPTION = + "Allow override of default transport channel provider to use REST instead of gRPC."; + private static final String SERVICE_RETRY_SETTINGS_DESCRIPTION = + "Allow override of retry settings at service level, applying to all of its RPC methods."; + private static final String METHOD_RETRY_SETTINGS_DESCRIPTION = + "Allow override of retry settings at method-level for %s. " + + "If defined, this takes precedence over service-level retry configurations for that RPC method."; + + public static List createClassHeaderComments( + String configuredClassName, String serviceName) { + + JavaDocComment.Builder javaDocCommentBuilder = + JavaDocComment.builder() + .addParagraph(String.format(CLASS_HEADER_GENERAL_DESCRIPTION, serviceName)); + return Arrays.asList( + CommentComposer.AUTO_GENERATED_CLASS_COMMENT, + CommentStatement.withComment(javaDocCommentBuilder.build())); + } + + public static CommentStatement createCredentialsPropertyComment() { + return toSimpleJavaDocComment(CREDENTIALS_DESCRIPTION); + } + + public static CommentStatement createQuotaProjectIdPropertyComment() { + return toSimpleJavaDocComment(QUOTA_PROJECT_ID_DESCRIPTION); + } + + public static CommentStatement createExecutorThreadCountPropertyComment() { + return toSimpleJavaDocComment(EXECUTOR_THREAD_COUNT_DESCRIPTION); + } + + public static CommentStatement createUseRestPropertyComment() { + return toSimpleJavaDocComment(USE_REST_DESCRIPTION); + } + + public static CommentStatement createServiceRetryPropertyComment() { + return toSimpleJavaDocComment(SERVICE_RETRY_SETTINGS_DESCRIPTION); + } + + public static CommentStatement createMethodRetryPropertyComment(String methodName) { + String comment = String.format(METHOD_RETRY_SETTINGS_DESCRIPTION, methodName); + return toSimpleJavaDocComment(comment); + } + + private static CommentStatement toSimpleJavaDocComment(String comment) { + return CommentStatement.withComment(JavaDocComment.withComment(comment)); + } +} diff --git a/src/main/java/com/google/api/generator/spring/utils/ComposerUtils.java b/src/main/java/com/google/api/generator/spring/utils/ComposerUtils.java new file mode 100644 index 0000000000..bf0fb07f55 --- /dev/null +++ b/src/main/java/com/google/api/generator/spring/utils/ComposerUtils.java @@ -0,0 +1,57 @@ +// Copyright 2022 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.spring.utils; + +import com.google.api.generator.engine.ast.AnnotationNode; +import com.google.api.generator.engine.ast.AssignmentExpr; +import com.google.api.generator.engine.ast.Expr; +import com.google.api.generator.engine.ast.ExprStatement; +import com.google.api.generator.engine.ast.ScopeNode; +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.Collections; +import java.util.List; + +public class ComposerUtils { + + public static ExprStatement createMemberVarStatement( + String varName, + TypeNode varType, + boolean isFinal, + Expr defaultVal, + List annotationNodes) { + Variable memberVar = Variable.builder().setName(varName).setType(varType).build(); + VariableExpr memberVarExpr = + VariableExpr.builder() + .setVariable(memberVar) + .setScope(ScopeNode.PRIVATE) + .setAnnotations(annotationNodes == null ? Collections.emptyList() : annotationNodes) + .setIsDecl(true) + .setIsFinal(isFinal) + .build(); + + if (defaultVal == null) { + return ExprStatement.withExpr(memberVarExpr); + } + AssignmentExpr assignmentExpr = + AssignmentExpr.builder() + .setVariableExpr(memberVarExpr.toBuilder().setIsDecl(true).build()) + .setValueExpr(defaultVal) + .build(); + + return ExprStatement.withExpr(assignmentExpr); + } +} diff --git a/src/main/java/com/google/api/generator/spring/utils/LoggerUtils.java b/src/main/java/com/google/api/generator/spring/utils/LoggerUtils.java new file mode 100644 index 0000000000..1a92319f5a --- /dev/null +++ b/src/main/java/com/google/api/generator/spring/utils/LoggerUtils.java @@ -0,0 +1,117 @@ +// Copyright 2022 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.spring.utils; + +import com.google.api.generator.engine.ast.ArithmeticOperationExpr; +import com.google.api.generator.engine.ast.AssignmentExpr; +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.IfStatement; +import com.google.api.generator.engine.ast.MethodInvocationExpr; +import com.google.api.generator.engine.ast.ScopeNode; +import com.google.api.generator.engine.ast.Statement; +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.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class LoggerUtils { + + private static final Map STATIC_TYPES = createStaticTypes(); + + public static Statement getLoggerDeclarationExpr(String className, Map types) { + + Variable loggerVar = + Variable.builder().setName("LOGGER").setType(STATIC_TYPES.get("Log")).build(); + VariableExpr loggerExpr = + VariableExpr.builder() + .setVariable(loggerVar) + .setScope(ScopeNode.PRIVATE) + .setIsStatic(true) + .setIsFinal(true) + .setIsDecl(true) + .build(); + + MethodInvocationExpr loggerValueExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType(STATIC_TYPES.get("LogFactory")) + .setMethodName("getLog") + .setArguments( + VariableExpr.builder() + .setVariable( + Variable.builder().setType(TypeNode.CLASS_OBJECT).setName("class").build()) + .setStaticReferenceType(types.get(className)) + .build()) + .setReturnType(STATIC_TYPES.get("Log")) + .build(); + + AssignmentExpr loggerAssignmentExpr = + AssignmentExpr.builder().setVariableExpr(loggerExpr).setValueExpr(loggerValueExpr).build(); + + return ExprStatement.withExpr(loggerAssignmentExpr); + } + + public static Statement createLoggerStatement(Expr value, Map types) { + Variable loggerVariable = + Variable.builder().setName("LOGGER").setType(STATIC_TYPES.get("Log")).build(); + MethodInvocationExpr loggerCallExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(VariableExpr.withVariable(loggerVariable)) + .setMethodName("trace") + .setArguments(value) + .build(); + return IfStatement.builder() + .setConditionExpr( + MethodInvocationExpr.builder() + .setExprReferenceExpr(VariableExpr.withVariable(loggerVariable)) + .setMethodName("isTraceEnabled") + .setReturnType(TypeNode.BOOLEAN) + .build()) + .setBody(Arrays.asList(ExprStatement.withExpr(loggerCallExpr))) + .build(); + } + + public static Expr concatManyWithExprs(Expr... exprs) { + List exprList = Arrays.asList(exprs); + return concatManyWithExprsHelper(Optional.empty(), exprList); + } + + private static Expr concatManyWithExprsHelper(Optional current, List exprs) { + if (!current.isPresent()) { + return concatManyWithExprsHelper(Optional.of(exprs.get(0)), exprs.subList(1, exprs.size())); + } + if (exprs.size() == 1) { + return ArithmeticOperationExpr.concatWithExprs(current.get(), exprs.get(0)); + } + return ArithmeticOperationExpr.concatWithExprs( + current.get(), + concatManyWithExprsHelper(Optional.of(exprs.get(0)), exprs.subList(1, exprs.size()))); + } + + private static Map createStaticTypes() { + List concreteClazzes = Arrays.asList(Log.class, LogFactory.class); + return concreteClazzes.stream() + .collect( + Collectors.toMap( + Class::getSimpleName, c -> TypeNode.withReference(ConcreteReference.withClazz(c)))); + } +} diff --git a/src/main/java/com/google/api/generator/spring/utils/Utils.java b/src/main/java/com/google/api/generator/spring/utils/Utils.java new file mode 100644 index 0000000000..98eb0a5d49 --- /dev/null +++ b/src/main/java/com/google/api/generator/spring/utils/Utils.java @@ -0,0 +1,99 @@ +// Copyright 2022 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.spring.utils; + +import com.google.api.generator.gapic.composer.store.TypeStore; +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.base.CaseFormat; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class Utils { + + private static final TypeStore FIXED_TYPESTORE = createStaticTypes(); + + private static final String BRAND_NAME = "spring.cloud.gcp"; + + public static String getServiceAutoConfigurationClassName(Service service) { + return JavaStyle.toUpperCamelCase(service.name()) + "SpringAutoConfiguration"; + } + + public static String getServicePropertiesClassName(Service service) { + return JavaStyle.toUpperCamelCase(service.name()) + "SpringProperties"; + } + + public static String getLibName(GapicContext context) { + // Returns parsed name of client library + // This should only be used in descriptive context, such as metadata and javadocs + + // Option 1: Use title from service yaml if available (e.g. Client Library Showcase API) + // However, service yaml is optionally parsed and not always available + // if (context.hasServiceYamlProto() + // && !Strings.isNullOrEmpty(context.serviceYamlProto().getTitle())) { + // return context.serviceYamlProto().getTitle(); + // } + + // Option 2: Parse ApiShortName from service proto's package name (e.g. + // com.google.cloud.vision.v1) + // This approach assumes pattern of xx.[...].xx.lib-name.v[version], which may have + // discrepancies + // eg. for vision proto: "com.google.cloud.vision.v1" + // https://github.com/googleapis/java-vision/blob/main/proto-google-cloud-vision-v1/src/main/proto/google/cloud/vision/v1/image_annotator.proto#L36 + // List pakkagePhrases = Splitter.on(".").splitToList(getPackageName(context)); + // return pakkagePhrases.get(pakkagePhrases.size() - 2); + + // Option 3: Use parsed apiShortName from service proto's default host + // (e.g. vision.googleapis.com => vision) + return context.services().get(0).apiShortName(); + } + + public static String getPackageName(GapicContext context) { + // Returns package name of client library + return context.services().get(0).pakkage(); + } + + public static String getSpringPackageName(String packageName) { + // Returns package name of generated spring autoconfiguration library + // e.g. for vision: com.google.cloud.vision.v1.spring + return packageName + ".spring"; + } + + public static String getSpringPropertyPrefix(String packageName, String serviceName) { + // Returns unique prefix for setting properties and enabling autoconfiguration + // Pattern: [package-name].spring.auto.[service-name] + // e.g. for vision's ImageAnnotator service: + // com.google.cloud.vision.v1.image-annotator + // Service name is converted to lower hyphen as required by ConfigurationPropertyName + // https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/context/properties/source/ConfigurationPropertyName.html + return packageName + "." + CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_HYPHEN, serviceName); + } + + public static List getMethodsForRetryConfiguration(Service service) { + // Returns list of methods with retry configuration support + // This currently excludes streaming and LRO methods + return service.methods().stream() + .filter(m -> m.stream().equals(Method.Stream.NONE) && !m.hasLro()) + .collect(Collectors.toList()); + } + + private static TypeStore createStaticTypes() { + List> concreteClazzes = Arrays.asList(org.threeten.bp.Duration.class); + return new TypeStore(concreteClazzes); + } +} diff --git a/src/test/java/com/google/api/generator/engine/ast/JavaDocCommentTest.java b/src/test/java/com/google/api/generator/engine/ast/JavaDocCommentTest.java index 0d3550971d..1fef68649f 100644 --- a/src/test/java/com/google/api/generator/engine/ast/JavaDocCommentTest.java +++ b/src/test/java/com/google/api/generator/engine/ast/JavaDocCommentTest.java @@ -178,6 +178,28 @@ public void createJavaDocComment_throwsAndDeprecated() { assertEquals(expected, javaDocComment.comment()); } + @Test + public void createJavaDocComment_paramsAndReturn() { + String paramName1 = "shelfName"; + String paramDescription1 = "The name of the shelf where books are published to."; + String paramName2 = "shelf"; + String paramDescription2 = "The shelf to create."; + String returnText = "This is the method return text."; + + JavaDocComment javaDocComment = + JavaDocComment.builder() + .addParam(paramName1, paramDescription1) + .addParam(paramName2, paramDescription2) + .setReturn(returnText) + .build(); + String expected = + LineFormatter.lines( + "@param shelfName The name of the shelf where books are published to.\n", + "@param shelf The shelf to create.\n", + "@return This is the method return text."); + assertEquals(expected, javaDocComment.comment()); + } + @Test public void createJavaDocComment_allComponents() { // No matter what order `setThrows`, `setDeprecated` are called, @@ -190,6 +212,7 @@ public void createJavaDocComment_allComponents() { String paramDescription1 = "The name of the shelf where books are published to."; String paramName2 = "shelf"; String paramDescription2 = "The shelf to create."; + String returnText = "This is the method return text."; String paragraph1 = "This class provides the ability to make remote calls to the backing service through" + " method calls that map to API methods. Sample code to get started:"; @@ -210,6 +233,7 @@ public void createJavaDocComment_allComponents() { .addParagraph(paragraph2) .addOrderedList(orderedList) .addParam(paramName2, paramDescription2) + .setReturn(returnText) .build(); String expected = LineFormatter.lines( @@ -225,6 +249,7 @@ public void createJavaDocComment_allComponents() { "\n", "@param shelfName The name of the shelf where books are published to.\n", "@param shelf The shelf to create.\n", + "@return This is the method return text.\n", "@throws com.google.api.gax.rpc.ApiException if the remote call fails.\n", "@deprecated Use the {@link ArchivedBookName} class instead."); assertEquals(expected, javaDocComment.comment()); diff --git a/src/test/java/com/google/api/generator/engine/writer/ImportWriterVisitorTest.java b/src/test/java/com/google/api/generator/engine/writer/ImportWriterVisitorTest.java index 865f505a62..26bc5a015f 100644 --- a/src/test/java/com/google/api/generator/engine/writer/ImportWriterVisitorTest.java +++ b/src/test/java/com/google/api/generator/engine/writer/ImportWriterVisitorTest.java @@ -62,6 +62,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.lang.annotation.Repeatable; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -390,6 +391,43 @@ public void writeVariableExprImports_withAnnotations() { writerVisitor.write()); } + @Test + public void writeVariableExprImports_annotationsWithDescription() { + Variable variable = + Variable.builder() + .setName("expr") + .setType(TypeNode.withReference(ConcreteReference.withClazz(Expr.class))) + .build(); + + VariableExpr annotationDescription = + VariableExpr.builder() + .setVariable(Variable.builder().setType(TypeNode.CLASS_OBJECT).setName("class").build()) + .setStaticReferenceType(TypeNode.withReference(ConcreteReference.withClazz(List.class))) + .build(); + + // Constructs with annotation @Repeatable(List.class) + VariableExpr variableExpr = + VariableExpr.builder() + .setVariable(variable) + .setIsDecl(true) + .setAnnotations( + Arrays.asList( + AnnotationNode.builder() + .setType( + TypeNode.withReference(ConcreteReference.withClazz(Repeatable.class))) + .setDescription(annotationDescription) + .build())) + .build(); + + variableExpr.accept(writerVisitor); + assertEquals( + LineFormatter.lines( + "import com.google.api.generator.engine.ast.Expr;\n", + "import java.lang.annotation.Repeatable;\n", + "import java.util.List;\n\n"), + writerVisitor.write()); + } + @Test public void writeAnonymousClassExprImports() { // [Constructing] Function, MethodDefinition> diff --git a/src/test/java/com/google/api/generator/gapic/composer/grpcrest/GrpcRestTestProtoLoader.java b/src/test/java/com/google/api/generator/gapic/composer/grpcrest/GrpcRestTestProtoLoader.java index 05c0241327..1e3b057170 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/grpcrest/GrpcRestTestProtoLoader.java +++ b/src/test/java/com/google/api/generator/gapic/composer/grpcrest/GrpcRestTestProtoLoader.java @@ -31,6 +31,7 @@ import com.google.protobuf.Descriptors.ServiceDescriptor; import com.google.protobuf.StructProto; import com.google.showcase.grpcrest.v1beta1.EchoGrpcrest; +import com.google.showcase.v1beta1.WickedOuterClass; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashSet; @@ -82,4 +83,35 @@ public GapicContext parseShowcaseEcho() { .setTransport(getTransport()) .build(); } + + public GapicContext parseShowcaseWicked() { + FileDescriptor fileDescriptor = WickedOuterClass.getDescriptor(); + ServiceDescriptor messagingService = fileDescriptor.getServices().get(0); + assertEquals("Wicked", messagingService.getName()); + + Map messageTypes = Parser.parseMessages(fileDescriptor); + messageTypes.putAll(Parser.parseMessages(OperationsProto.getDescriptor())); + messageTypes.putAll(Parser.parseMessages(StructProto.getDescriptor())); + + Map resourceNames = Parser.parseResourceNames(fileDescriptor); + Set outputResourceNames = new HashSet<>(); + List services = + Parser.parseService( + fileDescriptor, messageTypes, resourceNames, Optional.empty(), outputResourceNames); + + String jsonFilename = "showcase_grpc_service_config.json"; + Path jsonPath = Paths.get(getTestFilesDirectory(), jsonFilename); + Optional configOpt = ServiceConfigParser.parse(jsonPath.toString()); + assertTrue(configOpt.isPresent()); + GapicServiceConfig config = configOpt.get(); + + return GapicContext.builder() + .setMessages(messageTypes) + .setResourceNames(resourceNames) + .setServices(services) + .setServiceConfig(config) + .setHelperResourceNames(outputResourceNames) + .setTransport(getTransport()) + .build(); + } } diff --git a/src/test/java/com/google/api/generator/spring/SpringWriterTest.java b/src/test/java/com/google/api/generator/spring/SpringWriterTest.java new file mode 100644 index 0000000000..ff7566f464 --- /dev/null +++ b/src/test/java/com/google/api/generator/spring/SpringWriterTest.java @@ -0,0 +1,60 @@ +// Copyright 2022 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.spring; + +import static org.junit.Assert.assertEquals; + +import com.google.api.generator.gapic.composer.common.TestProtoLoader; +import com.google.api.generator.gapic.model.GapicContext; +import com.google.api.generator.test.framework.Assert; +import com.google.api.generator.test.framework.Utils; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.Before; +import org.junit.Test; + +public class SpringWriterTest { + private GapicContext context; + + @Before + public void setUp() { + this.context = TestProtoLoader.instance().parseShowcaseEcho(); + } + + @Test + public void buildAutoConfigRegistrationStringTest() { + String result = SpringWriter.buildAutoConfigRegistrationString(context); + String expected = "com.google.showcase.v1beta1.spring.EchoSpringAutoConfiguration"; + assertEquals(expected, result); + } + + @Test + public void buildSpringAdditionalMetadataJsonStringTest() { + String result = SpringWriter.buildSpringAdditionalMetadataJsonString(context); + String fileName = "SpringAdditionalMetadataJson.golden"; + Utils.saveCodegenToFile(this.getClass(), fileName, result); + Path goldenFilePath = Paths.get(Utils.getGoldenDir(this.getClass()), fileName); + Assert.assertCodeEquals(goldenFilePath, result); + } + + @Test + public void buildPomStringTest() { + String result = SpringWriter.buildPomString(context); + String fileName = "SpringPackagePom.golden"; + Utils.saveCodegenToFile(this.getClass(), fileName, result); + Path goldenFilePath = Paths.get(Utils.getGoldenDir(this.getClass()), fileName); + Assert.assertCodeEquals(goldenFilePath, result); + } +} diff --git a/src/test/java/com/google/api/generator/spring/composer/SpringAutoConfigClassComposerTest.java b/src/test/java/com/google/api/generator/spring/composer/SpringAutoConfigClassComposerTest.java new file mode 100644 index 0000000000..fed4cb96c5 --- /dev/null +++ b/src/test/java/com/google/api/generator/spring/composer/SpringAutoConfigClassComposerTest.java @@ -0,0 +1,68 @@ +// Copyright 2022 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.spring.composer; + +import com.google.api.generator.gapic.composer.common.TestProtoLoader; +import com.google.api.generator.gapic.composer.grpcrest.GrpcRestTestProtoLoader; +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.gapic.model.Transport; +import com.google.api.generator.test.framework.Assert; +import org.junit.Before; +import org.junit.Test; + +public class SpringAutoConfigClassComposerTest { + private GapicContext context; + private GapicContext wickedContext; + private Service echoProtoService; + private Service wickedProtoService; + + @Before + public void setUp() { + this.context = TestProtoLoader.instance().parseShowcaseEcho(); + this.echoProtoService = this.context.services().get(0); + this.wickedContext = GrpcRestTestProtoLoader.instance().parseShowcaseWicked(); + this.wickedProtoService = this.wickedContext.services().get(0); + } + + @Test + public void generateAutoConfigClazzGrpcTest() { + GapicClass clazz = + SpringAutoConfigClassComposer.instance().generate(this.context, this.echoProtoService); + String fileName = clazz.classDefinition().classIdentifier() + "Grpc.golden"; + Assert.assertGoldenClass(this.getClass(), clazz, fileName); + } + + @Test + public void generateAutoConfigClazzGrpcRestTest() { + GapicContext contextGrpcRest = + this.context.toBuilder().setTransport(Transport.GRPC_REST).build(); + GapicClass clazz = + SpringAutoConfigClassComposer.instance().generate(contextGrpcRest, this.echoProtoService); + String fileName = clazz.classDefinition().classIdentifier() + "GrpcRest.golden"; + Assert.assertGoldenClass(this.getClass(), clazz, fileName); + } + + @Test + public void generateAutoConfigClazzNoRestRpcsTest() { + GapicContext contextGrpcRest = + this.wickedContext.toBuilder().setTransport(Transport.GRPC_REST).build(); + GapicClass clazz = + SpringAutoConfigClassComposer.instance().generate(contextGrpcRest, this.wickedProtoService); + String fileName = clazz.classDefinition().classIdentifier() + "NoRestRpcs.golden"; + Assert.assertGoldenClass(this.getClass(), clazz, fileName); + } +} diff --git a/src/test/java/com/google/api/generator/spring/composer/SpringComposerTest.java b/src/test/java/com/google/api/generator/spring/composer/SpringComposerTest.java new file mode 100644 index 0000000000..6f9493f4c2 --- /dev/null +++ b/src/test/java/com/google/api/generator/spring/composer/SpringComposerTest.java @@ -0,0 +1,58 @@ +// Copyright 2022 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.spring.composer; + +import com.google.api.generator.engine.writer.JavaWriterVisitor; +import com.google.api.generator.gapic.composer.common.TestProtoLoader; +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.test.framework.Assert; +import com.google.api.generator.test.framework.Utils; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import org.junit.Before; +import org.junit.Test; + +public class SpringComposerTest { + private GapicContext context; + + @Before + public void setUp() { + this.context = TestProtoLoader.instance().parseShowcaseEcho(); + } + + @Test + public void spring_composer_test() { + + List gapicClasses = SpringComposer.composeServiceAutoConfigClasses(context); + GapicPackageInfo packageInfo = SpringComposer.composePackageInfo(context); + + // write to verify result for now + for (GapicClass gapicClazz : gapicClasses) { + String fileName = gapicClazz.classDefinition().classIdentifier() + "Full.golden"; + Assert.assertGoldenClass(this.getClass(), gapicClazz, fileName); + } + + String packageInfoFileName = "SpringPackageInfoFull.golden"; + JavaWriterVisitor visitor = new JavaWriterVisitor(); + packageInfo.packageInfo().accept(visitor); + Utils.saveCodegenToFile(this.getClass(), packageInfoFileName, visitor.write()); + Path packageInfoGoldenFilePath = + Paths.get(Utils.getGoldenDir(this.getClass()), packageInfoFileName); + Assert.assertCodeEquals(packageInfoGoldenFilePath, visitor.write()); + } +} diff --git a/src/test/java/com/google/api/generator/spring/composer/SpringPackageInfoComposerTest.java b/src/test/java/com/google/api/generator/spring/composer/SpringPackageInfoComposerTest.java new file mode 100644 index 0000000000..9a044c0c15 --- /dev/null +++ b/src/test/java/com/google/api/generator/spring/composer/SpringPackageInfoComposerTest.java @@ -0,0 +1,47 @@ +// Copyright 2022 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.spring.composer; + +import com.google.api.generator.engine.writer.JavaWriterVisitor; +import com.google.api.generator.gapic.composer.common.TestProtoLoader; +import com.google.api.generator.gapic.model.GapicContext; +import com.google.api.generator.gapic.model.GapicPackageInfo; +import com.google.api.generator.test.framework.Assert; +import com.google.api.generator.test.framework.Utils; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.Before; +import org.junit.Test; + +public class SpringPackageInfoComposerTest { + private GapicContext context; + + @Before + public void setUp() { + this.context = TestProtoLoader.instance().parseShowcaseEcho(); + } + + @Test + public void generateSpringPackageInfoTest() { + GapicPackageInfo packageInfo = SpringPackageInfoComposer.generatePackageInfo(this.context); + JavaWriterVisitor visitor = new JavaWriterVisitor(); + packageInfo.packageInfo().accept(visitor); + String fileName = "SpringPackageInfo.golden"; + + Utils.saveCodegenToFile(this.getClass(), fileName, visitor.write()); + Path goldenFilePath = Paths.get(Utils.getGoldenDir(this.getClass()), fileName); + Assert.assertCodeEquals(goldenFilePath, visitor.write()); + } +} diff --git a/src/test/java/com/google/api/generator/spring/composer/SpringPropertiesClassComposerTest.java b/src/test/java/com/google/api/generator/spring/composer/SpringPropertiesClassComposerTest.java new file mode 100644 index 0000000000..ff349804d5 --- /dev/null +++ b/src/test/java/com/google/api/generator/spring/composer/SpringPropertiesClassComposerTest.java @@ -0,0 +1,68 @@ +// Copyright 2022 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.spring.composer; + +import com.google.api.generator.gapic.composer.common.TestProtoLoader; +import com.google.api.generator.gapic.composer.grpcrest.GrpcRestTestProtoLoader; +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.gapic.model.Transport; +import com.google.api.generator.test.framework.Assert; +import org.junit.Before; +import org.junit.Test; + +public class SpringPropertiesClassComposerTest { + private GapicContext context; + private GapicContext wickedContext; + private Service echoProtoService; + private Service wickedProtoService; + + @Before + public void setUp() { + this.context = TestProtoLoader.instance().parseShowcaseEcho(); + this.echoProtoService = this.context.services().get(0); + this.wickedContext = GrpcRestTestProtoLoader.instance().parseShowcaseWicked(); + this.wickedProtoService = this.wickedContext.services().get(0); + } + + @Test + public void generatePropertiesClazzGrpcTest() { + GapicClass clazz = + SpringPropertiesClassComposer.instance().generate(this.context, this.echoProtoService); + String fileName = clazz.classDefinition().classIdentifier() + "Grpc.golden"; + Assert.assertGoldenClass(this.getClass(), clazz, fileName); + } + + @Test + public void generatePropertiesClazzGrpcRestTest() { + GapicContext contextGrpcRest = + this.context.toBuilder().setTransport(Transport.GRPC_REST).build(); + GapicClass clazz = + SpringPropertiesClassComposer.instance().generate(contextGrpcRest, this.echoProtoService); + String fileName = clazz.classDefinition().classIdentifier() + "GrpcRest.golden"; + Assert.assertGoldenClass(this.getClass(), clazz, fileName); + } + + @Test + public void generatePropertiesClazzNoRestRpcsTest() { + GapicContext contextGrpcRest = + this.wickedContext.toBuilder().setTransport(Transport.GRPC_REST).build(); + GapicClass clazz = + SpringPropertiesClassComposer.instance().generate(contextGrpcRest, this.wickedProtoService); + String fileName = clazz.classDefinition().classIdentifier() + "NoRestRpcs.golden"; + Assert.assertGoldenClass(this.getClass(), clazz, fileName); + } +} diff --git a/src/test/java/com/google/api/generator/spring/composer/goldens/EchoSpringAutoConfigurationFull.golden b/src/test/java/com/google/api/generator/spring/composer/goldens/EchoSpringAutoConfigurationFull.golden new file mode 100644 index 0000000000..ca203c82fe --- /dev/null +++ b/src/test/java/com/google/api/generator/spring/composer/goldens/EchoSpringAutoConfigurationFull.golden @@ -0,0 +1,250 @@ +/* + * Copyright 2022 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 + * + * https://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.showcase.v1beta1.spring; + +import com.google.api.core.BetaApi; +import com.google.api.gax.core.CredentialsProvider; +import com.google.api.gax.core.ExecutorProvider; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.HeaderProvider; +import com.google.api.gax.rpc.TransportChannelProvider; +import com.google.cloud.spring.autoconfigure.core.GcpContextAutoConfiguration; +import com.google.cloud.spring.core.Credentials; +import com.google.cloud.spring.core.DefaultCredentialsProvider; +import com.google.cloud.spring.core.Retry; +import com.google.cloud.spring.core.util.RetryUtil; +import com.google.showcase.v1beta1.EchoClient; +import com.google.showcase.v1beta1.EchoSettings; +import java.io.IOException; +import java.util.Collections; +import javax.annotation.Generated; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** + * Auto-configuration for {@link EchoClient}. + * + *

Provides auto-configuration for Spring Boot + * + *

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

    + *
  • The default transport provider is used. + *
  • Credentials are acquired automatically through Application Default Credentials. + *
  • Retries are configured for idempotent methods but not for non-idempotent methods. + *
+ */ +@Generated("by google-cloud-spring-generator") +@BetaApi("Autogenerated Spring autoconfiguration is not yet stable") +@AutoConfiguration +@AutoConfigureAfter(GcpContextAutoConfiguration.class) +@ConditionalOnClass(EchoClient.class) +@ConditionalOnProperty(value = "com.google.showcase.v1beta1.echo.enabled", matchIfMissing = true) +@EnableConfigurationProperties(EchoSpringProperties.class) +public class EchoSpringAutoConfiguration { + private final EchoSpringProperties clientProperties; + private final CredentialsProvider credentialsProvider; + private static final Log LOGGER = LogFactory.getLog(EchoSpringAutoConfiguration.class); + + protected EchoSpringAutoConfiguration( + EchoSpringProperties clientProperties, CredentialsProvider credentialsProvider) + throws IOException { + this.clientProperties = clientProperties; + if (this.clientProperties.getCredentials().hasKey()) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Using credentials from Echo-specific configuration"); + } + this.credentialsProvider = + ((CredentialsProvider) new DefaultCredentialsProvider(this.clientProperties)); + } else { + this.credentialsProvider = credentialsProvider; + } + } + + /** + * Provides a default transport channel provider bean. The default is gRPC and will default to it + * unless the useRest option is supported and provided to use HTTP transport instead + * + * @return a default transport channel provider. + */ + @Bean + @ConditionalOnMissingBean(name = "defaultEchoTransportChannelProvider") + public TransportChannelProvider defaultEchoTransportChannelProvider() { + return EchoSettings.defaultTransportChannelProvider(); + } + + /** + * Provides a EchoSettings bean configured to use a DefaultCredentialsProvider and the client + * library's default transport channel provider (defaultEchoTransportChannelProvider()). It also + * configures the quota project ID and executor thread count, if provided through properties. + * + *

Retry settings are also configured from service-level and method-level properties specified + * in EchoSpringProperties. Method-level properties will take precedence over service-level + * properties if available, and client library defaults will be used if neither are specified. + * + * @param defaultTransportChannelProvider TransportChannelProvider to use in the settings. + * @return a {@link EchoSettings} bean configured with {@link TransportChannelProvider} bean. + */ + @Bean + @ConditionalOnMissingBean + public EchoSettings echoSettings( + @Qualifier("defaultEchoTransportChannelProvider") + TransportChannelProvider defaultTransportChannelProvider) + throws IOException { + EchoSettings.Builder clientSettingsBuilder = EchoSettings.newBuilder(); + clientSettingsBuilder + .setCredentialsProvider(this.credentialsProvider) + .setTransportChannelProvider(defaultTransportChannelProvider) + .setHeaderProvider(this.userAgentHeaderProvider()); + if (this.clientProperties.getQuotaProjectId() != null) { + clientSettingsBuilder.setQuotaProjectId(this.clientProperties.getQuotaProjectId()); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace( + "Quota project id set to " + + this.clientProperties.getQuotaProjectId() + + ", this overrides project id from credentials."); + } + } + if (this.clientProperties.getExecutorThreadCount() != null) { + ExecutorProvider executorProvider = + EchoSettings.defaultExecutorProviderBuilder() + .setExecutorThreadCount(this.clientProperties.getExecutorThreadCount()) + .build(); + clientSettingsBuilder.setBackgroundExecutorProvider(executorProvider); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace( + "Background executor thread count is " + + this.clientProperties.getExecutorThreadCount()); + } + } + Retry serviceRetry = clientProperties.getRetry(); + if (serviceRetry != null) { + RetrySettings echoRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.echoSettings().getRetrySettings(), serviceRetry); + clientSettingsBuilder.echoSettings().setRetrySettings(echoRetrySettings); + + RetrySettings pagedExpandRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.pagedExpandSettings().getRetrySettings(), serviceRetry); + clientSettingsBuilder.pagedExpandSettings().setRetrySettings(pagedExpandRetrySettings); + + RetrySettings simplePagedExpandRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.simplePagedExpandSettings().getRetrySettings(), serviceRetry); + clientSettingsBuilder + .simplePagedExpandSettings() + .setRetrySettings(simplePagedExpandRetrySettings); + + RetrySettings blockRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.blockSettings().getRetrySettings(), serviceRetry); + clientSettingsBuilder.blockSettings().setRetrySettings(blockRetrySettings); + + RetrySettings collideNameRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.collideNameSettings().getRetrySettings(), serviceRetry); + clientSettingsBuilder.collideNameSettings().setRetrySettings(collideNameRetrySettings); + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Configured service-level retry settings from properties."); + } + } + Retry echoRetry = clientProperties.getEchoRetry(); + if (echoRetry != null) { + RetrySettings echoRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.echoSettings().getRetrySettings(), echoRetry); + clientSettingsBuilder.echoSettings().setRetrySettings(echoRetrySettings); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Configured method-level retry settings for echo from properties."); + } + } + Retry pagedExpandRetry = clientProperties.getPagedExpandRetry(); + if (pagedExpandRetry != null) { + RetrySettings pagedExpandRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.pagedExpandSettings().getRetrySettings(), pagedExpandRetry); + clientSettingsBuilder.pagedExpandSettings().setRetrySettings(pagedExpandRetrySettings); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Configured method-level retry settings for pagedExpand from properties."); + } + } + Retry simplePagedExpandRetry = clientProperties.getSimplePagedExpandRetry(); + if (simplePagedExpandRetry != null) { + RetrySettings simplePagedExpandRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.simplePagedExpandSettings().getRetrySettings(), + simplePagedExpandRetry); + clientSettingsBuilder + .simplePagedExpandSettings() + .setRetrySettings(simplePagedExpandRetrySettings); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace( + "Configured method-level retry settings for simplePagedExpand from properties."); + } + } + Retry blockRetry = clientProperties.getBlockRetry(); + if (blockRetry != null) { + RetrySettings blockRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.blockSettings().getRetrySettings(), blockRetry); + clientSettingsBuilder.blockSettings().setRetrySettings(blockRetrySettings); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Configured method-level retry settings for block from properties."); + } + } + Retry collideNameRetry = clientProperties.getCollideNameRetry(); + if (collideNameRetry != null) { + RetrySettings collideNameRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.collideNameSettings().getRetrySettings(), collideNameRetry); + clientSettingsBuilder.collideNameSettings().setRetrySettings(collideNameRetrySettings); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Configured method-level retry settings for collideName from properties."); + } + } + return clientSettingsBuilder.build(); + } + + /** + * Provides a EchoClient bean configured with EchoSettings. + * + * @param echoSettings settings to configure an instance of client bean. + * @return a {@link EchoClient} bean configured with {@link EchoSettings} + */ + @Bean + @ConditionalOnMissingBean + public EchoClient echoClient(EchoSettings echoSettings) throws IOException { + return EchoClient.create(echoSettings); + } + + private HeaderProvider userAgentHeaderProvider() { + String springLibrary = "spring-autogen-echo"; + String version = this.getClass().getPackage().getImplementationVersion(); + return () -> Collections.singletonMap("user-agent", springLibrary + "/" + version); + } +} diff --git a/src/test/java/com/google/api/generator/spring/composer/goldens/EchoSpringAutoConfigurationGrpc.golden b/src/test/java/com/google/api/generator/spring/composer/goldens/EchoSpringAutoConfigurationGrpc.golden new file mode 100644 index 0000000000..c3b708b68b --- /dev/null +++ b/src/test/java/com/google/api/generator/spring/composer/goldens/EchoSpringAutoConfigurationGrpc.golden @@ -0,0 +1,230 @@ +package com.google.showcase.v1beta1.spring; + +import com.google.api.gax.core.CredentialsProvider; +import com.google.api.gax.core.ExecutorProvider; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.HeaderProvider; +import com.google.api.gax.rpc.TransportChannelProvider; +import com.google.cloud.spring.autoconfigure.core.GcpContextAutoConfiguration; +import com.google.cloud.spring.core.Credentials; +import com.google.cloud.spring.core.DefaultCredentialsProvider; +import com.google.cloud.spring.core.Retry; +import com.google.cloud.spring.core.util.RetryUtil; +import com.google.showcase.v1beta1.EchoClient; +import com.google.showcase.v1beta1.EchoSettings; +import java.io.IOException; +import java.util.Collections; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** + * Auto-configuration for {@link EchoClient}. + * + *

Provides auto-configuration for Spring Boot + * + *

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

    + *
  • The default transport provider is used. + *
  • Credentials are acquired automatically through Application Default Credentials. + *
  • Retries are configured for idempotent methods but not for non-idempotent methods. + *
+ */ +@AutoConfiguration +@AutoConfigureAfter(GcpContextAutoConfiguration.class) +@ConditionalOnClass(EchoClient.class) +@ConditionalOnProperty(value = "com.google.showcase.v1beta1.echo.enabled", matchIfMissing = true) +@EnableConfigurationProperties(EchoSpringProperties.class) +public class EchoSpringAutoConfiguration { + private final EchoSpringProperties clientProperties; + private final CredentialsProvider credentialsProvider; + private static final Log LOGGER = LogFactory.getLog(EchoSpringAutoConfiguration.class); + + protected EchoSpringAutoConfiguration( + EchoSpringProperties clientProperties, CredentialsProvider credentialsProvider) + throws IOException { + this.clientProperties = clientProperties; + if (this.clientProperties.getCredentials().hasKey()) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Using credentials from Echo-specific configuration"); + } + this.credentialsProvider = + ((CredentialsProvider) new DefaultCredentialsProvider(this.clientProperties)); + } else { + this.credentialsProvider = credentialsProvider; + } + } + + /** + * Provides a default transport channel provider bean. The default is gRPC and will default to it + * unless the useRest option is supported and provided to use HTTP transport instead + * + * @return a default transport channel provider. + */ + @Bean + @ConditionalOnMissingBean(name = "defaultEchoTransportChannelProvider") + public TransportChannelProvider defaultEchoTransportChannelProvider() { + return EchoSettings.defaultTransportChannelProvider(); + } + + /** + * Provides a EchoSettings bean configured to use a DefaultCredentialsProvider and the client + * library's default transport channel provider (defaultEchoTransportChannelProvider()). It also + * configures the quota project ID and executor thread count, if provided through properties. + * + *

Retry settings are also configured from service-level and method-level properties specified + * in EchoSpringProperties. Method-level properties will take precedence over service-level + * properties if available, and client library defaults will be used if neither are specified. + * + * @param defaultTransportChannelProvider TransportChannelProvider to use in the settings. + * @return a {@link EchoSettings} bean configured with {@link TransportChannelProvider} bean. + */ + @Bean + @ConditionalOnMissingBean + public EchoSettings echoSettings( + @Qualifier("defaultEchoTransportChannelProvider") + TransportChannelProvider defaultTransportChannelProvider) + throws IOException { + EchoSettings.Builder clientSettingsBuilder = EchoSettings.newBuilder(); + clientSettingsBuilder + .setCredentialsProvider(this.credentialsProvider) + .setTransportChannelProvider(defaultTransportChannelProvider) + .setHeaderProvider(this.userAgentHeaderProvider()); + if (this.clientProperties.getQuotaProjectId() != null) { + clientSettingsBuilder.setQuotaProjectId(this.clientProperties.getQuotaProjectId()); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace( + "Quota project id set to " + + this.clientProperties.getQuotaProjectId() + + ", this overrides project id from credentials."); + } + } + if (this.clientProperties.getExecutorThreadCount() != null) { + ExecutorProvider executorProvider = + EchoSettings.defaultExecutorProviderBuilder() + .setExecutorThreadCount(this.clientProperties.getExecutorThreadCount()) + .build(); + clientSettingsBuilder.setBackgroundExecutorProvider(executorProvider); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace( + "Background executor thread count is " + + this.clientProperties.getExecutorThreadCount()); + } + } + Retry serviceRetry = clientProperties.getRetry(); + if (serviceRetry != null) { + RetrySettings echoRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.echoSettings().getRetrySettings(), serviceRetry); + clientSettingsBuilder.echoSettings().setRetrySettings(echoRetrySettings); + + RetrySettings pagedExpandRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.pagedExpandSettings().getRetrySettings(), serviceRetry); + clientSettingsBuilder.pagedExpandSettings().setRetrySettings(pagedExpandRetrySettings); + + RetrySettings simplePagedExpandRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.simplePagedExpandSettings().getRetrySettings(), serviceRetry); + clientSettingsBuilder + .simplePagedExpandSettings() + .setRetrySettings(simplePagedExpandRetrySettings); + + RetrySettings blockRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.blockSettings().getRetrySettings(), serviceRetry); + clientSettingsBuilder.blockSettings().setRetrySettings(blockRetrySettings); + + RetrySettings collideNameRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.collideNameSettings().getRetrySettings(), serviceRetry); + clientSettingsBuilder.collideNameSettings().setRetrySettings(collideNameRetrySettings); + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Configured service-level retry settings from properties."); + } + } + Retry echoRetry = clientProperties.getEchoRetry(); + if (echoRetry != null) { + RetrySettings echoRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.echoSettings().getRetrySettings(), echoRetry); + clientSettingsBuilder.echoSettings().setRetrySettings(echoRetrySettings); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Configured method-level retry settings for echo from properties."); + } + } + Retry pagedExpandRetry = clientProperties.getPagedExpandRetry(); + if (pagedExpandRetry != null) { + RetrySettings pagedExpandRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.pagedExpandSettings().getRetrySettings(), pagedExpandRetry); + clientSettingsBuilder.pagedExpandSettings().setRetrySettings(pagedExpandRetrySettings); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Configured method-level retry settings for pagedExpand from properties."); + } + } + Retry simplePagedExpandRetry = clientProperties.getSimplePagedExpandRetry(); + if (simplePagedExpandRetry != null) { + RetrySettings simplePagedExpandRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.simplePagedExpandSettings().getRetrySettings(), + simplePagedExpandRetry); + clientSettingsBuilder + .simplePagedExpandSettings() + .setRetrySettings(simplePagedExpandRetrySettings); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace( + "Configured method-level retry settings for simplePagedExpand from properties."); + } + } + Retry blockRetry = clientProperties.getBlockRetry(); + if (blockRetry != null) { + RetrySettings blockRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.blockSettings().getRetrySettings(), blockRetry); + clientSettingsBuilder.blockSettings().setRetrySettings(blockRetrySettings); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Configured method-level retry settings for block from properties."); + } + } + Retry collideNameRetry = clientProperties.getCollideNameRetry(); + if (collideNameRetry != null) { + RetrySettings collideNameRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.collideNameSettings().getRetrySettings(), collideNameRetry); + clientSettingsBuilder.collideNameSettings().setRetrySettings(collideNameRetrySettings); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Configured method-level retry settings for collideName from properties."); + } + } + return clientSettingsBuilder.build(); + } + + /** + * Provides a EchoClient bean configured with EchoSettings. + * + * @param echoSettings settings to configure an instance of client bean. + * @return a {@link EchoClient} bean configured with {@link EchoSettings} + */ + @Bean + @ConditionalOnMissingBean + public EchoClient echoClient(EchoSettings echoSettings) throws IOException { + return EchoClient.create(echoSettings); + } + + private HeaderProvider userAgentHeaderProvider() { + String springLibrary = "spring-autogen-echo"; + String version = this.getClass().getPackage().getImplementationVersion(); + return () -> Collections.singletonMap("user-agent", springLibrary + "/" + version); + } +} diff --git a/src/test/java/com/google/api/generator/spring/composer/goldens/EchoSpringAutoConfigurationGrpcRest.golden b/src/test/java/com/google/api/generator/spring/composer/goldens/EchoSpringAutoConfigurationGrpcRest.golden new file mode 100644 index 0000000000..50fd2c2bef --- /dev/null +++ b/src/test/java/com/google/api/generator/spring/composer/goldens/EchoSpringAutoConfigurationGrpcRest.golden @@ -0,0 +1,242 @@ +package com.google.showcase.v1beta1.spring; + +import com.google.api.gax.core.CredentialsProvider; +import com.google.api.gax.core.ExecutorProvider; +import com.google.api.gax.httpjson.InstantiatingHttpJsonChannelProvider; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.HeaderProvider; +import com.google.api.gax.rpc.TransportChannelProvider; +import com.google.cloud.spring.autoconfigure.core.GcpContextAutoConfiguration; +import com.google.cloud.spring.core.Credentials; +import com.google.cloud.spring.core.DefaultCredentialsProvider; +import com.google.cloud.spring.core.Retry; +import com.google.cloud.spring.core.util.RetryUtil; +import com.google.showcase.v1beta1.EchoClient; +import com.google.showcase.v1beta1.EchoSettings; +import java.io.IOException; +import java.util.Collections; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** + * Auto-configuration for {@link EchoClient}. + * + *

Provides auto-configuration for Spring Boot + * + *

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

    + *
  • The default transport provider is used. + *
  • Credentials are acquired automatically through Application Default Credentials. + *
  • Retries are configured for idempotent methods but not for non-idempotent methods. + *
+ */ +@AutoConfiguration +@AutoConfigureAfter(GcpContextAutoConfiguration.class) +@ConditionalOnClass(EchoClient.class) +@ConditionalOnProperty(value = "com.google.showcase.v1beta1.echo.enabled", matchIfMissing = true) +@EnableConfigurationProperties(EchoSpringProperties.class) +public class EchoSpringAutoConfiguration { + private final EchoSpringProperties clientProperties; + private final CredentialsProvider credentialsProvider; + private static final Log LOGGER = LogFactory.getLog(EchoSpringAutoConfiguration.class); + + protected EchoSpringAutoConfiguration( + EchoSpringProperties clientProperties, CredentialsProvider credentialsProvider) + throws IOException { + this.clientProperties = clientProperties; + if (this.clientProperties.getCredentials().hasKey()) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Using credentials from Echo-specific configuration"); + } + this.credentialsProvider = + ((CredentialsProvider) new DefaultCredentialsProvider(this.clientProperties)); + } else { + this.credentialsProvider = credentialsProvider; + } + } + + /** + * Provides a default transport channel provider bean. The default is gRPC and will default to it + * unless the useRest option is supported and provided to use HTTP transport instead + * + * @return a default transport channel provider. + */ + @Bean + @ConditionalOnMissingBean(name = "defaultEchoTransportChannelProvider") + public TransportChannelProvider defaultEchoTransportChannelProvider() { + if (this.clientProperties.getUseRest()) { + return EchoSettings.defaultHttpJsonTransportProviderBuilder().build(); + } + return EchoSettings.defaultTransportChannelProvider(); + } + + /** + * Provides a EchoSettings bean configured to use a DefaultCredentialsProvider and the client + * library's default transport channel provider (defaultEchoTransportChannelProvider()). It also + * configures the quota project ID and executor thread count, if provided through properties. + * + *

Retry settings are also configured from service-level and method-level properties specified + * in EchoSpringProperties. Method-level properties will take precedence over service-level + * properties if available, and client library defaults will be used if neither are specified. + * + * @param defaultTransportChannelProvider TransportChannelProvider to use in the settings. + * @return a {@link EchoSettings} bean configured with {@link TransportChannelProvider} bean. + */ + @Bean + @ConditionalOnMissingBean + public EchoSettings echoSettings( + @Qualifier("defaultEchoTransportChannelProvider") + TransportChannelProvider defaultTransportChannelProvider) + throws IOException { + EchoSettings.Builder clientSettingsBuilder; + if (this.clientProperties.getUseRest()) { + clientSettingsBuilder = EchoSettings.newHttpJsonBuilder(); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Using REST (HTTP/JSON) transport."); + } + } else { + clientSettingsBuilder = EchoSettings.newBuilder(); + } + clientSettingsBuilder + .setCredentialsProvider(this.credentialsProvider) + .setTransportChannelProvider(defaultTransportChannelProvider) + .setHeaderProvider(this.userAgentHeaderProvider()); + if (this.clientProperties.getQuotaProjectId() != null) { + clientSettingsBuilder.setQuotaProjectId(this.clientProperties.getQuotaProjectId()); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace( + "Quota project id set to " + + this.clientProperties.getQuotaProjectId() + + ", this overrides project id from credentials."); + } + } + if (this.clientProperties.getExecutorThreadCount() != null) { + ExecutorProvider executorProvider = + EchoSettings.defaultExecutorProviderBuilder() + .setExecutorThreadCount(this.clientProperties.getExecutorThreadCount()) + .build(); + clientSettingsBuilder.setBackgroundExecutorProvider(executorProvider); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace( + "Background executor thread count is " + + this.clientProperties.getExecutorThreadCount()); + } + } + Retry serviceRetry = clientProperties.getRetry(); + if (serviceRetry != null) { + RetrySettings echoRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.echoSettings().getRetrySettings(), serviceRetry); + clientSettingsBuilder.echoSettings().setRetrySettings(echoRetrySettings); + + RetrySettings pagedExpandRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.pagedExpandSettings().getRetrySettings(), serviceRetry); + clientSettingsBuilder.pagedExpandSettings().setRetrySettings(pagedExpandRetrySettings); + + RetrySettings simplePagedExpandRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.simplePagedExpandSettings().getRetrySettings(), serviceRetry); + clientSettingsBuilder + .simplePagedExpandSettings() + .setRetrySettings(simplePagedExpandRetrySettings); + + RetrySettings blockRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.blockSettings().getRetrySettings(), serviceRetry); + clientSettingsBuilder.blockSettings().setRetrySettings(blockRetrySettings); + + RetrySettings collideNameRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.collideNameSettings().getRetrySettings(), serviceRetry); + clientSettingsBuilder.collideNameSettings().setRetrySettings(collideNameRetrySettings); + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Configured service-level retry settings from properties."); + } + } + Retry echoRetry = clientProperties.getEchoRetry(); + if (echoRetry != null) { + RetrySettings echoRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.echoSettings().getRetrySettings(), echoRetry); + clientSettingsBuilder.echoSettings().setRetrySettings(echoRetrySettings); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Configured method-level retry settings for echo from properties."); + } + } + Retry pagedExpandRetry = clientProperties.getPagedExpandRetry(); + if (pagedExpandRetry != null) { + RetrySettings pagedExpandRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.pagedExpandSettings().getRetrySettings(), pagedExpandRetry); + clientSettingsBuilder.pagedExpandSettings().setRetrySettings(pagedExpandRetrySettings); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Configured method-level retry settings for pagedExpand from properties."); + } + } + Retry simplePagedExpandRetry = clientProperties.getSimplePagedExpandRetry(); + if (simplePagedExpandRetry != null) { + RetrySettings simplePagedExpandRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.simplePagedExpandSettings().getRetrySettings(), + simplePagedExpandRetry); + clientSettingsBuilder + .simplePagedExpandSettings() + .setRetrySettings(simplePagedExpandRetrySettings); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace( + "Configured method-level retry settings for simplePagedExpand from properties."); + } + } + Retry blockRetry = clientProperties.getBlockRetry(); + if (blockRetry != null) { + RetrySettings blockRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.blockSettings().getRetrySettings(), blockRetry); + clientSettingsBuilder.blockSettings().setRetrySettings(blockRetrySettings); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Configured method-level retry settings for block from properties."); + } + } + Retry collideNameRetry = clientProperties.getCollideNameRetry(); + if (collideNameRetry != null) { + RetrySettings collideNameRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.collideNameSettings().getRetrySettings(), collideNameRetry); + clientSettingsBuilder.collideNameSettings().setRetrySettings(collideNameRetrySettings); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Configured method-level retry settings for collideName from properties."); + } + } + return clientSettingsBuilder.build(); + } + + /** + * Provides a EchoClient bean configured with EchoSettings. + * + * @param echoSettings settings to configure an instance of client bean. + * @return a {@link EchoClient} bean configured with {@link EchoSettings} + */ + @Bean + @ConditionalOnMissingBean + public EchoClient echoClient(EchoSettings echoSettings) throws IOException { + return EchoClient.create(echoSettings); + } + + private HeaderProvider userAgentHeaderProvider() { + String springLibrary = "spring-autogen-echo"; + String version = this.getClass().getPackage().getImplementationVersion(); + return () -> Collections.singletonMap("user-agent", springLibrary + "/" + version); + } +} diff --git a/src/test/java/com/google/api/generator/spring/composer/goldens/EchoSpringPropertiesFull.golden b/src/test/java/com/google/api/generator/spring/composer/goldens/EchoSpringPropertiesFull.golden new file mode 100644 index 0000000000..37755e6cb4 --- /dev/null +++ b/src/test/java/com/google/api/generator/spring/composer/goldens/EchoSpringPropertiesFull.golden @@ -0,0 +1,137 @@ +/* + * Copyright 2022 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 + * + * https://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.showcase.v1beta1.spring; + +import com.google.api.core.BetaApi; +import com.google.cloud.spring.core.Credentials; +import com.google.cloud.spring.core.CredentialsSupplier; +import com.google.cloud.spring.core.Retry; +import javax.annotation.Generated; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** Provides default property values for Echo client bean */ +@Generated("by google-cloud-spring-generator") +@BetaApi("Autogenerated Spring autoconfiguration is not yet stable") +@ConfigurationProperties("com.google.showcase.v1beta1.echo") +public class EchoSpringProperties implements CredentialsSupplier { + /** OAuth2 credentials to authenticate and authorize calls to Google Cloud Client Libraries. */ + @NestedConfigurationProperty + private final Credentials credentials = + new Credentials("https://www.googleapis.com/auth/cloud-platform"); + /** Quota project to use for billing. */ + private String quotaProjectId; + /** Number of threads used for executors. */ + private Integer executorThreadCount; + /** Allow override of retry settings at service level, applying to all of its RPC methods. */ + @NestedConfigurationProperty private Retry retry; + /** + * Allow override of retry settings at method-level for echo. If defined, this takes precedence + * over service-level retry configurations for that RPC method. + */ + @NestedConfigurationProperty private Retry echoRetry; + /** + * Allow override of retry settings at method-level for pagedExpand. If defined, this takes + * precedence over service-level retry configurations for that RPC method. + */ + @NestedConfigurationProperty private Retry pagedExpandRetry; + /** + * Allow override of retry settings at method-level for simplePagedExpand. If defined, this takes + * precedence over service-level retry configurations for that RPC method. + */ + @NestedConfigurationProperty private Retry simplePagedExpandRetry; + /** + * Allow override of retry settings at method-level for block. If defined, this takes precedence + * over service-level retry configurations for that RPC method. + */ + @NestedConfigurationProperty private Retry blockRetry; + /** + * Allow override of retry settings at method-level for collideName. If defined, this takes + * precedence over service-level retry configurations for that RPC method. + */ + @NestedConfigurationProperty private Retry collideNameRetry; + + @Override + public Credentials getCredentials() { + return this.credentials; + } + + public String getQuotaProjectId() { + return this.quotaProjectId; + } + + public void setQuotaProjectId(String quotaProjectId) { + this.quotaProjectId = quotaProjectId; + } + + public Integer getExecutorThreadCount() { + return this.executorThreadCount; + } + + public void setExecutorThreadCount(Integer executorThreadCount) { + this.executorThreadCount = executorThreadCount; + } + + public Retry getRetry() { + return this.retry; + } + + public void setRetry(Retry retry) { + this.retry = retry; + } + + public Retry getEchoRetry() { + return this.echoRetry; + } + + public void setEchoRetry(Retry echoRetry) { + this.echoRetry = echoRetry; + } + + public Retry getPagedExpandRetry() { + return this.pagedExpandRetry; + } + + public void setPagedExpandRetry(Retry pagedExpandRetry) { + this.pagedExpandRetry = pagedExpandRetry; + } + + public Retry getSimplePagedExpandRetry() { + return this.simplePagedExpandRetry; + } + + public void setSimplePagedExpandRetry(Retry simplePagedExpandRetry) { + this.simplePagedExpandRetry = simplePagedExpandRetry; + } + + public Retry getBlockRetry() { + return this.blockRetry; + } + + public void setBlockRetry(Retry blockRetry) { + this.blockRetry = blockRetry; + } + + public Retry getCollideNameRetry() { + return this.collideNameRetry; + } + + public void setCollideNameRetry(Retry collideNameRetry) { + this.collideNameRetry = collideNameRetry; + } +} diff --git a/src/test/java/com/google/api/generator/spring/composer/goldens/EchoSpringPropertiesGrpc.golden b/src/test/java/com/google/api/generator/spring/composer/goldens/EchoSpringPropertiesGrpc.golden new file mode 100644 index 0000000000..6b53f4ac6a --- /dev/null +++ b/src/test/java/com/google/api/generator/spring/composer/goldens/EchoSpringPropertiesGrpc.golden @@ -0,0 +1,117 @@ +package com.google.showcase.v1beta1.spring; + +import com.google.cloud.spring.core.Credentials; +import com.google.cloud.spring.core.CredentialsSupplier; +import com.google.cloud.spring.core.Retry; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** Provides default property values for Echo client bean */ +@ConfigurationProperties("com.google.showcase.v1beta1.echo") +public class EchoSpringProperties implements CredentialsSupplier { + /** OAuth2 credentials to authenticate and authorize calls to Google Cloud Client Libraries. */ + @NestedConfigurationProperty + private final Credentials credentials = + new Credentials("https://www.googleapis.com/auth/cloud-platform"); + /** Quota project to use for billing. */ + private String quotaProjectId; + /** Number of threads used for executors. */ + private Integer executorThreadCount; + /** Allow override of retry settings at service level, applying to all of its RPC methods. */ + @NestedConfigurationProperty private Retry retry; + /** + * Allow override of retry settings at method-level for echo. If defined, this takes precedence + * over service-level retry configurations for that RPC method. + */ + @NestedConfigurationProperty private Retry echoRetry; + /** + * Allow override of retry settings at method-level for pagedExpand. If defined, this takes + * precedence over service-level retry configurations for that RPC method. + */ + @NestedConfigurationProperty private Retry pagedExpandRetry; + /** + * Allow override of retry settings at method-level for simplePagedExpand. If defined, this takes + * precedence over service-level retry configurations for that RPC method. + */ + @NestedConfigurationProperty private Retry simplePagedExpandRetry; + /** + * Allow override of retry settings at method-level for block. If defined, this takes precedence + * over service-level retry configurations for that RPC method. + */ + @NestedConfigurationProperty private Retry blockRetry; + /** + * Allow override of retry settings at method-level for collideName. If defined, this takes + * precedence over service-level retry configurations for that RPC method. + */ + @NestedConfigurationProperty private Retry collideNameRetry; + + @Override + public Credentials getCredentials() { + return this.credentials; + } + + public String getQuotaProjectId() { + return this.quotaProjectId; + } + + public void setQuotaProjectId(String quotaProjectId) { + this.quotaProjectId = quotaProjectId; + } + + public Integer getExecutorThreadCount() { + return this.executorThreadCount; + } + + public void setExecutorThreadCount(Integer executorThreadCount) { + this.executorThreadCount = executorThreadCount; + } + + public Retry getRetry() { + return this.retry; + } + + public void setRetry(Retry retry) { + this.retry = retry; + } + + public Retry getEchoRetry() { + return this.echoRetry; + } + + public void setEchoRetry(Retry echoRetry) { + this.echoRetry = echoRetry; + } + + public Retry getPagedExpandRetry() { + return this.pagedExpandRetry; + } + + public void setPagedExpandRetry(Retry pagedExpandRetry) { + this.pagedExpandRetry = pagedExpandRetry; + } + + public Retry getSimplePagedExpandRetry() { + return this.simplePagedExpandRetry; + } + + public void setSimplePagedExpandRetry(Retry simplePagedExpandRetry) { + this.simplePagedExpandRetry = simplePagedExpandRetry; + } + + public Retry getBlockRetry() { + return this.blockRetry; + } + + public void setBlockRetry(Retry blockRetry) { + this.blockRetry = blockRetry; + } + + public Retry getCollideNameRetry() { + return this.collideNameRetry; + } + + public void setCollideNameRetry(Retry collideNameRetry) { + this.collideNameRetry = collideNameRetry; + } +} diff --git a/src/test/java/com/google/api/generator/spring/composer/goldens/EchoSpringPropertiesGrpcRest.golden b/src/test/java/com/google/api/generator/spring/composer/goldens/EchoSpringPropertiesGrpcRest.golden new file mode 100644 index 0000000000..86de23b342 --- /dev/null +++ b/src/test/java/com/google/api/generator/spring/composer/goldens/EchoSpringPropertiesGrpcRest.golden @@ -0,0 +1,127 @@ +package com.google.showcase.v1beta1.spring; + +import com.google.cloud.spring.core.Credentials; +import com.google.cloud.spring.core.CredentialsSupplier; +import com.google.cloud.spring.core.Retry; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** Provides default property values for Echo client bean */ +@ConfigurationProperties("com.google.showcase.v1beta1.echo") +public class EchoSpringProperties implements CredentialsSupplier { + /** OAuth2 credentials to authenticate and authorize calls to Google Cloud Client Libraries. */ + @NestedConfigurationProperty + private final Credentials credentials = + new Credentials("https://www.googleapis.com/auth/cloud-platform"); + /** Quota project to use for billing. */ + private String quotaProjectId; + /** Number of threads used for executors. */ + private Integer executorThreadCount; + /** Allow override of default transport channel provider to use REST instead of gRPC. */ + private boolean useRest = false; + /** Allow override of retry settings at service level, applying to all of its RPC methods. */ + @NestedConfigurationProperty private Retry retry; + /** + * Allow override of retry settings at method-level for echo. If defined, this takes precedence + * over service-level retry configurations for that RPC method. + */ + @NestedConfigurationProperty private Retry echoRetry; + /** + * Allow override of retry settings at method-level for pagedExpand. If defined, this takes + * precedence over service-level retry configurations for that RPC method. + */ + @NestedConfigurationProperty private Retry pagedExpandRetry; + /** + * Allow override of retry settings at method-level for simplePagedExpand. If defined, this takes + * precedence over service-level retry configurations for that RPC method. + */ + @NestedConfigurationProperty private Retry simplePagedExpandRetry; + /** + * Allow override of retry settings at method-level for block. If defined, this takes precedence + * over service-level retry configurations for that RPC method. + */ + @NestedConfigurationProperty private Retry blockRetry; + /** + * Allow override of retry settings at method-level for collideName. If defined, this takes + * precedence over service-level retry configurations for that RPC method. + */ + @NestedConfigurationProperty private Retry collideNameRetry; + + @Override + public Credentials getCredentials() { + return this.credentials; + } + + public String getQuotaProjectId() { + return this.quotaProjectId; + } + + public void setQuotaProjectId(String quotaProjectId) { + this.quotaProjectId = quotaProjectId; + } + + public boolean getUseRest() { + return this.useRest; + } + + public void setUseRest(boolean useRest) { + this.useRest = useRest; + } + + public Integer getExecutorThreadCount() { + return this.executorThreadCount; + } + + public void setExecutorThreadCount(Integer executorThreadCount) { + this.executorThreadCount = executorThreadCount; + } + + public Retry getRetry() { + return this.retry; + } + + public void setRetry(Retry retry) { + this.retry = retry; + } + + public Retry getEchoRetry() { + return this.echoRetry; + } + + public void setEchoRetry(Retry echoRetry) { + this.echoRetry = echoRetry; + } + + public Retry getPagedExpandRetry() { + return this.pagedExpandRetry; + } + + public void setPagedExpandRetry(Retry pagedExpandRetry) { + this.pagedExpandRetry = pagedExpandRetry; + } + + public Retry getSimplePagedExpandRetry() { + return this.simplePagedExpandRetry; + } + + public void setSimplePagedExpandRetry(Retry simplePagedExpandRetry) { + this.simplePagedExpandRetry = simplePagedExpandRetry; + } + + public Retry getBlockRetry() { + return this.blockRetry; + } + + public void setBlockRetry(Retry blockRetry) { + this.blockRetry = blockRetry; + } + + public Retry getCollideNameRetry() { + return this.collideNameRetry; + } + + public void setCollideNameRetry(Retry collideNameRetry) { + this.collideNameRetry = collideNameRetry; + } +} diff --git a/src/test/java/com/google/api/generator/spring/composer/goldens/SpringPackageInfo.golden b/src/test/java/com/google/api/generator/spring/composer/goldens/SpringPackageInfo.golden new file mode 100644 index 0000000000..acbc3206cd --- /dev/null +++ b/src/test/java/com/google/api/generator/spring/composer/goldens/SpringPackageInfo.golden @@ -0,0 +1,2 @@ +/** Spring Boot auto-configurations for localhost:7469. */ +package com.google.showcase.v1beta1.spring; diff --git a/src/test/java/com/google/api/generator/spring/composer/goldens/SpringPackageInfoFull.golden b/src/test/java/com/google/api/generator/spring/composer/goldens/SpringPackageInfoFull.golden new file mode 100644 index 0000000000..711231fe5f --- /dev/null +++ b/src/test/java/com/google/api/generator/spring/composer/goldens/SpringPackageInfoFull.golden @@ -0,0 +1,23 @@ +/* + * Copyright 2022 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 + * + * https://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. + */ + +/** Spring Boot auto-configurations for localhost:7469. */ +@Generated("by google-cloud-spring-generator") +@BetaApi("Autogenerated Spring autoconfiguration is not yet stable") +package com.google.showcase.v1beta1.spring; + +import com.google.api.core.BetaApi; +import javax.annotation.Generated; diff --git a/src/test/java/com/google/api/generator/spring/composer/goldens/WickedSpringAutoConfigurationNoRestRpcs.golden b/src/test/java/com/google/api/generator/spring/composer/goldens/WickedSpringAutoConfigurationNoRestRpcs.golden new file mode 100644 index 0000000000..9ee75624d0 --- /dev/null +++ b/src/test/java/com/google/api/generator/spring/composer/goldens/WickedSpringAutoConfigurationNoRestRpcs.golden @@ -0,0 +1,164 @@ +package com.google.showcase.v1beta1.spring; + +import com.google.api.gax.core.CredentialsProvider; +import com.google.api.gax.core.ExecutorProvider; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.HeaderProvider; +import com.google.api.gax.rpc.TransportChannelProvider; +import com.google.cloud.spring.autoconfigure.core.GcpContextAutoConfiguration; +import com.google.cloud.spring.core.Credentials; +import com.google.cloud.spring.core.DefaultCredentialsProvider; +import com.google.cloud.spring.core.Retry; +import com.google.cloud.spring.core.util.RetryUtil; +import com.google.showcase.v1beta1.WickedClient; +import com.google.showcase.v1beta1.WickedSettings; +import java.io.IOException; +import java.util.Collections; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** + * Auto-configuration for {@link WickedClient}. + * + *

Provides auto-configuration for Spring Boot + * + *

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

    + *
  • The default transport provider is used. + *
  • Credentials are acquired automatically through Application Default Credentials. + *
  • Retries are configured for idempotent methods but not for non-idempotent methods. + *
+ */ +@AutoConfiguration +@AutoConfigureAfter(GcpContextAutoConfiguration.class) +@ConditionalOnClass(WickedClient.class) +@ConditionalOnProperty(value = "com.google.showcase.v1beta1.wicked.enabled", matchIfMissing = true) +@EnableConfigurationProperties(WickedSpringProperties.class) +public class WickedSpringAutoConfiguration { + private final WickedSpringProperties clientProperties; + private final CredentialsProvider credentialsProvider; + private static final Log LOGGER = LogFactory.getLog(WickedSpringAutoConfiguration.class); + + protected WickedSpringAutoConfiguration( + WickedSpringProperties clientProperties, CredentialsProvider credentialsProvider) + throws IOException { + this.clientProperties = clientProperties; + if (this.clientProperties.getCredentials().hasKey()) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Using credentials from Wicked-specific configuration"); + } + this.credentialsProvider = + ((CredentialsProvider) new DefaultCredentialsProvider(this.clientProperties)); + } else { + this.credentialsProvider = credentialsProvider; + } + } + + /** + * Provides a default transport channel provider bean. The default is gRPC and will default to it + * unless the useRest option is supported and provided to use HTTP transport instead + * + * @return a default transport channel provider. + */ + @Bean + @ConditionalOnMissingBean(name = "defaultWickedTransportChannelProvider") + public TransportChannelProvider defaultWickedTransportChannelProvider() { + return WickedSettings.defaultTransportChannelProvider(); + } + + /** + * Provides a WickedSettings bean configured to use a DefaultCredentialsProvider and the client + * library's default transport channel provider (defaultWickedTransportChannelProvider()). It also + * configures the quota project ID and executor thread count, if provided through properties. + * + *

Retry settings are also configured from service-level and method-level properties specified + * in WickedSpringProperties. Method-level properties will take precedence over service-level + * properties if available, and client library defaults will be used if neither are specified. + * + * @param defaultTransportChannelProvider TransportChannelProvider to use in the settings. + * @return a {@link WickedSettings} bean configured with {@link TransportChannelProvider} bean. + */ + @Bean + @ConditionalOnMissingBean + public WickedSettings wickedSettings( + @Qualifier("defaultWickedTransportChannelProvider") + TransportChannelProvider defaultTransportChannelProvider) + throws IOException { + WickedSettings.Builder clientSettingsBuilder = WickedSettings.newBuilder(); + clientSettingsBuilder + .setCredentialsProvider(this.credentialsProvider) + .setTransportChannelProvider(defaultTransportChannelProvider) + .setHeaderProvider(this.userAgentHeaderProvider()); + if (this.clientProperties.getQuotaProjectId() != null) { + clientSettingsBuilder.setQuotaProjectId(this.clientProperties.getQuotaProjectId()); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace( + "Quota project id set to " + + this.clientProperties.getQuotaProjectId() + + ", this overrides project id from credentials."); + } + } + if (this.clientProperties.getExecutorThreadCount() != null) { + ExecutorProvider executorProvider = + WickedSettings.defaultExecutorProviderBuilder() + .setExecutorThreadCount(this.clientProperties.getExecutorThreadCount()) + .build(); + clientSettingsBuilder.setBackgroundExecutorProvider(executorProvider); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace( + "Background executor thread count is " + + this.clientProperties.getExecutorThreadCount()); + } + } + Retry serviceRetry = clientProperties.getRetry(); + if (serviceRetry != null) { + RetrySettings craftEvilPlanRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.craftEvilPlanSettings().getRetrySettings(), serviceRetry); + clientSettingsBuilder.craftEvilPlanSettings().setRetrySettings(craftEvilPlanRetrySettings); + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Configured service-level retry settings from properties."); + } + } + Retry craftEvilPlanRetry = clientProperties.getCraftEvilPlanRetry(); + if (craftEvilPlanRetry != null) { + RetrySettings craftEvilPlanRetrySettings = + RetryUtil.updateRetrySettings( + clientSettingsBuilder.craftEvilPlanSettings().getRetrySettings(), craftEvilPlanRetry); + clientSettingsBuilder.craftEvilPlanSettings().setRetrySettings(craftEvilPlanRetrySettings); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Configured method-level retry settings for craftEvilPlan from properties."); + } + } + return clientSettingsBuilder.build(); + } + + /** + * Provides a WickedClient bean configured with WickedSettings. + * + * @param wickedSettings settings to configure an instance of client bean. + * @return a {@link WickedClient} bean configured with {@link WickedSettings} + */ + @Bean + @ConditionalOnMissingBean + public WickedClient wickedClient(WickedSettings wickedSettings) throws IOException { + return WickedClient.create(wickedSettings); + } + + private HeaderProvider userAgentHeaderProvider() { + String springLibrary = "spring-autogen-wicked"; + String version = this.getClass().getPackage().getImplementationVersion(); + return () -> Collections.singletonMap("user-agent", springLibrary + "/" + version); + } +} diff --git a/src/test/java/com/google/api/generator/spring/composer/goldens/WickedSpringPropertiesNoRestRpcs.golden b/src/test/java/com/google/api/generator/spring/composer/goldens/WickedSpringPropertiesNoRestRpcs.golden new file mode 100644 index 0000000000..1efaf25408 --- /dev/null +++ b/src/test/java/com/google/api/generator/spring/composer/goldens/WickedSpringPropertiesNoRestRpcs.golden @@ -0,0 +1,63 @@ +package com.google.showcase.v1beta1.spring; + +import com.google.cloud.spring.core.Credentials; +import com.google.cloud.spring.core.CredentialsSupplier; +import com.google.cloud.spring.core.Retry; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +// AUTO-GENERATED DOCUMENTATION AND CLASS. +/** Provides default property values for Wicked client bean */ +@ConfigurationProperties("com.google.showcase.v1beta1.wicked") +public class WickedSpringProperties implements CredentialsSupplier { + /** OAuth2 credentials to authenticate and authorize calls to Google Cloud Client Libraries. */ + @NestedConfigurationProperty private final Credentials credentials = new Credentials(); + /** Quota project to use for billing. */ + private String quotaProjectId; + /** Number of threads used for executors. */ + private Integer executorThreadCount; + /** Allow override of retry settings at service level, applying to all of its RPC methods. */ + @NestedConfigurationProperty private Retry retry; + /** + * Allow override of retry settings at method-level for craftEvilPlan. If defined, this takes + * precedence over service-level retry configurations for that RPC method. + */ + @NestedConfigurationProperty private Retry craftEvilPlanRetry; + + @Override + public Credentials getCredentials() { + return this.credentials; + } + + public String getQuotaProjectId() { + return this.quotaProjectId; + } + + public void setQuotaProjectId(String quotaProjectId) { + this.quotaProjectId = quotaProjectId; + } + + public Integer getExecutorThreadCount() { + return this.executorThreadCount; + } + + public void setExecutorThreadCount(Integer executorThreadCount) { + this.executorThreadCount = executorThreadCount; + } + + public Retry getRetry() { + return this.retry; + } + + public void setRetry(Retry retry) { + this.retry = retry; + } + + public Retry getCraftEvilPlanRetry() { + return this.craftEvilPlanRetry; + } + + public void setCraftEvilPlanRetry(Retry craftEvilPlanRetry) { + this.craftEvilPlanRetry = craftEvilPlanRetry; + } +} diff --git a/src/test/java/com/google/api/generator/spring/goldens/SpringAdditionalMetadataJson.golden b/src/test/java/com/google/api/generator/spring/goldens/SpringAdditionalMetadataJson.golden new file mode 100644 index 0000000000..4e42cb414c --- /dev/null +++ b/src/test/java/com/google/api/generator/spring/goldens/SpringAdditionalMetadataJson.golden @@ -0,0 +1,10 @@ +{ + "properties": [ + { + "name": "com.google.showcase.v1beta1.echo.enabled", + "type": "java.lang.Boolean", + "description": "Auto-configure Google Cloud localhost:7469/Echo components.", + "defaultValue": true + } + ] +} \ No newline at end of file diff --git a/src/test/java/com/google/api/generator/spring/goldens/SpringPackagePom.golden b/src/test/java/com/google/api/generator/spring/goldens/SpringPackagePom.golden new file mode 100644 index 0000000000..6d583c2b5a --- /dev/null +++ b/src/test/java/com/google/api/generator/spring/goldens/SpringPackagePom.golden @@ -0,0 +1,35 @@ + + + 4.0.0 + + + com.google.cloud + spring-cloud-gcp-starters + {{parent-version}} + ../../spring-cloud-gcp-starters/pom.xml + + {{client-library-artifact-id}}-spring-starter + ${project.parent.version}-preview + Spring Boot Starter - localhost:7469 + Spring Boot Starter with AutoConfiguration for localhost:7469 + + + + + {{client-library-group-id}} + {{client-library-artifact-id}} + + + + org.springframework.boot + spring-boot-starter + + + + com.google.cloud + spring-cloud-gcp-autoconfigure + + + + \ No newline at end of file diff --git a/src/test/proto/wicked.proto b/src/test/proto/wicked.proto new file mode 100644 index 0000000000..ea45fee918 --- /dev/null +++ b/src/test/proto/wicked.proto @@ -0,0 +1,49 @@ +// Copyright 2023 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 +// +// https://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. + +syntax = "proto3"; + +import "google/api/client.proto"; + +package google.showcase.v1beta1; + +option go_package = "github.com/googleapis/gapic-showcase/server/genproto"; +option java_package = "com.google.showcase.v1beta1"; +option java_multiple_files = true; +option ruby_package = "Google::Showcase::V1beta1"; + +// This service is used to show a Service with either non-enabled or non-eligible +// RPCs for HttpJson (Http 1.1). +// Non-Enabled: Missing the (google.api.http) annotation to enabled it +// Non-Eligible: BIDI and Client side streaming are not supported with Http 1.1 +// Service name is reference to `No REST for the Wicked` +service Wicked { + // This service is meant to only run locally on the port 7469 (keypad digits + // for "show"). + option (google.api.default_host) = "localhost:7469"; + + rpc CraftEvilPlan(EvilRequest) returns (EvilResponse); + + rpc BrainstormEvilPlans(stream EvilRequest) returns (stream EvilResponse); + + rpc PersuadeEvilPlan(stream EvilRequest) returns (EvilResponse); +} + +message EvilRequest { + string malicious_idea = 1; +} + +message EvilResponse { + string malicious_plan = 1; +} \ No newline at end of file