Skip to content

Commit 1a154c3

Browse files
committed
Consistently throw FileNotFoundException even for NIO access
Issue: SPR-16334
1 parent 37f0e8c commit 1a154c3

File tree

6 files changed

+186
-130
lines changed

6 files changed

+186
-130
lines changed

spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java

Lines changed: 69 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626
import java.net.URLConnection;
2727
import java.nio.channels.FileChannel;
2828
import java.nio.channels.ReadableByteChannel;
29+
import java.nio.file.NoSuchFileException;
2930
import java.nio.file.StandardOpenOption;
3031

3132
import org.springframework.util.ResourceUtils;
@@ -42,6 +43,68 @@
4243
*/
4344
public abstract class AbstractFileResolvingResource extends AbstractResource {
4445

46+
@Override
47+
public boolean exists() {
48+
try {
49+
URL url = getURL();
50+
if (ResourceUtils.isFileURL(url)) {
51+
// Proceed with file system resolution
52+
return getFile().exists();
53+
}
54+
else {
55+
// Try a URL connection content-length header
56+
URLConnection con = url.openConnection();
57+
customizeConnection(con);
58+
HttpURLConnection httpCon =
59+
(con instanceof HttpURLConnection ? (HttpURLConnection) con : null);
60+
if (httpCon != null) {
61+
int code = httpCon.getResponseCode();
62+
if (code == HttpURLConnection.HTTP_OK) {
63+
return true;
64+
}
65+
else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
66+
return false;
67+
}
68+
}
69+
if (con.getContentLength() >= 0) {
70+
return true;
71+
}
72+
if (httpCon != null) {
73+
// no HTTP OK status, and no content-length header: give up
74+
httpCon.disconnect();
75+
return false;
76+
}
77+
else {
78+
// Fall back to stream existence: can we open the stream?
79+
InputStream is = getInputStream();
80+
is.close();
81+
return true;
82+
}
83+
}
84+
}
85+
catch (IOException ex) {
86+
return false;
87+
}
88+
}
89+
90+
@Override
91+
public boolean isReadable() {
92+
try {
93+
URL url = getURL();
94+
if (ResourceUtils.isFileURL(url)) {
95+
// Proceed with file system resolution
96+
File file = getFile();
97+
return (file.canRead() && !file.isDirectory());
98+
}
99+
else {
100+
return true;
101+
}
102+
}
103+
catch (IOException ex) {
104+
return false;
105+
}
106+
}
107+
45108
@Override
46109
public boolean isFile() {
47110
try {
@@ -123,81 +186,20 @@ protected File getFile(URI uri) throws IOException {
123186
* This implementation returns a FileChannel for the given URI-identified
124187
* resource, provided that it refers to a file in the file system.
125188
* @since 5.0
126-
* @see #getFile(URI)
189+
* @see #getFile()
127190
*/
128191
@Override
129192
public ReadableByteChannel readableChannel() throws IOException {
130-
if (isFile()) {
193+
try {
194+
// Try file system channel
131195
return FileChannel.open(getFile().toPath(), StandardOpenOption.READ);
132196
}
133-
else {
197+
catch (FileNotFoundException | NoSuchFileException ex) {
198+
// Fall back to InputStream adaptation in superclass
134199
return super.readableChannel();
135200
}
136201
}
137202

138-
139-
@Override
140-
public boolean exists() {
141-
try {
142-
URL url = getURL();
143-
if (ResourceUtils.isFileURL(url)) {
144-
// Proceed with file system resolution
145-
return getFile().exists();
146-
}
147-
else {
148-
// Try a URL connection content-length header
149-
URLConnection con = url.openConnection();
150-
customizeConnection(con);
151-
HttpURLConnection httpCon =
152-
(con instanceof HttpURLConnection ? (HttpURLConnection) con : null);
153-
if (httpCon != null) {
154-
int code = httpCon.getResponseCode();
155-
if (code == HttpURLConnection.HTTP_OK) {
156-
return true;
157-
}
158-
else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
159-
return false;
160-
}
161-
}
162-
if (con.getContentLength() >= 0) {
163-
return true;
164-
}
165-
if (httpCon != null) {
166-
// no HTTP OK status, and no content-length header: give up
167-
httpCon.disconnect();
168-
return false;
169-
}
170-
else {
171-
// Fall back to stream existence: can we open the stream?
172-
InputStream is = getInputStream();
173-
is.close();
174-
return true;
175-
}
176-
}
177-
}
178-
catch (IOException ex) {
179-
return false;
180-
}
181-
}
182-
183-
@Override
184-
public boolean isReadable() {
185-
try {
186-
URL url = getURL();
187-
if (ResourceUtils.isFileURL(url)) {
188-
// Proceed with file system resolution
189-
File file = getFile();
190-
return (file.canRead() && !file.isDirectory());
191-
}
192-
else {
193-
return true;
194-
}
195-
}
196-
catch (IOException ex) {
197-
return false;
198-
}
199-
}
200-
201203
@Override
202204
public long contentLength() throws IOException {
203205
URL url = getURL();
@@ -231,7 +233,6 @@ public long lastModified() throws IOException {
231233
return con.getLastModified();
232234
}
233235

234-
235236
/**
236237
* Customize the given {@link URLConnection}, obtained in the course of an
237238
* {@link #exists()}, {@link #contentLength()} or {@link #lastModified()} call.

spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.core.io;
1818

1919
import java.io.File;
20+
import java.io.FileNotFoundException;
2021
import java.io.IOException;
2122
import java.io.InputStream;
2223
import java.io.OutputStream;
@@ -26,6 +27,7 @@
2627
import java.nio.channels.ReadableByteChannel;
2728
import java.nio.channels.WritableByteChannel;
2829
import java.nio.file.Files;
30+
import java.nio.file.NoSuchFileException;
2931
import java.nio.file.StandardOpenOption;
3032

3133
import org.springframework.util.Assert;
@@ -115,12 +117,17 @@ public boolean isReadable() {
115117
}
116118

117119
/**
118-
* This implementation opens a FileInputStream for the underlying file.
120+
* This implementation opens a NIO file stream for the underlying file.
119121
* @see java.io.FileInputStream
120122
*/
121123
@Override
122124
public InputStream getInputStream() throws IOException {
123-
return Files.newInputStream(this.file.toPath());
125+
try {
126+
return Files.newInputStream(this.file.toPath());
127+
}
128+
catch (NoSuchFileException ex) {
129+
throw new FileNotFoundException(ex.getMessage());
130+
}
124131
}
125132

126133
/**
@@ -183,7 +190,12 @@ public File getFile() {
183190
*/
184191
@Override
185192
public ReadableByteChannel readableChannel() throws IOException {
186-
return FileChannel.open(this.file.toPath(), StandardOpenOption.READ);
193+
try {
194+
return FileChannel.open(this.file.toPath(), StandardOpenOption.READ);
195+
}
196+
catch (NoSuchFileException ex) {
197+
throw new FileNotFoundException(ex.getMessage());
198+
}
187199
}
188200

189201
/**

spring-core/src/main/java/org/springframework/core/io/FileUrlResource.java

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,11 +26,13 @@
2626
import java.nio.file.Files;
2727
import java.nio.file.StandardOpenOption;
2828

29+
import org.springframework.lang.Nullable;
2930
import org.springframework.util.ResourceUtils;
3031

3132
/**
3233
* Subclass of {@link UrlResource} which assumes file resolution, to the degree
33-
* of implementing the {@link WritableResource} interface for it.
34+
* of implementing the {@link WritableResource} interface for it. This resource
35+
* variant also caches resolved {@link File} handles from {@link #getFile()}.
3436
*
3537
* <p>This is the class resolved by {@link DefaultResourceLoader} for a "file:..."
3638
* URL location, allowing a downcast to {@link WritableResource} for it.
@@ -44,6 +46,10 @@
4446
*/
4547
public class FileUrlResource extends UrlResource implements WritableResource {
4648

49+
@Nullable
50+
private volatile File file;
51+
52+
4753
/**
4854
* Create a new {@code FileUrlResource} based on the given URL object.
4955
* <p>Note that this does not enforce "file" as URL protocol. If a protocol
@@ -71,11 +77,14 @@ public FileUrlResource(String location) throws MalformedURLException {
7177

7278

7379
@Override
74-
public Resource createRelative(String relativePath) throws MalformedURLException {
75-
if (relativePath.startsWith("/")) {
76-
relativePath = relativePath.substring(1);
80+
public File getFile() throws IOException {
81+
File file = this.file;
82+
if (file != null) {
83+
return file;
7784
}
78-
return new FileUrlResource(new URL(getURL(), relativePath));
85+
file = super.getFile();
86+
this.file = file;
87+
return file;
7988
}
8089

8190
@Override
@@ -106,4 +115,12 @@ public WritableByteChannel writableChannel() throws IOException {
106115
return FileChannel.open(getFile().toPath(), StandardOpenOption.WRITE);
107116
}
108117

118+
@Override
119+
public Resource createRelative(String relativePath) throws MalformedURLException {
120+
if (relativePath.startsWith("/")) {
121+
relativePath = relativePath.substring(1);
122+
}
123+
return new FileUrlResource(new URL(getURL(), relativePath));
124+
}
125+
109126
}

spring-core/src/main/java/org/springframework/core/io/PathResource.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626
import java.nio.channels.ReadableByteChannel;
2727
import java.nio.channels.WritableByteChannel;
2828
import java.nio.file.Files;
29+
import java.nio.file.NoSuchFileException;
2930
import java.nio.file.OpenOption;
3031
import java.nio.file.Path;
3132
import java.nio.file.Paths;
@@ -202,7 +203,12 @@ public File getFile() throws IOException {
202203
*/
203204
@Override
204205
public ReadableByteChannel readableChannel() throws IOException {
205-
return Files.newByteChannel(this.path, StandardOpenOption.READ);
206+
try {
207+
return Files.newByteChannel(this.path, StandardOpenOption.READ);
208+
}
209+
catch (NoSuchFileException ex) {
210+
throw new FileNotFoundException(ex.getMessage());
211+
}
206212
}
207213

208214
/**
@@ -215,7 +221,7 @@ public WritableByteChannel writableChannel() throws IOException {
215221
}
216222

217223
/**
218-
* This implementation returns the underlying File's length.
224+
* This implementation returns the underlying file's length.
219225
*/
220226
@Override
221227
public long contentLength() throws IOException {

0 commit comments

Comments
 (0)