Introduce java.nio.file.Path based Resource
Develop new org.springframework.core.io.Resource implementation backed by java.nio.file.Path. Primarily developed to allow custom file system implementations to be used with Spring. Since the minimum requirement for Spring is still Java 6 the existing FileSystemResource can't be retrofitted (and no #getPath method can be added to the Resource interface). Unlike FileSystemResource, PathResource delegates to the underlying file system instead of StringUtils. It has therefore slightly different semantics. First, when building relative resources via createRelative the relative path will apply to this path (like URL or Unix). Second, equality is delegated to the underlying file system provider so it's case-insensitive on Windows. Issue: SPR-10608
This commit is contained in:
parent
1f5467a29d
commit
2313c9a007
|
@ -0,0 +1,253 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.io;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link Resource} implementation for {@code java.nio.file.Path} handles.
|
||||
* Supports resolution as File, and also as URL.
|
||||
* Implements the extended {@link WritableResource} interface.
|
||||
*
|
||||
* @author Philippe Marschall
|
||||
* @since 4.0
|
||||
* @see java.nio.file.Path
|
||||
*/
|
||||
public class PathResource extends AbstractResource implements WritableResource {
|
||||
|
||||
private final Path path;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new PathResource from a Path handle.
|
||||
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources
|
||||
* via {@link #createRelative}, the relative path will be built <i>underneath</i> the
|
||||
* given root:
|
||||
* e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
|
||||
* @param path a Path handle
|
||||
*/
|
||||
public PathResource(Path path) {
|
||||
Assert.notNull(path, "Path must not be null");
|
||||
this.path = path.normalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new PathResource from a Path handle.
|
||||
* Create a new PathResource from a Path handle.
|
||||
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources
|
||||
* via {@link #createRelative}, the relative path will be built <i>underneath</i> the
|
||||
* given root:
|
||||
* e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
|
||||
* @param path a path
|
||||
* @see java.nio.file.Paths#get(String, String...)
|
||||
*/
|
||||
public PathResource(String path) {
|
||||
Assert.notNull(path, "Path must not be null");
|
||||
this.path = Paths.get(path).normalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new PathResource from a Path handle.
|
||||
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources
|
||||
* via {@link #createRelative}, the relative path will be built <i>underneath</i> the
|
||||
* given root:
|
||||
* e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
|
||||
* @see java.nio.file.Paths#get(URI)
|
||||
* @param uri a path URI
|
||||
*/
|
||||
public PathResource(URI uri) {
|
||||
Assert.notNull(uri, "URI must not be null");
|
||||
this.path = Paths.get(uri).normalize();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the file path for this resource.
|
||||
*/
|
||||
public final String getPath() {
|
||||
return this.path.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation returns whether the underlying file exists.
|
||||
* @see org.springframework.core.io.PathResource#exists()
|
||||
*/
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return Files.exists(this.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation checks whether the underlying file is marked as readable
|
||||
* (and corresponds to an actual file with content, not to a directory).
|
||||
* @see java.nio.file.Files#isReadable(Path)
|
||||
* @see java.nio.file.Files#isDirectory(Path, java.nio.file.LinkOption...)
|
||||
*/
|
||||
@Override
|
||||
public boolean isReadable() {
|
||||
return (Files.isReadable(this.path) && !Files.isDirectory(this.path));
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation opens a InputStream for the underlying file.
|
||||
* @see java.nio.file.spi.FileSystemProvider#newInputStream(Path, OpenOption...)
|
||||
*/
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
if(!exists()) {
|
||||
throw new FileNotFoundException(getPath() + " (No such file or directory)");
|
||||
}
|
||||
if(Files.isDirectory(this.path)) {
|
||||
throw new FileNotFoundException(getPath() + " (Is a directory)");
|
||||
}
|
||||
return Files.newInputStream(this.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation returns a URL for the underlying file.
|
||||
* @see java.nio.file.Path#toUri()
|
||||
* @see java.net.URI#toURL()
|
||||
*/
|
||||
@Override
|
||||
public URL getURL() throws IOException {
|
||||
return this.path.toUri().toURL();
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation returns a URI for the underlying file.
|
||||
* @see java.nio.file.Path#toUri()
|
||||
*/
|
||||
@Override
|
||||
public URI getURI() throws IOException {
|
||||
return this.path.toUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation returns the underlying File reference.
|
||||
*/
|
||||
@Override
|
||||
public File getFile() throws IOException {
|
||||
try {
|
||||
return this.path.toFile();
|
||||
}
|
||||
catch (UnsupportedOperationException ex) {
|
||||
// only Paths on the default file system can be converted to a File
|
||||
// do exception translation for cases where conversion is not possible
|
||||
throw new FileNotFoundException(this.path + " cannot be resolved to "
|
||||
+ "absolute file path");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation returns the underlying File's length.
|
||||
*/
|
||||
@Override
|
||||
public long contentLength() throws IOException {
|
||||
return Files.size(this.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation returns the underlying File's timestamp.
|
||||
* @see java.nio.file.Files#getLastModifiedTime(Path, java.nio.file.LinkOption...)
|
||||
*/
|
||||
@Override
|
||||
public long lastModified() throws IOException {
|
||||
// we can not use the super class method since it uses conversion to a File and
|
||||
// only Paths on the default file system can be converted to a File
|
||||
return Files.getLastModifiedTime(path).toMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation creates a FileResource, applying the given path
|
||||
* relative to the path of the underlying file of this resource descriptor.
|
||||
* @see java.nio.file.Path#resolve(String)
|
||||
*/
|
||||
@Override
|
||||
public Resource createRelative(String relativePath) throws IOException {
|
||||
return new PathResource(this.path.resolve(relativePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation returns the name of the file.
|
||||
* @see java.nio.file.Path#getFileName()
|
||||
*/
|
||||
@Override
|
||||
public String getFilename() {
|
||||
return this.path.getFileName().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "path [" + this.path.toAbsolutePath() + "]";
|
||||
}
|
||||
|
||||
// implementation of WritableResource
|
||||
|
||||
/**
|
||||
* This implementation checks whether the underlying file is marked as writable
|
||||
* (and corresponds to an actual file with content, not to a directory).
|
||||
* @see java.nio.file.Files#isWritable(Path)
|
||||
* @see java.nio.file.Files#isDirectory(Path, java.nio.file.LinkOption...)
|
||||
*/
|
||||
@Override
|
||||
public boolean isWritable() {
|
||||
return Files.isWritable(this.path) && !Files.isDirectory(this.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation opens a OutputStream for the underlying file.
|
||||
* @see java.nio.file.spi.FileSystemProvider#newOutputStream(Path, OpenOption...)
|
||||
*/
|
||||
@Override
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
if(Files.isDirectory(this.path)) {
|
||||
throw new FileNotFoundException(getPath() + " (Is a directory)");
|
||||
}
|
||||
return Files.newOutputStream(this.path);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This implementation compares the underlying Path references.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return (obj == this ||
|
||||
(obj instanceof PathResource && this.path.equals(((PathResource) obj).path)));
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation returns the hash code of the underlying Path reference.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.path.hashCode();
|
||||
}
|
||||
|
||||
}
|
|
@ -42,6 +42,7 @@ import java.net.URL;
|
|||
* @see UrlResource
|
||||
* @see ByteArrayResource
|
||||
* @see InputStreamResource
|
||||
* @see PathResource
|
||||
*/
|
||||
public interface Resource extends InputStreamSource {
|
||||
|
||||
|
|
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.io;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.BDDMockito.*;
|
||||
|
||||
/**
|
||||
* Unit tests for the {@link PathResource} class.
|
||||
*
|
||||
* @author Philippe Marschall
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class PathResourceTests {
|
||||
|
||||
private static final String TEST_DIR = "src/test/java/org/springframework/core/io";
|
||||
|
||||
private static final String TEST_FILE = "src/test/java/org/springframework/core/io/example.properties";
|
||||
|
||||
private static final String NON_EXISTING_FILE = "src/test/java/org/springframework/core/io/doesnotexist.properties";
|
||||
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
|
||||
@Test
|
||||
public void nullPath() throws Exception {
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Path must not be null");
|
||||
new PathResource((Path) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullPathString() throws Exception {
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Path must not be null");
|
||||
new PathResource((String) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullUri() throws Exception {
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("URI must not be null");
|
||||
new PathResource((URI) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createFromPath() throws Exception {
|
||||
Path path = Paths.get(TEST_FILE);
|
||||
PathResource resource = new PathResource(path);
|
||||
assertThat(resource.getPath(), equalTo(TEST_FILE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createFromString() throws Exception {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
assertThat(resource.getPath(), equalTo(TEST_FILE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createFromUri() throws Exception {
|
||||
File file = new File(TEST_FILE);
|
||||
PathResource resource = new PathResource(file.toURI());
|
||||
assertThat(resource.getPath(), equalTo(file.getAbsoluteFile().toString()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPathForFile() throws Exception {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
assertThat(resource.getPath(), equalTo(TEST_FILE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPathForDir() throws Exception {
|
||||
PathResource resource = new PathResource(TEST_DIR);
|
||||
assertThat(resource.getPath(), equalTo(TEST_DIR));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileExists() throws Exception {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
assertThat(resource.exists(), equalTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dirExists() throws Exception {
|
||||
PathResource resource = new PathResource(TEST_DIR);
|
||||
assertThat(resource.exists(), equalTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileDoesNotExist() throws Exception {
|
||||
PathResource resource = new PathResource(NON_EXISTING_FILE);
|
||||
assertThat(resource.exists(), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileIsReadable() throws Exception {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
assertThat(resource.isReadable(), equalTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotExistIsNotReadable() throws Exception {
|
||||
PathResource resource = new PathResource(NON_EXISTING_FILE);
|
||||
assertThat(resource.isReadable(), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void directoryIsNotReadable() throws Exception {
|
||||
PathResource resource = new PathResource(TEST_DIR);
|
||||
assertThat(resource.isReadable(), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInputStream() throws Exception {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
byte[] bytes = FileCopyUtils.copyToByteArray(resource.getInputStream());
|
||||
assertThat(bytes.length, greaterThan(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInputStreamForDir() throws Exception {
|
||||
PathResource resource = new PathResource(TEST_DIR);
|
||||
thrown.expect(FileNotFoundException.class);
|
||||
resource.getInputStream();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInputStreamDoesNotExist() throws Exception {
|
||||
PathResource resource = new PathResource(NON_EXISTING_FILE);
|
||||
thrown.expect(FileNotFoundException.class);
|
||||
resource.getInputStream();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUrl() throws Exception {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
assertTrue(resource.getURL().toString().endsWith(TEST_FILE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUri() throws Exception {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
assertTrue(resource.getURI().toString().endsWith(TEST_FILE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFile() throws Exception {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
File file = new File(TEST_FILE);
|
||||
assertThat(resource.getFile().getAbsoluteFile(), equalTo(file.getAbsoluteFile()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFileUnsupported() throws Exception {
|
||||
Path path = mock(Path.class);
|
||||
given(path.normalize()).willReturn(path);
|
||||
given(path.toFile()).willThrow(new UnsupportedOperationException());
|
||||
PathResource resource = new PathResource(path);
|
||||
thrown.expect(FileNotFoundException.class);
|
||||
resource.getFile();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentLength() throws Exception {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
File file = new File(TEST_FILE);
|
||||
assertThat(resource.contentLength(), equalTo(file.length()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentLengthForDirectory() throws Exception {
|
||||
PathResource resource = new PathResource(TEST_DIR);
|
||||
File file = new File(TEST_DIR);
|
||||
assertThat(resource.contentLength(), equalTo(file.length()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lastModified() throws Exception {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
File file = new File(TEST_FILE);
|
||||
assertThat(resource.lastModified(), equalTo(file.lastModified()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createRelativeFromDir() throws Exception {
|
||||
Resource resource = new PathResource(TEST_DIR).createRelative("example.properties");
|
||||
assertThat(resource, equalTo((Resource) new PathResource(TEST_FILE)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createRelativeFromFile() throws Exception {
|
||||
Resource resource = new PathResource(TEST_FILE).createRelative("../example.properties");
|
||||
assertThat(resource, equalTo((Resource) new PathResource(TEST_FILE)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filename() throws Exception {
|
||||
Resource resource = new PathResource(TEST_FILE);
|
||||
assertThat(resource.getFilename(), equalTo("example.properties"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void description() throws Exception {
|
||||
Resource resource = new PathResource(TEST_FILE);
|
||||
assertThat(resource.getDescription(), containsString("path ["));
|
||||
assertThat(resource.getDescription(), containsString(TEST_FILE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileIsWritable() throws Exception {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
assertThat(resource.isWritable(), equalTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void directoryIsNotWritable() throws Exception {
|
||||
PathResource resource = new PathResource(TEST_DIR);
|
||||
assertThat(resource.isWritable(), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void outputStream() throws Exception {
|
||||
PathResource resource = new PathResource(temporaryFolder.newFile("test").toPath());
|
||||
FileCopyUtils.copy("test".getBytes(), resource.getOutputStream());
|
||||
assertThat(resource.contentLength(), equalTo(4L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotExistOutputStream() throws Exception {
|
||||
File file = temporaryFolder.newFile("test");
|
||||
file.delete();
|
||||
PathResource resource = new PathResource(file.toPath());
|
||||
FileCopyUtils.copy("test".getBytes(), resource.getOutputStream());
|
||||
assertThat(resource.contentLength(), equalTo(4L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void directoryOutputStream() throws Exception {
|
||||
PathResource resource = new PathResource(TEST_DIR);
|
||||
thrown.expect(FileNotFoundException.class);
|
||||
resource.getOutputStream();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue