001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.client; 018 019import java.io.IOException; 020import java.lang.reflect.Type; 021import java.util.List; 022 023import org.apache.commons.logging.Log; 024import org.apache.commons.logging.LogFactory; 025 026import org.springframework.core.ResolvableType; 027import org.springframework.http.MediaType; 028import org.springframework.http.client.ClientHttpResponse; 029import org.springframework.http.converter.GenericHttpMessageConverter; 030import org.springframework.http.converter.HttpMessageConverter; 031import org.springframework.http.converter.HttpMessageNotReadableException; 032import org.springframework.lang.Nullable; 033import org.springframework.util.Assert; 034import org.springframework.util.FileCopyUtils; 035 036/** 037 * Response extractor that uses the given {@linkplain HttpMessageConverter entity converters} 038 * to convert the response into a type {@code T}. 039 * 040 * @author Arjen Poutsma 041 * @author Sam Brannen 042 * @since 3.0 043 * @param <T> the data type 044 * @see RestTemplate 045 */ 046public class HttpMessageConverterExtractor<T> implements ResponseExtractor<T> { 047 048 private final Type responseType; 049 050 @Nullable 051 private final Class<T> responseClass; 052 053 private final List<HttpMessageConverter<?>> messageConverters; 054 055 private final Log logger; 056 057 058 /** 059 * Create a new instance of the {@code HttpMessageConverterExtractor} with the given response 060 * type and message converters. The given converters must support the response type. 061 */ 062 public HttpMessageConverterExtractor(Class<T> responseType, List<HttpMessageConverter<?>> messageConverters) { 063 this((Type) responseType, messageConverters); 064 } 065 066 /** 067 * Creates a new instance of the {@code HttpMessageConverterExtractor} with the given response 068 * type and message converters. The given converters must support the response type. 069 */ 070 public HttpMessageConverterExtractor(Type responseType, List<HttpMessageConverter<?>> messageConverters) { 071 this(responseType, messageConverters, LogFactory.getLog(HttpMessageConverterExtractor.class)); 072 } 073 074 @SuppressWarnings("unchecked") 075 HttpMessageConverterExtractor(Type responseType, List<HttpMessageConverter<?>> messageConverters, Log logger) { 076 Assert.notNull(responseType, "'responseType' must not be null"); 077 Assert.notEmpty(messageConverters, "'messageConverters' must not be empty"); 078 Assert.noNullElements(messageConverters, "'messageConverters' must not contain null elements"); 079 this.responseType = responseType; 080 this.responseClass = (responseType instanceof Class ? (Class<T>) responseType : null); 081 this.messageConverters = messageConverters; 082 this.logger = logger; 083 } 084 085 086 @Override 087 @SuppressWarnings({"unchecked", "rawtypes", "resource"}) 088 public T extractData(ClientHttpResponse response) throws IOException { 089 MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response); 090 if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) { 091 return null; 092 } 093 MediaType contentType = getContentType(responseWrapper); 094 095 try { 096 for (HttpMessageConverter<?> messageConverter : this.messageConverters) { 097 if (messageConverter instanceof GenericHttpMessageConverter) { 098 GenericHttpMessageConverter<?> genericMessageConverter = 099 (GenericHttpMessageConverter<?>) messageConverter; 100 if (genericMessageConverter.canRead(this.responseType, null, contentType)) { 101 if (logger.isDebugEnabled()) { 102 ResolvableType resolvableType = ResolvableType.forType(this.responseType); 103 logger.debug("Reading to [" + resolvableType + "]"); 104 } 105 return (T) genericMessageConverter.read(this.responseType, null, responseWrapper); 106 } 107 } 108 if (this.responseClass != null) { 109 if (messageConverter.canRead(this.responseClass, contentType)) { 110 if (logger.isDebugEnabled()) { 111 String className = this.responseClass.getName(); 112 logger.debug("Reading to [" + className + "] as \"" + contentType + "\""); 113 } 114 return (T) messageConverter.read((Class) this.responseClass, responseWrapper); 115 } 116 } 117 } 118 } 119 catch (IOException | HttpMessageNotReadableException ex) { 120 throw new RestClientException("Error while extracting response for type [" + 121 this.responseType + "] and content type [" + contentType + "]", ex); 122 } 123 124 throw new UnknownContentTypeException(this.responseType, contentType, 125 response.getRawStatusCode(), response.getStatusText(), response.getHeaders(), 126 getResponseBody(response)); 127 } 128 129 /** 130 * Determine the Content-Type of the response based on the "Content-Type" 131 * header or otherwise default to {@link MediaType#APPLICATION_OCTET_STREAM}. 132 * @param response the response 133 * @return the MediaType, or "application/octet-stream" 134 */ 135 protected MediaType getContentType(ClientHttpResponse response) { 136 MediaType contentType = response.getHeaders().getContentType(); 137 if (contentType == null) { 138 if (logger.isTraceEnabled()) { 139 logger.trace("No content-type, using 'application/octet-stream'"); 140 } 141 contentType = MediaType.APPLICATION_OCTET_STREAM; 142 } 143 return contentType; 144 } 145 146 private static byte[] getResponseBody(ClientHttpResponse response) { 147 try { 148 return FileCopyUtils.copyToByteArray(response.getBody()); 149 } 150 catch (IOException ex) { 151 // ignore 152 } 153 return new byte[0]; 154 } 155}