1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 package software.amazon.awssdk.codegen.poet.transform;
17 
18 import com.squareup.javapoet.ClassName;
19 import com.squareup.javapoet.MethodSpec;
20 import com.squareup.javapoet.ParameterizedTypeName;
21 import com.squareup.javapoet.TypeName;
22 import com.squareup.javapoet.TypeSpec;
23 import java.util.ArrayList;
24 import java.util.List;
25 import javax.lang.model.element.Modifier;
26 import software.amazon.awssdk.annotations.SdkInternalApi;
27 import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
28 import software.amazon.awssdk.codegen.model.intermediate.ShapeModel;
29 import software.amazon.awssdk.codegen.poet.ClassSpec;
30 import software.amazon.awssdk.codegen.poet.PoetExtension;
31 import software.amazon.awssdk.codegen.poet.PoetUtils;
32 import software.amazon.awssdk.codegen.poet.transform.protocols.EventStreamJsonMarshallerSpec;
33 import software.amazon.awssdk.codegen.poet.transform.protocols.JsonMarshallerSpec;
34 import software.amazon.awssdk.codegen.poet.transform.protocols.MarshallerProtocolSpec;
35 import software.amazon.awssdk.codegen.poet.transform.protocols.QueryMarshallerSpec;
36 import software.amazon.awssdk.codegen.poet.transform.protocols.XmlMarshallerSpec;
37 import software.amazon.awssdk.core.exception.SdkClientException;
38 import software.amazon.awssdk.core.runtime.transform.Marshaller;
39 import software.amazon.awssdk.http.SdkHttpFullRequest;
40 import software.amazon.awssdk.utils.Validate;
41 
42 public class MarshallerSpec implements ClassSpec {
43 
44     private final IntermediateModel intermediateModel;
45     private final ShapeModel shapeModel;
46     private final ClassName baseMashallerName;
47     private final TypeName httpRequestName;
48     private final ClassName requestName;
49     private final ClassName className;
50     private final ClassName requestClassName;
51     private final MarshallerProtocolSpec protocolSpec;
52 
MarshallerSpec(IntermediateModel intermediateModel, ShapeModel shapeModel)53     public MarshallerSpec(IntermediateModel intermediateModel, ShapeModel shapeModel) {
54         this.intermediateModel = intermediateModel;
55         this.shapeModel = shapeModel;
56         String modelPackage = intermediateModel.getMetadata().getFullModelPackageName();
57         this.baseMashallerName = ClassName.get(Marshaller.class);
58         this.httpRequestName = ClassName.get(SdkHttpFullRequest.class);
59         this.requestName = ClassName.get(modelPackage, shapeModel.getShapeName());
60         this.className = new PoetExtension(intermediateModel).getRequestTransformClass(shapeModel.getShapeName() + "Marshaller");
61         this.requestClassName = ClassName.get(modelPackage, shapeModel.getShapeName());
62         this.protocolSpec = getProtocolSpecs(intermediateModel.getMetadata().getProtocol());
63     }
64 
65     @Override
poetSpec()66     public TypeSpec poetSpec() {
67         return TypeSpec.classBuilder(className)
68                        .addJavadoc("{@link $T} Marshaller", requestClassName)
69                        .addModifiers(Modifier.PUBLIC)
70                        .addAnnotation(PoetUtils.generatedAnnotation())
71                        .addAnnotation(SdkInternalApi.class)
72                        .addSuperinterface(ParameterizedTypeName.get(baseMashallerName, requestName))
73                        .addFields(protocolSpec.memberVariables())
74                        .addFields(protocolSpec.additionalFields())
75                        .addMethods(methods())
76                        .build();
77     }
78 
79 
80     @Override
className()81     public ClassName className() {
82         return className;
83     }
84 
methods()85     private List<MethodSpec> methods() {
86         List<MethodSpec> methodSpecs = new ArrayList<>();
87 
88         protocolSpec.constructor().ifPresent(methodSpecs::add);
89         methodSpecs.add(marshallMethod());
90         methodSpecs.addAll(protocolSpec.additionalMethods());
91         return methodSpecs;
92     }
93 
marshallMethod()94     private MethodSpec marshallMethod() {
95         String variableName = shapeModel.getVariable().getVariableName();
96         MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder("marshall")
97                                                          .addAnnotation(Override.class)
98                                                          .addModifiers(Modifier.PUBLIC)
99                                                          .addParameter(requestClassName, variableName)
100                                                          .returns(httpRequestName);
101 
102         methodSpecBuilder.addStatement("$T.paramNotNull($L, $S)", ClassName.get(Validate.class), variableName, variableName);
103         methodSpecBuilder.beginControlFlow("try");
104 
105         methodSpecBuilder.addCode(protocolSpec.marshalCodeBlock(requestClassName));
106 
107         methodSpecBuilder.endControlFlow();
108         methodSpecBuilder.beginControlFlow("catch (Exception e)");
109         methodSpecBuilder.addStatement("throw $T.builder().message(\"Unable to marshall request to JSON: \" + " +
110                 "e.getMessage()).cause(e).build()", ClassName
111             .get(SdkClientException.class));
112         methodSpecBuilder.endControlFlow();
113         return methodSpecBuilder.build();
114     }
115 
getProtocolSpecs(software.amazon.awssdk.codegen.model.intermediate.Protocol protocol)116     private MarshallerProtocolSpec getProtocolSpecs(software.amazon.awssdk.codegen.model.intermediate.Protocol protocol) {
117         switch (protocol) {
118             case REST_JSON:
119             case CBOR:
120             case AWS_JSON:
121                 return getJsonMarshallerSpec();
122 
123             case QUERY:
124             case EC2:
125                 return new QueryMarshallerSpec(intermediateModel, shapeModel);
126 
127             case REST_XML:
128                 return new XmlMarshallerSpec(intermediateModel, shapeModel);
129 
130             default:
131                 throw new RuntimeException("Unknown protocol: " + protocol.name());
132         }
133     }
134 
getJsonMarshallerSpec()135     private MarshallerProtocolSpec getJsonMarshallerSpec() {
136         if (shapeModel.isEvent()) {
137             return new EventStreamJsonMarshallerSpec(intermediateModel, shapeModel);
138         }
139         return new JsonMarshallerSpec(shapeModel);
140     }
141 }
142