Browse Source

Provide graceful fallback for non-default NIO file systems

Closes gh-35443
pull/35587/head
Juergen Hoeller 4 months ago
parent
commit
ba52164373
  1. 6
      spring-core/src/main/java/org/springframework/core/io/Resource.java
  2. 9
      spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java
  3. 46
      spring-core/src/main/java/org/springframework/util/FileSystemUtils.java
  4. 47
      spring-core/src/test/java/org/springframework/util/FileSystemUtilsTests.java
  5. 4
      spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java

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

@ -117,8 +117,10 @@ public interface Resource extends InputStreamSource { @@ -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()
*/

9
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,17 +222,17 @@ public abstract class DataBufferUtils { @@ -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));
}

46
spring-core/src/main/java/org/springframework/util/FileSystemUtils.java

@ -86,13 +86,12 @@ public abstract class FileSystemUtils { @@ -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 { @@ -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);

47
spring-core/src/test/java/org/springframework/util/FileSystemUtilsTests.java

@ -17,20 +17,29 @@ @@ -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 { @@ -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 { @@ -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<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();
@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();
}
}

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

@ -203,8 +203,8 @@ public class ResourceHttpMessageWriter implements HttpMessageWriter<Resource> { @@ -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;

Loading…
Cancel
Save