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