• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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