Skip to content

Commit 69f14a2

Browse files
committed
ClassPathResource.isReadable() checks InputStream (for jar directories)
Resource.isReadable() is defined to semantically imply exists() now. Issue: SPR-16832
1 parent 8593fec commit 69f14a2

File tree

6 files changed

+54
-20
lines changed

6 files changed

+54
-20
lines changed

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

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.io.File;
2020
import java.io.FileNotFoundException;
2121
import java.io.IOException;
22-
import java.io.InputStream;
2322
import java.net.HttpURLConnection;
2423
import java.net.URI;
2524
import java.net.URL;
@@ -70,19 +69,18 @@ else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
7069
return true;
7170
}
7271
if (httpCon != null) {
73-
// no HTTP OK status, and no content-length header: give up
72+
// No HTTP OK status, and no content-length header: give up
7473
httpCon.disconnect();
7574
return false;
7675
}
7776
else {
7877
// Fall back to stream existence: can we open the stream?
79-
InputStream is = getInputStream();
80-
is.close();
78+
getInputStream().close();
8179
return true;
8280
}
8381
}
8482
}
85-
catch (IOException ex) {
83+
catch (Exception ex) {
8684
return false;
8785
}
8886
}
@@ -97,10 +95,33 @@ public boolean isReadable() {
9795
return (file.canRead() && !file.isDirectory());
9896
}
9997
else {
98+
// Try InputStream resolution for jar resources
99+
URLConnection con = url.openConnection();
100+
customizeConnection(con);
101+
if (con instanceof HttpURLConnection) {
102+
HttpURLConnection httpCon = (HttpURLConnection) con;
103+
int code = httpCon.getResponseCode();
104+
if (code != HttpURLConnection.HTTP_OK) {
105+
httpCon.disconnect();
106+
return false;
107+
}
108+
}
109+
int contentLength = con.getContentLength();
110+
if (contentLength > 0) {
111+
return true;
112+
}
113+
else if (contentLength < 0) {
114+
return false;
115+
}
116+
// 0 length: either an empty file or a directory...
117+
// On current JDKs, this will trigger an NPE from within the close() call
118+
// for directories, only returning true for actual files with 0 length.
119+
getInputStream().close();
100120
return true;
101121
}
102122
}
103-
catch (IOException ex) {
123+
catch (Exception ex) {
124+
// Usually an IOException but potentially a NullPointerException (see above)
104125
return false;
105126
}
106127
}
@@ -114,7 +135,7 @@ public boolean isFile() {
114135
}
115136
return ResourceUtils.URL_PROTOCOL_FILE.equals(url.getProtocol());
116137
}
117-
catch (IOException ex) {
138+
catch (Exception ex) {
118139
return false;
119140
}
120141
}
@@ -165,7 +186,7 @@ protected boolean isFile(URI uri) {
165186
}
166187
return ResourceUtils.URL_PROTOCOL_FILE.equals(uri.getScheme());
167188
}
168-
catch (IOException ex) {
189+
catch (Exception ex) {
169190
return false;
170191
}
171192
}

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

Lines changed: 5 additions & 5 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.
@@ -57,8 +57,7 @@ public boolean exists() {
5757
catch (IOException ex) {
5858
// Fall back to stream existence: can we open the stream?
5959
try {
60-
InputStream is = getInputStream();
61-
is.close();
60+
getInputStream().close();
6261
return true;
6362
}
6463
catch (Throwable isEx) {
@@ -68,11 +67,12 @@ public boolean exists() {
6867
}
6968

7069
/**
71-
* This implementation always returns {@code true}.
70+
* This implementation always returns {@code true} for a resource
71+
* that {@link #exists() exists} (revised as of 5.1).
7272
*/
7373
@Override
7474
public boolean isReadable() {
75-
return true;
75+
return exists();
7676
}
7777

7878
/**

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

Lines changed: 6 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.
@@ -62,14 +62,16 @@ public interface Resource extends InputStreamSource {
6262
/**
6363
* Indicate whether the contents of this resource can be read via
6464
* {@link #getInputStream()}.
65-
* <p>Will be {@code true} for typical resource descriptors;
66-
* note that actual content reading may still fail when attempted.
65+
* <p>Will be {@code true} for typical resource descriptors that exist
66+
* since it strictly implies {@link #exists()} semantics as of 5.1.
67+
* Note that actual content reading may still fail when attempted.
6768
* However, a value of {@code false} is a definitive indication
6869
* that the resource content cannot be read.
6970
* @see #getInputStream()
71+
* @see #exists()
7072
*/
7173
default boolean isReadable() {
72-
return true;
74+
return exists();
7375
}
7476

7577
/**

spring-core/src/test/java/org/springframework/core/io/ClassPathResourceTests.java

Lines changed: 12 additions & 1 deletion
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.
@@ -109,6 +109,17 @@ public void preserveLeadingSlashForClassRelativeAccess() {
109109
assertEquals("/test.html", ((ClassPathResource) new ClassPathResource("", getClass()).createRelative("/test.html")).getPath());
110110
}
111111

112+
@Test
113+
public void directoryNotReadable() {
114+
Resource fileDir = new ClassPathResource("org/springframework/core");
115+
assertTrue(fileDir.exists());
116+
assertFalse(fileDir.isReadable());
117+
118+
Resource jarDir = new ClassPathResource("reactor/core");
119+
assertTrue(jarDir.exists());
120+
assertFalse(jarDir.isReadable());
121+
}
122+
112123

113124
private void assertDescriptionContainsExpectedPath(ClassPathResource resource, String expectedPath) {
114125
Matcher matcher = DESCRIPTION_PATTERN.matcher(resource.getDescription());

spring-webflux/src/main/java/org/springframework/web/reactive/resource/PathResourceResolver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ private Mono<Resource> getResource(String resourcePath, List<? extends Resource>
109109
protected Mono<Resource> getResource(String resourcePath, Resource location) {
110110
try {
111111
Resource resource = location.createRelative(resourcePath);
112-
if (resource.exists() && resource.isReadable()) {
112+
if (resource.isReadable()) {
113113
if (checkResource(resource, location)) {
114114
if (logger.isTraceEnabled()) {
115115
logger.trace("Found match: " + resource);

spring-webmvc/src/main/java/org/springframework/web/servlet/resource/PathResourceResolver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ else if (logger.isTraceEnabled()) {
183183
@Nullable
184184
protected Resource getResource(String resourcePath, Resource location) throws IOException {
185185
Resource resource = location.createRelative(resourcePath);
186-
if (resource.exists() && resource.isReadable()) {
186+
if (resource.isReadable()) {
187187
if (checkResource(resource, location)) {
188188
return resource;
189189
}

0 commit comments

Comments
 (0)