/* * 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.customization.processors; import com.amazonaws.codegen.customization.CodegenCustomizationProcessor; import com.amazonaws.codegen.internal.Utils; import com.amazonaws.codegen.model.config.customization.ShapeSubstitution; import com.amazonaws.codegen.model.intermediate.IntermediateModel; import com.amazonaws.codegen.model.intermediate.MemberModel; import com.amazonaws.codegen.model.intermediate.ShapeModel; import com.amazonaws.codegen.model.service.ErrorMap; import com.amazonaws.codegen.model.service.Member; import com.amazonaws.codegen.model.service.Operation; import com.amazonaws.codegen.model.service.ServiceModel; import com.amazonaws.codegen.model.service.Shape; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; /** * This processor internally keeps track of all the structure members whose * shape is substituted during pre-processing, therefore the caller needs to * make sure this processor is only invoked once. */ final class ShapeSubstitutionsProcessor implements CodegenCustomizationProcessor { private final Map<String, ShapeSubstitution> shapeSubstitutions; /** * parentShapeName -> {memberName -> originalShape} */ private final Map<String, Map<String, String>> substitutedShapeMemberReferences = new HashMap<String, Map<String, String>>(); /** * parentShapeName -> {listTypeMemberName -> originalShapeOfTheMemberOfTheListTypeMember...} */ private final Map<String, Map<String, String>> substitutedListMemberReferences = new HashMap<String, Map<String, String>>(); ShapeSubstitutionsProcessor( Map<String, ShapeSubstitution> shapeSubstitutions) { this.shapeSubstitutions = shapeSubstitutions; } @Override public void preprocess(ServiceModel serviceModel) { if (shapeSubstitutions == null) return; // Make sure the substituted shapes exist in the service model for (String substitutedShape : shapeSubstitutions.keySet()) { if ( !serviceModel.getShapes().containsKey(substitutedShape) ) { throw new IllegalStateException( "shapeSubstitution customization found for shape " + substitutedShape + ", which does not exist in the service model."); } } // Make sure the substituted shapes are not referenced by any operation // as the input, output or error shape for (Operation operation : serviceModel.getOperations().values()) { preprocess_AssertNoSubstitutedShapeReferenceInOperation(operation); } // Substitute references from within shape members for (Entry<String, Shape> entry : serviceModel.getShapes().entrySet()) { String shapeName = entry.getKey(); Shape shape = entry.getValue(); preprocess_SubstituteShapeReferencesInShape(shapeName, shape, serviceModel); } } @Override public void postprocess(IntermediateModel intermediateModel) { if (shapeSubstitutions == null) return; for (ShapeModel shapeModel : intermediateModel.getShapes().values()) { postprocess_HandleEmitAsMember(shapeModel, intermediateModel); } } private void preprocess_AssertNoSubstitutedShapeReferenceInOperation(Operation operation) { // Check input if (operation.getInput() != null && operation.getInput().getShape() != null) { String inputShape = operation.getInput().getShape(); if (shapeSubstitutions.containsKey(inputShape)) { throw new IllegalStateException( "shapeSubstitution customization found for shape " + inputShape + ", but this shape is referenced as the input for operation " + operation.getName()); } } // Check output if (operation.getOutput() != null && operation.getOutput().getShape() != null) { String outputShape = operation.getOutput().getShape(); if (shapeSubstitutions.containsKey(outputShape)) { throw new IllegalStateException( "shapeSubstitution customization found for shape " + outputShape + ", but this shape is referenced as the output for operation " + operation.getName()); } } // Check errors if (operation.getErrors() != null) { for (ErrorMap error : operation.getErrors()) { String errorShape = error.getShape(); if (shapeSubstitutions.containsKey(errorShape)) { throw new IllegalStateException( "shapeSubstitution customization found for shape " + errorShape + ", but this shape is referenced as an error for operation " + operation.getName()); } } } } /** * We only handle emitAsShape in the pre-process stage; emitAsMember is * handled in post-process stage, after the marshaller/unmarshaller location * names are calculated in the intermediate model. */ private void preprocess_SubstituteShapeReferencesInShape( String shapeName, Shape shape, ServiceModel serviceModel) { // structure members if (shape.getMembers() != null) { for (Entry<String, Member> entry : shape.getMembers().entrySet()) { String memberName = entry.getKey(); Member member = entry.getValue(); String memberShapeName = member.getShape(); Shape memberShape = serviceModel.getShapes().get(memberShapeName); // First check if it's a list-type member and that the shape of // its list element should be substituted if (Utils.isListShape(memberShape)) { Member nestedListMember = memberShape.getListMember(); String nestedListMemberOriginalShape = nestedListMember.getShape(); ShapeSubstitution appliedSubstitutionOnListMember = substitueMemberShape(nestedListMember); if (appliedSubstitutionOnListMember != null && appliedSubstitutionOnListMember.getEmitFromMember() != null) { // we will handle the emitFromMember customizations in post-process stage trackListMemberSubstitution(shapeName, memberName, nestedListMemberOriginalShape); } } // Then check if the shape of the member itself is to be substituted else { ShapeSubstitution appliedSubstitution = substitueMemberShape(member); if (appliedSubstitution != null && appliedSubstitution.getEmitFromMember() != null) { // we will handle the emitFromMember customizations in post-process stage trackShapeMemberSubstitution(shapeName, memberName, memberShapeName); } } } } // no need to check if the shape is a list, since a list shape is // always referenced by a top-level structure shape and that's already // handled by the code above // map key is not allowed to be substituted else if (shape.getMapKeyType() != null) { String mapKeyShape = shape.getMapKeyType().getShape(); if (shapeSubstitutions.containsKey(mapKeyShape)) { throw new IllegalStateException( "shapeSubstitution customization found for shape " + mapKeyShape + ", but this shape is the key for a map shape."); } } // map value is not allowed to be substituted else if (shape.getMapValueType() != null) { String mapValShape = shape.getMapValueType().getShape(); if (shapeSubstitutions.containsKey(mapValShape)) { throw new IllegalStateException( "shapeSubstitution customization found for shape " + mapValShape + ", but this shape is the value for a map shape."); } } } /** * @return the ShapeSubstitution customization that should be applied to * this member, or null if there is no such customization specified * for this member. */ private ShapeSubstitution substitueMemberShape(Member member) { ShapeSubstitution substitute = shapeSubstitutions.get(member.getShape()); if (substitute != null) { member.setShape(substitute.getEmitAsShape()); return substitute; } return null; } private void postprocess_HandleEmitAsMember( ShapeModel shape, IntermediateModel intermediateModel) { /* * For structure members whose shape is substituted, we need to add the * additional marshalling/unmarshalling path to the corresponding member * model */ for (Entry<String, Map<String, String>> ref : substitutedShapeMemberReferences.entrySet()) { String parentShapeC2jName = ref.getKey(); Map<String, String> memberOriginalShapeMap = ref.getValue(); ShapeModel parentShape = Utils.findShapeModelByC2jName( intermediateModel, parentShapeC2jName); for (Entry<String, String> entry : memberOriginalShapeMap.entrySet()) { String memberC2jName = entry.getKey(); String originalShapeC2jName = entry.getValue(); MemberModel member = parentShape.findMemberModelByC2jName(memberC2jName); ShapeModel originalShape = Utils.findShapeModelByC2jName(intermediateModel, originalShapeC2jName); MemberModel emitFromMember = originalShape.findMemberModelByC2jName( shapeSubstitutions.get(originalShapeC2jName) .getEmitFromMember()); // Pass in the original member model's marshalling/unmarshalling location name /** * This customization is specifically added for * EC2 where we replace all occurrences of AttributeValue with Value in * the model classes. However the wire representation is not changed. * * TODO This customization has been added to preserve backwards * compatiblity of EC2 APIs. This should be removed as part of next major * version bump. */ if (!shouldSkipAddingMarshallingPath(shapeSubstitutions.get (originalShapeC2jName), parentShapeC2jName)) { member.getHttp().setAdditionalMarshallingPath( emitFromMember.getHttp().getMarshallLocationName()); } member.getHttp().setAdditionalUnmarshallingPath( emitFromMember.getHttp().getUnmarshallLocationName()); } } /* * For list shapes whose member shape is substituted, we need to add the * additional path into the "http" metadata of all the shape members * that reference to this list-type shape. */ for (Entry<String, Map<String, String>> ref : substitutedListMemberReferences.entrySet()) { String parentShapeC2jName = ref.getKey(); // {listTypeMemberName -> nestedListMemberOriginalShape} Map<String, String> nestedListMemberOriginalShapeMap = ref.getValue(); ShapeModel parentShape = Utils.findShapeModelByC2jName( intermediateModel, parentShapeC2jName); for (Entry<String, String> entry : nestedListMemberOriginalShapeMap.entrySet()) { String listTypeMemberC2jName = entry.getKey(); String nestedListMemberOriginalShapeC2jName = entry.getValue(); MemberModel listTypeMember = parentShape.findMemberModelByC2jName(listTypeMemberC2jName); ShapeModel nestedListMemberOriginalShape = Utils.findShapeModelByC2jName(intermediateModel, nestedListMemberOriginalShapeC2jName); MemberModel emitFromMember = nestedListMemberOriginalShape.findMemberModelByC2jName( shapeSubstitutions .get(nestedListMemberOriginalShapeC2jName) .getEmitFromMember() ); /** * This customization is specifically added for * EC2 where we replace all occurrences of AttributeValue with Value in * the model classes. However the wire representation is not changed. * * TODO This customization has been added to preserve backwards * compatiblity of EC2 APIs. This should be removed as part of next major * version bump. */ if (!shouldSkipAddingMarshallingPath(shapeSubstitutions.get (nestedListMemberOriginalShapeC2jName), parentShapeC2jName)) { listTypeMember.getListModel().setMemberAdditionalMarshallingPath( emitFromMember.getHttp().getMarshallLocationName()); } listTypeMember.getListModel().setMemberAdditionalUnmarshallingPath( emitFromMember.getHttp().getUnmarshallLocationName()); } } } private void trackShapeMemberSubstitution(String shapeName, String memberName, String originalShape) { System.out.println(String.format("%s -> {%s -> %s}", shapeName, memberName, originalShape)); if ( !substitutedShapeMemberReferences.containsKey(shapeName) ) { substitutedShapeMemberReferences.put(shapeName, new HashMap<String, String>()); } substitutedShapeMemberReferences.get(shapeName).put(memberName, originalShape); } private void trackListMemberSubstitution(String shapeName, String listTypeMemberName, String nestedListMemberOriginalShape) { System.out.println(String.format("%s -> {%s -> %s}", shapeName, listTypeMemberName, nestedListMemberOriginalShape)); if ( !substitutedListMemberReferences.containsKey(shapeName) ) { substitutedListMemberReferences.put(shapeName, new HashMap<String, String>()); } substitutedListMemberReferences.get(shapeName).put(listTypeMemberName, nestedListMemberOriginalShape); } private boolean shouldSkipAddingMarshallingPath(ShapeSubstitution substitutionConfig, String parentShapeName) { return substitutionConfig.getSkipMarshallPathForShapes() == null ? false : substitutionConfig.getSkipMarshallPathForShapes().contains(parentShapeName); } }