1 /*
2  * Copyright 2010-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */

15 package com.amazonaws.internal.config;
16
17 import java.io.IOException;
18 import java.net.URL;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.LinkedList;
22 import java.util.List;
23 import java.util.Map;
24
25 import com.amazonaws.annotation.Immutable;
26 import com.amazonaws.log.InternalLogApi;
27 import com.amazonaws.log.InternalLogFactory;
28 import com.amazonaws.util.ClassLoaderHelper;
29 import com.fasterxml.jackson.core.JsonParseException;
30 import com.fasterxml.jackson.core.JsonParser;
31 import com.fasterxml.jackson.databind.DeserializationFeature;
32 import com.fasterxml.jackson.databind.JsonMappingException;
33 import com.fasterxml.jackson.databind.MapperFeature;
34 import com.fasterxml.jackson.databind.ObjectMapper;
35
36 /**
37  * Internal configuration for the AWS Java SDK.
38  */

39 @Immutable
40 public class InternalConfig {
41
42     //@formatter:off
43     private static final ObjectMapper MAPPER = new ObjectMapper()
44             .disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)
45             .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
46             .configure(JsonParser.Feature.ALLOW_COMMENTS, true);
47     //@formatter:on
48
49     private static final InternalLogApi log = InternalLogFactory.getLog(InternalConfig.class);
50
51     static final String DEFAULT_CONFIG_RESOURCE_RELATIVE_PATH = "awssdk_config_default.json";
52     static final String DEFAULT_CONFIG_RESOURCE_ABSOLUTE_PATH = "/com/amazonaws/internal/config/"
53             + DEFAULT_CONFIG_RESOURCE_RELATIVE_PATH;
54
55     static final String CONFIG_OVERRIDE_RESOURCE = "awssdk_config_override.json";
56
57     static final String ENDPOINT_DISCOVERY_CONFIG_ABSOLUTE_PATH =
58             "/com/amazonaws/endpointdiscovery/endpoint-discovery.json";
59
60     private static final String SERVICE_REGION_DELIMITOR = "/";
61
62     private final SignerConfig defaultSignerConfig;
63     private final Map<String, SignerConfig> serviceRegionSigners;
64     private final Map<String, SignerConfig> regionSigners;
65     private final Map<String, SignerConfig> serviceSigners;
66     private final Map<String, HttpClientConfig> httpClients;
67
68     private final List<HostRegexToRegionMapping> hostRegexToRegionMappings;
69
70     private final String userAgentTemplate;
71
72     private final boolean endpointDiscoveryEnabled;
73
74     /**
75      * @param defaults
76      *            default configuration
77      * @param override
78      *            override configuration
79      */

80     InternalConfig(InternalConfigJsonHelper defaults,
81                    InternalConfigJsonHelper override,
82                    EndpointDiscoveryConfig endpointDiscoveryConfig) {
83         SignerConfigJsonHelper scb = defaults.getDefaultSigner();
84         this.defaultSignerConfig = scb == null ? null : scb.build();
85
86         regionSigners = mergeSignerMap(defaults.getRegionSigners(), override.getRegionSigners(), "region");
87         serviceSigners = mergeSignerMap(defaults.getServiceSigners(), override.getServiceSigners(), "service");
88         serviceRegionSigners = mergeSignerMap(defaults.getServiceRegionSigners(), override.getServiceRegionSigners(),
89                 "service" + SERVICE_REGION_DELIMITOR + "region");
90         httpClients = merge(defaults.getHttpClients(), override.getHttpClients());
91
92         hostRegexToRegionMappings = append(override.getHostRegexToRegionMappings(),
93                 defaults.getHostRegexToRegionMappings());
94
95         if (override.getUserAgentTemplate() != null) {
96             userAgentTemplate = override.getUserAgentTemplate();
97         } else {
98             userAgentTemplate = defaults.getUserAgentTemplate();
99         }
100
101         endpointDiscoveryEnabled = endpointDiscoveryConfig.isEndpointDiscoveryEnabled();
102     }
103
104     /**
105      * Returns an immutable map by merging the override signer configuration into the default signer
106      * configuration for the given theme.
107      *
108      * @param defaults
109      *            default signer configuration
110      * @param override
111      *            signer configurations overrides
112      * @param theme
113      *            used for message logging. eg region, service, region+service
114      */

