use of com.google.api.generator.gapic.model.Message in project gapic-generator-java by googleapis.
the class MethodSignatureParser method parseMethodSignatures.
/**
* Parses a list of method signature annotations out of an RPC.
*/
public static List<List<MethodArgument>> parseMethodSignatures(MethodDescriptor methodDescriptor, String servicePackage, TypeNode methodInputType, Map<String, Message> messageTypes, Map<String, ResourceName> resourceNames, Set<ResourceName> outputArgResourceNames) {
List<String> stringSigs = methodDescriptor.getOptions().getExtension(ClientProto.methodSignature);
List<List<MethodArgument>> signatures = new ArrayList<>();
if (stringSigs.isEmpty()) {
return signatures;
}
Map<String, ResourceName> patternsToResourceNames = ResourceParserHelpers.createPatternResourceNameMap(resourceNames);
Message inputMessage = messageTypes.get(methodInputType.reference().fullName());
// stringSigs: ["content,error", "content,error,info"].
for (String stringSig : stringSigs) {
if (Strings.isNullOrEmpty(stringSig)) {
signatures.add(Collections.emptyList());
continue;
}
List<String> argumentNames = new ArrayList<>();
Map<String, List<MethodArgument>> argumentNameToOverloads = new HashMap<>();
// stringSig.split: ["content", "error"].
for (String argumentName : stringSig.split(METHOD_SIGNATURE_DELIMITER)) {
// For resource names, this will be empty.
List<Field> argumentFieldPathAcc = new ArrayList<>();
// There should be more than one type returned only when we encounter a resource name.
Map<TypeNode, Field> argumentTypes = parseTypeFromArgumentName(argumentName, servicePackage, inputMessage, messageTypes, resourceNames, patternsToResourceNames, argumentFieldPathAcc, outputArgResourceNames);
int dotLastIndex = argumentName.lastIndexOf(DOT);
String actualArgumentName = dotLastIndex < 0 ? argumentName : argumentName.substring(dotLastIndex + 1);
argumentNames.add(actualArgumentName);
argumentNameToOverloads.put(actualArgumentName, argumentTypes.entrySet().stream().map(e -> MethodArgument.builder().setName(actualArgumentName).setType(e.getKey()).setField(e.getValue()).setIsResourceNameHelper(argumentTypes.size() > 1 && !e.getKey().equals(TypeNode.STRING)).setNestedFields(argumentFieldPathAcc).build()).collect(Collectors.toList()));
}
signatures.addAll(flattenMethodSignatureVariants(argumentNames, argumentNameToOverloads));
}
// Make the method signature order deterministic, which helps with unit testing and per-version
// diffs.
List<List<MethodArgument>> sortedMethodSignatures = signatures.stream().sorted((s1, s2) -> {
// Sort by number of arguments first.
if (s1.size() != s2.size()) {
return s1.size() - s2.size();
}
// Then by MethodSignature properties.
for (int i = 0; i < s1.size(); i++) {
int compareVal = s1.get(i).compareTo(s2.get(i));
if (compareVal != 0) {
return compareVal;
}
}
return 0;
}).collect(Collectors.toList());
return sortedMethodSignatures;
}
use of com.google.api.generator.gapic.model.Message in project gapic-generator-java by googleapis.
the class Parser method parseMessages.
public static Map<String, Message> parseMessages(CodeGeneratorRequest request, Set<ResourceReference> outputResourceReferencesSeen) {
Map<String, FileDescriptor> fileDescriptors = getFilesToGenerate(request);
Map<String, Message> messages = new HashMap<>();
// ensure we track commonly-used protos like Empty.
for (FileDescriptor fileDescriptor : fileDescriptors.values()) {
messages.putAll(parseMessages(fileDescriptor, outputResourceReferencesSeen));
}
return messages;
}
use of com.google.api.generator.gapic.model.Message in project gapic-generator-java by googleapis.
the class Parser method parse.
public static GapicContext parse(CodeGeneratorRequest request) {
Optional<String> gapicYamlConfigPathOpt = PluginArgumentParser.parseGapicYamlConfigPath(request);
Optional<List<GapicBatchingSettings>> batchingSettingsOpt = BatchingSettingsConfigParser.parse(gapicYamlConfigPathOpt);
Optional<List<GapicLroRetrySettings>> lroRetrySettingsOpt = GapicLroRetrySettingsParser.parse(gapicYamlConfigPathOpt);
Optional<GapicLanguageSettings> languageSettingsOpt = GapicLanguageSettingsParser.parse(gapicYamlConfigPathOpt);
Optional<String> transportOpt = PluginArgumentParser.parseTransport(request);
boolean willGenerateMetadata = PluginArgumentParser.hasMetadataFlag(request);
Optional<String> serviceConfigPathOpt = PluginArgumentParser.parseJsonConfigPath(request);
Optional<GapicServiceConfig> serviceConfigOpt = ServiceConfigParser.parse(serviceConfigPathOpt.orElse(null));
if (serviceConfigOpt.isPresent()) {
GapicServiceConfig serviceConfig = serviceConfigOpt.get();
serviceConfig.setLroRetrySettings(lroRetrySettingsOpt);
serviceConfig.setBatchingSettings(batchingSettingsOpt);
serviceConfig.setLanguageSettings(languageSettingsOpt);
serviceConfigOpt = Optional.of(serviceConfig);
}
Optional<String> serviceYamlConfigPathOpt = PluginArgumentParser.parseServiceYamlConfigPath(request);
Optional<com.google.api.Service> serviceYamlProtoOpt = serviceYamlConfigPathOpt.flatMap(ServiceYamlParser::parse);
// Collect the resource references seen in messages.
Set<ResourceReference> outputResourceReferencesSeen = new HashSet<>();
// Keep message and resource name parsing separate for cleaner logic.
// While this takes an extra pass through the protobufs, the extra time is relatively trivial
// and is worth the larger reduced maintenance cost.
Map<String, Message> messages = parseMessages(request, outputResourceReferencesSeen);
Map<String, ResourceName> resourceNames = parseResourceNames(request);
messages = updateResourceNamesInMessages(messages, resourceNames.values());
// Contains only resource names that are actually used. Usage refers to the presence of a
// request message's field in an RPC's method_signature annotation. That is, resource name
// definitions
// or references that are simply defined, but not used in such a manner, will not have
// corresponding Java helper
// classes generated.
Set<ResourceName> outputArgResourceNames = new HashSet<>();
List<Service> mixinServices = new ArrayList<>();
Transport transport = Transport.parse(transportOpt.orElse(Transport.GRPC.toString()));
List<Service> services = parseServices(request, messages, resourceNames, outputArgResourceNames, serviceYamlProtoOpt, serviceConfigOpt, mixinServices, transport);
Preconditions.checkState(!services.isEmpty(), "No services found to generate");
// Temporary workaround for Ads, who still need these resource names.
if (services.get(0).protoPakkage().startsWith("google.ads.googleads.v")) {
Function<ResourceName, String> typeNameFn = r -> r.resourceTypeString().substring(r.resourceTypeString().indexOf("/") + 1);
Function<Set<ResourceName>, Set<String>> typeStringSetFn = sr -> sr.stream().map(typeNameFn).collect(Collectors.toSet());
// Include all resource names present in message types for backwards-compatibility with the
// monolith. In the future, this should be removed on a client library major semver update.
// Resolve type name collisions with the ones present in the method arguments.
final Set<String> typeStringSet = typeStringSetFn.apply(outputArgResourceNames);
outputArgResourceNames.addAll(resourceNames.values().stream().filter(r -> r.hasParentMessageName() && !typeStringSet.contains(typeNameFn.apply(r))).collect(Collectors.toSet()));
String servicePackage = services.get(0).pakkage();
Map<String, ResourceName> patternsToResourceNames = ResourceParserHelpers.createPatternResourceNameMap(resourceNames);
for (ResourceReference resourceReference : outputResourceReferencesSeen) {
final Set<String> interimTypeStringSet = typeStringSetFn.apply(outputArgResourceNames);
outputArgResourceNames.addAll(ResourceReferenceParser.parseResourceNames(resourceReference, servicePackage, null, resourceNames, patternsToResourceNames).stream().filter(r -> !interimTypeStringSet.contains(typeNameFn.apply(r))).collect(Collectors.toSet()));
}
}
return GapicContext.builder().setServices(services).setMixinServices(mixinServices).setMessages(messages).setResourceNames(resourceNames).setHelperResourceNames(outputArgResourceNames).setServiceConfig(serviceConfigOpt.orElse(null)).setGapicMetadataEnabled(willGenerateMetadata).setServiceYamlProto(serviceYamlProtoOpt.orElse(null)).setTransport(transport).build();
}
use of com.google.api.generator.gapic.model.Message in project gapic-generator-java by googleapis.
the class Parser method parseLro.
@VisibleForTesting
static LongrunningOperation parseLro(String servicePackage, MethodDescriptor methodDescriptor, Map<String, Message> messageTypes) {
MethodOptions methodOptions = methodDescriptor.getOptions();
TypeNode operationServiceStubType = null;
String responseTypeName = null;
String metadataTypeName = null;
if (methodOptions.hasExtension(OperationsProto.operationInfo)) {
OperationInfo lroInfo = methodDescriptor.getOptions().getExtension(OperationsProto.operationInfo);
responseTypeName = lroInfo.getResponseType();
metadataTypeName = lroInfo.getMetadataType();
}
if (methodOptions.hasExtension(ExtendedOperationsProto.operationService)) {
// TODO: support full package name for operations_service annotation value
String opServiceName = methodOptions.getExtension(ExtendedOperationsProto.operationService);
operationServiceStubType = TypeNode.withReference(VaporReference.builder().setName(opServiceName + "Stub").setPakkage(servicePackage + ".stub").build());
if (responseTypeName == null) {
responseTypeName = methodDescriptor.getOutputType().getFullName();
}
if (metadataTypeName == null) {
metadataTypeName = methodDescriptor.getOutputType().getFullName();
}
}
if (responseTypeName == null || metadataTypeName == null) {
return null;
}
Message responseMessage = null;
Message metadataMessage = null;
int lastDotIndex = responseTypeName.lastIndexOf('.');
boolean isResponseTypeNameShortOnly = lastDotIndex < 0;
String responseTypeShortName = lastDotIndex >= 0 ? responseTypeName.substring(lastDotIndex + 1) : responseTypeName;
lastDotIndex = metadataTypeName.lastIndexOf('.');
boolean isMetadataTypeNameShortOnly = lastDotIndex < 0;
String metadataTypeShortName = lastDotIndex >= 0 ? metadataTypeName.substring(lastDotIndex + 1) : metadataTypeName;
// The messageTypes map keys to the Java fully-qualified name.
for (Map.Entry<String, Message> messageEntry : messageTypes.entrySet()) {
String messageKey = messageEntry.getKey();
int messageLastDotIndex = messageEntry.getKey().lastIndexOf('.');
String messageShortName = messageLastDotIndex >= 0 ? messageKey.substring(messageLastDotIndex + 1) : messageKey;
if (responseMessage == null) {
if (isResponseTypeNameShortOnly && responseTypeName.equals(messageShortName)) {
responseMessage = messageEntry.getValue();
} else if (!isResponseTypeNameShortOnly && responseTypeShortName.equals(messageShortName)) {
// Ensure that the full proto name matches.
Message candidateMessage = messageEntry.getValue();
if (candidateMessage.fullProtoName().equals(responseTypeName)) {
responseMessage = candidateMessage;
}
}
}
if (metadataMessage == null) {
if (isMetadataTypeNameShortOnly && metadataTypeName.equals(messageShortName)) {
metadataMessage = messageEntry.getValue();
} else if (!isMetadataTypeNameShortOnly && metadataTypeShortName.equals(messageShortName)) {
// Ensure that the full proto name matches.
Message candidateMessage = messageEntry.getValue();
if (candidateMessage.fullProtoName().equals(metadataTypeName)) {
metadataMessage = candidateMessage;
}
}
}
}
Preconditions.checkNotNull(responseMessage, String.format("LRO response message %s not found on method %s", responseTypeName, methodDescriptor.getName()));
// TODO(miraleung): Check that the packages are equal if those strings are not empty.
Preconditions.checkNotNull(metadataMessage, String.format("LRO metadata message %s not found in method %s", metadataTypeName, methodDescriptor.getName()));
return LongrunningOperation.builder().setResponseType(responseMessage.type()).setMetadataType(metadataMessage.type()).setOperationServiceStubType(operationServiceStubType).build();
}
use of com.google.api.generator.gapic.model.Message in project gapic-generator-java by googleapis.
the class Parser method parseServices.
public static List<Service> parseServices(CodeGeneratorRequest request, Map<String, Message> messageTypes, Map<String, ResourceName> resourceNames, Set<ResourceName> outputArgResourceNames, Optional<com.google.api.Service> serviceYamlProtoOpt, Optional<GapicServiceConfig> serviceConfigOpt, List<Service> outputMixinServices, Transport transport) {
Map<String, FileDescriptor> fileDescriptors = getFilesToGenerate(request);
List<Service> services = new ArrayList<>();
for (String fileToGenerate : request.getFileToGenerateList()) {
FileDescriptor fileDescriptor = Preconditions.checkNotNull(fileDescriptors.get(fileToGenerate), "Missing file descriptor for [%s]", fileToGenerate);
services.addAll(parseService(fileDescriptor, messageTypes, resourceNames, serviceYamlProtoOpt, serviceConfigOpt, outputArgResourceNames, transport));
}
// Prevent codegen for mixed-in services if there are other services present, since that is an
// indicator that we are not generating a GAPIC client for the mixed-in service on its own.
Function<Service, String> serviceFullNameFn = s -> String.format("%s.%s", s.protoPakkage(), s.name());
Set<Service> blockedCodegenMixinApis = new HashSet<>();
Set<Service> definedServices = new HashSet<>();
for (Service s : services) {
if (MIXIN_ALLOWLIST.contains(serviceFullNameFn.apply(s))) {
blockedCodegenMixinApis.add(s);
} else {
definedServices.add(s);
}
}
// It's very unlikely the blocklisted APIs will contain the other, or any other service.
boolean servicesContainBlocklistedApi = !blockedCodegenMixinApis.isEmpty() && !definedServices.isEmpty();
// Service names that are stated in the YAML file (as mixins). Used to filter
// blockedCodegenMixinApis.
Set<String> mixedInApis = !serviceYamlProtoOpt.isPresent() ? Collections.emptySet() : serviceYamlProtoOpt.get().getApisList().stream().filter(a -> MIXIN_ALLOWLIST.contains(a.getName())).map(a -> a.getName()).collect(Collectors.toSet());
// Holds the methods to be mixed in.
// Key: proto_package.ServiceName.RpcName.
// Value: HTTP rules, which clobber those in the proto.
// Assumes that http.rules.selector always specifies RPC names in the above format.
Map<String, HttpBindings> mixedInMethodsToHttpRules = new HashMap<>();
Map<String, String> mixedInMethodsToDocs = new HashMap<>();
// Parse HTTP rules and documentation, which will override the proto.
if (serviceYamlProtoOpt.isPresent()) {
for (HttpRule httpRule : serviceYamlProtoOpt.get().getHttp().getRulesList()) {
HttpBindings httpBindings = HttpRuleParser.parseHttpRule(httpRule);
if (httpBindings == null) {
continue;
}
for (String rpcFullNameRaw : httpRule.getSelector().split(",")) {
String rpcFullName = rpcFullNameRaw.trim();
mixedInMethodsToHttpRules.put(rpcFullName, httpBindings);
}
}
for (DocumentationRule docRule : serviceYamlProtoOpt.get().getDocumentation().getRulesList()) {
for (String rpcFullNameRaw : docRule.getSelector().split(",")) {
String rpcFullName = rpcFullNameRaw.trim();
mixedInMethodsToDocs.put(rpcFullName, docRule.getDescription());
}
}
}
// Sort potential mixin services alphabetically.
List<Service> orderedBlockedCodegenMixinApis = blockedCodegenMixinApis.stream().sorted((s1, s2) -> s2.name().compareTo(s1.name())).collect(Collectors.toList());
Set<String> apiDefinedRpcs = new HashSet<>();
for (Service service : services) {
if (orderedBlockedCodegenMixinApis.contains(service)) {
continue;
}
apiDefinedRpcs.addAll(service.methods().stream().map(m -> m.name()).collect(Collectors.toSet()));
}
// Mix-in APIs only if the protos are present and they're defined in the service.yaml file.
Set<Service> outputMixinServiceSet = new HashSet<>();
if (servicesContainBlocklistedApi && !mixedInApis.isEmpty()) {
for (int i = 0; i < services.size(); i++) {
Service originalService = services.get(i);
List<Method> updatedOriginalServiceMethods = new ArrayList<>(originalService.methods());
// If mixin APIs are present, add the methods to all other services.
for (Service mixinService : orderedBlockedCodegenMixinApis) {
final String mixinServiceFullName = serviceFullNameFn.apply(mixinService);
if (!mixedInApis.contains(mixinServiceFullName)) {
continue;
}
Function<Method, String> methodToFullProtoNameFn = m -> String.format("%s.%s", mixinServiceFullName, m.name());
// Filter mixed-in methods based on those listed in the HTTP rules section of
// service.yaml.
List<Method> updatedMixinMethods = mixinService.methods().stream().filter(m -> mixedInMethodsToHttpRules.containsKey(methodToFullProtoNameFn.apply(m))).map(m -> {
// HTTP rules and RPC documentation in the service.yaml file take
// precedence.
String fullMethodName = methodToFullProtoNameFn.apply(m);
HttpBindings httpBindings = mixedInMethodsToHttpRules.containsKey(fullMethodName) ? mixedInMethodsToHttpRules.get(fullMethodName) : m.httpBindings();
String docs = mixedInMethodsToDocs.containsKey(fullMethodName) ? mixedInMethodsToDocs.get(fullMethodName) : m.description();
return m.toBuilder().setHttpBindings(httpBindings).setDescription(docs).build();
}).collect(Collectors.toList());
// Overridden RPCs defined in the protos take precedence.
updatedMixinMethods.stream().filter(m -> !apiDefinedRpcs.contains(m.name())).forEach(m -> updatedOriginalServiceMethods.add(m.toBuilder().setMixedInApiName(serviceFullNameFn.apply(mixinService)).build()));
// Sort by method name, to ensure a deterministic method ordering (for tests).
updatedMixinMethods = updatedMixinMethods.stream().sorted((m1, m2) -> m2.name().compareTo(m1.name())).collect(Collectors.toList());
outputMixinServiceSet.add(mixinService.toBuilder().setMethods(updatedMixinMethods).build());
}
services.set(i, originalService.toBuilder().setMethods(updatedOriginalServiceMethods).build());
}
}
if (servicesContainBlocklistedApi) {
services = services.stream().filter(s -> !MIXIN_ALLOWLIST.contains(serviceFullNameFn.apply(s))).collect(Collectors.toList());
}
// Use a list to ensure ordering for deterministic tests.
outputMixinServices.addAll(outputMixinServiceSet.stream().sorted((s1, s2) -> s2.name().compareTo(s1.name())).collect(Collectors.toList()));
return services;
}
Aggregations