• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.protocols.json.internal.unmarshall;
17 
18 import static software.amazon.awssdk.protocols.core.StringToValueConverter.TO_SDK_BYTES;
19 
20 import java.io.IOException;
21 import java.time.Instant;
22 import java.util.EnumMap;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Optional;
27 import java.util.stream.Collectors;
28 import software.amazon.awssdk.annotations.SdkInternalApi;
29 import software.amazon.awssdk.annotations.ThreadSafe;
30 import software.amazon.awssdk.core.SdkBytes;
31 import software.amazon.awssdk.core.SdkField;
32 import software.amazon.awssdk.core.SdkPojo;
33 import software.amazon.awssdk.core.document.Document;
34 import software.amazon.awssdk.core.protocol.MarshallLocation;
35 import software.amazon.awssdk.core.protocol.MarshallingType;
36 import software.amazon.awssdk.core.traits.ListTrait;
37 import software.amazon.awssdk.core.traits.MapTrait;
38 import software.amazon.awssdk.core.traits.PayloadTrait;
39 import software.amazon.awssdk.core.traits.TimestampFormatTrait;
40 import software.amazon.awssdk.http.AbortableInputStream;
41 import software.amazon.awssdk.http.SdkHttpFullResponse;
42 import software.amazon.awssdk.protocols.core.StringToInstant;
43 import software.amazon.awssdk.protocols.core.StringToValueConverter;
44 import software.amazon.awssdk.protocols.json.internal.MarshallerUtil;
45 import software.amazon.awssdk.protocols.json.internal.unmarshall.document.DocumentUnmarshaller;
46 import software.amazon.awssdk.protocols.jsoncore.JsonNode;
47 import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser;
48 import software.amazon.awssdk.utils.builder.Buildable;
49 
50 /**
51  * Unmarshaller implementation for both JSON RPC and REST JSON services. This class is thread-safe and it is
52  * recommended to reuse a single instance for best performance.
53  */
54 @SdkInternalApi
55 @ThreadSafe
56 public final class JsonProtocolUnmarshaller {
57 
58     public final StringToValueConverter.StringToValue<Instant> instantStringToValue;
59 
60     private final JsonUnmarshallerRegistry registry;
61 
62     private final JsonNodeParser parser;
63 
JsonProtocolUnmarshaller(Builder builder)64     private JsonProtocolUnmarshaller(Builder builder) {
65         this.parser = builder.parser;
66         this.instantStringToValue = StringToInstant.create(builder.defaultTimestampFormats.isEmpty() ?
67                                                            new EnumMap<>(MarshallLocation.class) :
68                                                            new EnumMap<>(builder.defaultTimestampFormats));
69         this.registry = createUnmarshallerRegistry(instantStringToValue);
70     }
71 
createUnmarshallerRegistry( StringToValueConverter.StringToValue<Instant> instantStringToValue)72     private static JsonUnmarshallerRegistry createUnmarshallerRegistry(
73         StringToValueConverter.StringToValue<Instant> instantStringToValue) {
74 
75         return JsonUnmarshallerRegistry
76             .builder()
77             .statusCodeUnmarshaller(MarshallingType.INTEGER, (context, json, f) -> context.response().statusCode())
78             .headerUnmarshaller(MarshallingType.STRING, HeaderUnmarshaller.STRING)
79             .headerUnmarshaller(MarshallingType.INTEGER, HeaderUnmarshaller.INTEGER)
80             .headerUnmarshaller(MarshallingType.LONG, HeaderUnmarshaller.LONG)
81             .headerUnmarshaller(MarshallingType.SHORT, HeaderUnmarshaller.SHORT)
82             .headerUnmarshaller(MarshallingType.DOUBLE, HeaderUnmarshaller.DOUBLE)
83             .headerUnmarshaller(MarshallingType.BOOLEAN, HeaderUnmarshaller.BOOLEAN)
84             .headerUnmarshaller(MarshallingType.INSTANT, HeaderUnmarshaller.createInstantHeaderUnmarshaller(instantStringToValue))
85             .headerUnmarshaller(MarshallingType.FLOAT, HeaderUnmarshaller.FLOAT)
86             .headerUnmarshaller(MarshallingType.LIST, HeaderUnmarshaller.LIST)
87 
88             .payloadUnmarshaller(MarshallingType.STRING, new SimpleTypeJsonUnmarshaller<>(StringToValueConverter.TO_STRING))
89             .payloadUnmarshaller(MarshallingType.INTEGER, new SimpleTypeJsonUnmarshaller<>(StringToValueConverter.TO_INTEGER))
90             .payloadUnmarshaller(MarshallingType.LONG, new SimpleTypeJsonUnmarshaller<>(StringToValueConverter.TO_LONG))
91             .payloadUnmarshaller(MarshallingType.SHORT, new SimpleTypeJsonUnmarshaller<>(StringToValueConverter.TO_SHORT))
92             .payloadUnmarshaller(MarshallingType.FLOAT, new SimpleTypeJsonUnmarshaller<>(StringToValueConverter.TO_FLOAT))
93             .payloadUnmarshaller(MarshallingType.DOUBLE, new SimpleTypeJsonUnmarshaller<>(StringToValueConverter.TO_DOUBLE))
94             .payloadUnmarshaller(MarshallingType.BIG_DECIMAL, new SimpleTypeJsonUnmarshaller<>(
95                 StringToValueConverter.TO_BIG_DECIMAL))
96             .payloadUnmarshaller(MarshallingType.BOOLEAN, new SimpleTypeJsonUnmarshaller<>(StringToValueConverter.TO_BOOLEAN))
97             .payloadUnmarshaller(MarshallingType.SDK_BYTES, JsonProtocolUnmarshaller::unmarshallSdkBytes)
98             .payloadUnmarshaller(MarshallingType.INSTANT, new SimpleTypeJsonUnmarshaller<>(instantStringToValue))
99             .payloadUnmarshaller(MarshallingType.SDK_POJO, JsonProtocolUnmarshaller::unmarshallStructured)
100             .payloadUnmarshaller(MarshallingType.LIST, JsonProtocolUnmarshaller::unmarshallList)
101             .payloadUnmarshaller(MarshallingType.MAP, JsonProtocolUnmarshaller::unmarshallMap)
102             .payloadUnmarshaller(MarshallingType.DOCUMENT, JsonProtocolUnmarshaller::unmarshallDocument)
103                 .build();
104     }
105 
unmarshallSdkBytes(JsonUnmarshallerContext context, JsonNode jsonContent, SdkField<SdkBytes> field)106     private static SdkBytes unmarshallSdkBytes(JsonUnmarshallerContext context,
107                                                JsonNode jsonContent,
108                                                SdkField<SdkBytes> field) {
109         if (jsonContent == null || jsonContent.isNull()) {
110             return null;
111         }
112         // Binary protocols like CBOR may already have the raw bytes extracted.
113         if (jsonContent.isEmbeddedObject()) {
114             return SdkBytes.fromByteArray((byte[]) jsonContent.asEmbeddedObject());
115         } else {
116             // Otherwise decode the JSON string as Base64
117             return TO_SDK_BYTES.convert(jsonContent.text(), field);
118         }
119     }
120 
unmarshallStructured(JsonUnmarshallerContext context, JsonNode jsonContent, SdkField<SdkPojo> f)121     private static SdkPojo unmarshallStructured(JsonUnmarshallerContext context, JsonNode jsonContent, SdkField<SdkPojo> f) {
122         if (jsonContent == null || jsonContent.isNull()) {
123             return null;
124         } else {
125             return unmarshallStructured(f.constructor().get(), jsonContent, context);
126         }
127     }
128 
unmarshallDocument(JsonUnmarshallerContext context, JsonNode jsonContent, SdkField<Document> field)129     private static Document unmarshallDocument(JsonUnmarshallerContext context,
130                                                JsonNode jsonContent,
131                                                SdkField<Document> field) {
132         if (jsonContent == null) {
133             return null;
134         }
135         return jsonContent.isNull() ? Document.fromNull() : getDocumentFromJsonContent(jsonContent);
136     }
137 
getDocumentFromJsonContent(JsonNode jsonContent)138     private static Document getDocumentFromJsonContent(JsonNode jsonContent) {
139         return jsonContent.visit(new DocumentUnmarshaller());
140     }
141 
unmarshallMap(JsonUnmarshallerContext context, JsonNode jsonContent, SdkField<Map<String, ?>> field)142     private static Map<String, ?> unmarshallMap(JsonUnmarshallerContext context,
143                                                 JsonNode jsonContent,
144                                                 SdkField<Map<String, ?>> field) {
145         if (jsonContent == null || jsonContent.isNull()) {
146             return null;
147         }
148         SdkField<Object> valueInfo = field.getTrait(MapTrait.class).valueFieldInfo();
149         Map<String, Object> map = new HashMap<>();
150         jsonContent.asObject().forEach((fieldName, value) -> {
151             JsonUnmarshaller<Object> unmarshaller = context.getUnmarshaller(valueInfo.location(), valueInfo.marshallingType());
152             map.put(fieldName, unmarshaller.unmarshall(context, value, valueInfo));
153         });
154         return map;
155     }
156 
unmarshallList(JsonUnmarshallerContext context, JsonNode jsonContent, SdkField<List<?>> field)157     private static List<?> unmarshallList(JsonUnmarshallerContext context, JsonNode jsonContent, SdkField<List<?>> field) {
158         if (jsonContent == null || jsonContent.isNull()) {
159             return null;
160         }
161         return jsonContent.asArray()
162                           .stream()
163                           .map(item -> {
164                               SdkField<Object> memberInfo = field.getTrait(ListTrait.class).memberFieldInfo();
165                               JsonUnmarshaller<Object> unmarshaller = context.getUnmarshaller(memberInfo.location(),
166                                                                                               memberInfo.marshallingType());
167                               return unmarshaller.unmarshall(context, item, memberInfo);
168                           })
169                           .collect(Collectors.toList());
170     }
171 
172     private static class SimpleTypeJsonUnmarshaller<T> implements JsonUnmarshaller<T> {
173 
174         private final StringToValueConverter.StringToValue<T> stringToValue;
175 
SimpleTypeJsonUnmarshaller(StringToValueConverter.StringToValue<T> stringToValue)176         private SimpleTypeJsonUnmarshaller(StringToValueConverter.StringToValue<T> stringToValue) {
177             this.stringToValue = stringToValue;
178         }
179 
180         @Override
unmarshall(JsonUnmarshallerContext context, JsonNode jsonContent, SdkField<T> field)181         public T unmarshall(JsonUnmarshallerContext context,
182                             JsonNode jsonContent,
183                             SdkField<T> field) {
184             return jsonContent != null && !jsonContent.isNull() ? stringToValue.convert(jsonContent.text(), field) : null;
185         }
186     }
187 
unmarshall(SdkPojo sdkPojo, SdkHttpFullResponse response)188     public <TypeT extends SdkPojo> TypeT unmarshall(SdkPojo sdkPojo,
189                             SdkHttpFullResponse response) throws IOException {
190         JsonNode jsonNode = hasJsonPayload(sdkPojo, response) ? parser.parse(response.content().get()) : null;
191         return unmarshall(sdkPojo, response, jsonNode);
192     }
193 
hasJsonPayload(SdkPojo sdkPojo, SdkHttpFullResponse response)194     private boolean hasJsonPayload(SdkPojo sdkPojo, SdkHttpFullResponse response) {
195         return sdkPojo.sdkFields()
196                       .stream()
197                       .anyMatch(f -> isPayloadMemberOnUnmarshall(f) && !isExplicitBlobPayloadMember(f)
198                                      && !isExplicitStringPayloadMember(f))
199                && response.content().isPresent();
200     }
201 
isExplicitBlobPayloadMember(SdkField<?> f)202     private boolean isExplicitBlobPayloadMember(SdkField<?> f) {
203         return isExplicitPayloadMember(f) && f.marshallingType() == MarshallingType.SDK_BYTES;
204     }
205 
isExplicitStringPayloadMember(SdkField<?> f)206     private boolean isExplicitStringPayloadMember(SdkField<?> f) {
207         return isExplicitPayloadMember(f) && f.marshallingType() == MarshallingType.STRING;
208     }
209 
isExplicitPayloadMember(SdkField<?> f)210     private static boolean isExplicitPayloadMember(SdkField<?> f) {
211         return f.containsTrait(PayloadTrait.class);
212     }
213 
isPayloadMemberOnUnmarshall(SdkField<?> f)214     private boolean isPayloadMemberOnUnmarshall(SdkField<?> f) {
215         return f.location() == MarshallLocation.PAYLOAD || MarshallerUtil.isInUri(f.location());
216     }
217 
unmarshall(SdkPojo sdkPojo, SdkHttpFullResponse response, JsonNode jsonContent)218     public <TypeT extends SdkPojo> TypeT unmarshall(SdkPojo sdkPojo,
219                             SdkHttpFullResponse response,
220                             JsonNode jsonContent) {
221         JsonUnmarshallerContext context = JsonUnmarshallerContext.builder()
222                                                                  .unmarshallerRegistry(registry)
223                                                                  .response(response)
224                                                                  .build();
225         return unmarshallStructured(sdkPojo, jsonContent, context);
226     }
227 
228     @SuppressWarnings("unchecked")
unmarshallStructured(SdkPojo sdkPojo, JsonNode jsonContent, JsonUnmarshallerContext context)229     private static <TypeT extends SdkPojo> TypeT unmarshallStructured(SdkPojo sdkPojo,
230                                                                       JsonNode jsonContent,
231                                                                       JsonUnmarshallerContext context) {
232         for (SdkField<?> field : sdkPojo.sdkFields()) {
233             if (isExplicitPayloadMember(field) && field.marshallingType() == MarshallingType.SDK_BYTES) {
234                 Optional<AbortableInputStream> responseContent = context.response().content();
235                 if (responseContent.isPresent()) {
236                     field.set(sdkPojo, SdkBytes.fromInputStream(responseContent.get()));
237                 } else {
238                     field.set(sdkPojo, SdkBytes.fromByteArrayUnsafe(new byte[0]));
239                 }
240             } else if (isExplicitPayloadMember(field) && field.marshallingType() == MarshallingType.STRING) {
241                 Optional<AbortableInputStream> responseContent = context.response().content();
242                 if (responseContent.isPresent()) {
243                     field.set(sdkPojo, SdkBytes.fromInputStream(responseContent.get()).asUtf8String());
244                 } else {
245                     field.set(sdkPojo, "");
246                 }
247             } else {
248                 JsonNode jsonFieldContent = getJsonNode(jsonContent, field);
249                 JsonUnmarshaller<Object> unmarshaller = context.getUnmarshaller(field.location(), field.marshallingType());
250                 field.set(sdkPojo, unmarshaller.unmarshall(context, jsonFieldContent, (SdkField<Object>) field));
251             }
252         }
253         return (TypeT) ((Buildable) sdkPojo).build();
254     }
255 
getJsonNode(JsonNode jsonContent, SdkField<?> field)256     private static JsonNode getJsonNode(JsonNode jsonContent, SdkField<?> field) {
257         if (jsonContent == null) {
258             return null;
259         }
260         return isFieldExplicitlyTransferredAsJson(field) ? jsonContent : jsonContent.field(field.locationName()).orElse(null);
261     }
262 
isFieldExplicitlyTransferredAsJson(SdkField<?> field)263     private static boolean isFieldExplicitlyTransferredAsJson(SdkField<?> field) {
264         return isExplicitPayloadMember(field) && !MarshallingType.DOCUMENT.equals(field.marshallingType());
265     }
266 
267     /**
268      * @return New instance of {@link Builder}.
269      */
builder()270     public static Builder builder() {
271         return new Builder();
272     }
273 
274     /**
275      * Builder for {@link JsonProtocolUnmarshaller}.
276      */
277     public static final class Builder {
278 
279         private JsonNodeParser parser;
280         private Map<MarshallLocation, TimestampFormatTrait.Format> defaultTimestampFormats;
281 
Builder()282         private Builder() {
283         }
284 
285         /**
286          * @param parser JSON parser to use.
287          * @return This builder for method chaining.
288          */
parser(JsonNodeParser parser)289         public Builder parser(JsonNodeParser parser) {
290             this.parser = parser;
291             return this;
292         }
293 
294         /**
295          * @param formats The default timestamp formats for each location in the HTTP response.
296          * @return This builder for method chaining.
297          */
defaultTimestampFormats(Map<MarshallLocation, TimestampFormatTrait.Format> formats)298         public Builder defaultTimestampFormats(Map<MarshallLocation, TimestampFormatTrait.Format> formats) {
299             this.defaultTimestampFormats = formats;
300             return this;
301         }
302 
303         /**
304          * @return New instance of {@link JsonProtocolUnmarshaller}.
305          */
build()306         public JsonProtocolUnmarshaller build() {
307             return new JsonProtocolUnmarshaller(this);
308         }
309     }
310 
311 }
312