/* * 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.model.intermediate.ExceptionModel; import com.amazonaws.codegen.model.intermediate.OperationModel; import com.amazonaws.codegen.model.intermediate.ReturnTypeModel; import com.amazonaws.codegen.model.intermediate.VariableModel; import com.amazonaws.codegen.model.service.AuthType; import com.amazonaws.codegen.model.service.ErrorMap; import com.amazonaws.codegen.model.service.ErrorTrait; 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.amazonaws.codegen.naming.NamingStrategy; import java.util.Map; import java.util.TreeMap; import static com.amazonaws.codegen.internal.Utils.unCapitialize; /** * Constructs the operation model for every operation defined by the service. */ final class AddOperations { private final ServiceModel serviceModel; private final NamingStrategy namingStrategy; public AddOperations(IntermediateModelBuilder builder) { this.serviceModel = builder.getService(); this.namingStrategy = builder.getNamingStrategy(); } public Map<String, OperationModel> constructOperations() { Map<String, OperationModel> javaOperationModels = new TreeMap<String, OperationModel>(); Map<String, Shape> c2jShapes = serviceModel.getShapes(); for (Map.Entry<String, Operation> entry : serviceModel.getOperations().entrySet()) { final String operationName = entry.getKey(); final Operation op = entry.getValue(); OperationModel operationModel = new OperationModel(); operationModel.setOperationName(operationName); operationModel.setDeprecated(op.isDeprecated()); operationModel.setDocumentation(op.getDocumentation()); operationModel.setIsAuthenticated(isAuthenticated(op)); final Input input = op.getInput(); if (input != null) { String originalShapeName = input.getShape(); String inputShape = namingStrategy.getRequestClassName(operationName); String documentation = input.getDocumentation() != null ? input.getDocumentation() : c2jShapes.get(originalShapeName).getDocumentation(); operationModel.setInput(new VariableModel(unCapitialize(inputShape), inputShape) .withDocumentation(documentation)); } final Output output = op.getOutput(); if (output != null) { final String outputShapeName = getResultShapeName(op, c2jShapes); final Shape outputShape = c2jShapes.get(outputShapeName); final String responseClassName = outputShape.isWrapper() ? outputShapeName : namingStrategy.getResponseClassName(operationName); final String documentation = getOperationDocumentation(output, outputShape); operationModel.setReturnType( new ReturnTypeModel(responseClassName).withDocumentation(documentation)); if (isBlobShape(getPayloadShape(c2jShapes, outputShape))) { operationModel.setHasBlobMemberAsPayload(true); } } if (op.getErrors() != null) { for (ErrorMap error : op.getErrors()) { final String documentation = error.getDocumentation() != null ? error.getDocumentation() : c2jShapes.get(error.getShape()).getDocumentation(); final Integer httpStatusCode = getHttpStatusCode(error, c2jShapes.get(error.getShape())); operationModel.addException( new ExceptionModel(namingStrategy.getExceptionName(error.getShape())) .withDocumentation(documentation) .withHttpStatusCode(httpStatusCode)); } } // TODO: find the stream input parameter operationModel.setInputStreamPropertyName(null); javaOperationModels.put(operationName, operationModel); } return javaOperationModels; } /** * Get HTTP status code either from error trait on the operation reference or the error trait on the shape. * * @param error ErrorMap on operation reference. * @param shape Error shape. * @return HTTP status code or null if not present. */ private Integer getHttpStatusCode(ErrorMap error, Shape shape) { final Integer httpStatusCode = getHttpStatusCode(error.getErrorTrait()); return httpStatusCode == null ? getHttpStatusCode(shape.getErrorTrait()) : httpStatusCode; } /** * @param errorTrait Error trait. * @return HTTP status code from trait or null if not present. */ private Integer getHttpStatusCode(ErrorTrait errorTrait) { return errorTrait == null ? null : errorTrait.getHttpStatusCode(); } private static boolean isAuthenticated(Operation op) { return op.getAuthType() == null || !op.getAuthType().equals(AuthType.NONE); } private static String getOperationDocumentation(final Output output, final Shape outputShape) { return output.getDocumentation() != null ? output.getDocumentation() : outputShape.getDocumentation(); } /** * @return True if shape is a Blob type. False otherwise */ private static boolean isBlobShape(Shape shape) { return shape != null && "blob".equals(shape.getType()); } /** * If there is a member in the output shape that is explicitly marked as the payload (with the * payload trait) this method returns the target shape of that member. Otherwise this method * returns null. * * @param c2jShapes * All C2J shapes * @param outputShape * Output shape of operation that may contain a member designated as the payload */ public static Shape getPayloadShape(Map<String, Shape> c2jShapes, Shape outputShape) { if (outputShape.getPayload() == null) { return null; } Member payloadMember = outputShape.getMembers().get(outputShape.getPayload()); return c2jShapes.get(payloadMember.getShape()); } /** * In query protocol, the wrapped result is the real return type for the given operation. In the c2j model, * if the output shape has only one member, and the member shape is wrapped (wrapper is true), then the * return type is the wrapped member shape instead of the output shape. In the following example, the service API is: * * public Foo operation(OperationRequest operationRequest); * * And the wire log is: * <OperationResponse> * <OperationResult> * <Foo> * ... * </Foo> * </OperationResult> * <OperationMetadata> * </OperationMetadata> * </OperationResponse> * * The C2j model is: * "Operation": { * "input": {"shape": "OperationRequest"}, * "output": { * "shape": "OperationResult", * "resultWrapper": "OperationResult" * } * }, * "OperationResult": { * ... * "members": { * "Foo": {"shape": "Foo"} * } * }, * "Foo" : { * ... * "wrapper" : true * } * * Return the wrapped shape name from the given operation if it conforms to the condition * described above, otherwise, simply return the direct output shape name. */ private static String getResultShapeName(Operation operation, Map<String, Shape> shapes) { Output output = operation.getOutput(); if (output == null) return null; Shape outputShape = shapes.get(output.getShape()); if (outputShape.getMembers().keySet().size() != 1) return output.getShape(); Member wrappedMember = outputShape.getMembers().values().toArray(new Member[0])[0]; Shape wrappedResult = shapes.get(wrappedMember.getShape()); return wrappedResult.isWrapper() ? wrappedMember.getShape() : output.getShape(); } }