All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazonaws.codegen.customization.processors.ShapeSubstitutionsProcessor Maven / Gradle / Ivy

/*
 * 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 shapeSubstitutions;

    /**
     * parentShapeName -> {memberName -> originalShape}
     */
    private final Map> substitutedShapeMemberReferences = new HashMap>();

    /**
     * parentShapeName -> {listTypeMemberName -> originalShapeOfTheMemberOfTheListTypeMember...}
     */
    private final Map> substitutedListMemberReferences = new HashMap>();

    ShapeSubstitutionsProcessor(
            Map 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 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 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> ref : substitutedShapeMemberReferences.entrySet()) {
            String parentShapeC2jName = ref.getKey();
            Map memberOriginalShapeMap = ref.getValue();

            ShapeModel parentShape = Utils.findShapeModelByC2jName(
                    intermediateModel, parentShapeC2jName);

            for (Entry 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> ref : substitutedListMemberReferences.entrySet()) {
            String parentShapeC2jName = ref.getKey();
            // {listTypeMemberName -> nestedListMemberOriginalShape}
            Map nestedListMemberOriginalShapeMap = ref.getValue();

            ShapeModel parentShape = Utils.findShapeModelByC2jName(
                    intermediateModel, parentShapeC2jName);

            for (Entry 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());
        }
        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());
        }
        substitutedListMemberReferences.get(shapeName).put(listTypeMemberName, nestedListMemberOriginalShape);
    }

    private boolean shouldSkipAddingMarshallingPath(ShapeSubstitution substitutionConfig,
                                                    String parentShapeName) {
        return substitutionConfig.getSkipMarshallPathForShapes() == null
                ? false
                : substitutionConfig.getSkipMarshallPathForShapes().contains(parentShapeName);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy