1 /*
2  * Copyright 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 
16 package software.amazon.awssdk.codegen.lite.defaultsmode;
17 
18 import java.io.File;
19 import java.io.FileInputStream;
20 import java.io.IOException;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26 import software.amazon.awssdk.annotations.SdkInternalApi;
27 import software.amazon.awssdk.protocols.jsoncore.JsonNode;
28 import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser;
29 import software.amazon.awssdk.protocols.jsoncore.JsonNodeVisitor;
30 import software.amazon.awssdk.utils.Logger;
31 
32 /**
33  * Loads sdk-default-configuration.json into memory. It filters out unsupported configuration options from the file
34  */
35 @SdkInternalApi
36 public final class DefaultsLoader {
37     private static final Logger log = Logger.loggerFor(DefaultsLoader.class);
38 
39     private static final Set<String> UNSUPPORTED_OPTIONS = new HashSet<>();
40 
41     static {
42         UNSUPPORTED_OPTIONS.add("stsRegionalEndpoints");
43     }
44 
DefaultsLoader()45     private DefaultsLoader() {
46     }
47 
load(File path)48     public static DefaultConfiguration load(File path) {
49         return loadDefaultsFromFile(path);
50     }
51 
loadDefaultsFromFile(File path)52     private static DefaultConfiguration loadDefaultsFromFile(File path) {
53         DefaultConfiguration defaultsResolution = new DefaultConfiguration();
54         Map<String, Map<String, String>> resolvedDefaults = new HashMap<>();
55 
56         try (FileInputStream fileInputStream = new FileInputStream(path)) {
57             JsonNodeParser jsonNodeParser = JsonNodeParser.builder().build();
58 
59             Map<String, JsonNode> sdkDefaultConfiguration = jsonNodeParser.parse(fileInputStream)
60                                                                           .asObject();
61 
62             Map<String, JsonNode> base = sdkDefaultConfiguration.get("base").asObject();
63             Map<String, JsonNode> modes = sdkDefaultConfiguration.get("modes").asObject();
64 
65             modes.forEach((mode, modifiers) -> applyModificationToOneMode(resolvedDefaults, base, mode, modifiers));
66 
67             Map<String, JsonNode> documentation = sdkDefaultConfiguration.get("documentation").asObject();
68             Map<String, JsonNode> modesDocumentation = documentation.get("modes").asObject();
69             Map<String, JsonNode> configDocumentation = documentation.get("configuration").asObject();
70 
71             defaultsResolution.modesDocumentation(
72                 modesDocumentation.entrySet()
73                                   .stream()
74                                   .collect(HashMap::new, (m, e) -> m.put(e.getKey(), e.getValue().asString()), Map::putAll));
75             defaultsResolution.configurationDocumentation(
76                 configDocumentation.entrySet()
77                                    .stream()
78                                    .filter(e -> !UNSUPPORTED_OPTIONS.contains(e.getKey()))
79                                    .collect(HashMap::new, (m, e) -> m.put(e.getKey(), e.getValue().asString()), Map::putAll));
80 
81         } catch (IOException e) {
82             throw new RuntimeException(e);
83         }
84 
85         defaultsResolution.modeDefaults(resolvedDefaults);
86 
87         return defaultsResolution;
88     }
89 
applyModificationToOneConfigurationOption(Map<String, String> resolvedDefaultsForCurrentMode, String option, JsonNode modifier)90     private static void applyModificationToOneConfigurationOption(Map<String, String> resolvedDefaultsForCurrentMode,
91                                                                   String option,
92                                                                   JsonNode modifier) {
93         String resolvedValue;
94         String baseValue = resolvedDefaultsForCurrentMode.get(option);
95 
96         if (UNSUPPORTED_OPTIONS.contains(option)) {
97             return;
98         }
99 
100         Map<String, JsonNode> modifierMap = modifier.asObject();
101 
102         if (modifierMap.size() != 1) {
103             throw new IllegalStateException("More than one modifier exists for option " + option);
104         }
105 
106         String modifierString = modifierMap.keySet().iterator().next();
107 
108         switch (modifierString) {
109             case "override":
110                 resolvedValue = modifierMap.get("override").visit(new StringJsonNodeVisitor());
111                 break;
112             case "multiply":
113                 resolvedValue = processMultiply(baseValue, modifierMap);
114                 break;
115             case "add":
116                 resolvedValue = processAdd(baseValue, modifierMap);
117                 break;
118             default:
119                 throw new UnsupportedOperationException("Unsupported modifier: " + modifierString);
120         }
121 
122         resolvedDefaultsForCurrentMode.put(option, resolvedValue);
123     }
124 
applyModificationToOneMode(Map<String, Map<String, String>> resolvedDefaults, Map<String, JsonNode> base, String mode, JsonNode modifiers)125     private static void applyModificationToOneMode(Map<String, Map<String, String>> resolvedDefaults,
126                                                    Map<String, JsonNode> base,
127                                                    String mode,
128                                                    JsonNode modifiers) {
129 
130         log.info(() -> "Apply modification for mode: " + mode);
131         Map<String, String> resolvedDefaultsForCurrentMode =
132             base.entrySet().stream().filter(e -> !UNSUPPORTED_OPTIONS.contains(e.getKey()))
133                 .collect(HashMap::new, (m, e) -> m.put(e.getKey(),
134                                                        e.getValue().visit(new StringJsonNodeVisitor())), Map::putAll);
135 
136 
137         // Iterate the configuration options and apply modification.
138         modifiers.asObject().forEach((option, modifier) -> applyModificationToOneConfigurationOption(
139             resolvedDefaultsForCurrentMode, option, modifier));
140 
141         resolvedDefaults.put(mode, resolvedDefaultsForCurrentMode);
142     }
143 
processAdd(String baseValue, Map<String, JsonNode> modifierMap)144     private static String processAdd(String baseValue, Map<String, JsonNode> modifierMap) {
145         String resolvedValue;
146         String add = modifierMap.get("add").asNumber();
147         int parsedAdd = Integer.parseInt(add);
148         int number = Math.addExact(Integer.parseInt(baseValue), parsedAdd);
149         resolvedValue = String.valueOf(number);
150         return resolvedValue;
151     }
152 
processMultiply(String baseValue, Map<String, JsonNode> modifierMap)153     private static String processMultiply(String baseValue, Map<String, JsonNode> modifierMap) {
154         String resolvedValue;
155         String multiply = modifierMap.get("multiply").asNumber();
156         double parsedValue = Double.parseDouble(multiply);
157 
158         double resolvedNumber = Integer.parseInt(baseValue) * parsedValue;
159         int castValue = (int) resolvedNumber;
160 
161         if (castValue != resolvedNumber) {
162             throw new IllegalStateException("The transformed value must be be a float number: " + castValue);
163         }
164 
165         resolvedValue = String.valueOf(castValue);
166         return resolvedValue;
167     }
168 
169     private static final class StringJsonNodeVisitor implements JsonNodeVisitor<String> {
170         @Override
visitNull()171         public String visitNull() {
172             throw new IllegalStateException("Invalid type encountered");
173         }
174 
175         @Override
visitBoolean(boolean b)176         public String visitBoolean(boolean b) {
177             throw new IllegalStateException("Invalid type (boolean) encountered " + b);
178         }
179 
180         @Override
visitNumber(String s)181         public String visitNumber(String s) {
182             return s;
183         }
184 
185         @Override
visitString(String s)186         public String visitString(String s) {
187             return s;
188         }
189 
190         @Override
visitArray(List<JsonNode> list)191         public String visitArray(List<JsonNode> list) {
192             throw new IllegalStateException("Invalid type (list) encountered: " + list);
193         }
194 
195         @Override
visitObject(Map<String, JsonNode> map)196         public String visitObject(Map<String, JsonNode> map) {
197             throw new IllegalStateException("Invalid type (map) encountered: " + map);
198         }
199 
200         @Override
visitEmbeddedObject(Object o)201         public String visitEmbeddedObject(Object o) {
202             throw new IllegalStateException("Invalid type (embedded) encountered: " + o);
203         }
204     }
205 }
206