jaulib v1.3.0
Jau Support Library (C++, Java, ..)
TempJarCache.java
Go to the documentation of this file.
1/**
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2021 Gothel Software e.K.
4 * Copyright (c) 2011 Gothel Software e.K.
5 * Copyright (c) 2011 JogAmp Community.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining
8 * a copy of this software and associated documentation files (the
9 * "Software"), to deal in the Software without restriction, including
10 * without limitation the rights to use, copy, modify, merge, publish,
11 * distribute, sublicense, and/or sell copies of the Software, and to
12 * permit persons to whom the Software is furnished to do so, subject to
13 * the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be
16 * included in all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 */
26package org.jau.pkg.cache;
27
28import java.io.File;
29import java.io.IOException;
30import java.net.URISyntaxException;
31import java.security.cert.Certificate;
32import java.util.HashMap;
33import java.util.Map;
34import java.util.jar.JarFile;
35
36import org.jau.net.Uri;
37import org.jau.pkg.JarUtil;
38import org.jau.sec.SecurityUtil;
39import org.jau.sys.Debug;
40import org.jau.sys.JNILibrary;
41
42/**
43 * Static Jar file cache handler using an underlying instance of {@link TempFileCache}, see {@link #getTempFileCache()}.
44 * <p>
45 * Lifecycle: Concurrently running JVMs and ClassLoader
46 * </p>
47 */
48public class TempJarCache {
49 private static final boolean DEBUG = Debug.debug("TempJarCache");
50
51 // A HashMap of native libraries that can be loaded with System.load()
52 // The key is the string name of the library as passed into the loadLibrary
53 // call; it is the file name without the directory or the platform-dependent
54 // library prefix and suffix. The value is the absolute path name to the
55 // unpacked library file in nativeTmpDir.
56 private static Map<String, String> nativeLibMap;
57
58 public enum LoadState {
59 LOOKED_UP, LOADED;
60
61 public boolean compliesWith(final LoadState o2) {
62 return null != o2 ? compareTo(o2) >= 0 : false;
63 }
64 }
65 private static boolean testLoadState(final LoadState has, final LoadState exp) {
66 if(null == has) {
67 return null == exp;
68 }
69 return has.compliesWith(exp);
70 }
71
72 // Set of jar files added
73 private static Map<Uri, LoadState> nativeLibJars;
74 private static Map<Uri, LoadState> classFileJars;
75 private static Map<Uri, LoadState> resourceFileJars;
76
77 private static TempFileCache tmpFileCache;
78
79 private static volatile boolean staticInitError = false;
80 private static volatile boolean staticTempIsExecutable = true;
81 private static volatile boolean isInit = false;
82
83 /**
84 * Documented way to kick off static initialization.
85 *
86 * @return true is static initialization was successful
87 */
88 public static boolean initSingleton() {
89 if (!isInit) { // volatile: ok
90 synchronized (TempJarCache.class) {
91 if (!isInit) {
92 staticInitError = !TempFileCache.initSingleton();
93
94 if(!staticInitError) {
95 tmpFileCache = new TempFileCache();
96 staticInitError = !tmpFileCache.isValid(false);
97 staticTempIsExecutable = tmpFileCache.isValid(true);
98 }
99
100 if(!staticInitError) {
101 // Initialize the collections of resources
102 nativeLibMap = new HashMap<String, String>();
103 nativeLibJars = new HashMap<Uri, LoadState>();
104 classFileJars = new HashMap<Uri, LoadState>();
105 resourceFileJars = new HashMap<Uri, LoadState>();
106 }
107 if(DEBUG) {
108 final File tempDir = null != tmpFileCache ? tmpFileCache.getTempDir() : null;
109 final String tempDirAbsPath = null != tempDir ? tempDir.getAbsolutePath() : null;
110 System.err.println("TempJarCache.initSingleton(): ok "+(false==staticInitError)+", "+ tempDirAbsPath+", executable "+staticTempIsExecutable);
111 }
112 isInit = true;
113 }
114 }
115 }
116 return !staticInitError;
117 }
118
119 /**
120 * This is <b>not recommended</b> since the JNI libraries may still be
121 * in use by the ClassLoader they are loaded via {@link System#load(String)}.
122 * </p>
123 * <p>
124 * In JogAmp, JNI native libraries loaded and registered by {@link JNIJarLibrary}
125 * derivations, where the native JARs might be loaded via {@link JNIJarLibrary#addNativeJarLibs(Class, String) }.
126 * </p>
127 * <p>
128 * The only valid use case to shutdown the TempJarCache is at bootstrapping,
129 * i.e. when no native library is guaranteed to be loaded. This could be useful
130 * if bootstrapping needs to find the proper native library type.
131 * </p>
132 *
133 public static void shutdown() {
134 if (isInit) { // volatile: ok
135 synchronized (TempJarCache.class) {
136 if (isInit) {
137 if(DEBUG) {
138 System.err.println("TempJarCache.shutdown(): real "+(false==staticInitError)+", "+ tmpFileCache.getTempDir());
139 }
140 isInit = false;
141 if(!staticInitError) {
142 nativeLibMap.clear();
143 nativeLibMap = null;
144 nativeLibJars.clear();
145 nativeLibJars = null;
146 classFileJars.clear();
147 classFileJars = null;
148 resourceFileJars.clear();
149 resourceFileJars = null;
150
151 tmpFileCache.destroy();
152 tmpFileCache = null;
153 }
154 }
155 }
156 }
157 } */
158
159 private static boolean isInitializedImpl() {
160 if (!isInit) { // volatile: ok
161 synchronized (TempJarCache.class) {
162 if (!isInit) {
163 return false;
164 }
165 }
166 }
167 return true;
168 }
169
170 /**
171 * @param forExecutables if {@code true}, method also tests whether the underlying cache is suitable to load native libraries or launch executables
172 * @return true if this class has been properly initialized, ie. is in use. Otherwise returns false.
173 */
174 public static boolean isInitialized(final boolean forExecutables) {
175 return isInitializedImpl() && !staticInitError && ( !forExecutables || staticTempIsExecutable );
176 }
177
178 /**
179 * @param forExecutables if {@code true}, method also tests whether the underlying cache is suitable to load native libraries or launch executables
180 */
181 /* package */ static void checkInitialized(final boolean forExecutables) {
182 if(!isInitializedImpl()) {
183 throw new RuntimeException("initSingleton() has to be called first.");
184 }
185 if(staticInitError) {
186 throw new RuntimeException("initSingleton() failed.");
187 }
188 if( forExecutables && !staticTempIsExecutable ) {
189 final File tempDir = null != tmpFileCache ? tmpFileCache.getTempDir() : null;
190 final String tempDirAbsPath = null != tempDir ? tempDir.getAbsolutePath() : null;
191 throw new RuntimeException("TempJarCache folder not suitable for executables: "+tempDirAbsPath);
192 }
193 }
194
195 /**
196 * @return the underlying {@link TempFileCache}
197 * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(false)}
198 */
200 checkInitialized(false);
201 return tmpFileCache;
202 }
203
204 /**
205 * @param jarUri
206 * @param exp
207 * @return
208 * @throws IOException
209 * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(false)}
210 */
211 public synchronized static boolean checkNativeLibs(final Uri jarUri, final LoadState exp) throws IOException {
212 checkInitialized(false);
213 if(null == jarUri) {
214 throw new IllegalArgumentException("jarUri is null");
215 }
216 return testLoadState(nativeLibJars.get(jarUri), exp);
217 }
218
219 /**
220 * @param jarUri
221 * @param exp
222 * @return
223 * @throws IOException
224 * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(false)}
225 */
226 public synchronized static boolean checkClasses(final Uri jarUri, final LoadState exp) throws IOException {
227 checkInitialized(false);
228 if(null == jarUri) {
229 throw new IllegalArgumentException("jarUri is null");
230 }
231 return testLoadState(classFileJars.get(jarUri), exp);
232 }
233
234 /**
235 *
236 * @param jarUri
237 * @param exp
238 * @return
239 * @throws IOException
240 * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(false)}
241 */
242 public synchronized static boolean checkResources(final Uri jarUri, final LoadState exp) throws IOException {
243 checkInitialized(false);
244 if(null == jarUri) {
245 throw new IllegalArgumentException("jarUri is null");
246 }
247 return testLoadState(resourceFileJars.get(jarUri), exp);
248 }
249
250 /**
251 * Adds native libraries, if not yet added.
252 *
253 * @param certClass if class is certified, the JarFile entries needs to have the same certificate
254 * @param jarUri
255 * @param nativeLibraryPath if not null, only extracts native libraries within this path.
256 * @return true if native libraries were added or previously loaded from given jarUri, otherwise false
257 * @throws IOException if the <code>jarUri</code> could not be loaded or a previous load attempt failed
258 * @throws SecurityException
259 * @throws URISyntaxException
260 * @throws IllegalArgumentException
261 * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(true)}
262 */
263 public synchronized static final boolean addNativeLibs(final Class<?> certClass, final Uri jarUri, final String nativeLibraryPath) throws IOException, SecurityException, IllegalArgumentException, URISyntaxException {
264 checkInitialized(true);
265 final LoadState nativeLibJarsLS = nativeLibJars.get(jarUri);
266 if( !testLoadState(nativeLibJarsLS, LoadState.LOOKED_UP) ) {
267 nativeLibJars.put(jarUri, LoadState.LOOKED_UP);
268 final JarFile jarFile = JarUtil.getJarFile(jarUri);
269 if(DEBUG) {
270 System.err.println("TempJarCache: addNativeLibs: "+jarUri+": nativeJar "+jarFile.getName()+" (NEW)");
271 }
272 validateCertificates(certClass, jarFile);
273 final int num = JarUtil.extract(tmpFileCache.getTempDir(), nativeLibMap, jarFile, nativeLibraryPath, true, false, false);
274 nativeLibJars.put(jarUri, LoadState.LOADED);
275 return num > 0;
276 } else if( testLoadState(nativeLibJarsLS, LoadState.LOADED) ) {
277 if(DEBUG) {
278 System.err.println("TempJarCache: addNativeLibs: "+jarUri+": nativeJar "+jarUri+" (REUSE)");
279 }
280 return true;
281 }
282 throw new IOException("TempJarCache: addNativeLibs: "+jarUri+", previous load attempt failed");
283 }
284
285 /**
286 * Adds native classes, if not yet added.
287 *
288 * TODO class access pending
289 * needs Classloader.defineClass(..) access, ie. own derivation - will do when needed ..
290 *
291 * @param certClass if class is certified, the JarFile entries needs to have the same certificate
292 * @param jarUri
293 * @throws IOException if the <code>jarUri</code> could not be loaded or a previous load attempt failed
294 * @throws SecurityException
295 * @throws URISyntaxException
296 * @throws IllegalArgumentException
297 * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(false)}
298 */
299 public synchronized static final void addClasses(final Class<?> certClass, final Uri jarUri) throws IOException, SecurityException, IllegalArgumentException, URISyntaxException {
300 checkInitialized(false);
301 final LoadState classFileJarsLS = classFileJars.get(jarUri);
302 if( !testLoadState(classFileJarsLS, LoadState.LOOKED_UP) ) {
303 classFileJars.put(jarUri, LoadState.LOOKED_UP);
304 final JarFile jarFile = JarUtil.getJarFile(jarUri);
305 if(DEBUG) {
306 System.err.println("TempJarCache: addClasses: "+jarUri+": nativeJar "+jarFile.getName());
307 }
308 validateCertificates(certClass, jarFile);
309 JarUtil.extract(tmpFileCache.getTempDir(), null, jarFile,
310 null /* nativeLibraryPath */, false, true, false);
311 classFileJars.put(jarUri, LoadState.LOADED);
312 } else if( !testLoadState(classFileJarsLS, LoadState.LOADED) ) {
313 throw new IOException("TempJarCache: addClasses: "+jarUri+", previous load attempt failed");
314 }
315 }
316
317 /**
318 * Adds native resources, if not yet added.
319 *
320 * @param certClass if class is certified, the JarFile entries needs to have the same certificate
321 * @param jarUri
322 * @return
323 * @throws IOException if the <code>jarUri</code> could not be loaded or a previous load attempt failed
324 * @throws SecurityException
325 * @throws URISyntaxException
326 * @throws IllegalArgumentException
327 * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(false)}
328 */
329 public synchronized static final void addResources(final Class<?> certClass, final Uri jarUri) throws IOException, SecurityException, IllegalArgumentException, URISyntaxException {
330 checkInitialized(false);
331 final LoadState resourceFileJarsLS = resourceFileJars.get(jarUri);
332 if( !testLoadState(resourceFileJarsLS, LoadState.LOOKED_UP) ) {
333 resourceFileJars.put(jarUri, LoadState.LOOKED_UP);
334 final JarFile jarFile = JarUtil.getJarFile(jarUri);
335 if(DEBUG) {
336 System.err.println("TempJarCache: addResources: "+jarUri+": nativeJar "+jarFile.getName());
337 }
338 validateCertificates(certClass, jarFile);
339 JarUtil.extract(tmpFileCache.getTempDir(), null, jarFile,
340 null /* nativeLibraryPath */, false, false, true);
341 resourceFileJars.put(jarUri, LoadState.LOADED);
342 } else if( !testLoadState(resourceFileJarsLS, LoadState.LOADED) ) {
343 throw new IOException("TempJarCache: addResources: "+jarUri+", previous load attempt failed");
344 }
345 }
346
347 /**
348 * Adds all types, native libraries, class files and other files (resources)
349 * if not yet added.
350 *
351 * TODO class access pending
352 * needs Classloader.defineClass(..) access, ie. own derivation - will do when needed ..
353 *
354 * @param certClass if class is certified, the JarFile entries needs to have the same certificate
355 * @param jarUri
356 * @throws IOException if the <code>jarUri</code> could not be loaded or a previous load attempt failed
357 * @throws SecurityException
358 * @throws URISyntaxException
359 * @throws IllegalArgumentException
360 * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(false)}
361 */
362 public synchronized static final void addAll(final Class<?> certClass, final Uri jarUri) throws IOException, SecurityException, IllegalArgumentException, URISyntaxException {
363 checkInitialized(false);
364 if(null == jarUri) {
365 throw new IllegalArgumentException("jarUri is null");
366 }
367 final LoadState nativeLibJarsLS = nativeLibJars.get(jarUri);
368 final LoadState classFileJarsLS = classFileJars.get(jarUri);
369 final LoadState resourceFileJarsLS = resourceFileJars.get(jarUri);
370 if( !testLoadState(nativeLibJarsLS, LoadState.LOOKED_UP) ||
371 !testLoadState(classFileJarsLS, LoadState.LOOKED_UP) ||
372 !testLoadState(resourceFileJarsLS, LoadState.LOOKED_UP) ) {
373
374 final boolean extractNativeLibraries = staticTempIsExecutable && !testLoadState(nativeLibJarsLS, LoadState.LOADED);
375 final boolean extractClassFiles = !testLoadState(classFileJarsLS, LoadState.LOADED);
376 final boolean extractOtherFiles = !testLoadState(resourceFileJarsLS, LoadState.LOOKED_UP);
377
378 // mark looked-up (those who are not loaded)
379 if(extractNativeLibraries) {
380 nativeLibJars.put(jarUri, LoadState.LOOKED_UP);
381 }
382 if(extractClassFiles) {
383 classFileJars.put(jarUri, LoadState.LOOKED_UP);
384 }
385 if(extractOtherFiles) {
386 resourceFileJars.put(jarUri, LoadState.LOOKED_UP);
387 }
388
389 final JarFile jarFile = JarUtil.getJarFile(jarUri);
390 if(DEBUG) {
391 System.err.println("TempJarCache: addAll: "+jarUri+": nativeJar "+jarFile.getName());
392 }
393 validateCertificates(certClass, jarFile);
394 JarUtil.extract(tmpFileCache.getTempDir(), nativeLibMap, jarFile,
395 null /* nativeLibraryPath */, extractNativeLibraries, extractClassFiles, extractOtherFiles);
396
397 // mark loaded (those were just loaded)
398 if(extractNativeLibraries) {
399 nativeLibJars.put(jarUri, LoadState.LOADED);
400 }
401 if(extractClassFiles) {
402 classFileJars.put(jarUri, LoadState.LOADED);
403 }
404 if(extractOtherFiles) {
405 resourceFileJars.put(jarUri, LoadState.LOADED);
406 }
407 } else if( !testLoadState(nativeLibJarsLS, LoadState.LOADED) ||
408 !testLoadState(classFileJarsLS, LoadState.LOADED) ||
409 !testLoadState(resourceFileJarsLS, LoadState.LOADED) ) {
410 throw new IOException("TempJarCache: addAll: "+jarUri+", previous load attempt failed");
411 }
412 }
413
414 /**
415 * If {@link #isInitialized(boolean) isInitialized(true)} is false due to lack of executable support only,
416 * this method always returns false.
417 * @param libName
418 * @return the found native library path within this cache or null if not found
419 * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(false)}
420 */
421 public synchronized static final String findLibrary(final String libName) {
422 checkInitialized(false);
423 if( !staticTempIsExecutable ) {
424 return null;
425 }
426 // try with mapped library basename first
427 String path = nativeLibMap.get(libName);
428 if(null == path) {
429 // if valid library name, try absolute path in temp-dir
430 if(null != JNILibrary.isValidNativeLibraryName(libName, false)) {
431 final File f = new File(tmpFileCache.getTempDir(), libName);
432 if(f.exists()) {
433 path = f.getAbsolutePath();
434 }
435 }
436 }
437 return path;
438 }
439
440 /** TODO class access pending
441 * needs Classloader.defineClass(..) access, ie. own derivation - will do when needed ..
442 public static Class<?> findClass(String name, ClassLoader cl) throws IOException, ClassFormatError {
443 checkInitialized();
444 final File f = new File(nativeTmpFileCache.getTempDir(), IOUtil.getClassFileName(name));
445 if(f.exists()) {
446 Class.forName(fname, initialize, loader)
447 URL url = new URL(f.getAbsolutePath());
448 byte[] b = IOUtil.copyStream2ByteArray(new BufferedInputStream( url.openStream() ));
449 MyClassLoader mcl = new MyClassLoader(cl);
450 return mcl.defineClass(name, b, 0, b.length);
451 }
452 return null;
453 } */
454
455 /**
456 * Similar to {@link ClassLoader#getResource(String)}.
457 * @param name
458 * @return
459 * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(false)}
460 */
461 public synchronized static final String findResource(final String name) {
462 checkInitialized(false);
463 final File f = new File(tmpFileCache.getTempDir(), name);
464 if(f.exists()) {
465 return f.getAbsolutePath();
466 }
467 return null;
468 }
469
470 /**
471 * Similar to {@link ClassLoader#getResource(String)}.
472 * @param name
473 * @return
474 * @throws URISyntaxException
475 * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(false)}
476 */
477 public synchronized static final Uri getResourceUri(final String name) throws URISyntaxException {
478 checkInitialized(false);
479 final File f = new File(tmpFileCache.getTempDir(), name);
480 if(f.exists()) {
481 return Uri.valueOf(f);
482 }
483 return null;
484 }
485
486 private static void validateCertificates(final Class<?> certClass, final JarFile jarFile) throws IOException, SecurityException {
487 if(null == certClass) {
488 throw new IllegalArgumentException("certClass is null");
489 }
490 final Certificate[] rootCerts = SecurityUtil.getCerts(certClass);
491 if( null != rootCerts ) {
492 // Only validate the jarFile's certs with ours, if we have any.
493 // Otherwise we may run uncertified JARs (application).
494 // In case one tries to run uncertified JARs, the wrapping applet/JNLP
495 // SecurityManager will kick in and throw a SecurityException.
496 JarUtil.validateCertificates(rootCerts, jarFile);
497 if(DEBUG) {
498 System.err.println("TempJarCache: validateCertificates: OK - Matching rootCerts in given class "+certClass.getName()+", nativeJar "+jarFile.getName());
499 }
500 } else if(DEBUG) {
501 System.err.println("TempJarCache: validateCertificates: OK - No rootCerts in given class "+certClass.getName()+", nativeJar "+jarFile.getName());
502 }
503 }
504}
This class implements an immutable Uri as defined by RFC 2396.
Definition: Uri.java:162
static Uri valueOf(final File file)
Creates a new Uri instance using the given File instance.
Definition: Uri.java:1126
static JarFile getJarFile(final String clazzBinName, final ClassLoader cl)
Definition: JarUtil.java:379
static final int extract(final File dest, final Map< String, String > nativeLibMap, final JarFile jarFile, final String nativeLibraryPath, final boolean extractNativeLibraries, final boolean extractClassFiles, final boolean extractOtherFiles)
Extract the files of the given jar file.
Definition: JarUtil.java:541
static final void validateCertificates(final Certificate[] rootCerts, final JarFile jarFile)
Validate the certificates for each native Lib in the jar file.
Definition: JarUtil.java:688
static boolean initSingleton()
Documented way to kick off static initialization.
Static Jar file cache handler using an underlying instance of TempFileCache, see getTempFileCache().
static boolean isInitialized(final boolean forExecutables)
static synchronized final void addAll(final Class<?> certClass, final Uri jarUri)
Adds all types, native libraries, class files and other files (resources) if not yet added.
static synchronized final void addClasses(final Class<?> certClass, final Uri jarUri)
Adds native classes, if not yet added.
static synchronized final String findResource(final String name)
TODO class access pending needs Classloader.defineClass(..) access, ie.
static synchronized final boolean addNativeLibs(final Class<?> certClass, final Uri jarUri, final String nativeLibraryPath)
Adds native libraries, if not yet added.
static synchronized boolean checkClasses(final Uri jarUri, final LoadState exp)
static boolean initSingleton()
Documented way to kick off static initialization.
static synchronized boolean checkResources(final Uri jarUri, final LoadState exp)
static synchronized final String findLibrary(final String libName)
If isInitialized(true) is false due to lack of executable support only, this method always returns fa...
static synchronized boolean checkNativeLibs(final Uri jarUri, final LoadState exp)
static synchronized final Uri getResourceUri(final String name)
Similar to ClassLoader#getResource(String).
static synchronized final void addResources(final Class<?> certClass, final Uri jarUri)
Adds native resources, if not yet added.
static TempFileCache getTempFileCache()
static final Certificate[] getCerts(final Class<?> clz)
Helper routines for logging and debugging.
Definition: Debug.java:35
static final boolean debug(final String subcomponent)
Definition: Debug.java:63
Static JNI Native Libraries handler.
Definition: JNILibrary.java:47
static final String isValidNativeLibraryName(final String libName, final boolean isLowerCaseAlready)
Comparison of prefix and suffix of the given libName's basename is performed case insensitive
boolean compliesWith(final LoadState o2)