diff --git a/spring-core/src/main/java/org/springframework/core/io/PathResource.java b/spring-core/src/main/java/org/springframework/core/io/PathResource.java new file mode 100644 index 0000000000..e1e7bef2e6 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/io/PathResource.java @@ -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. + *
Note: Unlike {@link FileSystemResource}, when building relative resources + * via {@link #createRelative}, the relative path will be built underneath 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. + *
Note: Unlike {@link FileSystemResource}, when building relative resources + * via {@link #createRelative}, the relative path will be built underneath 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. + *
Note: Unlike {@link FileSystemResource}, when building relative resources + * via {@link #createRelative}, the relative path will be built underneath 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(); + } + +} 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 cc6eef308f..e3fc2a4e59 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 @@ -42,6 +42,7 @@ import java.net.URL; * @see UrlResource * @see ByteArrayResource * @see InputStreamResource + * @see PathResource */ public interface Resource extends InputStreamSource { diff --git a/spring-core/src/test/java/org/springframework/core/io/PathResourceTests.java b/spring-core/src/test/java/org/springframework/core/io/PathResourceTests.java new file mode 100644 index 0000000000..a4f87b638f --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/io/PathResourceTests.java @@ -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(); + } + +}