diff --git a/spring-core/src/main/java/org/springframework/core/codec/ResourceEncoder.java b/spring-core/src/main/java/org/springframework/core/codec/ResourceEncoder.java index 796b7cdb22..5f5fab3443 100644 --- a/spring-core/src/main/java/org/springframework/core/codec/ResourceEncoder.java +++ b/spring-core/src/main/java/org/springframework/core/codec/ResourceEncoder.java @@ -17,7 +17,7 @@ package org.springframework.core.codec; import java.io.IOException; -import java.io.InputStream; +import java.nio.channels.ReadableByteChannel; import java.util.Map; import reactor.core.publisher.Flux; @@ -66,8 +66,8 @@ public class ResourceEncoder extends AbstractSingleValueEncoder { protected Flux encode(Resource resource, DataBufferFactory dataBufferFactory, ResolvableType type, MimeType mimeType, Map hints) throws IOException { - InputStream is = resource.getInputStream(); - return DataBufferUtils.read(is, dataBufferFactory, bufferSize); + ReadableByteChannel channel = resource.readableChannel(); + return DataBufferUtils.read(channel, dataBufferFactory, bufferSize); } } diff --git a/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java b/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java index b811e1ab61..21797cd5ce 100644 --- a/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java @@ -17,12 +17,14 @@ package org.springframework.core.io; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.net.URLConnection; +import java.nio.channels.ReadableByteChannel; import org.springframework.util.ResourceUtils; @@ -115,6 +117,22 @@ public abstract class AbstractFileResolvingResource extends AbstractResource { return ResourceUtils.getFile(uri, getDescription()); } + /** + * This implementation returns a FileChannel for the given URI-identified + * resource, provided that it refers to a file in the file system. + * @since 5.0 + * @see #getFile(URI) + */ + @Override + public ReadableByteChannel readableChannel() throws IOException { + if (isFile()) { + return new FileInputStream(getFile()).getChannel(); + } + else { + return super.readableChannel(); + } + } + @Override public boolean exists() { diff --git a/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java b/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java index 627704e49f..15b4b8f882 100644 --- a/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java @@ -23,6 +23,8 @@ import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; import org.springframework.core.NestedIOException; import org.springframework.util.Assert; @@ -122,6 +124,15 @@ public abstract class AbstractResource implements Resource { throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path"); } + /** + * This implementation returns {@link Channels#newChannel(InputStream)} with the result of + * {@link #getInputStream()}. + */ + @Override + public ReadableByteChannel readableChannel() throws IOException { + return Channels.newChannel(getInputStream()); + } + /** * This implementation reads the entire InputStream to calculate the * content length. Subclasses will almost always be able to provide diff --git a/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java b/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java index 3e7966debf..b9fc1310ce 100644 --- a/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java @@ -24,6 +24,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URL; +import java.nio.channels.ReadableByteChannel; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -168,6 +169,15 @@ public class FileSystemResource extends AbstractResource implements WritableReso return this.file; } + /** + * This implementation opens a FileChannel for the underlying file. + * @see java.nio.channels.FileChannel + */ + @Override + public ReadableByteChannel readableChannel() throws IOException { + return new FileInputStream(this.file).getChannel(); + } + /** * This implementation returns the underlying File's length. */ diff --git a/spring-core/src/main/java/org/springframework/core/io/PathResource.java b/spring-core/src/main/java/org/springframework/core/io/PathResource.java index 1246e177c8..3fd2a2bc17 100644 --- a/spring-core/src/main/java/org/springframework/core/io/PathResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/PathResource.java @@ -23,10 +23,12 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URL; +import java.nio.channels.ReadableByteChannel; import java.nio.file.Files; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; import org.springframework.util.Assert; @@ -194,6 +196,15 @@ public class PathResource extends AbstractResource implements WritableResource { } } + /** + * This implementation opens a InputStream for the underlying file. + * @see java.nio.file.spi.FileSystemProvider#newInputStream(Path, OpenOption...) + */ + @Override + public ReadableByteChannel readableChannel() throws IOException { + return Files.newByteChannel(this.path, StandardOpenOption.READ); + } + /** * This implementation returns the underlying File's length. */ diff --git a/spring-core/src/main/java/org/springframework/core/io/Resource.java b/spring-core/src/main/java/org/springframework/core/io/Resource.java index e5db2a878e..1dbffa90e9 100644 --- a/spring-core/src/main/java/org/springframework/core/io/Resource.java +++ b/spring-core/src/main/java/org/springframework/core/io/Resource.java @@ -18,8 +18,11 @@ package org.springframework.core.io; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; /** * Interface for a resource descriptor that abstracts from the actual @@ -111,6 +114,19 @@ public interface Resource extends InputStreamSource { */ File getFile() throws IOException; + /** + * Return a {@link ReadableByteChannel}. + *

It is expected that each call creates a fresh channel. + *

The default implementation returns {@link Channels#newChannel(InputStream)} with the + * result of {@link #getInputStream()}. + * @return the byte channel for the underlying resource (must not be {@code null}) + * @throws IOException if the channel could not be opened + * @since 5.0 + */ + default ReadableByteChannel readableChannel() throws IOException { + return Channels.newChannel(getInputStream()); + } + /** * Determine the content length for this resource. * @throws IOException if the resource cannot be resolved diff --git a/spring-core/src/test/java/org/springframework/core/io/PathResourceTests.java b/spring-core/src/test/java/org/springframework/core/io/PathResourceTests.java index 8b6361de33..112dadfe2c 100644 --- a/spring-core/src/test/java/org/springframework/core/io/PathResourceTests.java +++ b/spring-core/src/test/java/org/springframework/core/io/PathResourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * 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. @@ -19,6 +19,9 @@ package org.springframework.core.io; import java.io.File; import java.io.FileNotFoundException; import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; @@ -42,6 +45,7 @@ import static org.mockito.BDDMockito.*; * @author Nicholas Williams * @author Stephane Nicoll * @author Juergen Hoeller + * @author Arjen Poutsma */ public class PathResourceTests { @@ -287,4 +291,36 @@ public class PathResourceTests { resource.getOutputStream(); } + @Test + public void getReadableByteChannel() throws Exception { + PathResource resource = new PathResource(TEST_FILE); + ReadableByteChannel channel = null; + try { + channel = resource.readableChannel(); + ByteBuffer buffer = ByteBuffer.allocate((int) resource.contentLength()); + channel.read(buffer); + buffer.rewind(); + assertThat(buffer.limit(), greaterThan(0)); + } + finally { + if (channel != null) { + channel.close(); + } + } + } + + @Test + public void getReadableByteChannelForDir() throws Exception { + PathResource resource = new PathResource(TEST_DIR); + thrown.expect(NoSuchFileException.class); + resource.readableChannel(); + } + + @Test + public void getReadableByteChannelDoesNotExist() throws Exception { + PathResource resource = new PathResource(NON_EXISTING_FILE); + thrown.expect(NoSuchFileException.class); + resource.readableChannel(); + } + } diff --git a/spring-core/src/test/java/org/springframework/core/io/ResourceTests.java b/spring-core/src/test/java/org/springframework/core/io/ResourceTests.java index bb4949699a..bb487fad98 100644 --- a/spring-core/src/test/java/org/springframework/core/io/ResourceTests.java +++ b/spring-core/src/test/java/org/springframework/core/io/ResourceTests.java @@ -21,6 +21,8 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; import java.util.HashSet; import org.junit.Ignore; @@ -254,4 +256,22 @@ public class ResourceTests { resource.contentLength(); } + @Test + public void testGetReadableByteChannel() throws IOException { + Resource resource = new FileSystemResource(getClass().getResource("Resource.class").getFile()); + ReadableByteChannel channel = null; + try { + channel = resource.readableChannel(); + ByteBuffer buffer = ByteBuffer.allocate((int) resource.contentLength()); + channel.read(buffer); + buffer.rewind(); + assertTrue(buffer.limit() > 0); + } + finally { + if (channel != null) { + channel.close(); + } + } + } + }