Skip to content

Commit a611c46

Browse files
AlekseiEfimovdfuch
andcommitted
8264048: Fix caching in Jar URL connections when an entry is missing
Co-authored-by: Daniel Fuchs <dfuchs@openjdk.org> Reviewed-by: bchristi, dfuchs
1 parent bf26a25 commit a611c46

File tree

6 files changed

+370
-26
lines changed

6 files changed

+370
-26
lines changed

src/java.base/share/classes/jdk/internal/loader/URLClassPath.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -644,14 +644,16 @@ Resource getResource(final String name, boolean check) {
644644
URLClassPath.check(url);
645645
}
646646
uc = url.openConnection();
647-
InputStream in = uc.getInputStream();
647+
648648
if (uc instanceof JarURLConnection) {
649649
/* Need to remember the jar file so it can be closed
650650
* in a hurry.
651651
*/
652652
JarURLConnection juc = (JarURLConnection)uc;
653653
jarfile = JarLoader.checkJar(juc.getJarFile());
654654
}
655+
656+
InputStream in = uc.getInputStream();
655657
} catch (Exception e) {
656658
return null;
657659
}

src/java.base/share/classes/sun/net/www/protocol/jar/JarURLConnection.java

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -117,36 +117,56 @@ public void close () throws IOException {
117117
}
118118
}
119119

120-
121-
122120
public void connect() throws IOException {
123121
if (!connected) {
124-
/* the factory call will do the security checks */
125-
jarFile = factory.get(getJarFileURL(), getUseCaches());
122+
boolean useCaches = getUseCaches();
123+
String entryName = this.entryName;
126124

127-
/* we also ask the factory the permission that was required
128-
* to get the jarFile, and set it as our permission.
129-
*/
130-
if (getUseCaches()) {
131-
boolean oldUseCaches = jarFileURLConnection.getUseCaches();
132-
jarFileURLConnection = factory.getConnection(jarFile);
133-
jarFileURLConnection.setUseCaches(oldUseCaches);
134-
}
125+
/* the factory call will do the security checks */
126+
URL url = getJarFileURL();
127+
// if we have an entry name, and the jarfile is local,
128+
// don't put the jar into the cache until after we have
129+
// validated that the entry name exists
130+
jarFile = entryName == null
131+
? factory.get(url, useCaches)
132+
: factory.getOrCreate(url, useCaches);
135133

136134
if ((entryName != null)) {
137-
jarEntry = (JarEntry)jarFile.getEntry(entryName);
135+
jarEntry = (JarEntry) jarFile.getEntry(entryName);
138136
if (jarEntry == null) {
139137
try {
140-
if (!getUseCaches()) {
141-
jarFile.close();
142-
}
138+
// only close the jar file if it isn't in the
139+
// cache. If the jar file is local, it won't be
140+
// in the cache yet, and so will be closed here.
141+
factory.closeIfNotCached(url, jarFile);
143142
} catch (Exception e) {
144143
}
145144
throw new FileNotFoundException("JAR entry " + entryName +
146-
" not found in " +
147-
jarFile.getName());
145+
" not found in " +
146+
jarFile.getName());
148147
}
149148
}
149+
150+
// we have validated that the entry exists.
151+
// if useCaches was requested, update the cache now.
152+
if (useCaches && entryName != null) {
153+
// someone may have beat us and updated the cache
154+
// already - in which case - cacheIfAbsent will
155+
// return false. cacheIfAbsent returns true if
156+
// our jarFile is in the cache when the method
157+
// returns, whether because it put it there or
158+
// because it found it there.
159+
useCaches = factory.cacheIfAbsent(url, jarFile);
160+
}
161+
162+
/* we also ask the factory the permission that was required
163+
* to get the jarFile, and set it as our permission.
164+
*/
165+
if (useCaches) {
166+
boolean oldUseCaches = jarFileURLConnection.getUseCaches();
167+
jarFileURLConnection = factory.getConnection(jarFile);
168+
jarFileURLConnection.setUseCaches(oldUseCaches);
169+
}
150170
connected = true;
151171
}
152172
}

