FileSystemResource supports java.nio.file.Path based setup
Issue: SPR-16833
This commit is contained in:
parent
bb6ab5dc91
commit
38f9a7b072
|
|
@ -28,32 +28,58 @@ import java.nio.channels.ReadableByteChannel;
|
||||||
import java.nio.channels.WritableByteChannel;
|
import java.nio.channels.WritableByteChannel;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
|
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link Resource} implementation for {@code java.io.File} handles.
|
* {@link Resource} implementation for {@code java.io.File} and
|
||||||
|
* {@code java.nio.file.Path} handles with a file system target.
|
||||||
* Supports resolution as a {@code File} and also as a {@code URL}.
|
* Supports resolution as a {@code File} and also as a {@code URL}.
|
||||||
* Implements the extended {@link WritableResource} interface.
|
* Implements the extended {@link WritableResource} interface.
|
||||||
*
|
*
|
||||||
* <p>Note: As of Spring Framework 5.0, this {@link Resource} implementation
|
* <p>Note: As of Spring Framework 5.0, this {@link Resource} implementation uses
|
||||||
* uses NIO.2 API for read/write interactions. Nevertheless, in contrast to
|
* NIO.2 API for read/write interactions. As of 5.1, it may be constructed with a
|
||||||
* {@link PathResource}, it primarily manages a {@code java.io.File} handle.
|
* {@link java.nio.file.Path} handle in which case it will perform all file system
|
||||||
|
* interactions via NIO.2, only resorting to {@link File} on {@link #getFile()}.
|
||||||
*
|
*
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @since 28.12.2003
|
* @since 28.12.2003
|
||||||
* @see PathResource
|
* @see #FileSystemResource(File)
|
||||||
|
* @see #FileSystemResource(Path)
|
||||||
* @see java.io.File
|
* @see java.io.File
|
||||||
* @see java.nio.file.Files
|
* @see java.nio.file.Files
|
||||||
*/
|
*/
|
||||||
public class FileSystemResource extends AbstractResource implements WritableResource {
|
public class FileSystemResource extends AbstractResource implements WritableResource {
|
||||||
|
|
||||||
private final File file;
|
|
||||||
|
|
||||||
private final String path;
|
private final String path;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final File file;
|
||||||
|
|
||||||
|
private final Path filePath;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@code FileSystemResource} from a file path.
|
||||||
|
* <p>Note: When building relative resources via {@link #createRelative},
|
||||||
|
* it makes a difference whether the specified resource base path here
|
||||||
|
* ends with a slash or not. In the case of "C:/dir1/", relative paths
|
||||||
|
* will be built underneath that root: e.g. relative path "dir2" ->
|
||||||
|
* "C:/dir1/dir2". In the case of "C:/dir1", relative paths will apply
|
||||||
|
* at the same directory level: relative path "dir2" -> "C:/dir2".
|
||||||
|
* @param path a file path
|
||||||
|
* @see #FileSystemResource(Path)
|
||||||
|
*/
|
||||||
|
public FileSystemResource(String path) {
|
||||||
|
Assert.notNull(path, "Path must not be null");
|
||||||
|
this.path = StringUtils.cleanPath(path);
|
||||||
|
this.file = new File(path);
|
||||||
|
this.filePath = this.file.toPath();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new {@code FileSystemResource} from a {@link File} handle.
|
* Create a new {@code FileSystemResource} from a {@link File} handle.
|
||||||
|
|
@ -65,27 +91,31 @@ public class FileSystemResource extends AbstractResource implements WritableReso
|
||||||
* to append a trailing slash to the root path: "C:/dir1/", which
|
* to append a trailing slash to the root path: "C:/dir1/", which
|
||||||
* indicates this directory as root for all relative paths.
|
* indicates this directory as root for all relative paths.
|
||||||
* @param file a File handle
|
* @param file a File handle
|
||||||
|
* @see #FileSystemResource(Path)
|
||||||
|
* @see #getFile()
|
||||||
*/
|
*/
|
||||||
public FileSystemResource(File file) {
|
public FileSystemResource(File file) {
|
||||||
Assert.notNull(file, "File must not be null");
|
Assert.notNull(file, "File must not be null");
|
||||||
this.file = file;
|
|
||||||
this.path = StringUtils.cleanPath(file.getPath());
|
this.path = StringUtils.cleanPath(file.getPath());
|
||||||
|
this.file = file;
|
||||||
|
this.filePath = file.toPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new {@code FileSystemResource} from a file path.
|
* Create a new {@code FileSystemResource} from a {@link Path} handle.
|
||||||
* <p>Note: When building relative resources via {@link #createRelative},
|
* <p>In contrast to {@link PathResource}, this variant strictly follows the
|
||||||
* it makes a difference whether the specified resource base path here
|
* general {@link FileSystemResource} conventions, in particular in terms of
|
||||||
* ends with a slash or not. In the case of "C:/dir1/", relative paths
|
* path cleaning and {@link #createRelative(String)} handling.
|
||||||
* will be built underneath that root: e.g. relative path "dir2" ->
|
* @param filePath a Path handle to a file
|
||||||
* "C:/dir1/dir2". In the case of "C:/dir1", relative paths will apply
|
* @since 5.1
|
||||||
* at the same directory level: relative path "dir2" -> "C:/dir2".
|
* @see #FileSystemResource(File)
|
||||||
* @param path a file path
|
* @see PathResource
|
||||||
*/
|
*/
|
||||||
public FileSystemResource(String path) {
|
public FileSystemResource(Path filePath) {
|
||||||
Assert.notNull(path, "Path must not be null");
|
Assert.notNull(filePath, "Path must not be null");
|
||||||
this.file = new File(path);
|
this.filePath = filePath;
|
||||||
this.path = StringUtils.cleanPath(path);
|
this.file = null;
|
||||||
|
this.path = StringUtils.cleanPath(filePath.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -102,7 +132,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean exists() {
|
public boolean exists() {
|
||||||
return this.file.exists();
|
return (this.file != null ? this.file.exists() : Files.exists(this.filePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -113,7 +143,8 @@ public class FileSystemResource extends AbstractResource implements WritableReso
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isReadable() {
|
public boolean isReadable() {
|
||||||
return (this.file.canRead() && !this.file.isDirectory());
|
return (this.file != null ? this.file.canRead() && !this.file.isDirectory() :
|
||||||
|
Files.isReadable(this.filePath) && !Files.isDirectory(this.filePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -123,7 +154,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
|
||||||
@Override
|
@Override
|
||||||
public InputStream getInputStream() throws IOException {
|
public InputStream getInputStream() throws IOException {
|
||||||
try {
|
try {
|
||||||
return Files.newInputStream(this.file.toPath());
|
return Files.newInputStream(this.filePath);
|
||||||
}
|
}
|
||||||
catch (NoSuchFileException ex) {
|
catch (NoSuchFileException ex) {
|
||||||
throw new FileNotFoundException(ex.getMessage());
|
throw new FileNotFoundException(ex.getMessage());
|
||||||
|
|
@ -138,7 +169,8 @@ public class FileSystemResource extends AbstractResource implements WritableReso
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isWritable() {
|
public boolean isWritable() {
|
||||||
return (this.file.canWrite() && !this.file.isDirectory());
|
return (this.file != null ? this.file.canWrite() && !this.file.isDirectory() :
|
||||||
|
Files.isWritable(this.filePath) && !Files.isDirectory(this.filePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -147,7 +179,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public OutputStream getOutputStream() throws IOException {
|
public OutputStream getOutputStream() throws IOException {
|
||||||
return Files.newOutputStream(this.file.toPath());
|
return Files.newOutputStream(this.filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -156,7 +188,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public URL getURL() throws IOException {
|
public URL getURL() throws IOException {
|
||||||
return this.file.toURI().toURL();
|
return (this.file != null ? this.file.toURI().toURL() : this.filePath.toUri().toURL());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -165,7 +197,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public URI getURI() throws IOException {
|
public URI getURI() throws IOException {
|
||||||
return this.file.toURI();
|
return (this.file != null ? this.file.toURI() : this.filePath.toUri());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -181,7 +213,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public File getFile() {
|
public File getFile() {
|
||||||
return this.file;
|
return (this.file != null ? this.file : this.filePath.toFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -191,7 +223,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
|
||||||
@Override
|
@Override
|
||||||
public ReadableByteChannel readableChannel() throws IOException {
|
public ReadableByteChannel readableChannel() throws IOException {
|
||||||
try {
|
try {
|
||||||
return FileChannel.open(this.file.toPath(), StandardOpenOption.READ);
|
return FileChannel.open(this.filePath, StandardOpenOption.READ);
|
||||||
}
|
}
|
||||||
catch (NoSuchFileException ex) {
|
catch (NoSuchFileException ex) {
|
||||||
throw new FileNotFoundException(ex.getMessage());
|
throw new FileNotFoundException(ex.getMessage());
|
||||||
|
|
@ -204,7 +236,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public WritableByteChannel writableChannel() throws IOException {
|
public WritableByteChannel writableChannel() throws IOException {
|
||||||
return FileChannel.open(this.file.toPath(), StandardOpenOption.WRITE);
|
return FileChannel.open(this.filePath, StandardOpenOption.WRITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -212,7 +244,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public long contentLength() throws IOException {
|
public long contentLength() throws IOException {
|
||||||
return this.file.length();
|
return (this.file != null ? this.file.length() : Files.size(this.filePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -232,7 +264,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String getFilename() {
|
public String getFilename() {
|
||||||
return this.file.getName();
|
return (this.file != null ? this.file.getName() : this.filePath.getFileName().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -242,7 +274,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
return "file [" + this.file.getAbsolutePath() + "]";
|
return "file [" + (this.file != null ? this.file.getAbsolutePath() : this.filePath.toAbsolutePath()) + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,14 +35,20 @@ import java.nio.file.StandardOpenOption;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link Resource} implementation for {@code java.nio.file.Path} handles.
|
* {@link Resource} implementation for {@link java.nio.file.Path} handles,
|
||||||
* Supports resolution as File, and also as URL.
|
* performing all operations and transformations via the {@code Path} API.
|
||||||
|
* Supports resolution as a {@link File} and also as a {@link URL}.
|
||||||
* Implements the extended {@link WritableResource} interface.
|
* Implements the extended {@link WritableResource} interface.
|
||||||
*
|
*
|
||||||
|
* <p>Note: As of 5.1, {@link java.nio.file.Path} support is also available
|
||||||
|
* in {@link FileSystemResource#FileSystemResource(Path) FileSystemResource},
|
||||||
|
* applying Spring's standard String-based path transformations but
|
||||||
|
* performing all operations via the {@link java.nio.file.Files} API.
|
||||||
|
*
|
||||||
* @author Philippe Marschall
|
* @author Philippe Marschall
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
* @see FileSystemResource
|
* @see FileSystemResource#FileSystemResource(Path)
|
||||||
* @see java.nio.file.Path
|
* @see java.nio.file.Path
|
||||||
* @see java.nio.file.Files
|
* @see java.nio.file.Files
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.ReadableByteChannel;
|
import java.nio.channels.ReadableByteChannel;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
|
|
@ -128,6 +130,16 @@ public class ResourceTests {
|
||||||
assertEquals(resource2, new FileSystemResource("core/../core/io/./Resource.class"));
|
assertEquals(resource2, new FileSystemResource("core/../core/io/./Resource.class"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFileSystemResourceWithFilePath() throws Exception {
|
||||||
|
Path filePath = Paths.get(getClass().getResource("Resource.class").toURI());
|
||||||
|
Resource resource = new FileSystemResource(filePath);
|
||||||
|
doTestResource(resource);
|
||||||
|
assertEquals(new FileSystemResource(filePath), resource);
|
||||||
|
Resource resource2 = new FileSystemResource("core/io/Resource.class");
|
||||||
|
assertEquals(resource2, new FileSystemResource("core/../core/io/./Resource.class"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUrlResource() throws IOException {
|
public void testUrlResource() throws IOException {
|
||||||
Resource resource = new UrlResource(getClass().getResource("Resource.class"));
|
Resource resource = new UrlResource(getClass().getResource("Resource.class"));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue