Add Resource.readableChannel()

Added readableChannel() to Resource, which returns a
java.nio.ReadableByteChannel. The default implementation uses
Channels.newChannel() to create a channel based on what is returned from
getInputStream(). Subclasses have more effecient, file-based
implementations.

Issue: SPR-14698
This commit is contained in:
Arjen Poutsma
2016-09-12 19:18:10 +02:00
committed by Rossen Stoyanchev
parent 06395f41cb
commit c6a61e0d85
8 changed files with 126 additions and 4 deletions

View File

@@ -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<Resource> {
protected Flux<DataBuffer> encode(Resource resource, DataBufferFactory dataBufferFactory,
ResolvableType type, MimeType mimeType, Map<String, Object> hints) throws IOException {
InputStream is = resource.getInputStream();
return DataBufferUtils.read(is, dataBufferFactory, bufferSize);
ReadableByteChannel channel = resource.readableChannel();
return DataBufferUtils.read(channel, dataBufferFactory, bufferSize);
}
}

View File

@@ -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() {

View File

@@ -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

View File

@@ -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.
*/

View File

@@ -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.
*/

View File

@@ -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}.
* <p>It is expected that each call creates a <i>fresh</i> channel.
* <p>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

View File

@@ -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();
}
}

View File

@@ -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();
}
}
}
}