src/java.base/share/classes/sun/net/www/protocol/jar/URLJarFile.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2001, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -104,7 +104,7 @@ private URLJarFile(URL url, URLJarFileCloseController closeController, Runtime.V
104104
this.closeController = closeController;
105105
}
106106

107-
private static boolean isFileURL(URL url) {
107+
static boolean isFileURL(URL url) {
108108
if (url.getProtocol().equalsIgnoreCase("file")) {
109109
/*
110110
* Consider this a 'file' only if it's a LOCAL file, because

src/java.base/unix/classes/sun/net/www/protocol/jar/JarFileFactory.java

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,75 @@ public JarFile get(URL url) throws IOException {
7171
return get(url, true);
7272
}
7373

74+
/**
75+
* Get or create a {@code JarFile} for the given {@code url}.
76+
* If {@code useCaches} is true, this method attempts to find
77+
* a jar file in the cache, and if so, returns it.
78+
* If no jar file is found in the cache, or {@code useCaches}
79+
* is false, the method creates a new jar file.
80+
* If the URL points to a local file, the returned jar file
81+
* will not be put in the cache yet.
82+
* The caller should then call {@link #cacheIfAbsent(URL, JarFile)}
83+
* with the returned jar file, if updating the cache is desired.
84+
* @param url the jar file url
85+
* @param useCaches whether the cache should be used
86+
* @return a new or cached jar file.
87+
* @throws IOException if the jar file couldn't be created
88+
*/
89+
JarFile getOrCreate(URL url, boolean useCaches) throws IOException {
90+
if (useCaches == false) {
91+
return get(url, false);
92+
}
93+
94+
if (!URLJarFile.isFileURL(url)) {
95+
// A temporary file will be created, we can prepopulate
96+
// the cache in this case.
97+
return get(url, useCaches);
98+
}
99+
100+
// We have a local file. Do not prepopulate the cache.
101+
JarFile result;
102+
synchronized (instance) {
103+
result = getCachedJarFile(url);
104+
}
105+
if (result == null) {
106+
result = URLJarFile.getJarFile(url, this);
107+
}
108+
if (result == null)
109+
throw new FileNotFoundException(url.toString());
110+
return result;
111+
}
112+
113+
/**
114+
* Close the given jar file if it isn't present in the cache.
115+
* Otherwise, does nothing.
116+
* @param url the jar file URL
117+
* @param jarFile the jar file to close
118+
* @return true if the jar file has been closed, false otherwise.
119+
* @throws IOException if an error occurs while closing the jar file.
120+
*/
121+
boolean closeIfNotCached(URL url, JarFile jarFile) throws IOException {
122+
JarFile result;
123+
synchronized (instance) {
124+
result = getCachedJarFile(url);
125+
}
126+
if (result != jarFile) jarFile.close();
127+
return result != jarFile;
128+
}
129+
130+
boolean cacheIfAbsent(URL url, JarFile jarFile) {
131+
JarFile cached;
132+
synchronized (instance) {
133+
String key = urlKey(url);
134+
cached = fileCache.get(key);
135+
if (cached == null) {
136+
fileCache.put(key, jarFile);
137+
urlCache.put(jarFile, url);
138+
}
139+
}
140+
return cached == null || cached == jarFile;
141+
}
142+
74143
JarFile get(URL url, boolean useCaches) throws IOException {
75144

76145
JarFile result;
@@ -106,7 +175,7 @@ JarFile get(URL url, boolean useCaches) throws IOException {
106175

107176
/**
108177
* Callback method of the URLJarFileCloseController to
109-
* indicate that the JarFile is close. This way we can
178+
* indicate that the JarFile is closed. This way we can
110179
* remove the JarFile from the cache
111180
*/
112181
public void close(JarFile jarFile) {

src/java.base/windows/classes/sun/net/www/protocol/jar/JarFileFactory.java

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,17 +71,99 @@ public JarFile get(URL url) throws IOException {
7171
return get(url, true);
7272
}
7373

74-
JarFile get(URL url, boolean useCaches) throws IOException {
74+
/**
75+
* Get or create a {@code JarFile} for the given {@code url}.
76+
* If {@code useCaches} is true, this method attempts to find
77+
* a jar file in the cache, and if so, returns it.
78+
* If no jar file is found in the cache, or {@code useCaches}
79+
* is false, the method creates a new jar file.
80+
* If the URL points to a local file, the returned jar file
81+
* will not be put in the cache yet.
82+
* The caller should then call {@link #cacheIfAbsent(URL, JarFile)}
83+
* with the returned jar file, if updating the cache is desired.
84+
* @param url the jar file url
85+
* @param useCaches whether the cache should be used
86+
* @return a new or cached jar file.
87+
* @throws IOException if the jar file couldn't be created
88+
*/
89+
JarFile getOrCreate(URL url, boolean useCaches) throws IOException {
90+
if (useCaches == false) {
91+
return get(url, false);
92+
}
93+
URL patched = urlFor(url);
94+
if (!URLJarFile.isFileURL(patched)) {
95+
// A temporary file will be created, we can prepopulate
96+
// the cache in this case.
97+
return get(url, useCaches);
98+
}
99+
100+
// We have a local file. Do not prepopulate the cache.
101+
JarFile result;
102+
synchronized (instance) {
103+
result = getCachedJarFile(patched);
104+
}
105+
if (result == null) {
106+
result = URLJarFile.getJarFile(patched, this);
107+
}
108+
if (result == null)
109+
throw new FileNotFoundException(url.toString());
110+
return result;
111+
}
112+
113+
/**
114+
* Close the given jar file if it isn't present in the cache.
115+
* Otherwise, does nothing.
116+
* @param url the jar file URL
117+
* @param jarFile the jar file to close
118+
* @return true if the jar file has been closed, false otherwise.
119+
* @throws IOException if an error occurs while closing the jar file.
120+
*/
121+
boolean closeIfNotCached(URL url, JarFile jarFile) throws IOException {
122+
url = urlFor(url);
123+
JarFile result;
124+
synchronized (instance) {
125+
result = getCachedJarFile(url);
126+
}
127+
if (result != jarFile) jarFile.close();
128+
return result != jarFile;
129+
}
130+
131+
boolean cacheIfAbsent(URL url, JarFile jarFile) {
132+
try {
133+
url = urlFor(url);
134+
} catch (IOException x) {
135+
// should not happen
136+
return false;
137+
}
138+
JarFile cached;
139+
synchronized (instance) {
140+
String key = urlKey(url);
141+
cached = fileCache.get(key);
142+
if (cached == null) {
143+
fileCache.put(key, jarFile);
144+
urlCache.put(jarFile, url);
145+
}
146+
}
147+
return cached == null || cached == jarFile;
148+
}
149+
150+
private URL urlFor(URL url) throws IOException {
75151
if (url.getProtocol().equalsIgnoreCase("file")) {
76152
// Deal with UNC pathnames specially. See 4180841
77153

78154
String host = url.getHost();
79155
if (host != null && !host.isEmpty() &&
80-
!host.equalsIgnoreCase("localhost")) {
156+
!host.equalsIgnoreCase("localhost")) {
81157

82158
url = new URL("file", "", "//" + host + url.getPath());
83159
}
84160
}
161+
return url;
162+
}
163+
164+
JarFile get(URL url, boolean useCaches) throws IOException {
165+
166+
url = urlFor(url);
85167

86168
JarFile result;
87169
JarFile local_result;
@@ -116,7 +198,7 @@ JarFile get(URL url, boolean useCaches) throws IOException {
116198

117199
/**
118200
* Callback method of the URLJarFileCloseController to
119-
* indicate that the JarFile is close. This way we can
201+
* indicate that the JarFile is closed. This way we can
120202
* remove the JarFile from the cache
121203
*/
122204
public void close(JarFile jarFile) {

0 commit comments

Comments
 (0)