Browse Source

Expose getFilePath() on Resource interface for consistent NIO support

Closes gh-35435
pull/35447/head
Juergen Hoeller 3 months ago
parent
commit
27221581a1
  1. 2
      spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.java
  2. 8
      spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java
  3. 19
      spring-core/src/main/java/org/springframework/core/io/PathResource.java
  4. 24
      spring-core/src/main/java/org/springframework/core/io/Resource.java
  5. 7
      spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java
  6. 15
      spring-core/src/test/java/org/springframework/core/io/PathResourceTests.java
  7. 12
      spring-core/src/test/java/org/springframework/core/io/ResourceTests.java
  8. 15
      spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java
  9. 6
      spring-webflux/src/main/java/org/springframework/web/reactive/resource/EncodedResourceResolver.java
  10. 6
      spring-webflux/src/main/java/org/springframework/web/reactive/resource/VersionResourceResolver.java
  11. 6
      spring-webmvc/src/main/java/org/springframework/web/servlet/resource/EncodedResourceResolver.java
  12. 6
      spring-webmvc/src/main/java/org/springframework/web/servlet/resource/VersionResourceResolver.java

2
spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.java

@ -108,7 +108,7 @@ public class PathEditor extends PropertyEditorSupport { @@ -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 + ": " +

8
spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java

@ -292,6 +292,14 @@ public class FileSystemResource extends AbstractResource implements WritableReso @@ -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

19
spring-core/src/main/java/org/springframework/core/io/PathResource.java

@ -219,15 +219,16 @@ public class PathResource extends AbstractResource implements WritableResource { @@ -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;
}
/**

24
spring-core/src/main/java/org/springframework/core/io/Resource.java

@ -25,6 +25,7 @@ import java.net.URL; @@ -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 { @@ -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 { @@ -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.

7
spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java

@ -16,7 +16,6 @@ @@ -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 { @@ -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 { @@ -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));
}

15
spring-core/src/test/java/org/springframework/core/io/PathResourceTests.java

@ -186,10 +186,11 @@ class PathResourceTests { @@ -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 { @@ -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 { @@ -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 { @@ -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);

12
spring-core/src/test/java/org/springframework/core/io/ResourceTests.java

@ -55,6 +55,8 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -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 { @@ -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());

15
spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java

@ -16,8 +16,9 @@ @@ -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> { @@ -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
spring-webflux/src/main/java/org/springframework/web/reactive/resource/EncodedResourceResolver.java

@ -23,6 +23,7 @@ import java.net.URI; @@ -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 { @@ -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
spring-webflux/src/main/java/org/springframework/web/reactive/resource/VersionResourceResolver.java

@ -23,6 +23,7 @@ import java.net.URI; @@ -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 { @@ -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
spring-webmvc/src/main/java/org/springframework/web/servlet/resource/EncodedResourceResolver.java

@ -23,6 +23,7 @@ import java.net.URI; @@ -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 { @@ -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
spring-webmvc/src/main/java/org/springframework/web/servlet/resource/VersionResourceResolver.java

@ -23,6 +23,7 @@ import java.net.URI; @@ -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 { @@ -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();

Loading…
Cancel
Save