115     private Map<String, SignerConfig> mergeSignerMap(JsonIndex<SignerConfigJsonHelper, SignerConfig>[] defaults,
116                                                      JsonIndex<SignerConfigJsonHelper, SignerConfig>[] overrides,
117                                                      String theme) {
118         Map<String, SignerConfig> map = buildSignerMap(defaults, theme);
119         Map<String, SignerConfig> mapOverride = buildSignerMap(overrides, theme);
120         map.putAll(mapOverride);
121         return Collections.unmodifiableMap(map);
122     }
123
124     private <C extends Builder<T>, T> Map<String, T> merge(JsonIndex<C, T>[] defaults, JsonIndex<C, T>[] overrides) {
125         Map<String, T> map = buildMap(defaults);
126         Map<String, T> mapOverride = buildMap(overrides);
127         map.putAll(mapOverride);
128         return Collections.unmodifiableMap(map);
129     }
130
131     private <C extends Builder<T>, T> Map<String, T> buildMap(JsonIndex<C, T>[] signerIndexes) {
132         Map<String, T> map = new HashMap<String, T>();
133         if (signerIndexes != null) {
134             for (JsonIndex<C, T> index : signerIndexes) {
135                 String region = index.getKey();
136                 T prev = map.put(region, index.newReadOnlyConfig());
137                 if (prev != null) {
138                     log.warn("Duplicate definition of signer for " + index.getKey());
139                 }
140             }
141         }
142         return map;
143     }
144
145     private <C extends Builder<T>, T> List<T> append(C[] defaults, C[] overrides) {
146         List<T> list = new LinkedList<T>();
147         if (defaults != null) {
148             for (C builder : defaults) {
149                 list.add(builder.build());
150             }
151         }
152         if (overrides != null) {
153             for (C builder : overrides) {
154                 list.add(builder.build());
155             }
156         }
157         return list;
158     }
159
160     /**
161      * Builds and returns a signer configuration map.
162      *
163      * @param signerIndexes
164      *            signer configuration entries loaded from JSON
165      * @param theme
166      *            used for message logging. eg region, service, region+service
167      */

168     private Map<String, SignerConfig> buildSignerMap(JsonIndex<SignerConfigJsonHelper, SignerConfig>[] signerIndexes,
169                                                      String theme) {
170         Map<String, SignerConfig> map = new HashMap<String, SignerConfig>();
171         if (signerIndexes != null) {
172             for (JsonIndex<SignerConfigJsonHelper, SignerConfig> index : signerIndexes) {
173                 String region = index.getKey();
174                 SignerConfig prev = map.put(region, index.newReadOnlyConfig());
175                 if (prev != null) {
176                     log.warn("Duplicate definition of signer for " + theme + " " + index.getKey());
177                 }
178             }
179         }
180         return map;
181     }
182
183     /**
184      * Returns the signer configuration for the specified service, not specific to any region.
185      */

186     public SignerConfig getSignerConfig(String serviceName) {
187         return getSignerConfig(serviceName, null);
188     }
189
190     /**
191      * Returns the http client configuration for the http client name.
192      */

193     public HttpClientConfig getHttpClientConfig(String httpClientName) {
194         return httpClients.get(httpClientName);
195     }
196
197     /**
198      * Returns the signer configuration for the specified service name and an optional region name.
199      * 
200      * @param serviceName
201      *            must not be null
202      * @param regionName
203      *            similar to the region name in <code>Regions</code>; can be null.
204      * @return the signer
205      */

206     public SignerConfig getSignerConfig(String serviceName, String regionName) {
207         if (serviceName == null)
208             throw new IllegalArgumentException();
209         SignerConfig signerConfig = null;
210         if (regionName != null) {
211             // Service+Region signer config has the highest precedence
212             String key = serviceName + SERVICE_REGION_DELIMITOR + regionName;
213             signerConfig = serviceRegionSigners.get(key);
214             if (signerConfig != null) {
215                 return signerConfig;
216             }
217             // Region signer config has the 2nd highest precedence
218             signerConfig = regionSigners.get(regionName);
219             if (signerConfig != null) {
220                 return signerConfig;
221             }
222         }
223         // Service signer config has the 3rd highest precedence
224         signerConfig = serviceSigners.get(serviceName);
225         // Fall back to the default
226         return signerConfig == null ? defaultSignerConfig : signerConfig;
227     }
228
229     /**
230      * @return all the host-name-regex to region-name mappings.
231      */

232     public List<HostRegexToRegionMapping> getHostRegexToRegionMappings() {
233         return Collections.unmodifiableList(hostRegexToRegionMappings);
234     }
235
236     /**
237      * @return the custom user agent template, if configured
238      */

