11
11
import java .security .ProtectionDomain ;
12
12
import java .security .cert .Certificate ;
13
13
import java .util .Objects ;
14
- import java .util .concurrent .locks .Lock ;
15
- import java .util .concurrent .locks .ReadWriteLock ;
16
- import java .util .concurrent .locks .ReentrantReadWriteLock ;
14
+ import java .util .concurrent .CompletableFuture ;
15
+ import java .util .concurrent .atomic .AtomicReference ;
17
16
import java .util .jar .JarEntry ;
18
17
import java .util .jar .JarFile ;
19
18
import java .util .zip .ZipEntry ;
20
- import java .util .zip .ZipFile ;
21
19
22
20
import io .smallrye .common .io .jar .JarEntries ;
23
- import io .smallrye .common .io .jar .JarFiles ;
24
21
25
22
/**
26
23
* A jar resource
27
24
*/
28
25
public class JarResource implements ClassLoadingResource {
29
26
30
- private final ManifestInfo manifestInfo ;
31
- private final Path jarPath ;
32
-
33
- private final Lock readLock ;
34
- private final Lock writeLock ;
35
-
36
27
private volatile ProtectionDomain protectionDomain ;
28
+ private final ManifestInfo manifestInfo ;
37
29
38
- //Guarded by the read/write lock; open/close operations on the JarFile require the exclusive lock,
39
- //while using an existing open reference can use the shared lock.
40
- //If a lock is acquired, and as long as it's owned, we ensure that the zipFile reference
41
- //points to an open JarFile instance, and read operations are valid.
42
- //To close the jar, the exclusive lock must be owned, and reference will be set to null before releasing it.
43
- //Likewise, opening a JarFile requires the exclusive lock.
44
- private volatile JarFile zipFile ;
30
+ final Path jarPath ;
31
+ final AtomicReference <CompletableFuture <JarFileReference >> jarFileReference = new AtomicReference <>();
45
32
46
33
public JarResource (ManifestInfo manifestInfo , Path jarPath ) {
47
34
this .manifestInfo = manifestInfo ;
48
35
this .jarPath = jarPath ;
49
- final ReadWriteLock readWriteLock = new ReentrantReadWriteLock ();
50
- this .readLock = readWriteLock .readLock ();
51
- this .writeLock = readWriteLock .writeLock ();
52
36
}
53
37
54
38
@ Override
@@ -69,38 +53,48 @@ public void init() {
69
53
70
54
@ Override
71
55
public byte [] getResourceData (String resource ) {
72
- final ZipFile zipFile = readLockAcquireAndGetJarReference ();
73
- try {
74
- ZipEntry entry = zipFile .getEntry (resource );
56
+ return JarFileReference .withJarFile (this , resource , JarResourceDataProvider .INSTANCE );
57
+ }
58
+
59
+ private static class JarResourceDataProvider implements JarFileReference .JarFileConsumer <byte []> {
60
+ private static final JarResourceDataProvider INSTANCE = new JarResourceDataProvider ();
61
+
62
+ @ Override
63
+ public byte [] apply (JarFile jarFile , Path path , String res ) {
64
+ ZipEntry entry = jarFile .getEntry (res );
75
65
if (entry == null ) {
76
66
return null ;
77
67
}
78
- try (InputStream is = zipFile .getInputStream (entry )) {
68
+ try (InputStream is = jarFile .getInputStream (entry )) {
79
69
byte [] data = new byte [(int ) entry .getSize ()];
80
70
int pos = 0 ;
81
71
int rem = data .length ;
82
72
while (rem > 0 ) {
83
73
int read = is .read (data , pos , rem );
84
74
if (read == -1 ) {
85
- throw new RuntimeException ("Failed to read all data for " + resource );
75
+ throw new RuntimeException ("Failed to read all data for " + res );
86
76
}
87
77
pos += read ;
88
78
rem -= read ;
89
79
}
90
80
return data ;
91
81
} catch (IOException e ) {
92
- throw new RuntimeException ("Failed to read zip entry " + resource , e );
82
+ throw new RuntimeException ("Failed to read zip entry " + res , e );
93
83
}
94
- } finally {
95
- readLock .unlock ();
96
84
}
97
85
}
98
86
99
87
@ Override
100
88
public URL getResourceURL (String resource ) {
101
- final JarFile jarFile = readLockAcquireAndGetJarReference ();
102
- try {
103
- JarEntry entry = jarFile .getJarEntry (resource );
89
+ return JarFileReference .withJarFile (this , resource , JarResourceURLProvider .INSTANCE );
90
+ }
91
+
92
+ private static class JarResourceURLProvider implements JarFileReference .JarFileConsumer <URL > {
93
+ private static final JarResourceURLProvider INSTANCE = new JarResourceURLProvider ();
94
+
95
+ @ Override
96
+ public URL apply (JarFile jarFile , Path path , String res ) {
97
+ JarEntry entry = jarFile .getJarEntry (res );
104
98
if (entry == null ) {
105
99
return null ;
106
100
}
@@ -110,15 +104,7 @@ public URL getResourceURL(String resource) {
110
104
if (realName .endsWith ("/" )) {
111
105
realName = realName .substring (0 , realName .length () - 1 );
112
106
}
113
- final URI jarUri = jarPath .toUri ();
114
- // first create a URI which includes both the jar file path and the relative resource name
115
- // and then invoke a toURL on it. The URI reconstruction allows for any encoding to be done
116
- // for the "path" which includes the "realName"
117
- var ssp = new StringBuilder (jarUri .getPath ().length () + realName .length () + 2 );
118
- ssp .append (jarUri .getPath ());
119
- ssp .append ("!/" );
120
- ssp .append (realName );
121
- final URL resUrl = new URI (jarUri .getScheme (), ssp .toString (), null ).toURL ();
107
+ final URL resUrl = getUrl (path , realName );
122
108
// wrap it up into a "jar" protocol URL
123
109
//horrible hack to deal with '?' characters in the URL
124
110
//seems to be the only way, the URI constructor just does not let you handle them in a sane way
@@ -136,8 +122,18 @@ public URL getResourceURL(String resource) {
136
122
} catch (MalformedURLException | URISyntaxException e ) {
137
123
throw new RuntimeException (e );
138
124
}
139
- } finally {
140
- readLock .unlock ();
125
+ }
126
+
127
+ private static URL getUrl (Path jarPath , String realName ) throws MalformedURLException , URISyntaxException {
128
+ final URI jarUri = jarPath .toUri ();
129
+ // first create a URI which includes both the jar file path and the relative resource name
130
+ // and then invoke a toURL on it. The URI reconstruction allows for any encoding to be done
131
+ // for the "path" which includes the "realName"
132
+ var ssp = new StringBuilder (jarUri .getPath ().length () + realName .length () + 2 );
133
+ ssp .append (jarUri .getPath ());
134
+ ssp .append ("!/" );
135
+ ssp .append (realName );
136
+ return new URI (jarUri .getScheme (), ssp .toString (), null ).toURL ();
141
137
}
142
138
}
143
139
@@ -151,60 +147,15 @@ public ProtectionDomain getProtectionDomain() {
151
147
return protectionDomain ;
152
148
}
153
149
154
- private JarFile readLockAcquireAndGetJarReference () {
155
- while (true ) {
156
- readLock .lock ();
157
- final JarFile zipFileLocal = this .zipFile ;
158
- if (zipFileLocal != null ) {
159
- //Expected fast path: returns a reference to the open JarFile while owning the readLock
160
- return zipFileLocal ;
161
- } else {
162
- //This Lock implementation doesn't allow upgrading a readLock to a writeLock, so release it
163
- //as we're going to need the WriteLock.
164
- readLock .unlock ();
165
- //trigger the JarFile being (re)opened.
166
- ensureJarFileIsOpen ();
167
- //Now since we no longer own any lock, we need to try again to obtain the readLock
168
- //and check for the reference still being valid.
169
- //This exposes us to a race with closing the just-opened JarFile;
170
- //however this should be extremely rare, so we can trust we won't loop much;
171
- //A counter doesn't seem necessary, as in fact we know that methods close()
172
- //and resetInternalCaches() are invoked each at most once, which limits the amount
173
- //of loops here in practice.
174
- }
175
- }
176
- }
177
-
178
- private void ensureJarFileIsOpen () {
179
- writeLock .lock ();
180
- try {
181
- if (this .zipFile == null ) {
182
- try {
183
- this .zipFile = JarFiles .create (jarPath .toFile ());
184
- } catch (IOException e ) {
185
- throw new RuntimeException ("Failed to open " + jarPath , e );
186
- }
187
- }
188
- } finally {
189
- writeLock .unlock ();
190
- }
191
- }
192
-
193
150
@ Override
194
151
public void close () {
195
- writeLock .lock ();
196
- try {
197
- final JarFile zipFileLocal = this .zipFile ;
198
- if (zipFileLocal != null ) {
199
- try {
200
- this .zipFile = null ;
201
- zipFileLocal .close ();
202
- } catch (Throwable e ) {
203
- //ignore
204
- }
205
- }
206
- } finally {
207
- writeLock .unlock ();
152
+ var futureRef = jarFileReference .get ();
153
+ if (futureRef != null ) {
154
+ // The jarfile has been already used and it's going to be removed from the cache,
155
+ // so the future must be already completed
156
+ var ref = futureRef .getNow (null );
157
+ assert (ref != null );
158
+ ref .close (this );
208
159
}
209
160
}
210
161
0 commit comments