/* * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License 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 org.springframework.web.client.reactive; import java.util.List; import java.util.Optional; import org.springframework.core.ResolvableType; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.http.client.reactive.ClientHttpResponse; import org.springframework.http.converter.reactive.HttpMessageConverter; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; /** * Static factory methods for {@link ResponseExtractor} based on the {@link Flux} and * {@link Mono} API. * * @author Brian Clozel */ public class ResponseExtractors { private static final Object EMPTY_BODY = new Object(); /** * Extract the response body and decode it, returning it as a {@code Mono<T>} * @see ResolvableType#forClassWithGenerics(Class, Class[]) */ public static <T> ResponseExtractor<Mono<T>> body(ResolvableType bodyType) { // noinspection unchecked return (clientResponse, messageConverters) -> (Mono<T>) clientResponse .flatMap(resp -> decodeResponseBody(resp, bodyType, messageConverters)) .next(); } /** * Extract the response body and decode it, returning it as a {@code Mono<T>} */ public static <T> ResponseExtractor<Mono<T>> body(Class<T> sourceClass) { ResolvableType bodyType = ResolvableType.forClass(sourceClass); return body(bodyType); } /** * Extract the response body and decode it, returning it as a {@code Flux<T>} * @see ResolvableType#forClassWithGenerics(Class, Class[]) */ public static <T> ResponseExtractor<Flux<T>> bodyStream(ResolvableType bodyType) { return (clientResponse, messageConverters) -> clientResponse .flatMap(resp -> decodeResponseBody(resp, bodyType, messageConverters)); } /** * Extract the response body and decode it, returning it as a {@code Flux<T>} */ public static <T> ResponseExtractor<Flux<T>> bodyStream(Class<T> sourceClass) { ResolvableType bodyType = ResolvableType.forClass(sourceClass); return bodyStream(bodyType); } /** * Extract the full response body as a {@code ResponseEntity} with its body decoded as * a single type {@code T} * @see ResolvableType#forClassWithGenerics(Class, Class[]) */ public static <T> ResponseExtractor<Mono<ResponseEntity<T>>> response( ResolvableType bodyType) { return (clientResponse, messageConverters) -> clientResponse.then(response -> { return Mono.when( decodeResponseBody(response, bodyType, messageConverters).next().defaultIfEmpty( EMPTY_BODY), Mono.just(response.getHeaders()), Mono.just(response.getStatusCode())); }).map(tuple -> { Object body = (tuple.getT1() != EMPTY_BODY ? tuple.getT1() : null); // noinspection unchecked return new ResponseEntity<>((T) body, tuple.getT2(), tuple.getT3()); }); } /** * Extract the full response body as a {@code ResponseEntity} with its body decoded as * a single type {@code T} */ public static <T> ResponseExtractor<Mono<ResponseEntity<T>>> response( Class<T> bodyClass) { ResolvableType bodyType = ResolvableType.forClass(bodyClass); return response(bodyType); } /** * Extract the full response body as a {@code ResponseEntity} with its body decoded as * a {@code Flux<T>} * @see ResolvableType#forClassWithGenerics(Class, Class[]) */ public static <T> ResponseExtractor<Mono<ResponseEntity<Flux<T>>>> responseStream( ResolvableType type) { return (clientResponse, messageConverters) -> clientResponse .map(response -> new ResponseEntity<>( decodeResponseBody(response, type, messageConverters), response.getHeaders(), response.getStatusCode())); } /** * Extract the full response body as a {@code ResponseEntity} with its body decoded as * a {@code Flux<T>} */ public static <T> ResponseExtractor<Mono<ResponseEntity<Flux<T>>>> responseStream( Class<T> sourceClass) { ResolvableType resolvableType = ResolvableType.forClass(sourceClass); return responseStream(resolvableType); } /** * Extract the response headers as an {@code HttpHeaders} instance */ public static ResponseExtractor<Mono<HttpHeaders>> headers() { return (clientResponse, messageConverters) -> clientResponse.map(resp -> resp.getHeaders()); } protected static <T> Flux<T> decodeResponseBody(ClientHttpResponse response, ResolvableType responseType, List<HttpMessageConverter<?>> messageConverters) { MediaType contentType = response.getHeaders().getContentType(); Optional<HttpMessageConverter<?>> converter = resolveConverter(messageConverters, responseType, contentType); if (!converter.isPresent()) { return Flux.error(new IllegalStateException( "Could not decode response body of type '" + contentType + "' with target type '" + responseType.toString() + "'")); } // noinspection unchecked return (Flux<T>) converter.get().read(responseType, response); } protected static Optional<HttpMessageConverter<?>> resolveConverter( List<HttpMessageConverter<?>> messageConverters, ResolvableType type, MediaType mediaType) { return messageConverters.stream().filter(e -> e.canRead(type, mediaType)) .findFirst(); } }