239     public String getUserAgentTemplate() {
240         return userAgentTemplate;
241     }
242
243     public boolean endpointDiscoveryEnabled() {
244         return endpointDiscoveryEnabled;
245     }
246
247     static <T> T loadfrom(URL url, Class<T> clazz) throws JsonParseException, JsonMappingException, IOException {
248         if (url == null)
249             throw new IllegalArgumentException();
250         T target = MAPPER.readValue(url, clazz);
251         return target;
252     }
253
254     /**
255      * Loads and returns the AWS Java SDK internal configuration from the classpath.
256      */

257     static InternalConfig load() throws JsonParseException, JsonMappingException, IOException {
258         URL configUrl = getResource(DEFAULT_CONFIG_RESOURCE_RELATIVE_PATH, truefalse);
259         if (configUrl == null) {
260             configUrl = getResource(DEFAULT_CONFIG_RESOURCE_ABSOLUTE_PATH, falsefalse);
261         }
262
263         InternalConfigJsonHelper config = loadfrom(configUrl, InternalConfigJsonHelper.class);
264         InternalConfigJsonHelper configOverride;
265
266         URL overrideUrl = getResource(CONFIG_OVERRIDE_RESOURCE, falsetrue);
267         if (overrideUrl == null) {
268             overrideUrl = getResource(CONFIG_OVERRIDE_RESOURCE, falsefalse);
269         }
270         if (overrideUrl == null) {
271             log.debug("Configuration override " + CONFIG_OVERRIDE_RESOURCE + " not found.");
272             configOverride = new InternalConfigJsonHelper();
273         } else {
274             configOverride = loadfrom(overrideUrl, InternalConfigJsonHelper.class);
275         }
276
277         EndpointDiscoveryConfig endpointDiscoveryConfig = new EndpointDiscoveryConfig();
278
279         URL endpointDiscoveryConfigUrl = getResource(ENDPOINT_DISCOVERY_CONFIG_ABSOLUTE_PATH, falsefalse);
280
281         if (endpointDiscoveryConfigUrl != null) {
282             endpointDiscoveryConfig = loadfrom(endpointDiscoveryConfigUrl, EndpointDiscoveryConfig.class);
283         }
284
285         InternalConfig merged = new InternalConfig(config, configOverride, endpointDiscoveryConfig);
286         merged.setDefaultConfigFileLocation(configUrl);
287         merged.setOverrideConfigFileLocation(overrideUrl);
288         return merged;
289     }
290
291     private static URL getResource(String path, boolean classesFirst, boolean addLeadingSlash) {
292         path = addLeadingSlash ? "/" + path : path;
293
294         URL resourceUrl = ClassLoaderHelper.getResource(path, classesFirst, InternalConfig.class);
295
296         return resourceUrl;
297     }
298
299     /*
300      * For debugging purposes
301      */

302
303     private URL defaultConfigFileLocation;
304     private URL overrideConfigFileLocation;
305
306     public URL getDefaultConfigFileLocation() {
307         return defaultConfigFileLocation;
308     }
309
310     public URL getOverrideConfigFileLocation() {
311         return overrideConfigFileLocation;
312     }
313
314     void setDefaultConfigFileLocation(URL url) {
315         this.defaultConfigFileLocation = url;
316     }
317
318     void setOverrideConfigFileLocation(URL url) {
319         this.overrideConfigFileLocation = url;
320     }
321
322     void dump() {
323         StringBuilder sb = new StringBuilder().append("defaultSignerConfig: ").append(defaultSignerConfig).append("\n")
324                 .append("serviceRegionSigners: ").append(serviceRegionSigners).append("\n").append("regionSigners: ")
325                 .append(regionSigners).append("\n").append("serviceSigners: ").append(serviceSigners).append("\n")
326                 .append("userAgentTemplate: ").append(userAgentTemplate);
327         log.debug(sb.toString());
328     }
329
330     public static class Factory {
331         private static final InternalConfig SINGELTON;
332
333         static {
334             InternalConfig config = null;
335             try {
336                 config = InternalConfig.load();
337             } catch (RuntimeException ex) {
338                 throw ex;
339             } catch (Exception ex) {
340                 throw new IllegalStateException("Fatal: Failed to load the internal config for AWS Java SDK", ex);
341             }
342             SINGELTON = config;
343         }
344
345         /**
346          * Returns a non-null and immutable instance of the AWS SDK internal configuration.
347          */

348         public static InternalConfig getInternalConfig() {
349             return SINGELTON;
350         }
351     }
352 }
353