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; 17 18 import static software.amazon.awssdk.codegen.internal.TypeUtils.getDataTypeMapping; 19 import static software.amazon.awssdk.codegen.internal.Utils.capitalize; 20 import static software.amazon.awssdk.codegen.internal.Utils.isListShape; 21 import static software.amazon.awssdk.codegen.internal.Utils.isMapShape; 22 import static software.amazon.awssdk.codegen.internal.Utils.isScalar; 23 24 import java.util.List; 25 import java.util.Map; 26 import java.util.Optional; 27 import software.amazon.awssdk.codegen.internal.TypeUtils; 28 import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig; 29 import software.amazon.awssdk.codegen.model.intermediate.EnumModel; 30 import software.amazon.awssdk.codegen.model.intermediate.ListModel; 31 import software.amazon.awssdk.codegen.model.intermediate.MapModel; 32 import software.amazon.awssdk.codegen.model.intermediate.MemberModel; 33 import software.amazon.awssdk.codegen.model.intermediate.ParameterHttpMapping; 34 import software.amazon.awssdk.codegen.model.intermediate.Protocol; 35 import software.amazon.awssdk.codegen.model.intermediate.ReturnTypeModel; 36 import software.amazon.awssdk.codegen.model.intermediate.ShapeModel; 37 import software.amazon.awssdk.codegen.model.intermediate.VariableModel; 38 import software.amazon.awssdk.codegen.model.service.Location; 39 import software.amazon.awssdk.codegen.model.service.Member; 40 import software.amazon.awssdk.codegen.model.service.ServiceModel; 41 import software.amazon.awssdk.codegen.model.service.Shape; 42 import software.amazon.awssdk.codegen.naming.NamingStrategy; 43 import software.amazon.awssdk.utils.StringUtils; 44 import software.amazon.awssdk.utils.Validate; 45 46 abstract class AddShapes { 47 48 private final IntermediateModelBuilder builder; 49 private final NamingStrategy namingStrategy; 50 AddShapes(IntermediateModelBuilder builder)51 AddShapes(IntermediateModelBuilder builder) { 52 this.builder = builder; 53 this.namingStrategy = builder.getNamingStrategy(); 54 } 55 getTypeUtils()56 protected final TypeUtils getTypeUtils() { 57 return builder.getTypeUtils(); 58 } 59 getNamingStrategy()60 protected final NamingStrategy getNamingStrategy() { 61 return namingStrategy; 62 } 63 getServiceModel()64 protected final ServiceModel getServiceModel() { 65 return builder.getService(); 66 } 67 getCustomizationConfig()68 protected final CustomizationConfig getCustomizationConfig() { 69 return builder.getCustomConfig(); 70 } 71 generateShapeModel(String javaClassName, String shapeName)72 protected final ShapeModel generateShapeModel(String javaClassName, String shapeName) { 73 ShapeModel shapeModel = new ShapeModel(shapeName); 74 shapeModel.setShapeName(javaClassName); 75 Shape shape = getServiceModel().getShapes().get(shapeName); 76 77 shapeModel.setDocumentation(shape.getDocumentation()); 78 shapeModel.setVariable(new VariableModel(getNamingStrategy().getVariableName(javaClassName), 79 javaClassName)); 80 // contains the list of c2j member names that are required for this shape. 81 shapeModel.setRequired(shape.getRequired()); 82 shapeModel.setDeprecated(shape.isDeprecated()); 83 shapeModel.setDeprecatedMessage(shape.getDeprecatedMessage()); 84 shapeModel.setWrapper(shape.isWrapper()); 85 shapeModel.withIsEventStream(shape.isEventstream()); 86 shapeModel.withIsEvent(shape.isEvent()); 87 shapeModel.withXmlNamespace(shape.getXmlNamespace()); 88 shapeModel.withIsUnion(shape.isUnion()); 89 shapeModel.withIsFault(shape.isFault()); 90 91 boolean hasHeaderMember = false; 92 boolean hasStatusCodeMember = false; 93 boolean hasPayloadMember = false; 94 boolean hasStreamingMember = false; 95 boolean hasRequiresLength = false; 96 97 Map<String, Member> members = shape.getMembers(); 98 99 if (members != null) { 100 for (Map.Entry<String, Member> memberEntry : members.entrySet()) { 101 102 String c2jMemberName = memberEntry.getKey(); 103 Member c2jMemberDefinition = memberEntry.getValue(); 104 Shape parentShape = shape; 105 106 MemberModel memberModel = generateMemberModel(c2jMemberName, c2jMemberDefinition, 107 getProtocol(), parentShape, 108 getServiceModel().getShapes()); 109 110 if (memberModel.getHttp().getLocation() == Location.HEADER) { 111 hasHeaderMember = true; 112 113 } else if (memberModel.getHttp().getLocation() == Location.STATUS_CODE) { 114 hasStatusCodeMember = true; 115 116 } else if (memberModel.getHttp().getIsPayload()) { 117 hasPayloadMember = true; 118 if (memberModel.getHttp().getIsStreaming()) { 119 hasStreamingMember = true; 120 } 121 if (memberModel.getHttp().isRequiresLength()) { 122 hasRequiresLength = true; 123 } 124 } 125 126 shapeModel.addMember(memberModel); 127 } 128 129 shapeModel.withHasHeaderMember(hasHeaderMember) 130 .withHasStatusCodeMember(hasStatusCodeMember) 131 .withHasPayloadMember(hasPayloadMember) 132 .withHasStreamingMember(hasStreamingMember) 133 .withHasRequiresLengthMember(hasRequiresLength); 134 } 135 136 List<String> enumValues = shape.getEnumValues(); 137 if (enumValues != null && !enumValues.isEmpty()) { 138 for (String enumValue : enumValues) { 139 shapeModel.addEnum( 140 new EnumModel(getNamingStrategy().getEnumValueName(enumValue), enumValue)); 141 } 142 } 143 144 return shapeModel; 145 } 146 generateMemberModel(String c2jMemberName, Member c2jMemberDefinition, String protocol, Shape parentShape, Map<String, Shape> allC2jShapes)147 private MemberModel generateMemberModel(String c2jMemberName, Member c2jMemberDefinition, 148 String protocol, Shape parentShape, 149 Map<String, Shape> allC2jShapes) { 150 String c2jShapeName = c2jMemberDefinition.getShape(); 151 Shape shape = allC2jShapes.get(c2jShapeName); 152 String variableName = getNamingStrategy().getVariableName(c2jMemberName); 153 String variableType = getTypeUtils().getJavaDataType(allC2jShapes, c2jShapeName); 154 String variableDeclarationType = getTypeUtils() 155 .getJavaDataType(allC2jShapes, c2jShapeName, getCustomizationConfig()); 156 157 //If member is idempotent, then it should be of string type 158 //Else throw IllegalArgumentException. 159 if (c2jMemberDefinition.isIdempotencyToken() && 160 !variableType.equals(String.class.getSimpleName())) { 161 throw new IllegalArgumentException(c2jMemberName + 162 " is idempotent. It's shape should be string type but it is of " + 163 variableType + " type."); 164 } 165 166 MemberModel memberModel = new MemberModel(); 167 168 memberModel.withC2jName(c2jMemberName) 169 .withC2jShape(c2jShapeName) 170 .withName(capitalize(c2jMemberName)) 171 .withVariable(new VariableModel(variableName, variableType, variableDeclarationType) 172 .withDocumentation(c2jMemberDefinition.getDocumentation())) 173 .withSetterModel(new VariableModel(variableName, variableType, variableDeclarationType)) 174 .withGetterModel(new ReturnTypeModel(variableType)) 175 .withTimestampFormat(resolveTimestampFormat(c2jMemberDefinition, shape)) 176 .withJsonValue(c2jMemberDefinition.getJsonvalue()); 177 memberModel.setDocumentation(c2jMemberDefinition.getDocumentation()); 178 memberModel.setDeprecated(c2jMemberDefinition.isDeprecated()); 179 memberModel.setDeprecatedMessage(c2jMemberDefinition.getDeprecatedMessage()); 180 memberModel.setSensitive(isSensitiveShapeOrContainer(c2jMemberDefinition, allC2jShapes)); 181 memberModel 182 .withFluentGetterMethodName(namingStrategy.getFluentGetterMethodName(c2jMemberName, parentShape, shape)) 183 .withFluentEnumGetterMethodName(namingStrategy.getFluentEnumGetterMethodName(c2jMemberName, parentShape, shape)) 184 .withFluentSetterMethodName(namingStrategy.getFluentSetterMethodName(c2jMemberName, parentShape, shape)) 185 .withFluentEnumSetterMethodName(namingStrategy.getFluentEnumSetterMethodName(c2jMemberName, parentShape, shape)) 186 .withExistenceCheckMethodName(namingStrategy.getExistenceCheckMethodName(c2jMemberName, parentShape)) 187 .withBeanStyleGetterMethodName(namingStrategy.getBeanStyleGetterMethodName(c2jMemberName, parentShape, shape)) 188 .withBeanStyleSetterMethodName(namingStrategy.getBeanStyleSetterMethodName(c2jMemberName, parentShape, shape)); 189 memberModel.setIdempotencyToken(c2jMemberDefinition.isIdempotencyToken()); 190 memberModel.setEventPayload(c2jMemberDefinition.isEventpayload()); 191 memberModel.setEventHeader(c2jMemberDefinition.isEventheader()); 192 memberModel.setEndpointDiscoveryId(c2jMemberDefinition.isEndpointdiscoveryid()); 193 memberModel.setXmlAttribute(c2jMemberDefinition.isXmlAttribute()); 194 memberModel.setUnionEnumTypeName(namingStrategy.getUnionEnumTypeName(memberModel)); 195 memberModel.setContextParam(c2jMemberDefinition.getContextParam()); 196 memberModel.setRequired(isRequiredMember(c2jMemberName, parentShape)); 197 memberModel.setSynthetic(shape.isSynthetic()); 198 199 200 // Pass the xmlNameSpace from the member reference 201 if (c2jMemberDefinition.getXmlNamespace() != null) { 202 memberModel.setXmlNameSpaceUri(c2jMemberDefinition.getXmlNamespace().getUri()); 203 } 204 205 // Additional member model metadata for list/map/enum types 206 fillContainerTypeMemberMetadata(allC2jShapes, c2jMemberDefinition.getShape(), memberModel, 207 protocol); 208 209 210 String deprecatedName = c2jMemberDefinition.getDeprecatedName(); 211 if (StringUtils.isNotBlank(deprecatedName)) { 212 checkForValidDeprecatedName(c2jMemberName, shape); 213 214 memberModel.setDeprecatedName(deprecatedName); 215 memberModel.setDeprecatedFluentGetterMethodName( 216 namingStrategy.getFluentGetterMethodName(deprecatedName, parentShape, shape)); 217 memberModel.setDeprecatedFluentSetterMethodName( 218 namingStrategy.getFluentSetterMethodName(deprecatedName, parentShape, shape)); 219 memberModel.setDeprecatedBeanStyleSetterMethodName( 220 namingStrategy.getBeanStyleSetterMethodName(deprecatedName, parentShape, shape)); 221 } 222 223 ParameterHttpMapping httpMapping = generateParameterHttpMapping(parentShape, 224 c2jMemberName, 225 c2jMemberDefinition, 226 protocol, 227 allC2jShapes); 228 229 String payload = parentShape.getPayload(); 230 231 boolean shapeIsStreaming = shape.isStreaming(); 232 boolean memberIsStreaming = c2jMemberDefinition.isStreaming(); 233 boolean payloadIsStreaming = shapeIsStreaming || memberIsStreaming; 234 boolean requiresLength = shape.isRequiresLength() || c2jMemberDefinition.isRequiresLength(); 235 236 httpMapping.withPayload(payload != null && payload.equals(c2jMemberName)) 237 .withStreaming(payloadIsStreaming) 238 .withRequiresLength(requiresLength); 239 240 memberModel.setHttp(httpMapping); 241 242 return memberModel; 243 } 244 checkForValidDeprecatedName(String c2jMemberName, Shape memberShape)245 private void checkForValidDeprecatedName(String c2jMemberName, Shape memberShape) { 246 if (memberShape.getEnumValues() != null) { 247 throw new IllegalStateException(String.format( 248 "Member %s has enum values and a deprecated name. Codegen does not support this.", 249 c2jMemberName)); 250 } 251 252 if (isListShape(memberShape)) { 253 throw new IllegalStateException(String.format( 254 "Member %s is a list and has a deprecated name. Codegen does not support this.", 255 c2jMemberName)); 256 } 257 258 if (isMapShape(memberShape)) { 259 throw new IllegalStateException(String.format( 260 "Member %s is a map and has a deprecated name. Codegen does not support this.", 261 c2jMemberName)); 262 } 263 } 264 isSensitiveShapeOrContainer(Member member, Map<String, Shape> allC2jShapes)265 private boolean isSensitiveShapeOrContainer(Member member, Map<String, Shape> allC2jShapes) { 266 if (member == null) { 267 return false; 268 } 269 270 return member.isSensitive() || 271 isSensitiveShapeOrContainer(allC2jShapes.get(member.getShape()), allC2jShapes); 272 } 273 isSensitiveShapeOrContainer(Shape c2jShape, Map<String, Shape> allC2jShapes)274 private boolean isSensitiveShapeOrContainer(Shape c2jShape, Map<String, Shape> allC2jShapes) { 275 if (c2jShape == null) { 276 return false; 277 } 278 279 return c2jShape.isSensitive() || 280 isSensitiveShapeOrContainer(c2jShape.getListMember(), allC2jShapes) || 281 isSensitiveShapeOrContainer(c2jShape.getMapKeyType(), allC2jShapes) || 282 isSensitiveShapeOrContainer(c2jShape.getMapValueType(), allC2jShapes); 283 } 284 resolveTimestampFormat(Member c2jMemberDefinition, Shape c2jShape)285 private String resolveTimestampFormat(Member c2jMemberDefinition, Shape c2jShape) { 286 return c2jMemberDefinition.getTimestampFormat() != null ? 287 c2jMemberDefinition.getTimestampFormat() : c2jShape.getTimestampFormat(); 288 } 289 generateParameterHttpMapping(Shape parentShape, String memberName, Member member, String protocol, Map<String, Shape> allC2jShapes)290 private ParameterHttpMapping generateParameterHttpMapping(Shape parentShape, 291 String memberName, 292 Member member, 293 String protocol, 294 Map<String, Shape> allC2jShapes) { 295 296 ParameterHttpMapping mapping = new ParameterHttpMapping(); 297 298 Shape memberShape = allC2jShapes.get(member.getShape()); 299 mapping.withLocation(Location.forValue(member.getLocation())) 300 .withPayload(member.isPayload()).withStreaming(member.isStreaming()) 301 .withFlattened(isFlattened(member, memberShape)) 302 .withUnmarshallLocationName(deriveUnmarshallerLocationName(memberShape, memberName, member)) 303 .withMarshallLocationName( 304 deriveMarshallerLocationName(memberShape, memberName, member, protocol)) 305 .withIsGreedy(isGreedy(parentShape, allC2jShapes, mapping)); 306 307 return mapping; 308 } 309 isFlattened(Member member, Shape memberShape)310 private boolean isFlattened(Member member, Shape memberShape) { 311 return member.isFlattened() 312 || memberShape.isFlattened(); 313 } 314 isRequiredMember(String memberName, Shape memberShape)315 private boolean isRequiredMember(String memberName, Shape memberShape) { 316 return Optional.ofNullable(memberShape.getRequired()) 317 .map(l -> l.contains(memberName)) 318 .orElse(false); 319 } 320 321 /** 322 * @param parentShape Shape containing the member in question. 323 * @param allC2jShapes All shapes in the service model. 324 * @param mapping Mapping being built. 325 * @return True if the member is bound to a greedy label, false otherwise. 326 */ isGreedy(Shape parentShape, Map<String, Shape> allC2jShapes, ParameterHttpMapping mapping)327 private boolean isGreedy(Shape parentShape, Map<String, Shape> allC2jShapes, ParameterHttpMapping mapping) { 328 if (mapping.getLocation() == Location.URI) { 329 // If the location is URI we can assume the parent shape is an input shape. 330 String requestUri = findRequestUri(parentShape, allC2jShapes); 331 if (requestUri.contains(String.format("{%s+}", mapping.getMarshallLocationName()))) { 332 return true; 333 } 334 } 335 return false; 336 } 337 338 /** 339 * Given an input shape, finds the Request URI for the operation that input is referenced from. 340 * 341 * @param parentShape Input shape to find operation's request URI for. 342 * @param allC2jShapes All shapes in the service model. 343 * @return Request URI for operation. 344 * @throws RuntimeException If operation can't be found. 345 */ findRequestUri(Shape parentShape, Map<String, Shape> allC2jShapes)346 private String findRequestUri(Shape parentShape, Map<String, Shape> allC2jShapes) { 347 return builder.getService().getOperations().values().stream() 348 .filter(o -> o.getInput() != null) 349 .filter(o -> allC2jShapes.get(o.getInput().getShape()).equals(parentShape)) 350 .map(o -> o.getHttp().getRequestUri()) 351 .findFirst().orElseThrow(() -> new RuntimeException("Could not find request URI for input shape")); 352 } 353 deriveUnmarshallerLocationName(Shape memberShape, String memberName, Member member)354 private String deriveUnmarshallerLocationName(Shape memberShape, String memberName, Member member) { 355 String locationName; 356 if (memberShape.getListMember() != null && memberShape.isFlattened()) { 357 locationName = deriveLocationNameForListMember(memberShape, member); 358 } else { 359 locationName = member.getLocationName(); 360 } 361 362 if (StringUtils.isNotBlank(locationName)) { 363 return locationName; 364 } 365 366 return memberName; 367 } 368 deriveMarshallerLocationName(Shape memberShape, String memberName, Member member, String protocol)369 private String deriveMarshallerLocationName(Shape memberShape, String memberName, Member member, String protocol) { 370 String queryName = member.getQueryName(); 371 372 if (StringUtils.isNotBlank(queryName)) { 373 return queryName; 374 } 375 376 String locationName; 377 378 if (Protocol.EC2.getValue().equalsIgnoreCase(protocol)) { 379 locationName = deriveLocationNameForEc2(member); 380 } else if (memberShape.getListMember() != null && memberShape.isFlattened()) { 381 locationName = deriveLocationNameForListMember(memberShape, member); 382 } else { 383 locationName = member.getLocationName(); 384 } 385 386 if (StringUtils.isNotBlank(locationName)) { 387 return locationName; 388 } 389 390 return memberName; 391 } 392 deriveLocationNameForEc2(Member member)393 private String deriveLocationNameForEc2(Member member) { 394 String locationName = member.getLocationName(); 395 396 if (StringUtils.isNotBlank(locationName)) { 397 return StringUtils.upperCase(locationName.substring(0, 1)) + 398 locationName.substring(1); 399 } 400 401 return null; 402 } 403 deriveLocationNameForListMember(Shape memberShape, Member member)404 private String deriveLocationNameForListMember(Shape memberShape, Member member) { 405 String locationName = memberShape.getListMember().getLocationName(); 406 407 if (StringUtils.isNotBlank(locationName)) { 408 return locationName; 409 } 410 411 return member.getLocationName(); 412 } 413 fillContainerTypeMemberMetadata(Map<String, Shape> c2jShapes, String memberC2jShapeName, MemberModel memberModel, String protocol)414 private void fillContainerTypeMemberMetadata(Map<String, Shape> c2jShapes, 415 String memberC2jShapeName, MemberModel memberModel, 416 String protocol) { 417 418 Shape memberC2jShape = c2jShapes.get(memberC2jShapeName); 419 420 if (isListShape(memberC2jShape)) { 421 Member listMemberDefinition = memberC2jShape.getListMember(); 422 String listMemberC2jShapeName = listMemberDefinition.getShape(); 423 424 MemberModel listMemberModel = generateMemberModel("member", listMemberDefinition, protocol, 425 memberC2jShape, c2jShapes); 426 String listImpl = getDataTypeMapping(TypeUtils.TypeKey.LIST_DEFAULT_IMPL); 427 memberModel.setListModel( 428 new ListModel(getTypeUtils().getJavaDataType(c2jShapes, listMemberC2jShapeName), 429 memberC2jShape.getListMember().getLocationName(), listImpl, 430 getDataTypeMapping(TypeUtils.TypeKey.LIST_INTERFACE), listMemberModel)); 431 } else if (isMapShape(memberC2jShape)) { 432 Member mapKeyMemberDefinition = memberC2jShape.getMapKeyType(); 433 String mapKeyShapeName = mapKeyMemberDefinition.getShape(); 434 Shape mapKeyShape = c2jShapes.get(mapKeyShapeName); 435 436 Member mapValueMemberDefinition = memberC2jShape.getMapValueType(); 437 438 // Complex map keys are not supported. 439 Validate.isTrue(isScalar(mapKeyShape), "The key type of %s must be a scalar!", mapKeyShapeName); 440 441 MemberModel mapKeyModel = generateMemberModel("key", mapKeyMemberDefinition, protocol, 442 memberC2jShape, c2jShapes); 443 MemberModel mapValueModel = generateMemberModel("value", mapValueMemberDefinition, protocol, 444 memberC2jShape, c2jShapes); 445 String mapImpl = getDataTypeMapping(TypeUtils.TypeKey.MAP_DEFAULT_IMPL); 446 447 String keyLocation = memberC2jShape.getMapKeyType().getLocationName() != null ? 448 memberC2jShape.getMapKeyType().getLocationName() : "key"; 449 450 String valueLocation = memberC2jShape.getMapValueType().getLocationName() != null ? 451 memberC2jShape.getMapValueType().getLocationName() : "value"; 452 453 memberModel.setMapModel(new MapModel(mapImpl, 454 getDataTypeMapping(TypeUtils.TypeKey.MAP_INTERFACE), 455 keyLocation, 456 mapKeyModel, 457 valueLocation, 458 mapValueModel)); 459 460 } else if (memberC2jShape.getEnumValues() != null) { // enum values 461 memberModel.withEnumType(getNamingStrategy().getShapeClassName(memberC2jShapeName)); 462 } 463 } 464 getProtocol()465 protected String getProtocol() { 466 return getServiceModel().getMetadata().getProtocol(); 467 } 468 } 469