1 /*
2  * Copyright 2016-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.partitions;
16
17 import com.amazonaws.annotation.SdkInternalApi;
18 import com.amazonaws.partitions.model.Endpoint;
19 import com.amazonaws.partitions.model.Partition;
20 import com.amazonaws.partitions.model.Service;
21 import com.amazonaws.regions.AbstractRegionMetadataProvider;
22 import com.amazonaws.regions.Region;
23 import com.amazonaws.util.ValidationUtils;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.concurrent.ConcurrentHashMap;
32
33 /**
34  * Region metadata provider based on partitions.
35  */

36 @SdkInternalApi
37 public class PartitionMetadataProvider extends AbstractRegionMetadataProvider {
38
39     private static final String STANDARD_PARTITION_HOSTNAME = "{service}.{region}.{dnsSuffix}";
40
41     private final Map<String, Partition> partitionMap = new HashMap<String, Partition>();
42
43     private final Map<String, Region> credentialScopeRegionByHost = new HashMap<String, Region>();
44
45     private final Set<String> standardHostnamePatternDnsSuffixes = new HashSet<String>();
46
47     private final Map<String, Region> regionCache = new ConcurrentHashMap<String, Region>();
48
49     public PartitionMetadataProvider(List<Partition> partitions) {
50         ValidationUtils.assertNotNull(partitions, "partitions");
51
52         for (Partition p : partitions) {
53             partitionMap.put(p.getPartition(), p);
54
55             if (p.getDefaults() != null && STANDARD_PARTITION_HOSTNAME.equals(p.getDefaults().getHostName())) {
56                 standardHostnamePatternDnsSuffixes.add(p.getDnsSuffix());
57             }
58
59             for (Service service : p.getServices().values()) {
60                 for (Endpoint endpoint : service.getEndpoints().values()) {
61                     if (endpoint.getHostName() != null &&
62                         endpoint.getCredentialScope() != null &&
63                         endpoint.getCredentialScope().getRegion() != null) {
64
65                         // Assume the same hostname will never be in two different partitions.
66                         Region region = cacheRegion(new PartitionRegionImpl(endpoint.getCredentialScope().getRegion(), p));
67                         credentialScopeRegionByHost.put(endpoint.getHostName(), region);
68                     }
69                 }
70             }
71         }
72     }
73
74     @Override
75     public List<Region> getRegions() {
76         final List<Region> regions = new ArrayList<Region>();
77
78         for (Partition p : partitionMap.values()) {
79             for (Map.Entry<String, com.amazonaws.partitions.model.Region>
80                     entry : p.getRegions().entrySet()) {
81                 regions.add(new Region(new PartitionRegionImpl(entry.getKey(),
82                         p)));
83             }
84         }
85         return Collections.unmodifiableList(regions);
86     }
87
88     @Override
89     public Region getRegion(String regionName) {
90
91         if (regionName == nullreturn null;
92
93         final Region regionFromCache = getRegionFromCache(regionName);
94
95         if (regionFromCache != null) {
96             return regionFromCache;
97         }
98
99         return createNewRegion(regionName);
100     }
101
102     private Region createNewRegion(String regionName) {
103         for (Partition p : partitionMap.values()) {
104             if (p.hasRegion(regionName)) {
105                 return cacheRegion(new PartitionRegionImpl(regionName, p));
106             }
107         }
108         // If we can't match the regex with any partition then assume the AWS partition if it's available.
109         Partition awsPartition = partitionMap.get("aws");
110         if (awsPartition != null) {
111             return cacheRegion(new PartitionRegionImpl(regionName, awsPartition));
112         } else {
113             return null;
114         }
115     }
116
117     private Region getRegionFromCache(String regionName) {
118         return regionCache.get(regionName);
119     }
120
121     private Region cacheRegion(PartitionRegionImpl regionImpl) {
122         final Region region = new Region(regionImpl);
123
124         regionCache.put(region.getName(), region);
125
126         return region;
127     }
128
129     @Override
130     public List<Region> getRegionsForService(String serviceName) {
131         final List<Region> allRegions = getRegions();
132         final List<Region> serviceSupportedRegions = new ArrayList<Region>();
133
134         for (Region r : allRegions) {
135             if (r.isServiceSupported(serviceName)) {
136                 serviceSupportedRegions.add(r);
137             }
138         }
139         return serviceSupportedRegions;
140     }
141
142     @Override
143     public Region tryGetRegionByExplicitEndpoint(String endpoint) {
144         String host = getHost(endpoint);
145         return credentialScopeRegionByHost.get(host);
146     }
147
148     @Override
149     public Region tryGetRegionByEndpointDnsSuffix(String endpoint) {
150         String host = getHost(endpoint);
151
152         for (String dnsSuffix : standardHostnamePatternDnsSuffixes) {
153             dnsSuffix = "." + dnsSuffix;
154
155             // This host name ends with a DNS suffix of a specific partition
156             // Assume it matches the partition hostname pattern
157             if (host.endsWith(dnsSuffix)) {
158                 String serviceRegion = host.substring(0, host.length() - dnsSuffix.length());
159                 String region = serviceRegion.substring(serviceRegion.lastIndexOf('.') + 1);
160
161                 if (region.isEmpty()) {
162                     return null;
163                 }
164
165                 // We don't use the partition that matched the DNS suffix because multiple partitions can have the same DNS
166                 // suffix. Now that we think we have a region out of the host name, make sure it's in the endpoints.json and
167                 // attach the associated partition that region is actually under.
168                 return getRegion(region);
169             }
170         }
171
172         return null;
173     }
174 }
175