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