PageRenderTime 47ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/sdk/core/azure-core/src/main/java/com/azure/core/implementation/serializer/HttpResponseBodyDecoder.java

http://github.com/WindowsAzure/azure-sdk-for-java
Java | 460 lines | 252 code | 53 blank | 155 comment | 79 complexity | ef205b84f367e32fe7f7555caa0dd179 MD5 | raw file
Possible License(s): MIT
  1. // Copyright (c) Microsoft Corporation. All rights reserved.
  2. // Licensed under the MIT License.
  3. package com.azure.core.implementation.serializer;
  4. import com.azure.core.annotation.ReturnValueWireType;
  5. import com.azure.core.exception.HttpResponseException;
  6. import com.azure.core.http.HttpMethod;
  7. import com.azure.core.http.HttpResponse;
  8. import com.azure.core.http.rest.Page;
  9. import com.azure.core.http.rest.Response;
  10. import com.azure.core.http.rest.ResponseBase;
  11. import com.azure.core.implementation.TypeUtil;
  12. import com.azure.core.implementation.UnixTime;
  13. import com.azure.core.util.Base64Url;
  14. import com.azure.core.util.BinaryData;
  15. import com.azure.core.util.DateTimeRfc1123;
  16. import com.azure.core.util.logging.ClientLogger;
  17. import com.azure.core.util.serializer.SerializerAdapter;
  18. import com.azure.core.util.serializer.SerializerEncoding;
  19. import reactor.core.publisher.Flux;
  20. import reactor.core.publisher.Mono;
  21. import java.io.IOException;
  22. import java.io.InputStream;
  23. import java.lang.reflect.ParameterizedType;
  24. import java.lang.reflect.Type;
  25. import java.nio.ByteBuffer;
  26. import java.nio.charset.StandardCharsets;
  27. import java.time.OffsetDateTime;
  28. import java.util.List;
  29. import java.util.Map;
  30. import java.util.Objects;
  31. import java.util.Set;
  32. import java.util.concurrent.ConcurrentHashMap;
  33. import java.util.function.Predicate;
  34. import static com.azure.core.implementation.TypeUtil.getRawClass;
  35. import static com.azure.core.implementation.TypeUtil.typeImplementsInterface;
  36. /**
  37. * Decoder to decode body of HTTP response.
  38. */
  39. public final class HttpResponseBodyDecoder {
  40. private static final Map<Type, Boolean> RETURN_TYPE_DECODEABLE_MAP = new ConcurrentHashMap<>();
  41. // TODO (jogiles) JavaDoc (even though it is non-public
  42. static Mono<Object> decode(final String body,
  43. final HttpResponse httpResponse,
  44. final SerializerAdapter serializer,
  45. final HttpResponseDecodeData decodeData) {
  46. return decodeByteArray(body == null ? null : body.getBytes(StandardCharsets.UTF_8),
  47. httpResponse, serializer, decodeData);
  48. }
  49. /**
  50. * Decodes body of a http response.
  51. *
  52. * The content reading and decoding happens when caller subscribe to the returned {@code Mono<Object>}, if the
  53. * response body is not decodable then {@code Mono.empty()} will be returned.
  54. *
  55. * @param body the response body to decode, null for this parameter indicate read body from {@code httpResponse}
  56. * parameter and decode it.
  57. * @param httpResponse the response containing the body to be decoded
  58. * @param serializer the adapter to use for decoding
  59. * @param decodeData the necessary data required to decode a Http response
  60. * @return publisher that emits decoded response body upon subscription if body is decodable, no emission if the
  61. * body is not-decodable
  62. */
  63. static Mono<Object> decodeByteArray(final byte[] body,
  64. final HttpResponse httpResponse,
  65. final SerializerAdapter serializer,
  66. final HttpResponseDecodeData decodeData) {
  67. ensureRequestSet(httpResponse);
  68. final ClientLogger logger = new ClientLogger(HttpResponseBodyDecoder.class);
  69. return Mono.defer(() -> {
  70. if (isErrorStatus(httpResponse, decodeData)) {
  71. Mono<byte[]> bodyMono = body == null ? httpResponse.getBodyAsByteArray() : Mono.just(body);
  72. return bodyMono.flatMap(bodyAsByteArray -> {
  73. try {
  74. final Object decodedErrorEntity = deserializeBody(bodyAsByteArray,
  75. decodeData.getUnexpectedException(httpResponse.getStatusCode()).getExceptionBodyType(),
  76. null, serializer, SerializerEncoding.fromHeaders(httpResponse.getHeaders()));
  77. return Mono.justOrEmpty(decodedErrorEntity);
  78. } catch (IOException | MalformedValueException ex) {
  79. // This translates in RestProxy as a RestException with no deserialized body.
  80. // The response content will still be accessible via the .response() member.
  81. logger.warning("Failed to deserialize the error entity.", ex);
  82. return Mono.empty();
  83. }
  84. });
  85. } else if (httpResponse.getRequest().getHttpMethod() == HttpMethod.HEAD) {
  86. // RFC: A response to a HEAD method should not have a body. If so, it must be ignored
  87. return Mono.empty();
  88. } else {
  89. if (!isReturnTypeDecodable(decodeData.getReturnType())) {
  90. return Mono.empty();
  91. }
  92. Mono<byte[]> bodyMono = body == null ? httpResponse.getBodyAsByteArray() : Mono.just(body);
  93. return bodyMono.flatMap(bodyAsByteArray -> {
  94. try {
  95. final Object decodedSuccessEntity = deserializeBody(bodyAsByteArray,
  96. extractEntityTypeFromReturnType(decodeData), decodeData.getReturnValueWireType(),
  97. serializer, SerializerEncoding.fromHeaders(httpResponse.getHeaders()));
  98. return Mono.justOrEmpty(decodedSuccessEntity);
  99. } catch (MalformedValueException e) {
  100. return Mono.error(new HttpResponseException("HTTP response has a malformed body.",
  101. httpResponse, e));
  102. } catch (IOException e) {
  103. return Mono.error(new HttpResponseException("Deserialization Failed.", httpResponse, e));
  104. }
  105. });
  106. }
  107. });
  108. }
  109. /**
  110. * @return the decoded type used to decode the response body, null if the body is not decodable.
  111. */
  112. static Type decodedType(final HttpResponse httpResponse, final HttpResponseDecodeData decodeData) {
  113. ensureRequestSet(httpResponse);
  114. if (isErrorStatus(httpResponse, decodeData)) {
  115. // For error cases we always try to decode the non-empty response body
  116. // either to a strongly typed exception model or to Object
  117. return decodeData.getUnexpectedException(httpResponse.getStatusCode()).getExceptionBodyType();
  118. } else if (httpResponse.getRequest().getHttpMethod() == HttpMethod.HEAD) {
  119. // RFC: A response to a HEAD method should not have a body. If so, it must be ignored
  120. return null;
  121. } else {
  122. return isReturnTypeDecodable(decodeData.getReturnType())
  123. ? extractEntityTypeFromReturnType(decodeData)
  124. : null;
  125. }
  126. }
  127. /**
  128. * Checks the response status code is considered as error.
  129. *
  130. * @param httpResponse the response to check
  131. * @param decodeData the response metadata
  132. * @return true if the response status code is considered as error, false otherwise.
  133. */
  134. static boolean isErrorStatus(HttpResponse httpResponse, HttpResponseDecodeData decodeData) {
  135. return !decodeData.isExpectedResponseStatusCode(httpResponse.getStatusCode());
  136. }
  137. /**
  138. * Deserialize the given string value representing content of a REST API response.
  139. *
  140. * If the {@link ReturnValueWireType} is of type {@link Page}, then the returned object will be an instance of that
  141. * {@param wireType}. Otherwise, the returned object is converted back to its {@param resultType}.
  142. *
  143. * @param value the string value to deserialize
  144. * @param resultType the return type of the java proxy method
  145. * @param wireType value of optional {@link ReturnValueWireType} annotation present in java proxy method indicating
  146. * 'entity type' (wireType) of REST API wire response body
  147. * @param encoding the encoding format of value
  148. * @return Deserialized object
  149. * @throws IOException When the body cannot be deserialized
  150. */
  151. private static Object deserializeBody(final byte[] value, final Type resultType, final Type wireType,
  152. final SerializerAdapter serializer, final SerializerEncoding encoding) throws IOException {
  153. if (wireType == null) {
  154. return serializer.deserialize(value, resultType, encoding);
  155. } else if (TypeUtil.isTypeOrSubTypeOf(wireType, Page.class)) {
  156. return deserializePage(value, resultType, wireType, serializer, encoding);
  157. } else {
  158. final Type wireResponseType = constructWireResponseType(resultType, wireType);
  159. final Object wireResponse = serializer.deserialize(value, wireResponseType, encoding);
  160. return convertToResultType(wireResponse, resultType, wireType);
  161. }
  162. }
  163. /**
  164. * Given: (1). the {@code java.lang.reflect.Type} (resultType) of java proxy method return value (2). and {@link
  165. * ReturnValueWireType} annotation value indicating 'entity type' (wireType) of same REST API's wire response body
  166. * this method construct 'response body Type'.
  167. *
  168. * Note: When {@link ReturnValueWireType} annotation is applied to a proxy method, then the raw HTTP response
  169. * content will need to parsed using the derived 'response body Type' then converted to actual {@code returnType}.
  170. *
  171. * @param resultType the {@code java.lang.reflect.Type} of java proxy method return value
  172. * @param wireType the {@code java.lang.reflect.Type} of entity in REST API response body
  173. * @return the {@code java.lang.reflect.Type} of REST API response body
  174. */
  175. private static Type constructWireResponseType(Type resultType, Type wireType) {
  176. Objects.requireNonNull(wireType);
  177. if (resultType == byte[].class) {
  178. if (wireType == Base64Url.class) {
  179. return Base64Url.class;
  180. }
  181. } else if (resultType == OffsetDateTime.class) {
  182. if (wireType == DateTimeRfc1123.class) {
  183. return DateTimeRfc1123.class;
  184. } else if (wireType == UnixTime.class) {
  185. return UnixTime.class;
  186. }
  187. } else if (TypeUtil.isTypeOrSubTypeOf(resultType, List.class)) {
  188. final Type resultElementType = TypeUtil.getTypeArgument(resultType);
  189. final Type wireResponseElementType = constructWireResponseType(resultElementType, wireType);
  190. return TypeUtil.createParameterizedType(((ParameterizedType) resultType).getRawType(),
  191. wireResponseElementType);
  192. } else if (TypeUtil.isTypeOrSubTypeOf(resultType, Map.class)) {
  193. final Type[] typeArguments = TypeUtil.getTypeArguments(resultType);
  194. final Type resultValueType = typeArguments[1];
  195. final Type wireResponseValueType = constructWireResponseType(resultValueType, wireType);
  196. return TypeUtil.createParameterizedType(((ParameterizedType) resultType).getRawType(),
  197. typeArguments[0], wireResponseValueType);
  198. }
  199. return resultType;
  200. }
  201. /**
  202. * Deserializes a response body as a Page&lt;T&gt; given that {@param wireType} is either: 1. A type that implements
  203. * the interface 2. Is of {@link Page}
  204. *
  205. * @param value The data to deserialize
  206. * @param resultType The type T, of the page contents.
  207. * @param wireType The {@link Type} that either is, or implements {@link Page}
  208. * @param serializer The serializer used to deserialize the value.
  209. * @param encoding Encoding used to deserialize string
  210. * @return An object representing an instance of {@param wireType}
  211. * @throws IOException if the serializer is unable to deserialize the value.
  212. */
  213. private static Object deserializePage(final byte[] value,
  214. final Type resultType,
  215. final Type wireType,
  216. final SerializerAdapter serializer,
  217. final SerializerEncoding encoding) throws IOException {
  218. // If the type is the 'Page' interface [@ReturnValueWireType(Page.class)] we will use the 'ItemPage' class.
  219. final Type wireResponseType = (wireType == Page.class)
  220. ? TypeUtil.createParameterizedType(ItemPage.class, resultType)
  221. : wireType;
  222. return serializer.deserialize(value, wireResponseType, encoding);
  223. }
  224. /**
  225. * Converts the object {@code wireResponse} that was deserialized using 'response body Type' (produced by {@code
  226. * constructWireResponseType(args)} method) to resultType.
  227. *
  228. * @param wireResponse the object to convert
  229. * @param resultType the {@code java.lang.reflect.Type} to convert wireResponse to
  230. * @param wireType the {@code java.lang.reflect.Type} of the wireResponse
  231. * @return converted object
  232. */
  233. private static Object convertToResultType(final Object wireResponse,
  234. final Type resultType,
  235. final Type wireType) {
  236. if (resultType == byte[].class) {
  237. if (wireType == Base64Url.class) {
  238. return ((Base64Url) wireResponse).decodedBytes();
  239. }
  240. } else if (resultType == OffsetDateTime.class) {
  241. if (wireType == DateTimeRfc1123.class) {
  242. return ((DateTimeRfc1123) wireResponse).getDateTime();
  243. } else if (wireType == UnixTime.class) {
  244. return ((UnixTime) wireResponse).getDateTime();
  245. }
  246. } else if (TypeUtil.isTypeOrSubTypeOf(resultType, List.class)) {
  247. final Type resultElementType = TypeUtil.getTypeArgument(resultType);
  248. @SuppressWarnings("unchecked") final List<Object> wireResponseList = (List<Object>) wireResponse;
  249. final int wireResponseListSize = wireResponseList.size();
  250. for (int i = 0; i < wireResponseListSize; ++i) {
  251. final Object wireResponseElement = wireResponseList.get(i);
  252. final Object resultElement =
  253. convertToResultType(wireResponseElement, resultElementType, wireType);
  254. if (wireResponseElement != resultElement) {
  255. wireResponseList.set(i, resultElement);
  256. }
  257. }
  258. return wireResponseList;
  259. } else if (TypeUtil.isTypeOrSubTypeOf(resultType, Map.class)) {
  260. final Type resultValueType = TypeUtil.getTypeArguments(resultType)[1];
  261. @SuppressWarnings("unchecked") final Map<String, Object> wireResponseMap =
  262. (Map<String, Object>) wireResponse;
  263. final Set<Map.Entry<String, Object>> wireResponseEntries = wireResponseMap.entrySet();
  264. for (Map.Entry<String, Object> wireResponseEntry : wireResponseEntries) {
  265. final Object wireResponseValue = wireResponseEntry.getValue();
  266. final Object resultValue = convertToResultType(wireResponseValue, resultValueType, wireType);
  267. if (wireResponseValue != resultValue) {
  268. wireResponseMap.put(wireResponseEntry.getKey(), resultValue);
  269. }
  270. }
  271. return wireResponseMap;
  272. }
  273. return wireResponse;
  274. }
  275. /**
  276. * Get the {@link Type} of the REST API 'returned entity'.
  277. *
  278. * In the declaration of a java proxy method corresponding to the REST API, the 'returned entity' can be:
  279. *
  280. * 1. emission value of the reactor publisher returned by proxy method
  281. *
  282. * e.g. {@code Mono<Foo> getFoo(args);} {@code Flux<Foo> getFoos(args);} where Foo is the REST API 'returned
  283. * entity'.
  284. *
  285. * 2. OR content (value) of {@link ResponseBase} emitted by the reactor publisher returned from proxy method
  286. *
  287. * e.g. {@code Mono<RestResponseBase<headers, Foo>> getFoo(args);} {@code Flux<RestResponseBase<headers, Foo>>
  288. * getFoos(args);} where Foo is the REST API return entity.
  289. *
  290. * @return the entity type.
  291. */
  292. private static Type extractEntityTypeFromReturnType(HttpResponseDecodeData decodeData) {
  293. Type token = decodeData.getReturnType();
  294. if (TypeUtil.isTypeOrSubTypeOf(token, Mono.class)) {
  295. token = TypeUtil.getTypeArgument(token);
  296. }
  297. if (TypeUtil.isTypeOrSubTypeOf(token, Response.class)) {
  298. token = TypeUtil.getRestResponseBodyType(token);
  299. }
  300. return token;
  301. }
  302. /**
  303. * Checks if the {@code returnType} is a decode-able type.
  304. * <p>
  305. * Types that aren't decode-able are the following (including sub-types):
  306. * <ul>
  307. * <li>BinaryData</li>
  308. * <li>byte[]</li>
  309. * <li>ByteBuffer</li>
  310. * <li>InputStream</li>
  311. * <li>Void</li>
  312. * <li>void</li>
  313. * </ul>
  314. *
  315. * Reactive, {@link Mono} and {@link Flux}, and Response, {@link Response} and {@link ResponseBase}, generics are
  316. * cracked open and their generic types are inspected for being one of the types above.
  317. *
  318. * @param returnType The return type of the method.
  319. * @return Flag indicating if the return type is decode-able.
  320. */
  321. public static boolean isReturnTypeDecodable(Type returnType) {
  322. if (returnType == null) {
  323. return false;
  324. }
  325. return RETURN_TYPE_DECODEABLE_MAP.computeIfAbsent(returnType, type -> {
  326. type = unwrapReturnType(type);
  327. return !TypeUtil.isTypeOrSubTypeOf(type, BinaryData.class)
  328. && !TypeUtil.isTypeOrSubTypeOf(type, byte[].class)
  329. && !TypeUtil.isTypeOrSubTypeOf(type, ByteBuffer.class)
  330. && !TypeUtil.isTypeOrSubTypeOf(type, InputStream.class)
  331. && !TypeUtil.isTypeOrSubTypeOf(type, Void.TYPE)
  332. && !TypeUtil.isTypeOrSubTypeOf(type, Void.class);
  333. });
  334. }
  335. /**
  336. * Checks if the network response body should be eagerly read based on its {@code returnType}.
  337. * <p>
  338. * The following types, including sub-types, aren't eagerly read from the network:
  339. * <ul>
  340. * <li>BinaryData</li>
  341. * <li>byte[]</li>
  342. * <li>ByteBuffer</li>
  343. * <li>InputStream</li>
  344. * </ul>
  345. *
  346. * Reactive, {@link Mono} and {@link Flux}, and Response, {@link Response} and {@link ResponseBase}, generics are
  347. * cracked open and their generic types are inspected for being one of the types above.
  348. *
  349. * @param returnType The return type of the method.
  350. * @return Flag indicating if the network response body should be eagerly read.
  351. */
  352. public static boolean shouldEagerlyReadResponse(Type returnType) {
  353. if (returnType == null) {
  354. return false;
  355. }
  356. return isReturnTypeDecodable(returnType)
  357. || TypeUtil.isTypeOrSubTypeOf(returnType, Void.TYPE)
  358. || TypeUtil.isTypeOrSubTypeOf(returnType, Void.class);
  359. }
  360. private static Type unwrapReturnType(Type returnType) {
  361. // First check if the return type is assignable, is a sub-type, to ResponseBase.
  362. // If it is begin walking up the super type hierarchy until ResponseBase is the raw type.
  363. // Then unwrap the second generic type (body type).
  364. if (TypeUtil.isTypeOrSubTypeOf(returnType, ResponseBase.class)) {
  365. returnType = walkSuperTypesUntil(returnType, type -> getRawClass(type) == ResponseBase.class);
  366. return unwrapReturnType(TypeUtil.getTypeArguments(returnType)[1]);
  367. }
  368. // Then, like ResponseBase, check if the return type is assignable to Response.
  369. // If it is begin walking up the super type hierarchy until the raw type implements Response.
  370. // Then unwrap its only generic type.
  371. if (TypeUtil.isTypeOrSubTypeOf(returnType, Response.class)) {
  372. // Handling for Response is slightly different as it is an interface unlike ResponseBase which is a class.
  373. // The super class hierarchy needs be walked until the super class itself implements Response.
  374. returnType = walkSuperTypesUntil(returnType, type -> typeImplementsInterface(type, Response.class));
  375. return unwrapReturnType(TypeUtil.getTypeArgument(returnType));
  376. }
  377. // Then check if the return type is a Mono or Flux and unwrap its only generic type.
  378. if (TypeUtil.isTypeOrSubTypeOf(returnType, Mono.class)) {
  379. returnType = walkSuperTypesUntil(returnType, type -> getRawClass(type) == Mono.class);
  380. return unwrapReturnType(TypeUtil.getTypeArgument(returnType));
  381. }
  382. if (TypeUtil.isTypeOrSubTypeOf(returnType, Flux.class)) {
  383. returnType = walkSuperTypesUntil(returnType, type -> getRawClass(type) == Flux.class);
  384. return unwrapReturnType(TypeUtil.getTypeArgument(returnType));
  385. }
  386. // Finally, there is no more unwrapping to perform and return the type as-is.
  387. return returnType;
  388. }
  389. /*
  390. * Helper method that walks up the super types until the type is an instance of the Class.
  391. */
  392. private static Type walkSuperTypesUntil(Type type, Predicate<Type> untilChecker) {
  393. while (!untilChecker.test(type)) {
  394. type = TypeUtil.getSuperType(type);
  395. }
  396. return type;
  397. }
  398. /**
  399. * Ensure that request property and method is set in the response.
  400. *
  401. * @param httpResponse the response to validate
  402. */
  403. private static void ensureRequestSet(HttpResponse httpResponse) {
  404. Objects.requireNonNull(httpResponse.getRequest());
  405. Objects.requireNonNull(httpResponse.getRequest().getHttpMethod());
  406. }
  407. }