Skip to content

Commit 2722158

Browse files
committed
Expose getFilePath() on Resource interface for consistent NIO support
Closes gh-35435
1 parent 114c3f7 commit 2722158

File tree

12 files changed

+94
-32
lines changed

12 files changed

+94
-32
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ else if (nioPathCandidate && (!resource.isFile() || !resource.exists())) {
108108
}
109109
else {
110110
try {
111-
setValue(resource.getFile().toPath());
111+
setValue(resource.getFilePath());
112112
}
113113
catch (IOException ex) {
114114
String msg = "Could not resolve \"" + text + "\" to 'java.nio.file.Path' for " + resource + ": " +

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,14 @@ public File getFile() {
292292
return (this.file != null ? this.file : this.filePath.toFile());
293293
}
294294

295+
/**
296+
* This implementation returns the underlying NIO Path reference.
297+
*/
298+
@Override
299+
public Path getFilePath() {
300+
return this.filePath;
301+
}
302+
295303
/**
296304
* This implementation opens a FileChannel for the underlying file.
297305
* @see java.nio.channels.FileChannel

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

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -219,15 +219,16 @@ public boolean isFile() {
219219
* This implementation returns the underlying {@link File} reference.
220220
*/
221221
@Override
222-
public File getFile() throws IOException {
223-
try {
224-
return this.path.toFile();
225-
}
226-
catch (UnsupportedOperationException ex) {
227-
// Only paths on the default file system can be converted to a File:
228-
// Do exception translation for cases where conversion is not possible.
229-
throw new FileNotFoundException(this.path + " cannot be resolved to absolute file path");
230-
}
222+
public File getFile() {
223+
return this.path.toFile();
224+
}
225+
226+
/**
227+
* This implementation returns the underlying {@link Path} reference.
228+
*/
229+
@Override
230+
public Path getFilePath() {
231+
return this.path;
231232
}
232233

233234
/**

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

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.nio.channels.Channels;
2626
import java.nio.channels.ReadableByteChannel;
2727
import java.nio.charset.Charset;
28+
import java.nio.file.Path;
2829

2930
import org.jspecify.annotations.Nullable;
3031

@@ -91,11 +92,13 @@ default boolean isOpen() {
9192

9293
/**
9394
* Determine whether this resource represents a file in a file system.
94-
* <p>A value of {@code true} strongly suggests (but does not guarantee)
95-
* that a {@link #getFile()} call will succeed.
95+
* <p>A value of {@code true} suggests (but does not guarantee) that a
96+
* {@link #getFile()} call will succeed. For non-default file systems,
97+
* {@link #getFilePath()} is the more reliable follow-up call.
9698
* <p>This is conservatively {@code false} by default.
9799
* @since 5.0
98100
* @see #getFile()
101+
* @see #getFilePath()
99102
*/
100103
default boolean isFile() {
101104
return false;
@@ -118,13 +121,26 @@ default boolean isFile() {
118121

119122
/**
120123
* Return a File handle for this resource.
121-
* @throws java.io.FileNotFoundException if the resource cannot be resolved as
122-
* absolute file path, i.e. if the resource is not available in a file system
124+
* <p>Note: This only works for files in the default file system.
125+
* @throws UnsupportedOperationException if the resource is a file but cannot be
126+
* exposed as a {@code java.io.File}; try {@link #getFilePath()} instead
127+
* @throws java.io.FileNotFoundException if the resource cannot be resolved as a file
123128
* @throws IOException in case of general resolution/reading failures
124129
* @see #getInputStream()
125130
*/
126131
File getFile() throws IOException;
127132

133+
/**
134+
* Return an NIO Path handle for this resource.
135+
* <p>Note: This works for files in non-default file systems as well.
136+
* @throws java.io.FileNotFoundException if the resource cannot be resolved as a file
137+
* @throws IOException in case of general resolution/reading failures
138+
* @since 7.0
139+
*/
140+
default Path getFilePath() throws IOException {
141+
return getFile().toPath();
142+
}
143+
128144
/**
129145
* Return a {@link ReadableByteChannel}.
130146
* <p>It is expected that each call creates a <i>fresh</i> channel.

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.core.io.buffer;
1818

19-
import java.io.File;
2019
import java.io.IOException;
2120
import java.io.InputStream;
2221
import java.io.OutputStream;
@@ -223,17 +222,17 @@ public static Flux<DataBuffer> read(
223222

224223
try {
225224
if (resource.isFile()) {
226-
File file = resource.getFile();
225+
Path filePath = resource.getFilePath();
227226
return readAsynchronousFileChannel(
228-
() -> AsynchronousFileChannel.open(file.toPath(), StandardOpenOption.READ),
227+
() -> AsynchronousFileChannel.open(filePath, StandardOpenOption.READ),
229228
position, bufferFactory, bufferSize);
230229
}
231230
}
232231
catch (IOException ignore) {
233232
// fallback to resource.readableChannel(), below
234233
}
235234
Flux<DataBuffer> result = readByteChannel(resource::readableChannel, bufferFactory, bufferSize);
236-
return position == 0 ? result : skipUntilByteCount(result, position);
235+
return (position == 0 ? result : skipUntilByteCount(result, position));
237236
}
238237

239238

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,11 @@ void getUri() throws IOException {
186186
}
187187

188188
@Test
189-
void getFile() throws IOException {
189+
void getFile() {
190190
PathResource resource = new PathResource(TEST_FILE);
191191
File file = new File(TEST_FILE);
192192
assertThat(resource.getFile().getAbsoluteFile()).isEqualTo(file.getAbsoluteFile());
193+
assertThat(resource.getFilePath()).isEqualTo(file.toPath());
193194
}
194195

195196
@Test
@@ -198,7 +199,7 @@ void getFileUnsupported() {
198199
given(path.normalize()).willReturn(path);
199200
given(path.toFile()).willThrow(new UnsupportedOperationException());
200201
PathResource resource = new PathResource(path);
201-
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(resource::getFile);
202+
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(resource::getFile);
202203
}
203204

204205
@Test
@@ -236,13 +237,13 @@ void createRelativeFromFile() {
236237

237238
@Test
238239
void filename() {
239-
Resource resource = new PathResource(TEST_FILE);
240+
PathResource resource = new PathResource(TEST_FILE);
240241
assertThat(resource.getFilename()).isEqualTo("example.properties");
241242
}
242243

243244
@Test
244245
void description() {
245-
Resource resource = new PathResource(TEST_FILE);
246+
PathResource resource = new PathResource(TEST_FILE);
246247
assertThat(resource.getDescription()).contains("path [");
247248
assertThat(resource.getDescription()).contains(TEST_FILE);
248249
}
@@ -261,9 +262,9 @@ void directoryIsNotWritable() {
261262

262263
@Test
263264
void equalsAndHashCode() {
264-
Resource resource1 = new PathResource(TEST_FILE);
265-
Resource resource2 = new PathResource(TEST_FILE);
266-
Resource resource3 = new PathResource(TEST_DIR);
265+
PathResource resource1 = new PathResource(TEST_FILE);
266+
PathResource resource2 = new PathResource(TEST_FILE);
267+
PathResource resource3 = new PathResource(TEST_DIR);
267268
assertThat(resource1).isEqualTo(resource1);
268269
assertThat(resource1).isEqualTo(resource2);
269270
assertThat(resource2).isEqualTo(resource1);

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
5656
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
5757
import static org.junit.jupiter.params.provider.Arguments.argumentSet;
58+
import static org.mockito.BDDMockito.given;
59+
import static org.mockito.Mockito.mock;
5860

5961
/**
6062
* Tests for various {@link Resource} implementations.
@@ -268,6 +270,16 @@ void relativeResourcesAreEqual() throws Exception {
268270
assertThat(relative).isEqualTo(new FileSystemResource("dir/subdir"));
269271
}
270272

273+
@Test
274+
void getFilePath() throws Exception {
275+
Path path = mock();
276+
given(path.normalize()).willReturn(path);
277+
given(path.toFile()).willThrow(new UnsupportedOperationException());
278+
Resource resource = new FileSystemResource(path);
279+
assertThat(resource.getFilePath()).isSameAs(path);
280+
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(resource::getFile);
281+
}
282+
271283
@Test
272284
void readableChannelProvidesContent() throws Exception {
273285
Resource resource = new FileSystemResource(getClass().getResource("ResourceTests.class").getFile());

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616

1717
package org.springframework.http.codec;
1818

19-
import java.io.File;
2019
import java.io.IOException;
20+
import java.nio.file.Files;
21+
import java.nio.file.Path;
2122
import java.util.List;
2223
import java.util.Map;
2324

@@ -193,17 +194,17 @@ private static Mono<Long> lengthOf(Resource resource) {
193194

194195
if (message instanceof ZeroCopyHttpOutputMessage zeroCopyHttpOutputMessage && resource.isFile()) {
195196
try {
196-
File file = resource.getFile();
197-
long pos = region != null ? region.getPosition() : 0;
198-
long count = region != null ? region.getCount() : file.length();
197+
Path filePath = resource.getFilePath();
198+
long pos = (region != null ? region.getPosition() : 0);
199+
long count = (region != null ? region.getCount() : Files.size(filePath));
199200
if (logger.isDebugEnabled()) {
200201
String formatted = region != null ? "region " + pos + "-" + (count) + " of " : "";
201202
logger.debug(Hints.getLogPrefix(hints) + "Zero-copy " + formatted + "[" + resource + "]");
202203
}
203-
return zeroCopyHttpOutputMessage.writeWith(file, pos, count);
204+
return zeroCopyHttpOutputMessage.writeWith(filePath, pos, count);
204205
}
205-
catch (IOException ex) {
206-
// should not happen
206+
catch (IOException ignore) {
207+
// returning null below leads to fallback code path
207208
}
208209
}
209210
return null;

spring-webflux/src/main/java/org/springframework/web/reactive/resource/EncodedResourceResolver.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.net.URL;
2424
import java.nio.channels.ReadableByteChannel;
2525
import java.nio.charset.Charset;
26+
import java.nio.file.Path;
2627
import java.util.ArrayList;
2728
import java.util.Arrays;
2829
import java.util.Collections;
@@ -240,6 +241,11 @@ public File getFile() throws IOException {
240241
return this.encoded.getFile();
241242
}
242243

244+
@Override
245+
public Path getFilePath() throws IOException {
246+
return this.encoded.getFilePath();
247+
}
248+
243249
@Override
244250
public InputStream getInputStream() throws IOException {
245251
return this.encoded.getInputStream();

spring-webflux/src/main/java/org/springframework/web/reactive/resource/VersionResourceResolver.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.net.URL;
2424
import java.nio.channels.ReadableByteChannel;
2525
import java.nio.charset.Charset;
26+
import java.nio.file.Path;
2627
import java.util.ArrayList;
2728
import java.util.Arrays;
2829
import java.util.Comparator;
@@ -283,6 +284,11 @@ public File getFile() throws IOException {
283284
return this.original.getFile();
284285
}
285286

287+
@Override
288+
public Path getFilePath() throws IOException {
289+
return this.original.getFilePath();
290+
}
291+
286292
@Override
287293
public InputStream getInputStream() throws IOException {
288294
return this.original.getInputStream();

0 commit comments

Comments
 (0)