/* * Copyright 2016-2017 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.model.config.customization.CustomizationConfig; import com.amazonaws.codegen.model.config.customization.ShapeModifier; import com.amazonaws.codegen.model.config.customization.ShapeModifier_ModifyModel; import com.amazonaws.codegen.model.config.customization.ShapeSubstitution; import com.amazonaws.codegen.model.intermediate.Example; import com.amazonaws.codegen.model.intermediate.ServiceExamples; import com.amazonaws.codegen.model.service.Input; import com.amazonaws.codegen.model.service.Member; import com.amazonaws.codegen.model.service.Operation; import com.amazonaws.codegen.model.service.Output; import com.amazonaws.codegen.model.service.ServiceModel; import com.amazonaws.codegen.model.service.Shape; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; /** * Transforms the examples so that they are in line with any shape related * customizations for the given service. */ public class ExamplesCustomizer { private static final ObjectMapper MAPPER = new ObjectMapper(); private final ServiceModel serviceModel; private final CustomizationConfig customizationConfig; public ExamplesCustomizer(ServiceModel serviceModel, CustomizationConfig customizationConfig) { this.serviceModel = serviceModel; this.customizationConfig = customizationConfig; } /** * Apply the configured {@link CustomizationConfig} to the given service * examples. * * @param serviceExamples The service examples. * * @return The customized service examples. */ public ServiceExamples applyCustomizationsToExamples(ServiceExamples serviceExamples) { if (customizationConfig == null) return serviceExamples; serviceExamples.getOperationExamples().entrySet() .forEach(e -> { String operationName = e.getKey(); Operation operation = serviceModel.getOperation(operationName); e.getValue().forEach(example -> applyCustomizationsToExample(example, operation)); }); return serviceExamples; } private Example applyCustomizationsToExample(Example example, Operation operation) { if (example == null) return null; System.out.println(String.format("Customizing operation example : %s", example.getId())); Input input = operation.getInput(); if (input != null) { String inputShapeName = input.getShape(); Shape inputShape = serviceModel.getShape(inputShapeName); JsonNode inputValue = example.getInput(); example.setInput(applyCustomizationsToShapeJson(inputShapeName, inputShape, inputValue)); } Output output = operation.getOutput(); if (output != null) { String outputShapeName = output.getShape(); Shape outputShape = serviceModel.getShape(outputShapeName); JsonNode outputValue = example.getOutput(); example.setOutput(applyCustomizationsToShapeJson(outputShapeName, outputShape, outputValue)); } return example; } /** * Recursively apply any declared customizations to this JSON value * according to the given shape and the customizations declared for the * shape. * * @param shapeName The name of the shape. * @param shape The shape of the JSON value. * @param valueNode The JSON value to customize. * * @return The customized JSON value. */ private JsonNode applyCustomizationsToShapeJson(String shapeName, Shape shape, JsonNode valueNode) { // Don't bother going any further if we're not dealing with an array or // object JSON node; there's not much we can do to "massage" this value if (valueNode == null || !valueNode.isContainerNode()) { return valueNode; } // Apply modifications first valueNode = applyModificationsToShapeJson(shapeName, valueNode); ShapeSubstitution shapeSub = null; if (customizationConfig.getShapeSubstitutions() != null) { shapeSub = customizationConfig.getShapeSubstitutions().get(shapeName); } if (shapeSub != null) { String substituteShapeName = shapeSub.getEmitAsShape(); Shape substituteShape = serviceModel.getShape(substituteShapeName); JsonNode substituteValue = valueNode; if (shapeSub.getEmitFromMember() != null) { substituteValue = valueNode.get(shapeSub.getEmitFromMember()); if (substituteValue == null) { System.err.println(String.format("Warning: Substituting shape '%s' for its" + " member '%s' as shape '%s' produced null value. Original" + " value: %s", shapeName, shapeSub.getEmitFromMember(), substituteShapeName, valueNode.toString())); } } System.out.println(String.format("Substituting shape %s with %s. %s -> %s", shapeName, substituteShapeName, valueNode.toString(), Objects.toString(substituteValue))); return applyCustomizationsToShapeJson(substituteShapeName, substituteShape, substituteValue); } else { switch (shape.getType()) { // Apply customizations to each member case "map": case "structure": { if (shape.getMembers() == null) { return valueNode; } ObjectNode obj = MAPPER.createObjectNode(); for (Map.Entry<String, Member> e : shape.getMembers().entrySet()) { Member member = e.getValue(); String memberName = e.getKey(); String memberShapeName = member.getShape(); Shape memberShape = serviceModel.getShape(memberShapeName); JsonNode memberValue = valueNode.get(memberName); // Only set if it's not null, otherwise the generated // sample code could potentially have lots of // unnecessary 'withProperty(null)' calls. if (memberValue != null) { obj.set(memberName, applyCustomizationsToShapeJson(memberShapeName, memberShape, memberValue)); } } return obj; } // Apply customizations to each element case "list": { ArrayNode list = MAPPER.createArrayNode(); String memberShapeName = shape.getListMember().getShape(); Shape memberShape = serviceModel.getShape(memberShapeName); for (JsonNode e : valueNode) { // apply any customizations to the list elements list.add(applyCustomizationsToShapeJson(memberShapeName, memberShape, e)); } return list; } default: throw new RuntimeException("Unknown shape type: " + shape.getType()); } } } private JsonNode applyModificationsToShapeJson(String shapeName, JsonNode valueNode) { if (customizationConfig.getShapeModifiers() == null) return valueNode; ShapeModifier allShapeMode = customizationConfig.getShapeModifiers().get("*"); ShapeModifier shapeMod = customizationConfig.getShapeModifiers().get(shapeName); valueNode = applyShapeModifier(valueNode, allShapeMode); valueNode = applyShapeModifier(valueNode, shapeMod); return valueNode; } /** * Apply any shape modifiers to the JSON value. This only takes care of * 'exclude' and 'emitPropertyName'. * * @param node The JSON node. * @param modifier The shape modifier. * @return The modified node. */ private JsonNode applyShapeModifier(JsonNode node, ShapeModifier modifier) { if (node == null || modifier == null) { return node; } if (modifier.getExclude() == null && modifier.getModify() == null) { return node; } if (!node.isObject()) return node; final ObjectNode obj = (ObjectNode) node; ObjectNode modified = MAPPER.createObjectNode(); // Filter any excluded members final List<String> excludes = modifier.getExclude() != null ? modifier.getExclude() : Collections.emptyList(); obj.fieldNames().forEachRemaining(m -> { if (!excludes.contains(m)) { modified.set(m, obj.get(m)); } }); // Apply property renames final List<Map<String, ShapeModifier_ModifyModel>> modify = modifier.getModify() != null ? modifier.getModify() : Collections.emptyList(); modify.forEach(memberMods -> memberMods.entrySet().forEach(memberMod -> { String memberName = memberMod.getKey(); ShapeModifier_ModifyModel modelModify = memberMod.getValue(); if (modelModify.getEmitPropertyName() != null) { String newName = modelModify.getEmitPropertyName(); modified.set(newName, modified.get(memberName)); modified.remove(memberName); memberName = newName; } }) ); return modified; } }