mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-10-03 21:49:03 +00:00
Expose getFilePath() on Resource interface for consistent NIO support
Closes gh-35435
This commit is contained in:
+1
-1
@@ -108,7 +108,7 @@ public class PathEditor extends PropertyEditorSupport {
|
||||
}
|
||||
else {
|
||||
try {
|
||||
setValue(resource.getFile().toPath());
|
||||
setValue(resource.getFilePath());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
String msg = "Could not resolve \"" + text + "\" to 'java.nio.file.Path' for " + resource + ": " +
|
||||
|
||||
@@ -292,6 +292,14 @@ public class FileSystemResource extends AbstractResource implements WritableReso
|
||||
return (this.file != null ? this.file : this.filePath.toFile());
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation returns the underlying NIO Path reference.
|
||||
*/
|
||||
@Override
|
||||
public Path getFilePath() {
|
||||
return this.filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation opens a FileChannel for the underlying file.
|
||||
* @see java.nio.channels.FileChannel
|
||||
|
||||
@@ -219,15 +219,16 @@ public class PathResource extends AbstractResource implements WritableResource {
|
||||
* This implementation returns the underlying {@link File} reference.
|
||||
*/
|
||||
@Override
|
||||
public File getFile() throws IOException {
|
||||
try {
|
||||
return this.path.toFile();
|
||||
}
|
||||
catch (UnsupportedOperationException ex) {
|
||||
// Only paths on the default file system can be converted to a File:
|
||||
// Do exception translation for cases where conversion is not possible.
|
||||
throw new FileNotFoundException(this.path + " cannot be resolved to absolute file path");
|
||||
}
|
||||
public File getFile() {
|
||||
return this.path.toFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation returns the underlying {@link Path} reference.
|
||||
*/
|
||||
@Override
|
||||
public Path getFilePath() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.net.URL;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@@ -91,11 +92,13 @@ public interface Resource extends InputStreamSource {
|
||||
|
||||
/**
|
||||
* Determine whether this resource represents a file in a file system.
|
||||
* <p>A value of {@code true} strongly suggests (but does not guarantee)
|
||||
* that a {@link #getFile()} call will succeed.
|
||||
* <p>A value of {@code true} suggests (but does not guarantee) that a
|
||||
* {@link #getFile()} call will succeed. For non-default file systems,
|
||||
* {@link #getFilePath()} is the more reliable follow-up call.
|
||||
* <p>This is conservatively {@code false} by default.
|
||||
* @since 5.0
|
||||
* @see #getFile()
|
||||
* @see #getFilePath()
|
||||
*/
|
||||
default boolean isFile() {
|
||||
return false;
|
||||
@@ -118,13 +121,26 @@ public interface Resource extends InputStreamSource {
|
||||
|
||||
/**
|
||||
* Return a File handle for this resource.
|
||||
* @throws java.io.FileNotFoundException if the resource cannot be resolved as
|
||||
* absolute file path, i.e. if the resource is not available in a file system
|
||||
* <p>Note: This only works for files in the default file system.
|
||||
* @throws UnsupportedOperationException if the resource is a file but cannot be
|
||||
* exposed as a {@code java.io.File}; try {@link #getFilePath()} instead
|
||||
* @throws java.io.FileNotFoundException if the resource cannot be resolved as a file
|
||||
* @throws IOException in case of general resolution/reading failures
|
||||
* @see #getInputStream()
|
||||
*/
|
||||
File getFile() throws IOException;
|
||||
|
||||
/**
|
||||
* Return an NIO Path handle for this resource.
|
||||
* <p>Note: This works for files in non-default file systems as well.
|
||||
* @throws java.io.FileNotFoundException if the resource cannot be resolved as a file
|
||||
* @throws IOException in case of general resolution/reading failures
|
||||
* @since 7.0
|
||||
*/
|
||||
default Path getFilePath() throws IOException {
|
||||
return getFile().toPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link ReadableByteChannel}.
|
||||
* <p>It is expected that each call creates a <i>fresh</i> channel.
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.core.io.buffer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@@ -223,9 +222,9 @@ public abstract class DataBufferUtils {
|
||||
|
||||
try {
|
||||
if (resource.isFile()) {
|
||||
File file = resource.getFile();
|
||||
Path filePath = resource.getFilePath();
|
||||
return readAsynchronousFileChannel(
|
||||
() -> AsynchronousFileChannel.open(file.toPath(), StandardOpenOption.READ),
|
||||
() -> AsynchronousFileChannel.open(filePath, StandardOpenOption.READ),
|
||||
position, bufferFactory, bufferSize);
|
||||
}
|
||||
}
|
||||
@@ -233,7 +232,7 @@ public abstract class DataBufferUtils {
|
||||
// fallback to resource.readableChannel(), below
|
||||
}
|
||||
Flux<DataBuffer> result = readByteChannel(resource::readableChannel, bufferFactory, bufferSize);
|
||||
return position == 0 ? result : skipUntilByteCount(result, position);
|
||||
return (position == 0 ? result : skipUntilByteCount(result, position));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -186,10 +186,11 @@ class PathResourceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFile() throws IOException {
|
||||
void getFile() {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
File file = new File(TEST_FILE);
|
||||
assertThat(resource.getFile().getAbsoluteFile()).isEqualTo(file.getAbsoluteFile());
|
||||
assertThat(resource.getFilePath()).isEqualTo(file.toPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -198,7 +199,7 @@ class PathResourceTests {
|
||||
given(path.normalize()).willReturn(path);
|
||||
given(path.toFile()).willThrow(new UnsupportedOperationException());
|
||||
PathResource resource = new PathResource(path);
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(resource::getFile);
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(resource::getFile);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -236,13 +237,13 @@ class PathResourceTests {
|
||||
|
||||
@Test
|
||||
void filename() {
|
||||
Resource resource = new PathResource(TEST_FILE);
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
assertThat(resource.getFilename()).isEqualTo("example.properties");
|
||||
}
|
||||
|
||||
@Test
|
||||
void description() {
|
||||
Resource resource = new PathResource(TEST_FILE);
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
assertThat(resource.getDescription()).contains("path [");
|
||||
assertThat(resource.getDescription()).contains(TEST_FILE);
|
||||
}
|
||||
@@ -261,9 +262,9 @@ class PathResourceTests {
|
||||
|
||||
@Test
|
||||
void equalsAndHashCode() {
|
||||
Resource resource1 = new PathResource(TEST_FILE);
|
||||
Resource resource2 = new PathResource(TEST_FILE);
|
||||
Resource resource3 = new PathResource(TEST_DIR);
|
||||
PathResource resource1 = new PathResource(TEST_FILE);
|
||||
PathResource resource2 = new PathResource(TEST_FILE);
|
||||
PathResource resource3 = new PathResource(TEST_DIR);
|
||||
assertThat(resource1).isEqualTo(resource1);
|
||||
assertThat(resource1).isEqualTo(resource2);
|
||||
assertThat(resource2).isEqualTo(resource1);
|
||||
|
||||
@@ -55,6 +55,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.junit.jupiter.params.provider.Arguments.argumentSet;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for various {@link Resource} implementations.
|
||||
@@ -268,6 +270,16 @@ class ResourceTests {
|
||||
assertThat(relative).isEqualTo(new FileSystemResource("dir/subdir"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFilePath() throws Exception {
|
||||
Path path = mock();
|
||||
given(path.normalize()).willReturn(path);
|
||||
given(path.toFile()).willThrow(new UnsupportedOperationException());
|
||||
Resource resource = new FileSystemResource(path);
|
||||
assertThat(resource.getFilePath()).isSameAs(path);
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(resource::getFile);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readableChannelProvidesContent() throws Exception {
|
||||
Resource resource = new FileSystemResource(getClass().getResource("ResourceTests.class").getFile());
|
||||
|
||||
+8
-7
@@ -16,8 +16,9 @@
|
||||
|
||||
package org.springframework.http.codec;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -193,17 +194,17 @@ public class ResourceHttpMessageWriter implements HttpMessageWriter<Resource> {
|
||||
|
||||
if (message instanceof ZeroCopyHttpOutputMessage zeroCopyHttpOutputMessage && resource.isFile()) {
|
||||
try {
|
||||
File file = resource.getFile();
|
||||
long pos = region != null ? region.getPosition() : 0;
|
||||
long count = region != null ? region.getCount() : file.length();
|
||||
Path filePath = resource.getFilePath();
|
||||
long pos = (region != null ? region.getPosition() : 0);
|
||||
long count = (region != null ? region.getCount() : Files.size(filePath));
|
||||
if (logger.isDebugEnabled()) {
|
||||
String formatted = region != null ? "region " + pos + "-" + (count) + " of " : "";
|
||||
logger.debug(Hints.getLogPrefix(hints) + "Zero-copy " + formatted + "[" + resource + "]");
|
||||
}
|
||||
return zeroCopyHttpOutputMessage.writeWith(file, pos, count);
|
||||
return zeroCopyHttpOutputMessage.writeWith(filePath, pos, count);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// should not happen
|
||||
catch (IOException ignore) {
|
||||
// returning null below leads to fallback code path
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
+6
@@ -23,6 +23,7 @@ import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -240,6 +241,11 @@ public class EncodedResourceResolver extends AbstractResourceResolver {
|
||||
return this.encoded.getFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getFilePath() throws IOException {
|
||||
return this.encoded.getFilePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return this.encoded.getInputStream();
|
||||
|
||||
+6
@@ -23,6 +23,7 @@ import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
@@ -283,6 +284,11 @@ public class VersionResourceResolver extends AbstractResourceResolver {
|
||||
return this.original.getFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getFilePath() throws IOException {
|
||||
return this.original.getFilePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return this.original.getInputStream();
|
||||
|
||||
+6
@@ -23,6 +23,7 @@ import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -236,6 +237,11 @@ public class EncodedResourceResolver extends AbstractResourceResolver {
|
||||
return this.encoded.getFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getFilePath() throws IOException {
|
||||
return this.encoded.getFilePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return this.encoded.getInputStream();
|
||||
|
||||
+6
@@ -23,6 +23,7 @@ import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
@@ -279,6 +280,11 @@ public class VersionResourceResolver extends AbstractResourceResolver {
|
||||
return this.original.getFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getFilePath() throws IOException {
|
||||
return this.original.getFilePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return this.original.getInputStream();
|
||||
|
||||
Reference in New Issue
Block a user