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