Java tutorial
/* Copyright 2016 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.api.codegen.transformer.go; import com.google.api.codegen.RetryParamsDefinitionProto; import com.google.api.codegen.config.ApiModel; import com.google.api.codegen.config.GapicInterfaceConfig; import com.google.api.codegen.config.GapicProductConfig; import com.google.api.codegen.config.InterfaceConfig; import com.google.api.codegen.config.InterfaceModel; import com.google.api.codegen.config.MethodConfig; import com.google.api.codegen.config.MethodModel; import com.google.api.codegen.config.ProductConfig; import com.google.api.codegen.config.ProtoApiModel; import com.google.api.codegen.config.ProtoInterfaceModel; import com.google.api.codegen.gapic.GapicCodePathMapper; import com.google.api.codegen.transformer.ApiCallableTransformer; import com.google.api.codegen.transformer.DefaultFeatureConfig; import com.google.api.codegen.transformer.FeatureConfig; import com.google.api.codegen.transformer.FileHeaderTransformer; import com.google.api.codegen.transformer.GapicInterfaceContext; import com.google.api.codegen.transformer.GrpcStubTransformer; import com.google.api.codegen.transformer.IamResourceTransformer; import com.google.api.codegen.transformer.ImportTypeTable; import com.google.api.codegen.transformer.InterfaceContext; import com.google.api.codegen.transformer.MethodContext; import com.google.api.codegen.transformer.ModelToViewTransformer; import com.google.api.codegen.transformer.ModelTypeTable; import com.google.api.codegen.transformer.PageStreamingTransformer; import com.google.api.codegen.transformer.PathTemplateTransformer; import com.google.api.codegen.transformer.ServiceTransformer; import com.google.api.codegen.transformer.StaticLangApiMethodTransformer; import com.google.api.codegen.transformer.SurfaceNamer; import com.google.api.codegen.util.CommonRenderingUtil; import com.google.api.codegen.util.go.GoTypeTable; import com.google.api.codegen.viewmodel.ImportSectionView; import com.google.api.codegen.viewmodel.LongRunningOperationDetailView; import com.google.api.codegen.viewmodel.PackageInfoView; import com.google.api.codegen.viewmodel.PageStreamingDescriptorClassView; import com.google.api.codegen.viewmodel.RetryConfigDefinitionView; import com.google.api.codegen.viewmodel.ServiceDocView; import com.google.api.codegen.viewmodel.StaticLangApiMethodView; import com.google.api.codegen.viewmodel.StaticLangClientExampleFileView; import com.google.api.codegen.viewmodel.StaticLangClientFileView; import com.google.api.codegen.viewmodel.ViewModel; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableTable; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; public class GoGapicSurfaceTransformer implements ModelToViewTransformer<ProtoApiModel> { private static final String API_TEMPLATE_FILENAME = "go/main.snip"; private static final String SAMPLE_TEMPLATE_FILENAME = "go/example.snip"; private static final String DOC_TEMPLATE_FILENAME = "go/doc.snip"; private static final int COMMENT_LINE_LENGTH = 75; private final ApiCallableTransformer apiCallableTransformer = new ApiCallableTransformer(); private final StaticLangApiMethodTransformer apiMethodTransformer = new StaticLangApiMethodTransformer(); private final FeatureConfig featureConfig = new DefaultFeatureConfig(); private final FileHeaderTransformer fileHeaderTransformer = new FileHeaderTransformer( new GoImportSectionTransformer()); private final GrpcStubTransformer grpcStubTransformer = new GrpcStubTransformer(); private final IamResourceTransformer iamResourceTransformer = new IamResourceTransformer(); private final PageStreamingTransformer pageStreamingTransformer = new PageStreamingTransformer(); private final PathTemplateTransformer pathTemplateTransformer = new PathTemplateTransformer(); private final ServiceTransformer serviceTransformer = new ServiceTransformer(); private final GapicCodePathMapper pathMapper; public GoGapicSurfaceTransformer(GapicCodePathMapper pathMapper) { this.pathMapper = pathMapper; } @Override public List<String> getTemplateFileNames() { return Arrays.asList(API_TEMPLATE_FILENAME, DOC_TEMPLATE_FILENAME, SAMPLE_TEMPLATE_FILENAME); } @Override public List<ViewModel> transform(ProtoApiModel model, GapicProductConfig productConfig) { List<ViewModel> models = new ArrayList<>(); GoSurfaceNamer namer = new GoSurfaceNamer(productConfig.getPackageName()); for (InterfaceModel apiInterface : model.getInterfaces()) { GapicInterfaceContext context = GapicInterfaceContext.create(apiInterface, productConfig, createTypeTable(), namer, featureConfig); models.add(generate(context)); context = GapicInterfaceContext.create(apiInterface, productConfig, createTypeTable(), namer, featureConfig); models.add(generateExample(context)); } models.add(generatePackageInfo(model, productConfig, namer)); return models; } private StaticLangClientFileView generate(GapicInterfaceContext context) { StaticLangClientFileView.Builder view = StaticLangClientFileView.newBuilder(); SurfaceNamer namer = context.getNamer(); ApiModel model = context.getApiModel(); ProtoInterfaceModel apiInterface = context.getInterfaceModel(); GapicProductConfig productConfig = context.getProductConfig(); GapicInterfaceConfig interfaceConfig = context.getInterfaceConfig(); view.templateFileName(API_TEMPLATE_FILENAME); view.serviceDoc(serviceTransformer.generateServiceDoc(context, null, productConfig)); view.domainLayerLocation(productConfig.getDomainLayerLocation()); view.clientTypeName(namer.getApiWrapperClassName(context.getInterfaceConfig())); view.clientConstructorName(namer.getApiWrapperClassConstructorName(interfaceConfig)); view.defaultClientOptionFunctionName(namer.getDefaultApiSettingsFunctionName(interfaceConfig)); view.defaultCallOptionFunctionName(namer.getDefaultCallSettingsFunctionName(interfaceConfig)); view.callOptionsTypeName(namer.getCallSettingsTypeName(interfaceConfig)); view.serviceOriginalName(model.getTitle()); view.servicePhraseName(namer.getServicePhraseName(interfaceConfig)); String outputPath = pathMapper.getOutputPath(apiInterface.getFullName(), productConfig); String fileName = namer.getServiceFileName(context.getInterfaceConfig()); view.outputPath(outputPath + File.separator + fileName); List<RetryConfigDefinitionView> retryDef = generateRetryConfigDefinitions(context, context.getSupportedMethods()); view.retryPairDefinitions(retryDef); view.callSettings(apiCallableTransformer.generateCallSettings(context)); List<StaticLangApiMethodView> apiMethods = generateApiMethods(context, context.getSupportedMethods()); view.apiMethods(apiMethods); // If any methods have header request params, "fmt" is needed for `fmt.Sprintf` calls. if (apiMethods.stream().anyMatch(m -> !m.headerRequestParams().isEmpty())) { context.getImportTypeTable().saveNicknameFor("fmt;;;"); } view.iamResources(iamResourceTransformer.generateIamResources(context)); if (!((GapicInterfaceConfig) productConfig.getInterfaceConfig(apiInterface.getFullName())).getIamResources() .isEmpty()) { context.getImportTypeTable().saveNicknameFor("cloud.google.com/go/iam;;;"); } // In Go, multiple methods share the same iterator type, one iterator type per resource type. // We have to dedupe the iterators. Map<String, PageStreamingDescriptorClassView> iterators = new TreeMap<>(); for (PageStreamingDescriptorClassView desc : pageStreamingTransformer.generateDescriptorClasses(context)) { iterators.put(desc.typeName(), desc); } view.pageStreamingDescriptorClasses(new ArrayList<>(iterators.values())); // Same with long running operations. Map<String, LongRunningOperationDetailView> lros = new TreeMap<>(); for (StaticLangApiMethodView apiMethod : apiMethods) { LongRunningOperationDetailView lro = apiMethod.operationMethod(); if (lro != null) { lros.put(lro.clientReturnTypeName(), lro); } } view.lroDetailViews(new ArrayList<>(lros.values())); view.serviceAddress(context.getApiModel().getServiceAddress()); view.servicePort(model.getServicePort()); view.stubs(grpcStubTransformer.generateGrpcStubs(context)); addXApiImports(context, context.getSupportedMethods()); view.fileHeader(fileHeaderTransformer.generateFileHeader(context)); return view.build(); } private StaticLangClientExampleFileView generateExample(InterfaceContext context) { StaticLangClientExampleFileView.Builder view = StaticLangClientExampleFileView.newBuilder(); SurfaceNamer namer = context.getNamer(); InterfaceModel apiInterface = context.getInterfaceModel(); ProductConfig productConfig = context.getProductConfig(); InterfaceConfig interfaceConfig = context.getInterfaceConfig(); view.templateFileName(SAMPLE_TEMPLATE_FILENAME); String outputPath = pathMapper.getOutputPath(apiInterface.getFullName(), productConfig); String fileName = namer.getExampleFileName(context.getInterfaceConfig()); view.outputPath(outputPath + File.separator + fileName); view.clientTypeName(namer.getApiWrapperClassName(context.getInterfaceConfig())); view.clientConstructorName(namer.getApiWrapperClassConstructorName(interfaceConfig)); view.clientConstructorExampleName(namer.getApiWrapperClassConstructorExampleName(interfaceConfig)); view.apiMethods(generateApiMethods(context, context.getPublicMethods())); view.iamResources(iamResourceTransformer.generateIamResources(context)); // Examples are different from the API. In particular, we use short declaration // and so we omit most type names. We only need // - Context, to initialize the client // - The VKit generated library, that's what the sample is for // - The input types of the methods, to initialize the requests // So, we clear all imports; addXExampleImports will add back the ones we want. context.getImportTypeTable().getImports().clear(); addXExampleImports(context, context.getPublicMethods()); view.fileHeader(fileHeaderTransformer.generateFileHeader(context)); return view.build(); } private PackageInfoView generatePackageInfo(ApiModel model, GapicProductConfig productConfig, SurfaceNamer namer) { String outputPath = productConfig.getPackageName(); String fileName = "doc.go"; PackageInfoView.Builder packageInfo = PackageInfoView.newBuilder(); packageInfo.templateFileName(DOC_TEMPLATE_FILENAME); packageInfo.outputPath(outputPath + File.separator + fileName); packageInfo.serviceTitle(model.getTitle()); packageInfo.importPath(productConfig.getPackageName()); packageInfo.serviceDocs(Collections.<ServiceDocView>emptyList()); packageInfo .packageDoc(CommonRenderingUtil.getDocLines(model.getDocumentationSummary(), COMMENT_LINE_LENGTH)); packageInfo.domainLayerLocation(productConfig.getDomainLayerLocation()); packageInfo.authScopes(model.getAuthScopes()); packageInfo.fileHeader(fileHeaderTransformer.generateFileHeader(productConfig, ImportSectionView.newBuilder().build(), namer)); packageInfo.releaseLevel(productConfig.getReleaseLevel()); return packageInfo.build(); } static ModelTypeTable createTypeTable() { return new ModelTypeTable(new GoTypeTable(), new GoModelTypeNameConverter()); } @VisibleForTesting List<StaticLangApiMethodView> generateApiMethods(InterfaceContext context, Iterable<MethodModel> methods) { List<StaticLangApiMethodView> apiMethods = new ArrayList<>(); for (MethodModel method : methods) { MethodConfig methodConfig = context.getMethodConfig(method); MethodContext methodContext = context.asRequestMethodContext(method); if (method.getRequestStreaming() || method.getResponseStreaming()) { apiMethods.add(apiMethodTransformer.generateGrpcStreamingRequestObjectMethod(methodContext)); } else if (methodConfig.isPageStreaming()) { apiMethods.add(apiMethodTransformer.generatePagedRequestObjectMethod(methodContext)); } else if (methodConfig.isLongRunningOperation()) { apiMethods.add(apiMethodTransformer.generateOperationRequestObjectMethod(methodContext)); } else { apiMethods.add(apiMethodTransformer.generateRequestObjectMethod(methodContext)); } } return apiMethods; } @VisibleForTesting List<RetryConfigDefinitionView> generateRetryConfigDefinitions(InterfaceContext context, List<MethodModel> methods) { Set<RetryConfigDefinitionView.Name> retryNames = new HashSet<>(); for (MethodModel method : methods) { MethodConfig conf = context.getMethodConfig(method); retryNames.add(RetryConfigDefinitionView.Name.create(conf.getRetrySettingsConfigName(), conf.getRetryCodesConfigName())); } TreeMap<RetryConfigDefinitionView.Name, RetryConfigDefinitionView> retryDef = new TreeMap<>(); Map<String, ImmutableSet<String>> retryCodesDef = context.getInterfaceConfig().getRetryCodesDefinition(); ImmutableMap<String, RetryParamsDefinitionProto> retryParamsDef = context.getInterfaceConfig() .getRetrySettingsDefinition(); for (RetryConfigDefinitionView.Name name : retryNames) { ImmutableSet<String> codes = retryCodesDef.get(name.retryCodesConfigName()); if (codes.isEmpty()) { continue; } List<String> retryCodeNames = new ArrayList<>(); for (String code : codes) { retryCodeNames.add(context.getNamer().getStatusCodeName(code)); } retryDef.put(name, RetryConfigDefinitionView.newBuilder().name(name).retryCodes(retryCodeNames) .params(retryParamsDef.get(name.retrySettingsConfigName())).build()); } if (!retryDef.isEmpty()) { context.getImportTypeTable().saveNicknameFor("time;;;"); context.getImportTypeTable().saveNicknameFor("google.golang.org/grpc/codes;;;"); } return new ArrayList<>(retryDef.values()); } private static final String EMPTY_PROTO_PKG = "github.com/golang/protobuf/ptypes/empty"; @VisibleForTesting void addXApiImports(InterfaceContext context, Collection<MethodModel> methods) { ImportTypeTable typeTable = context.getImportTypeTable(); typeTable.saveNicknameFor("cloud.google.com/go/internal/version;;;"); typeTable.saveNicknameFor("golang.org/x/net/context;;;"); typeTable.saveNicknameFor("google.golang.org/grpc;;;"); typeTable.saveNicknameFor("github.com/googleapis/gax-go;gax;;"); typeTable.saveNicknameFor("google.golang.org/api/option;;;"); typeTable.saveNicknameFor("google.golang.org/api/transport;;;"); typeTable.saveNicknameFor("google.golang.org/grpc/metadata;;;"); typeTable.getImports().remove(EMPTY_PROTO_PKG); addContextImports(context, ImportContext.CLIENT, methods); } @VisibleForTesting void addXExampleImports(InterfaceContext context, Iterable<MethodModel> methods) { ImportTypeTable typeTable = context.getImportTypeTable(); typeTable.saveNicknameFor("golang.org/x/net/context;;;"); typeTable.saveNicknameFor(context.getProductConfig().getPackageName() + ";;;"); for (MethodModel method : methods) { method.getAndSaveRequestTypeName(context.getImportTypeTable(), context.getNamer()); } addContextImports(context, ImportContext.EXAMPLE, methods); } private void addContextImports(InterfaceContext context, ImportContext importContext, Iterable<MethodModel> methods) { for (ImportKind kind : getImportKinds(context.getInterfaceConfig(), methods)) { ImmutableList<String> imps = CONTEXTUAL_IMPORTS.get(importContext, kind); if (imps != null) { for (String imp : imps) { context.getImportTypeTable().saveNicknameFor(imp); } } } } private Set<ImportKind> getImportKinds(InterfaceConfig interfaceConfig, Iterable<MethodModel> methods) { EnumSet<ImportKind> kinds = EnumSet.noneOf(ImportKind.class); for (MethodModel method : methods) { if (method.getResponseStreaming()) { kinds.add(ImportKind.SERVER_STREAM); } MethodConfig methodConfig = interfaceConfig.getMethodConfig(method); if (methodConfig.isLongRunningOperation()) { kinds.add(ImportKind.LRO); } if (methodConfig.isPageStreaming()) { kinds.add(ImportKind.PAGE_STREAM); } } return kinds; } private enum ImportContext { CLIENT, EXAMPLE, } private enum ImportKind { PAGE_STREAM, LRO, SERVER_STREAM, } private static final ImmutableTable<ImportContext, ImportKind, ImmutableList<String>> CONTEXTUAL_IMPORTS = ImmutableTable .<ImportContext, ImportKind, ImmutableList<String>>builder() .put(ImportContext.CLIENT, ImportKind.PAGE_STREAM, ImmutableList.<String>of("math;;;", "google.golang.org/api/iterator;;;", "github.com/golang/protobuf/proto;;;")) .put(ImportContext.EXAMPLE, ImportKind.PAGE_STREAM, ImmutableList.<String>of("google.golang.org/api/iterator;;;")) .put(ImportContext.CLIENT, ImportKind.LRO, ImmutableList.<String>of("time;;;", "cloud.google.com/go/longrunning;;;", "cloud.google.com/go/longrunning/autogen;lroauto;;")) .put(ImportContext.EXAMPLE, ImportKind.SERVER_STREAM, ImmutableList.<String>of("io;;;")).build(); }