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.model; 16 17 import com.google.protobuf.Duration; 18 import com.google.rpc.Code; 19 import io.grpc.serviceconfig.MethodConfig; 20 import io.grpc.serviceconfig.MethodConfig.RetryPolicy; 21 import io.grpc.serviceconfig.ServiceConfig; 22 import java.util.Collections; 23 import java.util.HashMap; 24 import java.util.LinkedHashMap; 25 import java.util.List; 26 import java.util.Map; 27 import java.util.Optional; 28 import java.util.stream.Collectors; 29 30 public class GapicServiceConfig { 31 public static final RetryPolicy EMPTY_RETRY_POLICY = RetryPolicy.newBuilder().build(); 32 public static final Duration EMPTY_TIMEOUT = Duration.newBuilder().setSeconds(5).build(); 33 public static final List<Code> EMPTY_RETRY_CODES = Collections.emptyList(); 34 35 private static final String NO_RETRY_CODES_NAME = "no_retry_codes"; 36 private static final String NO_RETRY_PARAMS_NAME = "no_retry_params"; 37 38 private static final String NO_RETRY_CODES_PATTERN = "no_retry_%d_codes"; 39 private static final String NO_RETRY_PARAMS_PATTERN = "no_retry_%d_params"; 40 private static final String RETRY_CODES_PATTERN = "retry_policy_%d_codes"; 41 private static final String RETRY_PARAMS_PATTERN = "retry_policy_%d_params"; 42 43 private final List<MethodConfig> methodConfigs; 44 private final Map<MethodConfig.Name, Integer> methodConfigTable; 45 private final Map<MethodConfig.Name, GapicLroRetrySettings> lroRetrySettingsTable = 46 new HashMap<>(); 47 private final Map<MethodConfig.Name, GapicBatchingSettings> batchingSettingsTable = 48 new HashMap<>(); 49 50 private Optional<GapicLanguageSettings> languageSettingsOpt = Optional.empty(); 51 GapicServiceConfig( List<MethodConfig> methodConfigs, Map<MethodConfig.Name, Integer> methodConfigTable)52 private GapicServiceConfig( 53 List<MethodConfig> methodConfigs, Map<MethodConfig.Name, Integer> methodConfigTable) { 54 this.methodConfigs = methodConfigs; 55 this.methodConfigTable = methodConfigTable; 56 } 57 create(Optional<ServiceConfig> serviceConfigOpt)58 public static GapicServiceConfig create(Optional<ServiceConfig> serviceConfigOpt) { 59 if (!serviceConfigOpt.isPresent()) { 60 return new GapicServiceConfig(Collections.emptyList(), Collections.emptyMap()); 61 } 62 63 ServiceConfig serviceConfig = serviceConfigOpt.get(); 64 Map<MethodConfig.Name, Integer> methodConfigTable = new HashMap<>(); 65 List<MethodConfig> methodConfigs = serviceConfig.getMethodConfigList(); 66 for (int i = 0; i < methodConfigs.size(); i++) { 67 MethodConfig methodConfig = methodConfigs.get(i); 68 for (MethodConfig.Name name : methodConfig.getNameList()) { 69 methodConfigTable.put(name, i); 70 } 71 } 72 73 return new GapicServiceConfig(methodConfigs, methodConfigTable); 74 } 75 setLroRetrySettings(Optional<List<GapicLroRetrySettings>> lroRetrySettingsOpt)76 public void setLroRetrySettings(Optional<List<GapicLroRetrySettings>> lroRetrySettingsOpt) { 77 if (!lroRetrySettingsOpt.isPresent()) { 78 return; 79 } 80 lroRetrySettingsTable.clear(); 81 for (GapicLroRetrySettings lroRetrySetting : lroRetrySettingsOpt.get()) { 82 lroRetrySettingsTable.put( 83 MethodConfig.Name.newBuilder() 84 .setService( 85 String.format( 86 "%s.%s", lroRetrySetting.protoPakkage(), lroRetrySetting.serviceName())) 87 .setMethod(lroRetrySetting.methodName()) 88 .build(), 89 lroRetrySetting); 90 } 91 } 92 setBatchingSettings(Optional<List<GapicBatchingSettings>> batchingSettingsOpt)93 public void setBatchingSettings(Optional<List<GapicBatchingSettings>> batchingSettingsOpt) { 94 if (!batchingSettingsOpt.isPresent()) { 95 return; 96 } 97 batchingSettingsTable.clear(); 98 for (GapicBatchingSettings batchingSetting : batchingSettingsOpt.get()) { 99 batchingSettingsTable.put( 100 MethodConfig.Name.newBuilder() 101 .setService( 102 String.format( 103 "%s.%s", batchingSetting.protoPakkage(), batchingSetting.serviceName())) 104 .setMethod(batchingSetting.methodName()) 105 .build(), 106 batchingSetting); 107 } 108 } 109 setLanguageSettings(Optional<GapicLanguageSettings> languageSettingsOpt)110 public void setLanguageSettings(Optional<GapicLanguageSettings> languageSettingsOpt) { 111 this.languageSettingsOpt = languageSettingsOpt; 112 } 113 getLanguageSettingsOpt()114 public Optional<GapicLanguageSettings> getLanguageSettingsOpt() { 115 return languageSettingsOpt; 116 } 117 getAllGapicRetrySettings(Service service)118 public Map<String, GapicRetrySettings> getAllGapicRetrySettings(Service service) { 119 return service.methods().stream() 120 .collect( 121 Collectors.toMap( 122 m -> getRetryParamsName(service, m), 123 m -> toGapicRetrySettings(service, m), 124 (r1, r2) -> r2, 125 LinkedHashMap::new)); 126 } 127 getAllRetryCodes(Service service)128 public Map<String, List<Code>> getAllRetryCodes(Service service) { 129 return service.methods().stream() 130 .collect( 131 Collectors.toMap( 132 m -> getRetryCodeName(service, m), 133 m -> retryCodesLookup(service, m), 134 (l1, l2) -> l2, 135 LinkedHashMap::new)); 136 } 137 getRetryCodeName(Service service, Method method)138 public String getRetryCodeName(Service service, Method method) { 139 Optional<Integer> retryPolicyIndexOpt = retryPolicyIndexLookup(service, method); 140 if (retryPolicyIndexOpt.isPresent()) { 141 return getRetryCodeName(retryPolicyIndexOpt.get()); 142 } 143 return NO_RETRY_CODES_NAME; 144 } 145 getRetryParamsName(Service service, Method method)146 public String getRetryParamsName(Service service, Method method) { 147 Optional<Integer> retryPolicyIndexOpt = retryPolicyIndexLookup(service, method); 148 if (retryPolicyIndexOpt.isPresent()) { 149 return getRetryParamsName(retryPolicyIndexOpt.get()); 150 } 151 return NO_RETRY_PARAMS_NAME; 152 } 153 hasLroRetrySetting(Service service, Method method)154 public boolean hasLroRetrySetting(Service service, Method method) { 155 return lroRetrySettingsTable.containsKey(toName(service, method)); 156 } 157 hasBatchingSetting(Service service, Method method)158 public boolean hasBatchingSetting(Service service, Method method) { 159 return batchingSettingsTable.containsKey(toName(service, method)); 160 } 161 hasBatchingSetting(String protoPakkage, String serviceName, String methodName)162 public boolean hasBatchingSetting(String protoPakkage, String serviceName, String methodName) { 163 return batchingSettingsTable.containsKey( 164 MethodConfig.Name.newBuilder() 165 .setService(String.format("%s.%s", protoPakkage, serviceName)) 166 .setMethod(methodName) 167 .build()); 168 } 169 getLroRetrySetting(Service service, Method method)170 public Optional<GapicLroRetrySettings> getLroRetrySetting(Service service, Method method) { 171 return hasLroRetrySetting(service, method) 172 ? Optional.of(lroRetrySettingsTable.get(toName(service, method))) 173 : Optional.empty(); 174 } 175 getBatchingSetting(Service service, Method method)176 public Optional<GapicBatchingSettings> getBatchingSetting(Service service, Method method) { 177 return hasBatchingSetting(service, method) 178 ? Optional.of(batchingSettingsTable.get(toName(service, method))) 179 : Optional.empty(); 180 } 181 toGapicRetrySettings(Service service, Method method)182 private GapicRetrySettings toGapicRetrySettings(Service service, Method method) { 183 GapicRetrySettings.Kind kind = GapicRetrySettings.Kind.FULL; 184 Optional<Integer> retryPolicyIndexOpt = retryPolicyIndexLookup(service, method); 185 if (!retryPolicyIndexOpt.isPresent()) { 186 kind = GapicRetrySettings.Kind.NONE; 187 } else { 188 MethodConfig methodConfig = methodConfigs.get(retryPolicyIndexOpt.get()); 189 if (!methodConfig.hasTimeout() && !methodConfig.hasRetryPolicy()) { 190 kind = GapicRetrySettings.Kind.NONE; 191 } else { 192 kind = 193 methodConfig.hasRetryPolicy() 194 ? GapicRetrySettings.Kind.FULL 195 : GapicRetrySettings.Kind.NO_RETRY; 196 } 197 } 198 199 return GapicRetrySettings.builder() 200 .setTimeout(timeoutLookup(service, method)) 201 .setRetryPolicy(retryPolicyLookup(service, method)) 202 .setKind(kind) 203 .build(); 204 } 205 retryCodesLookup(Service service, Method method)206 private List<Code> retryCodesLookup(Service service, Method method) { 207 RetryPolicy retryPolicy = retryPolicyLookup(service, method); 208 if (retryPolicy.equals(EMPTY_RETRY_POLICY)) { 209 return Collections.emptyList(); 210 } 211 return retryPolicy.getRetryableStatusCodesList(); 212 } 213 retryPolicyLookup(Service service, Method method)214 private RetryPolicy retryPolicyLookup(Service service, Method method) { 215 Optional<Integer> retryPolicyIndexOpt = retryPolicyIndexLookup(service, method); 216 if (retryPolicyIndexOpt.isPresent()) { 217 MethodConfig methodConfig = methodConfigs.get(retryPolicyIndexOpt.get()); 218 return methodConfig.hasRetryPolicy() ? methodConfig.getRetryPolicy() : EMPTY_RETRY_POLICY; 219 } 220 return EMPTY_RETRY_POLICY; 221 } 222 timeoutLookup(Service service, Method method)223 private Duration timeoutLookup(Service service, Method method) { 224 Optional<Integer> retryPolicyIndexOpt = retryPolicyIndexLookup(service, method); 225 if (retryPolicyIndexOpt.isPresent()) { 226 MethodConfig methodConfig = methodConfigs.get(retryPolicyIndexOpt.get()); 227 return methodConfig.hasTimeout() ? methodConfig.getTimeout() : EMPTY_TIMEOUT; 228 } 229 return EMPTY_TIMEOUT; 230 } 231 retryPolicyIndexLookup(Service service, Method method)232 private Optional<Integer> retryPolicyIndexLookup(Service service, Method method) { 233 MethodConfig.Name serviceMethodName = toName(service, method); 234 if (methodConfigTable.containsKey(serviceMethodName)) { 235 return Optional.of(methodConfigTable.get(serviceMethodName)); 236 } 237 MethodConfig.Name serviceName = toName(service); 238 if (methodConfigTable.containsKey(serviceName)) { 239 return Optional.of(methodConfigTable.get(serviceName)); 240 } 241 return Optional.empty(); 242 } 243 244 /** 245 * @param codeIndex The index of the retryable code in the method config list. 246 * @return The canonical name to be used in the generated client library. 247 */ getRetryCodeName(int methodConfigIndex)248 private String getRetryCodeName(int methodConfigIndex) { 249 MethodConfig methodConfig = methodConfigs.get(methodConfigIndex); 250 if (!methodConfig.hasTimeout() && !methodConfig.hasRetryPolicy()) { 251 return NO_RETRY_CODES_NAME; 252 } 253 return String.format( 254 methodConfig.hasRetryPolicy() ? RETRY_CODES_PATTERN : NO_RETRY_CODES_PATTERN, 255 methodConfigIndex); 256 } 257 258 /** 259 * @param retryParamsIndex The index of the retry params in the method config list. 260 * @return The canonical name to be used in the generated client library. 261 */ getRetryParamsName(int methodConfigIndex)262 private String getRetryParamsName(int methodConfigIndex) { 263 MethodConfig methodConfig = methodConfigs.get(methodConfigIndex); 264 if (!methodConfig.hasTimeout() && !methodConfig.hasRetryPolicy()) { 265 return NO_RETRY_PARAMS_NAME; 266 } 267 return String.format( 268 methodConfig.hasRetryPolicy() ? RETRY_PARAMS_PATTERN : NO_RETRY_PARAMS_PATTERN, 269 methodConfigIndex); 270 } 271 toName(Service service)272 private MethodConfig.Name toName(Service service) { 273 return MethodConfig.Name.newBuilder().setService(serviceToNameString(service)).build(); 274 } 275 toName(Service service, Method method)276 private MethodConfig.Name toName(Service service, Method method) { 277 return MethodConfig.Name.newBuilder() 278 .setService(serviceToNameString(service)) 279 .setMethod(method.name()) 280 .build(); 281 } 282 serviceToNameString(Service service)283 private String serviceToNameString(Service service) { 284 return String.format("%s.%s", service.protoPakkage(), service.name()); 285 } 286 } 287