mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Provide graceful fallback for non-default NIO file systems
Closes gh-35443
This commit is contained in:
@@ -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
|
||||
* <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}; 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()
|
||||
*/
|
||||
|
||||
@@ -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<DataBuffer> result = readByteChannel(resource::readableChannel, bufferFactory, bufferSize);
|
||||
return position == 0 ? result : skipUntilByteCount(result, position);
|
||||
return (position == 0 ? result : skipUntilByteCount(result, position));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
URI uri = URI.create("jar:file:/" + dest.toString().replace('\\', '/') + "/archive.zip");
|
||||
Map<String, String> 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();
|
||||
|
||||
zipfs.close();
|
||||
FileSystemUtils.deleteRecursively(src);
|
||||
assertThat(src).doesNotExist();
|
||||
}
|
||||
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -203,8 +203,8 @@ public class ResourceHttpMessageWriter implements HttpMessageWriter<Resource> {
|
||||
}
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user