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 a492ebc2ffc..9e13d4961f6 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 @@ -117,8 +117,10 @@ 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 + *

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}; an alternative to {@code FileNotFoundException} + * @throws java.io.FileNotFoundException if the resource cannot be resolved as a file * @throws IOException in case of general resolution/reading failures * @see #getInputStream() */ diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java index f705f16a935..568c7be82c5 100644 --- a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java +++ b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java @@ -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,17 +222,17 @@ public abstract class DataBufferUtils { try { if (resource.isFile()) { - File file = resource.getFile(); + Path filePath = resource.getFile().toPath(); return readAsynchronousFileChannel( - () -> AsynchronousFileChannel.open(file.toPath(), StandardOpenOption.READ), + () -> AsynchronousFileChannel.open(filePath, StandardOpenOption.READ), position, bufferFactory, bufferSize); } } - catch (IOException ignore) { + catch (IOException | UnsupportedOperationException ignore) { // fallback to resource.readableChannel(), below } Flux result = readByteChannel(resource::readableChannel, bufferFactory, bufferSize); - return position == 0 ? result : skipUntilByteCount(result, position); + return (position == 0 ? result : skipUntilByteCount(result, position)); } diff --git a/spring-core/src/main/java/org/springframework/util/FileSystemUtils.java b/spring-core/src/main/java/org/springframework/util/FileSystemUtils.java index be2ccc590b2..261c5574cc9 100644 --- a/spring-core/src/main/java/org/springframework/util/FileSystemUtils.java +++ b/spring-core/src/main/java/org/springframework/util/FileSystemUtils.java @@ -86,13 +86,12 @@ public abstract class FileSystemUtils { Files.walkFileTree(root, new SimpleFileVisitor<>() { @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + public FileVisitResult visitFile(Path file, BasicFileAttributes attr) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; } - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + public FileVisitResult postVisitDirectory(Path dir, IOException ex) throws IOException { Files.delete(dir); return FileVisitResult.CONTINUE; } @@ -127,19 +126,34 @@ public abstract class FileSystemUtils { BasicFileAttributes srcAttr = Files.readAttributes(src, BasicFileAttributes.class); if (srcAttr.isDirectory()) { - Files.walkFileTree(src, EnumSet.of(FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor<>() { - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { - Files.createDirectories(dest.resolve(src.relativize(dir))); - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - Files.copy(file, dest.resolve(src.relativize(file)), StandardCopyOption.REPLACE_EXISTING); - return FileVisitResult.CONTINUE; - } - }); + if (src.getClass() == dest.getClass()) { // dest.resolve(Path) only works for same Path type + Files.walkFileTree(src, EnumSet.of(FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor<>() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attr) throws IOException { + Files.createDirectories(dest.resolve(src.relativize(dir))); + return FileVisitResult.CONTINUE; + } + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attr) throws IOException { + Files.copy(file, dest.resolve(src.relativize(file)), StandardCopyOption.REPLACE_EXISTING); + return FileVisitResult.CONTINUE; + } + }); + } + else { // use dest.resolve(String) for different Path types + Files.walkFileTree(src, EnumSet.of(FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor<>() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attr) throws IOException { + Files.createDirectories(dest.resolve(src.relativize(dir).toString())); + return FileVisitResult.CONTINUE; + } + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attr) throws IOException { + Files.copy(file, dest.resolve(src.relativize(file).toString()), StandardCopyOption.REPLACE_EXISTING); + return FileVisitResult.CONTINUE; + } + }); + } } else if (srcAttr.isRegularFile()) { Files.copy(src, dest); diff --git a/spring-core/src/test/java/org/springframework/util/FileSystemUtilsTests.java b/spring-core/src/test/java/org/springframework/util/FileSystemUtilsTests.java index c91ccefc164..8f8368bbf58 100644 --- a/spring-core/src/test/java/org/springframework/util/FileSystemUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/FileSystemUtilsTests.java @@ -17,20 +17,29 @@ package org.springframework.util; import java.io.File; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.util.Map; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import static org.assertj.core.api.Assertions.assertThat; /** + * Tests for {@link FileSystemUtils}. + * * @author Rob Harrop + * @author Sam Brannen + * @author Juergen Hoeller */ class FileSystemUtilsTests { @Test - void deleteRecursively() throws Exception { - File root = new File("./tmp/root"); + void deleteRecursively(@TempDir File tempDir) throws Exception { + File root = new File(tempDir, "root"); File child = new File(root, "child"); File grandchild = new File(child, "grandchild"); @@ -53,8 +62,8 @@ class FileSystemUtilsTests { } @Test - void copyRecursively() throws Exception { - File src = new File("./tmp/src"); + void copyRecursively(@TempDir File tempDir) throws Exception { + File src = new File(tempDir, "src"); File child = new File(src, "child"); File grandchild = new File(child, "grandchild"); @@ -68,27 +77,25 @@ class FileSystemUtilsTests { assertThat(grandchild).exists(); assertThat(bar).exists(); - File dest = new File("./dest"); + File dest = new File(tempDir, "/dest"); FileSystemUtils.copyRecursively(src, dest); assertThat(dest).exists(); - assertThat(new File(dest, child.getName())).exists(); + assertThat(new File(dest, "child")).exists(); + assertThat(new File(dest, "child/bar.txt")).exists(); - FileSystemUtils.deleteRecursively(src); - assertThat(src).doesNotExist(); - } + URI uri = URI.create("jar:file:/" + dest.toString().replace('\\', '/') + "/archive.zip"); + Map env = Map.of("create", "true"); + FileSystem zipfs = FileSystems.newFileSystem(uri, env); + Path ziproot = zipfs.getPath("/"); + FileSystemUtils.copyRecursively(src.toPath(), ziproot); + assertThat(zipfs.getPath("/child")).exists(); + assertThat(zipfs.getPath("/child/bar.txt")).exists(); - @AfterEach - void tearDown() { - File tmp = new File("./tmp"); - if (tmp.exists()) { - FileSystemUtils.deleteRecursively(tmp); - } - File dest = new File("./dest"); - if (dest.exists()) { - FileSystemUtils.deleteRecursively(dest); - } + zipfs.close(); + FileSystemUtils.deleteRecursively(src); + assertThat(src).doesNotExist(); } } diff --git a/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java index d5de3865b60..ddf10aa40f4 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java @@ -203,8 +203,8 @@ public class ResourceHttpMessageWriter implements HttpMessageWriter { } return zeroCopyHttpOutputMessage.writeWith(file, pos, count); } - catch (IOException ex) { - // should not happen + catch (IOException | UnsupportedOperationException ignore) { + // returning null below leads to fallback code path } } return null;