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.http.converter; 018 019import java.io.FileNotFoundException; 020import java.io.IOException; 021import java.io.InputStream; 022 023import org.springframework.core.io.ByteArrayResource; 024import org.springframework.core.io.InputStreamResource; 025import org.springframework.core.io.Resource; 026import org.springframework.http.HttpInputMessage; 027import org.springframework.http.HttpOutputMessage; 028import org.springframework.http.MediaType; 029import org.springframework.http.MediaTypeFactory; 030import org.springframework.lang.Nullable; 031import org.springframework.util.StreamUtils; 032 033/** 034 * Implementation of {@link HttpMessageConverter} that can read/write {@link Resource Resources} 035 * and supports byte range requests. 036 * 037 * <p>By default, this converter can read all media types. The {@link MediaTypeFactory} is used 038 * to determine the {@code Content-Type} of written resources. 039 * 040 * @author Arjen Poutsma 041 * @author Juergen Hoeller 042 * @author Kazuki Shimizu 043 * @since 3.0.2 044 */ 045public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<Resource> { 046 047 private final boolean supportsReadStreaming; 048 049 050 /** 051 * Create a new instance of the {@code ResourceHttpMessageConverter} 052 * that supports read streaming, i.e. can convert an 053 * {@code HttpInputMessage} to {@code InputStreamResource}. 054 */ 055 public ResourceHttpMessageConverter() { 056 super(MediaType.ALL); 057 this.supportsReadStreaming = true; 058 } 059 060 /** 061 * Create a new instance of the {@code ResourceHttpMessageConverter}. 062 * @param supportsReadStreaming whether the converter should support 063 * read streaming, i.e. convert to {@code InputStreamResource} 064 * @since 5.0 065 */ 066 public ResourceHttpMessageConverter(boolean supportsReadStreaming) { 067 super(MediaType.ALL); 068 this.supportsReadStreaming = supportsReadStreaming; 069 } 070 071 072 @Override 073 protected boolean supports(Class<?> clazz) { 074 return Resource.class.isAssignableFrom(clazz); 075 } 076 077 @Override 078 protected Resource readInternal(Class<? extends Resource> clazz, HttpInputMessage inputMessage) 079 throws IOException, HttpMessageNotReadableException { 080 081 if (this.supportsReadStreaming && InputStreamResource.class == clazz) { 082 return new InputStreamResource(inputMessage.getBody()) { 083 @Override 084 public String getFilename() { 085 return inputMessage.getHeaders().getContentDisposition().getFilename(); 086 } 087 @Override 088 public long contentLength() throws IOException { 089 long length = inputMessage.getHeaders().getContentLength(); 090 return (length != -1 ? length : super.contentLength()); 091 } 092 }; 093 } 094 else if (Resource.class == clazz || ByteArrayResource.class.isAssignableFrom(clazz)) { 095 byte[] body = StreamUtils.copyToByteArray(inputMessage.getBody()); 096 return new ByteArrayResource(body) { 097 @Override 098 @Nullable 099 public String getFilename() { 100 return inputMessage.getHeaders().getContentDisposition().getFilename(); 101 } 102 }; 103 } 104 else { 105 throw new HttpMessageNotReadableException("Unsupported resource class: " + clazz, inputMessage); 106 } 107 } 108 109 @Override 110 protected MediaType getDefaultContentType(Resource resource) { 111 return MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM); 112 } 113 114 @Override 115 protected Long getContentLength(Resource resource, @Nullable MediaType contentType) throws IOException { 116 // Don't try to determine contentLength on InputStreamResource - cannot be read afterwards... 117 // Note: custom InputStreamResource subclasses could provide a pre-calculated content length! 118 if (InputStreamResource.class == resource.getClass()) { 119 return null; 120 } 121 long contentLength = resource.contentLength(); 122 return (contentLength < 0 ? null : contentLength); 123 } 124 125 @Override 126 protected void writeInternal(Resource resource, HttpOutputMessage outputMessage) 127 throws IOException, HttpMessageNotWritableException { 128 129 writeContent(resource, outputMessage); 130 } 131 132 protected void writeContent(Resource resource, HttpOutputMessage outputMessage) 133 throws IOException, HttpMessageNotWritableException { 134 try { 135 InputStream in = resource.getInputStream(); 136 try { 137 StreamUtils.copy(in, outputMessage.getBody()); 138 } 139 catch (NullPointerException ex) { 140 // ignore, see SPR-13620 141 } 142 finally { 143 try { 144 in.close(); 145 } 146 catch (Throwable ex) { 147 // ignore, see SPR-12999 148 } 149 } 150 } 151 catch (FileNotFoundException ex) { 152 // ignore, see SPR-12999 153 } 154 } 155 156}