1 // Copyright 2020 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 package com.google.api.generator.gapic.protoparser;
16 
17 import com.google.api.generator.gapic.model.GapicBatchingSettings;
18 import com.google.common.annotations.VisibleForTesting;
19 import com.google.common.base.Preconditions;
20 import com.google.common.base.Strings;
21 import java.io.File;
22 import java.io.IOException;
23 import java.nio.charset.StandardCharsets;
24 import java.nio.file.Files;
25 import java.nio.file.Paths;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Optional;
30 import org.yaml.snakeyaml.LoaderOptions;
31 import org.yaml.snakeyaml.Yaml;
32 import org.yaml.snakeyaml.constructor.SafeConstructor;
33 
34 public class BatchingSettingsConfigParser {
35   private static String LIMIT_EXCEEDED_BEHAVIOR_THROW_EXCEPTION_YAML_VALUE = "THROW_EXCEPTION";
36   private static String LIMIT_EXCEEDED_BEHAVIOR_BLOCK_YAML_VALUE = "BLOCK";
37 
38   private static String YAML_KEY_INTERFACES = "interfaces";
39   private static String YAML_KEY_NAME = "name";
40   private static String YAML_KEY_METHODS = "methods";
41   private static String YAML_KEY_BATCHING = "batching";
42   private static String YAML_KEY_THRESHOLDS = "thresholds";
43   private static String YAML_KEY_DESCRIPTOR = "batch_descriptor";
44 
45   private static String YAML_KEY_BATCHING_ELEMENT_COUNT_THRESHOLD = "element_count_threshold";
46   private static String YAML_KEY_BATCHING_DELAY_THRESHOLD_MILLIS = "delay_threshold_millis";
47   private static String YAML_KEY_BATCHING_REQUEST_BYTE_THRESHOLD = "request_byte_threshold";
48   private static String YAML_KEY_BATCHING_FLOW_CONTROL_ELEMENT_LIMIT = "flow_control_element_limit";
49   private static String YAML_KEY_BATCHING_FLOW_CONTROL_BYTE_LIMIT = "flow_control_byte_limit";
50   private static String YAML_KEY_BATCHING_FLOW_CONTROL_LIMIT_EXCEEDED_BEHAVIOR =
51       "flow_control_limit_exceeded_behavior";
52 
53   private static String YAML_KEY_DESCRIPTOR_BATCHED_FIELD = "batched_field";
54   private static String YAML_KEY_DESCRIPTOR_DISCRIMINATOR_FIELD = "discriminator_fields";
55   private static String YAML_KEY_DESCRIPTOR_SUBRESPONSE_FIELD = "subresponse_field";
56 
parse( Optional<String> gapicYamlConfigFilePathOpt)57   public static Optional<List<GapicBatchingSettings>> parse(
58       Optional<String> gapicYamlConfigFilePathOpt) {
59     return gapicYamlConfigFilePathOpt.isPresent()
60         ? parse(gapicYamlConfigFilePathOpt.get())
61         : Optional.empty();
62   }
63 
64   @VisibleForTesting
parse(String gapicYamlConfigFilePath)65   static Optional<List<GapicBatchingSettings>> parse(String gapicYamlConfigFilePath) {
66     if (Strings.isNullOrEmpty(gapicYamlConfigFilePath)
67         || !(new File(gapicYamlConfigFilePath)).exists()) {
68       return Optional.empty();
69     }
70 
71     String fileContents = null;
72 
73     try {
74       fileContents =
75           new String(
76               Files.readAllBytes(Paths.get(gapicYamlConfigFilePath)), StandardCharsets.UTF_8);
77     } catch (IOException e) {
78       return Optional.empty();
79     }
80 
81     Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()));
82     Map<String, Object> yamlMap = yaml.load(fileContents);
83     return parseFromMap(yamlMap);
84   }
85 
parseFromMap(Map<String, Object> yamlMap)86   private static Optional<List<GapicBatchingSettings>> parseFromMap(Map<String, Object> yamlMap) {
87     if (!yamlMap.containsKey(YAML_KEY_INTERFACES)) {
88       return Optional.empty();
89     }
90 
91     List<GapicBatchingSettings> settings = new ArrayList<>();
92     for (Map<String, Object> serviceYamlConfig :
93         (List<Map<String, Object>>) yamlMap.get(YAML_KEY_INTERFACES)) {
94       if (!serviceYamlConfig.containsKey(YAML_KEY_METHODS)) {
95         continue;
96       }
97       for (Map<String, Object> methodYamlConfig :
98           (List<Map<String, Object>>) serviceYamlConfig.get(YAML_KEY_METHODS)) {
99         if (!methodYamlConfig.containsKey(YAML_KEY_BATCHING)) {
100           continue;
101         }
102         Map<String, Object> batchingOuterYamlConfig =
103             (Map<String, Object>) methodYamlConfig.get(YAML_KEY_BATCHING);
104         if (!batchingOuterYamlConfig.containsKey(YAML_KEY_THRESHOLDS)) {
105           continue;
106         }
107         Preconditions.checkState(
108             batchingOuterYamlConfig.containsKey(YAML_KEY_DESCRIPTOR),
109             String.format(
110                 "%s key expected but not found for method %s",
111                 YAML_KEY_DESCRIPTOR, methodYamlConfig.get(YAML_KEY_NAME)));
112 
113         // Parse the threshold values first.
114         Map<String, Object> batchingYamlConfig =
115             (Map<String, Object>) batchingOuterYamlConfig.get(YAML_KEY_THRESHOLDS);
116         Preconditions.checkState(
117             batchingYamlConfig.containsKey(YAML_KEY_BATCHING_ELEMENT_COUNT_THRESHOLD)
118                 && batchingYamlConfig.containsKey(YAML_KEY_BATCHING_REQUEST_BYTE_THRESHOLD)
119                 && batchingYamlConfig.containsKey(YAML_KEY_BATCHING_DELAY_THRESHOLD_MILLIS),
120             String.format(
121                 "Batching YAML config is missing one of %s, %s, or %s fields",
122                 YAML_KEY_BATCHING_ELEMENT_COUNT_THRESHOLD,
123                 YAML_KEY_BATCHING_REQUEST_BYTE_THRESHOLD,
124                 YAML_KEY_BATCHING_DELAY_THRESHOLD_MILLIS));
125 
126         String interfaceName = (String) serviceYamlConfig.get(YAML_KEY_NAME);
127         int lastDotIndex = interfaceName.lastIndexOf(".");
128         String protoPakkage = interfaceName.substring(0, lastDotIndex);
129         String serviceName = interfaceName.substring(lastDotIndex + 1);
130         String methodName = (String) methodYamlConfig.get(YAML_KEY_NAME);
131         GapicBatchingSettings.Builder settingsBuilder =
132             GapicBatchingSettings.builder()
133                 .setProtoPakkage(protoPakkage)
134                 .setServiceName(serviceName)
135                 .setMethodName(methodName)
136                 .setElementCountThreshold(
137                     (Integer) batchingYamlConfig.get(YAML_KEY_BATCHING_ELEMENT_COUNT_THRESHOLD))
138                 .setRequestByteThreshold(
139                     (Integer) batchingYamlConfig.get(YAML_KEY_BATCHING_REQUEST_BYTE_THRESHOLD))
140                 .setDelayThresholdMillis(
141                     (Integer) batchingYamlConfig.get(YAML_KEY_BATCHING_DELAY_THRESHOLD_MILLIS));
142 
143         if (batchingYamlConfig.containsKey(YAML_KEY_BATCHING_FLOW_CONTROL_ELEMENT_LIMIT)) {
144           settingsBuilder.setFlowControlElementLimit(
145               (Integer) batchingYamlConfig.get(YAML_KEY_BATCHING_FLOW_CONTROL_ELEMENT_LIMIT));
146         }
147         if (batchingYamlConfig.containsKey(YAML_KEY_BATCHING_FLOW_CONTROL_BYTE_LIMIT)) {
148           settingsBuilder.setFlowControlByteLimit(
149               (Integer) batchingYamlConfig.get(YAML_KEY_BATCHING_FLOW_CONTROL_BYTE_LIMIT));
150         }
151         if (batchingYamlConfig.containsKey(
152             YAML_KEY_BATCHING_FLOW_CONTROL_LIMIT_EXCEEDED_BEHAVIOR)) {
153           String behaviorYamlValue =
154               (String)
155                   batchingYamlConfig.get(YAML_KEY_BATCHING_FLOW_CONTROL_LIMIT_EXCEEDED_BEHAVIOR);
156           GapicBatchingSettings.FlowControlLimitExceededBehavior behaviorSetting =
157               GapicBatchingSettings.FlowControlLimitExceededBehavior.IGNORE;
158           // IGNORE or UNSET_BEHAVIOR YAML values map to FlowControlLimitExceededBehavior.IGNOER.
159           if (behaviorYamlValue.equals(LIMIT_EXCEEDED_BEHAVIOR_THROW_EXCEPTION_YAML_VALUE)) {
160             behaviorSetting =
161                 GapicBatchingSettings.FlowControlLimitExceededBehavior.THROW_EXCEPTION;
162           } else if (behaviorYamlValue.equals(LIMIT_EXCEEDED_BEHAVIOR_BLOCK_YAML_VALUE)) {
163             behaviorSetting = GapicBatchingSettings.FlowControlLimitExceededBehavior.BLOCK;
164           }
165           settingsBuilder.setFlowControlLimitExceededBehavior(behaviorSetting);
166         }
167 
168         // Parse the descriptor values.
169         Map<String, Object> descriptorYamlConfig =
170             (Map<String, Object>) batchingOuterYamlConfig.get(YAML_KEY_DESCRIPTOR);
171         Preconditions.checkState(
172             descriptorYamlConfig.containsKey(YAML_KEY_DESCRIPTOR_BATCHED_FIELD)
173                 && descriptorYamlConfig.containsKey(YAML_KEY_DESCRIPTOR_DISCRIMINATOR_FIELD),
174             String.format(
175                 "Batching descriptor YAML config is missing one of %s or %s fields",
176                 YAML_KEY_DESCRIPTOR_BATCHED_FIELD, YAML_KEY_DESCRIPTOR_DISCRIMINATOR_FIELD));
177 
178         settingsBuilder.setBatchedFieldName(
179             (String) descriptorYamlConfig.get(YAML_KEY_DESCRIPTOR_BATCHED_FIELD));
180         settingsBuilder.setDiscriminatorFieldNames(
181             (List<String>) descriptorYamlConfig.get(YAML_KEY_DESCRIPTOR_DISCRIMINATOR_FIELD));
182 
183         if (descriptorYamlConfig.containsKey(YAML_KEY_DESCRIPTOR_SUBRESPONSE_FIELD)) {
184           settingsBuilder.setSubresponseFieldName(
185               (String) descriptorYamlConfig.get(YAML_KEY_DESCRIPTOR_SUBRESPONSE_FIELD));
186         }
187 
188         settings.add(settingsBuilder.build());
189       }
190     }
191 
192     return settings.isEmpty() ? Optional.empty() : Optional.of(settings);
193   }
194 }
195