diff --git a/spring-web/src/main/java/org/springframework/http/converter/ActivationMediaTypeFactory.java b/spring-web/src/main/java/org/springframework/http/converter/ActivationMediaTypeFactory.java new file mode 100644 index 00000000000..0961fbdc80b --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/converter/ActivationMediaTypeFactory.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2017 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.http.converter; + +import java.io.IOException; +import java.io.InputStream; +import javax.activation.FileTypeMap; +import javax.activation.MimetypesFileTypeMap; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; +import org.springframework.util.StringUtils; + +/** + * Resolve {@code MediaType} for a given {@link Resource} using JAF. + */ +class ActivationMediaTypeFactory { + + private static final FileTypeMap fileTypeMap; + + static { + fileTypeMap = loadFileTypeMapFromContextSupportModule(); + } + + private static FileTypeMap loadFileTypeMapFromContextSupportModule() { + // See if we can find the extended mime.types from the context-support module... + Resource mappingLocation = new ClassPathResource("org/springframework/mail/javamail/mime.types"); + if (mappingLocation.exists()) { + InputStream inputStream = null; + try { + inputStream = mappingLocation.getInputStream(); + return new MimetypesFileTypeMap(inputStream); + } + catch (IOException ex) { + // ignore + } + finally { + if (inputStream != null) { + try { + inputStream.close(); + } + catch (IOException ex) { + // ignore + } + } + } + } + return FileTypeMap.getDefaultFileTypeMap(); + } + + public static MediaType getMediaType(Resource resource) { + String filename = resource.getFilename(); + if (filename != null) { + String mediaType = fileTypeMap.getContentType(filename); + if (StringUtils.hasText(mediaType)) { + return MediaType.parseMediaType(mediaType); + } + } + return null; + } +} diff --git a/spring-web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java index 93bd3a3a2aa..5de8f26c3fc 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java @@ -19,11 +19,8 @@ package org.springframework.http.converter; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import javax.activation.FileTypeMap; -import javax.activation.MimetypesFileTypeMap; import org.springframework.core.io.ByteArrayResource; -import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpInputMessage; @@ -31,7 +28,6 @@ import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.util.ClassUtils; import org.springframework.util.StreamUtils; -import org.springframework.util.StringUtils; /** * Implementation of {@link HttpMessageConverter} that can read and write {@link Resource Resources} @@ -131,54 +127,4 @@ public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter { + private static final boolean jafPresent = ClassUtils.isPresent( + "javax.activation.FileTypeMap", ResourceHttpMessageConverter.class.getClassLoader()); + public ResourceRegionHttpMessageConverter() { super(MediaType.ALL); } @@ -71,6 +75,23 @@ public class ResourceRegionHttpMessageConverter extends AbstractGenericHttpMessa return null; } + @Override + @SuppressWarnings("unchecked") + protected MediaType getDefaultContentType(Object object) { + if (jafPresent) { + if(object instanceof ResourceRegion) { + return ActivationMediaTypeFactory.getMediaType(((ResourceRegion) object).getResource()); + } + else { + Collection regions = (Collection) object; + if(regions.size() > 0) { + return ActivationMediaTypeFactory.getMediaType(regions.iterator().next().getResource()); + } + } + } + return MediaType.APPLICATION_OCTET_STREAM; + } + @Override public boolean canWrite(Class clazz, MediaType mediaType) { return canWrite(clazz, null, mediaType); diff --git a/spring-web/src/test/java/org/springframework/http/converter/ResourceRegionHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/ResourceRegionHttpMessageConverterTests.java index a4f9c46584e..3ea2f1aaa86 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/ResourceRegionHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/ResourceRegionHttpMessageConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -16,13 +16,17 @@ package org.springframework.http.converter; +import java.io.ByteArrayInputStream; import java.lang.reflect.Type; import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.hamcrest.Matchers; import org.junit.Test; +import org.mockito.BDDMockito; +import org.mockito.Mockito; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.io.ClassPathResource; @@ -34,8 +38,10 @@ import org.springframework.http.MediaType; import org.springframework.http.MockHttpOutputMessage; import org.springframework.util.StringUtils; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; /** * Test cases for {@link ResourceRegionHttpMessageConverter} class. @@ -139,4 +145,21 @@ public class ResourceRegionHttpMessageConverterTests { assertThat(ranges[15], is("resource content.")); } + @Test // SPR-15041 + public void applicationOctetStreamDefaultContentType() throws Exception { + MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); + ClassPathResource body = Mockito.mock(ClassPathResource.class); + BDDMockito.given(body.getFilename()).willReturn("spring.dat"); + BDDMockito.given(body.contentLength()).willReturn(12L); + BDDMockito.given(body.getInputStream()).willReturn(new ByteArrayInputStream("Spring Framework".getBytes())); + HttpRange range = HttpRange.createByteRange(0, 5); + ResourceRegion resourceRegion = range.toResourceRegion(body); + + converter.write(Collections.singletonList(resourceRegion), null, outputMessage); + + assertThat(outputMessage.getHeaders().getContentType(), is(MediaType.APPLICATION_OCTET_STREAM)); + assertThat(outputMessage.getHeaders().getFirst(HttpHeaders.CONTENT_RANGE), is("bytes 0-5/12")); + assertThat(outputMessage.getBodyAsString(Charset.forName("UTF-8")), is("Spring")); + } + } \ No newline at end of file