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