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.poet.rules;
17 
18 import com.squareup.javapoet.ClassName;
19 import com.squareup.javapoet.CodeBlock;
20 import com.squareup.javapoet.FieldSpec;
21 import com.squareup.javapoet.MethodSpec;
22 import com.squareup.javapoet.TypeSpec;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.UncheckedIOException;
26 import java.util.Map;
27 import javax.lang.model.element.Modifier;
28 import software.amazon.awssdk.annotations.SdkInternalApi;
29 import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
30 import software.amazon.awssdk.codegen.poet.ClassSpec;
31 import software.amazon.awssdk.codegen.poet.PoetUtils;
32 import software.amazon.awssdk.protocols.jsoncore.JsonNode;
33 import software.amazon.awssdk.utils.IoUtils;
34 import software.amazon.awssdk.utils.Validate;
35 
36 public class DefaultPartitionDataProviderSpec implements ClassSpec {
37     // partitions
38     private static final String VERSION = "version";
39     private static final String PARTITIONS = "partitions";
40     // partition
41     private static final String ID = "id";
42     private static final String REGION_REGEX = "regionRegex";
43     private static final String REGIONS = "regions";
44     private static final String OUTPUTS = "outputs";
45     // outputs
46     private static final String DNS_SUFFIX = "dnsSuffix";
47     private static final String DUAL_STACK_DNS_SUFFIX = "dualStackDnsSuffix";
48     private static final String SUPPORTS_FIPS = "supportsFIPS";
49     private static final String SUPPORTS_DUAL_STACK = "supportsDualStack";
50 
51     private final IntermediateModel model;
52     private final EndpointRulesSpecUtils endpointRulesSpecUtils;
53     private final ClassName partitionsClass;
54     private final ClassName partitionClass;
55     private final ClassName regionOverrideClass;
56     private final ClassName outputsClass;
57 
DefaultPartitionDataProviderSpec(IntermediateModel model)58     public DefaultPartitionDataProviderSpec(IntermediateModel model) {
59         this.model = model;
60         this.endpointRulesSpecUtils = new EndpointRulesSpecUtils(model);
61         this.partitionsClass = endpointRulesSpecUtils.rulesRuntimeClassName("Partitions");
62         this.partitionClass = endpointRulesSpecUtils.rulesRuntimeClassName("Partition");
63         this.regionOverrideClass = endpointRulesSpecUtils.rulesRuntimeClassName("RegionOverride");
64         this.outputsClass = endpointRulesSpecUtils.rulesRuntimeClassName("Outputs");
65     }
66 
67     @Override
poetSpec()68     public TypeSpec poetSpec() {
69         TypeSpec.Builder builder = PoetUtils.createClassBuilder(className())
70                                             .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
71                                             .addAnnotation(SdkInternalApi.class)
72                                             .addSuperinterface(
73                                                 endpointRulesSpecUtils.rulesRuntimeClassName("PartitionDataProvider"));
74 
75         builder.addType(lazyPartitionsContainer());
76         builder.addMethod(loadPartitionsMethod());
77         return builder.build();
78     }
79 
80     @Override
className()81     public ClassName className() {
82         return endpointRulesSpecUtils.rulesRuntimeClassName("DefaultPartitionDataProvider");
83     }
84 
loadPartitionsMethod()85     private MethodSpec loadPartitionsMethod() {
86         MethodSpec.Builder builder = MethodSpec.methodBuilder("loadPartitions")
87                                                .addAnnotation(Override.class)
88                                                .addModifiers(Modifier.PUBLIC)
89                                                .returns(partitionsClass);
90 
91         builder.addStatement("return LazyPartitionsContainer.PARTITIONS");
92         return builder.build();
93     }
94 
lazyPartitionsContainer()95     private TypeSpec lazyPartitionsContainer() {
96         CodeBlock.Builder builder = CodeBlock.builder();
97         JsonNode node = JsonNode.parser().parse(readPartitionsJson());
98         codegenPartitions(builder, node);
99         return TypeSpec.classBuilder("LazyPartitionsContainer")
100                        .addModifiers(Modifier.STATIC)
101                        .addField(FieldSpec.builder(partitionsClass, "PARTITIONS", Modifier.STATIC, Modifier.FINAL)
102                                           .initializer(builder.build())
103                                           .build())
104                        .build();
105     }
106 
readPartitionsJson()107     private String readPartitionsJson() {
108         String jsonPath = endpointRulesSpecUtils.rulesEngineResourceFiles()
109                                                 .stream()
110                                                 .filter(e -> e.endsWith("partitions.json.resource"))
111                                                 .findFirst()
112                                                 .orElseThrow(
113                                                     () -> new RuntimeException("Could not find partitions.json.resource"));
114 
115         return loadResourceAsString("/" + jsonPath);
116     }
117 
loadResourceAsString(String path)118     private String loadResourceAsString(String path) {
119         try {
120             return IoUtils.toUtf8String(loadResource(path));
121         } catch (IOException e) {
122             throw new UncheckedIOException(e);
123         }
124     }
125 
loadResource(String name)126     private InputStream loadResource(String name) {
127         InputStream resourceAsStream = DefaultPartitionDataProviderSpec.class.getResourceAsStream(name);
128         Validate.notNull(resourceAsStream, "Failed to load resource from %s", name);
129         return resourceAsStream;
130     }
131 
codegenPartitions(CodeBlock.Builder builder, JsonNode node)132     private void codegenPartitions(CodeBlock.Builder builder, JsonNode node) {
133         builder.add("$T.builder()", partitionsClass);
134         Map<String, JsonNode> objNode = node.asObject();
135 
136         JsonNode version = objNode.get(VERSION);
137         if (version != null) {
138             builder.add(".version(");
139             builder.add("$S", version.asString());
140             builder.add(")");
141         }
142 
143         JsonNode partitions = objNode.get(PARTITIONS);
144         if (partitions != null) {
145             partitions.asArray().forEach(partNode -> {
146                 builder.add(".addPartition(");
147                 codegenPartition(builder, partNode);
148                 builder.add(")");
149             });
150         }
151         builder.add(".build()");
152     }
153 
codegenPartition(CodeBlock.Builder builder, JsonNode node)154     private void codegenPartition(CodeBlock.Builder builder, JsonNode node) {
155         builder.add("$T.builder()", partitionClass);
156         Map<String, JsonNode> objNode = node.asObject();
157 
158         JsonNode id = objNode.get(ID);
159         if (id != null) {
160             builder.add(".id(");
161             builder.add("$S", id.asString());
162             builder.add(")");
163         }
164 
165         JsonNode regionRegex = objNode.get(REGION_REGEX);
166         if (regionRegex != null) {
167             builder.add(".regionRegex(");
168             builder.add("$S", regionRegex.asString());
169             builder.add(")");
170         }
171 
172         JsonNode regions = objNode.get(REGIONS);
173         if (regions != null) {
174             // At the moment `RegionOverride.fromNode` does nothing. We need to fix it here **and** if we keep the
175             // loading from textual JSON also fix `RegionOverride.fromNode`.
176             Map<String, JsonNode> regionsObj = regions.asObject();
177             regionsObj.forEach((k, v) -> {
178                 builder.add(".putRegion($S, ", k);
179                 codegenRegionOverride(builder, v);
180                 builder.add(")");
181             });
182         }
183 
184         JsonNode outputs = objNode.get(OUTPUTS);
185         if (outputs != null) {
186             builder.add(".outputs(");
187             codegenOutputs(builder, outputs);
188             builder.add(")");
189         }
190         builder.add(".build()");
191     }
192 
codegenRegionOverride(CodeBlock.Builder builder, JsonNode node)193     private void codegenRegionOverride(CodeBlock.Builder builder, JsonNode node) {
194         builder.add("$T.builder().build()", regionOverrideClass);
195     }
196 
codegenOutputs(CodeBlock.Builder builder, JsonNode node)197     private void codegenOutputs(CodeBlock.Builder builder, JsonNode node) {
198         builder.add("$T.builder()", outputsClass);
199         Map<String, JsonNode> objNode = node.asObject();
200 
201         JsonNode dnsSuffix = objNode.get(DNS_SUFFIX);
202         if (dnsSuffix != null) {
203             builder.add(".dnsSuffix(");
204             builder.add("$S", dnsSuffix.asString());
205             builder.add(")");
206         }
207 
208         JsonNode dualStackDnsSuffix = objNode.get(DUAL_STACK_DNS_SUFFIX);
209         if (dualStackDnsSuffix != null) {
210             builder.add(".dualStackDnsSuffix(");
211             builder.add("$S", dualStackDnsSuffix.asString());
212             builder.add(")");
213         }
214 
215         JsonNode supportsFips = objNode.get(SUPPORTS_FIPS);
216         if (supportsFips != null) {
217             builder.add(".supportsFips(");
218             builder.add("$L", supportsFips.asBoolean());
219             builder.add(")");
220         }
221 
222         JsonNode supportsDualStack = objNode.get(SUPPORTS_DUAL_STACK);
223         if (supportsDualStack != null) {
224             builder.add(".supportsDualStack(");
225             builder.add("$L", supportsDualStack.asBoolean());
226             builder.add(")");
227         }
228         builder.add(".build()");
229     }
230 }
231 
232