/* * Copyright (c) 2016. Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package com.amazonaws.codegen; import com.amazonaws.codegen.internal.TypeUtils; import com.amazonaws.codegen.model.config.customization.CustomizationConfig; import com.amazonaws.codegen.model.intermediate.EnumModel; import com.amazonaws.codegen.model.intermediate.ListModel; import com.amazonaws.codegen.model.intermediate.MapModel; import com.amazonaws.codegen.model.intermediate.MemberModel; import com.amazonaws.codegen.model.intermediate.ParameterHttpMapping; import com.amazonaws.codegen.model.intermediate.Protocol; import com.amazonaws.codegen.model.intermediate.ReturnTypeModel; import com.amazonaws.codegen.model.intermediate.ShapeModel; import com.amazonaws.codegen.model.intermediate.VariableModel; import com.amazonaws.codegen.model.service.Location; import com.amazonaws.codegen.model.service.Member; import com.amazonaws.codegen.model.service.ServiceModel; import com.amazonaws.codegen.model.service.Shape; import com.amazonaws.codegen.naming.NamingStrategy; import com.amazonaws.util.StringUtils; import java.util.List; import java.util.Map; import static com.amazonaws.codegen.internal.DocumentationUtils.generateGetterDocumentation; import static com.amazonaws.codegen.internal.DocumentationUtils.generateSetterDocumentation; import static com.amazonaws.codegen.internal.TypeUtils.LIST_AUTO_CONSTRUCT_IMPL; import static com.amazonaws.codegen.internal.TypeUtils.LIST_DEFAULT_IMPL; import static com.amazonaws.codegen.internal.TypeUtils.LIST_INTERFACE; import static com.amazonaws.codegen.internal.TypeUtils.MAP_AUTO_CONSTRUCT_IMPL; import static com.amazonaws.codegen.internal.TypeUtils.MAP_DEFAULT_IMPL; import static com.amazonaws.codegen.internal.TypeUtils.MAP_INTERFACE; import static com.amazonaws.codegen.internal.TypeUtils.getDataTypeMapping; import static com.amazonaws.codegen.internal.Utils.capitialize; import static com.amazonaws.codegen.internal.Utils.isEnumShape; import static com.amazonaws.codegen.internal.Utils.isListShape; import static com.amazonaws.codegen.internal.Utils.isMapShape; import static com.amazonaws.codegen.internal.Utils.isScalar; abstract class AddShapes { private final IntermediateModelBuilder builder; private final NamingStrategy namingStrategy; AddShapes(IntermediateModelBuilder builder) { this.builder = builder; this.namingStrategy = builder.getNamingStrategy(); } protected final TypeUtils getTypeUtils() { return builder.getTypeUtils(); } protected final NamingStrategy getNamingStrategy() { return namingStrategy; } protected final ServiceModel getServiceModel() { return builder.getService(); } protected final CustomizationConfig getCustomizationConfig() { return builder.getCustomConfig(); } protected final ShapeModel generateShapeModel(String javaClassName, String shapeName) { final ShapeModel shapeModel = new ShapeModel(shapeName); shapeModel.setShapeName(javaClassName); final Shape shape = getServiceModel().getShapes().get(shapeName); shapeModel.setDocumentation(shape.getDocumentation()); shapeModel.setVariable(new VariableModel(getNamingStrategy().getVariableName(javaClassName), javaClassName)); // contains the list of c2j member names that are required for this shape. shapeModel.setRequired(shape.getRequired()); shapeModel.setDeprecated(shape.isDeprecated()); shapeModel.setWrapper(shape.isWrapper()); final Map<String, Member> members = shape.getMembers(); if (members != null) { boolean hasHeaderMember = false; boolean hasStatusCodeMember = false; boolean hasPayloadMember = false; boolean hasStreamingMember = false; for (Map.Entry<String, Member> memberEntry : members.entrySet()) { String c2jMemberName = memberEntry.getKey(); Member c2jMemberDefinition = memberEntry.getValue(); Shape parentShape = shape; MemberModel memberModel = generateMemberModel(c2jMemberName, c2jMemberDefinition, getProtocol(), parentShape, getServiceModel().getShapes()); if (memberModel.getHttp().getLocation() == Location.HEADER) { hasHeaderMember = true; } else if (memberModel.getHttp().getLocation() == Location.STATUS_CODE) { hasStatusCodeMember = true; } else if (memberModel.getHttp().getIsPayload()) { hasPayloadMember = true; if (memberModel.getHttp().getIsStreaming()) { hasStreamingMember = true; } } shapeModel.addMember(memberModel); } shapeModel.withHasHeaderMember(hasHeaderMember) .withHasStatusCodeMember(hasStatusCodeMember) .withHasPayloadMember(hasPayloadMember) .withHasStreamingMember(hasStreamingMember); } final List<String> enumValues = shape.getEnumValues(); if (enumValues != null && !enumValues.isEmpty()) { for (String enumValue : enumValues) { // TODO handle useRealName from Coral if explicitly mentioned in // the customization. shapeModel.addEnum( new EnumModel(getNamingStrategy().getEnumValueName(enumValue), enumValue)); } } return shapeModel; } private MemberModel generateMemberModel(String c2jMemberName, Member c2jMemberDefinition, String protocol, Shape parentShape, Map<String, Shape> allC2jShapes) { final String c2jShapeName = c2jMemberDefinition.getShape(); final String variableName = getNamingStrategy().getVariableName(c2jMemberName); final String variableType = getTypeUtils().getJavaDataType(allC2jShapes, c2jShapeName); final String variableDeclarationType = getTypeUtils() .getJavaDataType(allC2jShapes, c2jShapeName, getCustomizationConfig()); //If member is idempotent, then it should be of string type //Else throw IllegalArgumentException. if (c2jMemberDefinition.isIdempotencyToken() && !variableType.equals(String.class.getSimpleName())) { throw new IllegalArgumentException(c2jMemberName + " is idempotent. It's shape should be string type but it is of " + variableType + " type."); } final MemberModel memberModel = new MemberModel(); memberModel.withC2jName(c2jMemberName) .withC2jShape(c2jShapeName) .withName(capitialize(c2jMemberName)) .withVariable(new VariableModel(variableName, variableType, variableDeclarationType) .withDocumentation(c2jMemberDefinition.getDocumentation())) .withSetterModel(new VariableModel(variableName, variableType, variableDeclarationType) .withDocumentation(generateSetterDocumentation())) .withGetterModel(new ReturnTypeModel(variableType).withDocumentation(generateGetterDocumentation())); memberModel.setDocumentation(c2jMemberDefinition.getDocumentation()); memberModel.setDeprecated(c2jMemberDefinition.isDeprecated()); memberModel.withGetterMethodName(namingStrategy.getGetterMethodName(c2jMemberName)) .withSetterMethodName(namingStrategy.getSetterMethodName(c2jMemberName)) .withFluentSetterMethodName(namingStrategy.getFluentSetterMethodName(c2jMemberName)); memberModel.setIdempotencyToken(c2jMemberDefinition.isIdempotencyToken()); // Pass the xmlNameSpace from the member reference if (c2jMemberDefinition.getXmlNamespace() != null) { memberModel.setXmlNameSpaceUri(c2jMemberDefinition.getXmlNamespace().getUri()); } // Additional member model metadata for list/map/enum types fillContainerTypeMemberMetadata(allC2jShapes, c2jMemberDefinition.getShape(), memberModel, protocol); final ParameterHttpMapping httpMapping = generateParameterHttpMapping(parentShape, c2jMemberName, c2jMemberDefinition, protocol, allC2jShapes); final String payload = parentShape.getPayload(); httpMapping.withPayload(payload != null && payload.equals(c2jMemberName)) .withStreaming(allC2jShapes.get(c2jMemberDefinition.getShape()).isStreaming()); memberModel.setHttp(httpMapping); memberModel.setJsonValue(c2jMemberDefinition.isJsonvalue()); return memberModel; } private ParameterHttpMapping generateParameterHttpMapping(Shape parentShape, String memberName, Member member, String protocol, Map<String, Shape> allC2jShapes) { ParameterHttpMapping mapping = new ParameterHttpMapping(); Shape memberShape = allC2jShapes.get(member.getShape()); mapping.withLocation(Location.forValue(member.getLocation())) .withPayload(member.isPayload()) .withStreaming(member.isStreaming()) .withFlattened(member.isFlattened() || memberShape.isFlattened()) .withUnmarshallLocationName(deriveUnmarshallerLocationName(memberName, member)) .withMarshallLocationName(deriveMarshallerLocationName(memberName, member, protocol)) .withIsGreedy(isGreedy(parentShape, allC2jShapes, mapping)); return mapping; } /** * @param parentShape Shape containing the member in question. * @param allC2jShapes All shapes in the service model. * @param mapping Mapping being built. * @return True if the member is bound to a greedy label, false otherwise. */ private boolean isGreedy(Shape parentShape, Map<String, Shape> allC2jShapes, ParameterHttpMapping mapping) { if (mapping.getLocation() == Location.URI) { // If the location is URI we can assume the parent shape is an input shape. final String requestUri = findRequestUri(parentShape, allC2jShapes); if (requestUri.contains(String.format("{%s+}", mapping.getMarshallLocationName()))) { return true; } } return false; } /** * Given an input shape, finds the Request URI for the operation that input is referenced from. * * @param parentShape Input shape to find operation's request URI for. * @param allC2jShapes All shapes in the service model. * @return Request URI for operation. * @throws RuntimeException If operation can't be found. */ private String findRequestUri(Shape parentShape, Map<String, Shape> allC2jShapes) { return builder.getService().getOperations().values().stream() .filter(o -> o.getInput() != null) .filter(o -> allC2jShapes.get(o.getInput().getShape()).equals(parentShape)) .map(o -> o.getHttp().getRequestUri()) .findFirst().orElseThrow(() -> new RuntimeException("Could not find request URI for input shape")); } private String deriveUnmarshallerLocationName(String memberName, Member member) { final String locationName = member.getLocationName(); if (locationName != null && !locationName.trim().isEmpty()) { return locationName; } return memberName; } private String deriveMarshallerLocationName(String memberName, Member member, String protocol) { final String queryName = member.getQueryName(); if (queryName != null && !queryName.trim().isEmpty()) { return queryName; } else { final String locationName = member.getLocationName(); if (locationName != null && !locationName.trim().isEmpty()) { if (protocol.equals(Protocol.EC2.getValue())) { return StringUtils.upperCase(locationName.substring(0, 1)) + locationName.substring(1); } return locationName; } else { return memberName; } } } private void fillContainerTypeMemberMetadata(Map<String, Shape> c2jShapes, String memberC2jShapeName, MemberModel memberModel, String protocol) { final Shape memberC2jShape = c2jShapes.get(memberC2jShapeName); if (isListShape(memberC2jShape)) { MemberModel listMemberModel; Member listMemberDefinition = memberC2jShape.getListMember(); String listMemberC2jShapeName = listMemberDefinition.getShape(); Shape listMemberC2jShape = c2jShapes.get(listMemberC2jShapeName); listMemberModel = generateMemberModel("member", listMemberDefinition, protocol, memberC2jShape, c2jShapes); final String listImpl = getCustomizationConfig().isUseAutoConstructList() ? getDataTypeMapping(LIST_AUTO_CONSTRUCT_IMPL) : getDataTypeMapping(LIST_DEFAULT_IMPL); memberModel.setListModel( new ListModel(getTypeUtils().getJavaDataType(c2jShapes, listMemberC2jShapeName), memberC2jShape.getListMember().getLocationName(), listImpl, getDataTypeMapping(LIST_INTERFACE), listMemberModel)); if (listMemberC2jShape.getEnumValues() != null) { memberModel .setEnumType(getNamingStrategy().getJavaClassName(listMemberC2jShapeName)); } } else if (isMapShape(memberC2jShape)) { MemberModel mapKeyModel = null; MemberModel mapValueModel; Member mapKeyMemberDefinition = memberC2jShape.getMapKeyType(); String mapKeyShapeName = mapKeyMemberDefinition.getShape(); Shape mapKeyShape = c2jShapes.get(mapKeyShapeName); Member mapValueMemberDefinition = memberC2jShape.getMapValueType(); // Only construct the nested key model if the key of the map // itself is Enum shape. Throw exception if the nested key type is complex // because we don't support complex map keys. if (isEnumShape(mapKeyShape)) { mapKeyModel = generateMemberModel("key", mapKeyMemberDefinition, protocol, memberC2jShape, c2jShapes); } else if (!isScalar(mapKeyShape)) { throw new IllegalStateException( "The key type of " + mapKeyShapeName + " must be a scalar!"); } mapValueModel = generateMemberModel("value", mapValueMemberDefinition, protocol, memberC2jShape, c2jShapes); final String mapImpl = getCustomizationConfig().isUseAutoConstructMap() ? getDataTypeMapping(MAP_AUTO_CONSTRUCT_IMPL) : getDataTypeMapping(MAP_DEFAULT_IMPL); String keyLocation = memberC2jShape.getMapKeyType().getLocationName() != null ? memberC2jShape.getMapKeyType().getLocationName() : "key"; String valueLocation = memberC2jShape.getMapValueType().getLocationName() != null ? memberC2jShape.getMapValueType().getLocationName() : "value"; memberModel.setMapModel(new MapModel(mapImpl, getDataTypeMapping(MAP_INTERFACE), getTypeUtils().getJavaDataType(c2jShapes, memberC2jShape .getMapKeyType() .getShape()), keyLocation, mapKeyModel, getTypeUtils() .getJavaDataType(c2jShapes, memberC2jShape .getMapValueType().getShape()), valueLocation, mapValueModel)); } else if (memberC2jShape.getEnumValues() != null) { // enum values memberModel.withEnumType(getNamingStrategy().getJavaClassName(memberC2jShapeName)); } } protected String getProtocol() { return getServiceModel().getMetadata().getProtocol(); } }