Skip to content

Commit 746ba02

Browse files
author
Vincent Potucek
committed
use try-with-resources statement in ResourceRegionHttpMessageConverter
Signed-off-by: Vincent Potucek <[email protected]>
1 parent d8ac3ff commit 746ba02

File tree

2 files changed

+176
-12
lines changed

2 files changed

+176
-12
lines changed

spring-web/src/main/java/org/springframework/http/converter/ResourceRegionHttpMessageConverter.java

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -173,19 +173,10 @@ protected void writeResourceRegion(ResourceRegion region, HttpOutputMessage outp
173173
responseHeaders.add("Content-Range", "bytes " + start + '-' + end + '/' + resourceLength);
174174
responseHeaders.setContentLength(rangeLength);
175175

176-
InputStream in = region.getResource().getInputStream();
177-
// We cannot use try-with-resources here for the InputStream, since we have
178-
// custom handling of the close() method in a finally-block.
179-
try {
176+
try (InputStream in = region.getResource().getInputStream()) {
180177
StreamUtils.copyRange(in, outputMessage.getBody(), start, end);
181-
}
182-
finally {
183-
try {
184-
in.close();
185-
}
186-
catch (IOException ex) {
187-
// ignore
188-
}
178+
} catch (IOException ex) {
179+
// ignore
189180
}
190181
}
191182

spring-web/src/test/java/org/springframework/http/converter/ResourceRegionHttpMessageConverterTests.java

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.http.converter;
1818

1919
import java.io.ByteArrayInputStream;
20+
import java.io.IOException;
2021
import java.lang.reflect.Type;
2122
import java.nio.charset.StandardCharsets;
2223
import java.util.ArrayList;
@@ -27,6 +28,7 @@
2728

2829
import org.springframework.core.ParameterizedTypeReference;
2930
import org.springframework.core.io.ClassPathResource;
31+
import org.springframework.core.io.InputStreamResource;
3032
import org.springframework.core.io.Resource;
3133
import org.springframework.core.io.support.ResourceRegion;
3234
import org.springframework.http.HttpHeaders;
@@ -36,6 +38,7 @@
3638
import org.springframework.web.testfixture.http.MockHttpOutputMessage;
3739

3840
import static org.assertj.core.api.Assertions.assertThat;
41+
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
3942
import static org.mockito.BDDMockito.given;
4043
import static org.mockito.Mockito.mock;
4144

@@ -198,4 +201,174 @@ public void applicationOctetStreamDefaultContentType() throws Exception {
198201
assertThat(outputMessage.getBodyAsString(StandardCharsets.UTF_8)).isEqualTo("Spring");
199202
}
200203

204+
@Test
205+
void shouldNotWriteForUnsupportedType() {
206+
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
207+
Object unsupportedBody = new Object();
208+
209+
assertThatThrownBy(() -> converter.write(unsupportedBody, null, outputMessage))
210+
.isInstanceOfAny(ClassCastException.class, HttpMessageNotWritableException.class);
211+
}
212+
213+
@Test
214+
void shouldGetDefaultContentTypeForResourceRegion() {
215+
Resource resource = new ClassPathResource("byterangeresource.txt", getClass());
216+
ResourceRegion region = new ResourceRegion(resource, 0, 10);
217+
218+
MediaType contentType = converter.getDefaultContentType(region);
219+
assertThat(contentType).isEqualTo(MediaType.TEXT_PLAIN);
220+
}
221+
222+
@Test
223+
void shouldGetDefaultOctetStreamContentTypeForUnknownResource() {
224+
Resource resource = mock(Resource.class);
225+
given(resource.getFilename()).willReturn("unknown.dat");
226+
ResourceRegion region = new ResourceRegion(resource, 0, 10);
227+
228+
MediaType contentType = converter.getDefaultContentType(region);
229+
assertThat(contentType).isEqualTo(MediaType.APPLICATION_OCTET_STREAM);
230+
}
231+
232+
@Test
233+
void shouldSupportRepeatableWritesForNonInputStreamResource() {
234+
Resource resource = new ClassPathResource("byterangeresource.txt", getClass());
235+
ResourceRegion region = new ResourceRegion(resource, 0, 10);
236+
237+
assertThat(converter.supportsRepeatableWrites(region)).isTrue();
238+
}
239+
240+
@Test
241+
void shouldNotSupportRepeatableWritesForInputStreamResource() {
242+
Resource resource = mock(InputStreamResource.class);
243+
ResourceRegion region = new ResourceRegion(resource, 0, 10);
244+
245+
assertThat(converter.supportsRepeatableWrites(region)).isFalse();
246+
}
247+
248+
@Test
249+
void shouldHandleIOExceptionWhenWritingRegion() throws Exception {
250+
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
251+
Resource resource = mock(Resource.class);
252+
given(resource.contentLength()).willReturn(10L);
253+
given(resource.getInputStream()).willThrow(new IOException("Simulated error"));
254+
ResourceRegion region = new ResourceRegion(resource, 0, 5);
255+
256+
// Should not throw exception
257+
converter.write(region, MediaType.TEXT_PLAIN, outputMessage);
258+
259+
// Verify Content-Range header is set correctly
260+
assertThat(outputMessage.getHeaders().getFirst(HttpHeaders.CONTENT_RANGE))
261+
.isEqualTo("bytes 0-4/10");
262+
263+
// Verify no content was written due to the IOException
264+
assertThat(outputMessage.getBodyAsString(StandardCharsets.UTF_8)).isEmpty();
265+
}
266+
@Test
267+
void shouldHandleIOExceptionWhenWritingRegionCollection() throws Exception {
268+
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
269+
Resource resource = mock(Resource.class);
270+
given(resource.contentLength()).willReturn(10L);
271+
given(resource.getInputStream()).willThrow(new IOException("Simulated error"));
272+
ResourceRegion region = new ResourceRegion(resource, 0, 5);
273+
List<ResourceRegion> regions = Collections.singletonList(region);
274+
275+
// Should not throw exception
276+
converter.write(regions, MediaType.TEXT_PLAIN, outputMessage);
277+
278+
assertThat(outputMessage.getHeaders().getContentType().toString())
279+
.isEqualTo("text/plain");
280+
}
281+
282+
@Test
283+
void shouldHandleNullResourceRegion() {
284+
assertThatThrownBy(() -> converter.write(null, null, new MockHttpOutputMessage()))
285+
.isInstanceOf(NullPointerException.class);
286+
}
287+
288+
@Test
289+
void shouldHandleInvalidRangeBeyondResourceLength() throws Exception {
290+
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
291+
Resource resource = new ClassPathResource("byterangeresource.txt", getClass());
292+
ResourceRegion region = new ResourceRegion(resource, 35, 10); // Goes beyond resource length
293+
294+
converter.write(region, MediaType.TEXT_PLAIN, outputMessage);
295+
296+
assertThat(outputMessage.getHeaders().getFirst(HttpHeaders.CONTENT_RANGE))
297+
.isEqualTo("bytes 35-38/39");
298+
assertThat(outputMessage.getBodyAsString(StandardCharsets.UTF_8)).hasSize(4);
299+
}
300+
301+
@Test
302+
void shouldHandleZeroLengthResourceRegion() throws Exception {
303+
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
304+
Resource resource = new ClassPathResource("byterangeresource.txt", getClass());
305+
ResourceRegion region = new ResourceRegion(resource, 5, 0);
306+
307+
converter.write(region, MediaType.TEXT_PLAIN, outputMessage);
308+
309+
assertThat(outputMessage.getHeaders().getFirst(HttpHeaders.CONTENT_RANGE))
310+
.isEqualTo("bytes 5-4/39");
311+
assertThat(outputMessage.getBodyAsString(StandardCharsets.UTF_8)).isEmpty();
312+
}
313+
314+
@Test
315+
void shouldHandleMultipleResourcesInCollection() throws Exception {
316+
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
317+
Resource resource1 = new ClassPathResource("byterangeresource.txt", getClass());
318+
Resource resource2 = new ClassPathResource("byterangeresource.txt", getClass());
319+
List<ResourceRegion> regions = List.of(
320+
new ResourceRegion(resource1, 0, 5), // "Spring" is 6 bytes (0-5)
321+
new ResourceRegion(resource2, 7, 8) // "Framework" is 8 bytes (7-14)
322+
);
323+
324+
converter.write(regions, MediaType.TEXT_PLAIN, outputMessage);
325+
326+
String content = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
327+
328+
// Verify multipart structure
329+
assertThat(content).contains("Content-Type: text/plain");
330+
assertThat(content).contains("Content-Range: bytes 7-14/39");
331+
332+
// Verify partial content (note the ranges only include parts of the words)
333+
assertThat(content).contains("Sprin"); // First 5 bytes of "Spring" (0-4)
334+
assertThat(content).contains("Framewor"); // First 7 bytes of "Framework" (7-13)
335+
}
336+
@Test
337+
void shouldHandleNullContentType() throws Exception {
338+
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
339+
Resource resource = new ClassPathResource("byterangeresource.txt", getClass());
340+
ResourceRegion region = new ResourceRegion(resource, 0, 5);
341+
342+
converter.write(region, null, outputMessage);
343+
344+
assertThat(outputMessage.getHeaders().getContentType()).isEqualTo(MediaType.TEXT_PLAIN);
345+
}
346+
347+
@Test
348+
void shouldHandleUnreadableResource() throws Exception {
349+
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
350+
Resource resource = mock(Resource.class);
351+
given(resource.contentLength()).willReturn(10L);
352+
given(resource.getInputStream()).willThrow(new IOException("Cannot read resource"));
353+
ResourceRegion region = new ResourceRegion(resource, 0, 5);
354+
355+
converter.write(region, MediaType.TEXT_PLAIN, outputMessage);
356+
357+
assertThat(outputMessage.getHeaders().getFirst(HttpHeaders.CONTENT_RANGE))
358+
.isEqualTo("bytes 0-4/10");
359+
assertThat(outputMessage.getBodyAsString(StandardCharsets.UTF_8)).isEmpty();
360+
}
361+
362+
@Test
363+
void shouldHandleCanWriteWithNullType() {
364+
assertThat(converter.canWrite(null, null, null)).isFalse();
365+
}
366+
367+
@Test
368+
void shouldHandleCanWriteWithNonParameterizedType() {
369+
assertThat(converter.canWrite(ResourceRegion.class, null, null)).isTrue();
370+
assertThat(converter.canWrite(String.class, null, null)).isFalse();
371+
}
372+
373+
201374
}

0 commit comments

Comments
 (0)