/** * Copyright 2011 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of JogAmp Community. */ package com.jogamp.common.util.cache; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.security.cert.Certificate; import java.util.HashMap; import java.util.Map; import java.util.jar.JarFile; import jogamp.common.Debug; import com.jogamp.common.JogampRuntimeException; import com.jogamp.common.net.Uri; import com.jogamp.common.os.NativeLibrary; import com.jogamp.common.util.JarUtil; import com.jogamp.common.util.SecurityUtil; /** * Static Jar file cache handler using an underlying instance of {@link TempFileCache}, see {@link #getTempFileCache()}. *
* Lifecycle: Concurrently running JVMs and ClassLoader *
*/ public class TempJarCache { private static final boolean DEBUG = Debug.debug("TempJarCache"); // A HashMap of native libraries that can be loaded with System.load() // The key is the string name of the library as passed into the loadLibrary // call; it is the file name without the directory or the platform-dependent // library prefix and suffix. The value is the absolute path name to the // unpacked library file in nativeTmpDir. private static Map* In JogAmp, JNI native libraries loaded and registered by {@link JNILibLoaderBase} * derivations, where the native JARs might be loaded via {@link JNILibLoaderBase#addNativeJarLibs(Class, String) }. *
** The only valid use case to shutdown the TempJarCache is at bootstrapping, * i.e. when no native library is guaranteed to be loaded. This could be useful * if bootstrapping needs to find the proper native library type. *
* public static void shutdown() { if (isInit) { // volatile: ok synchronized (TempJarCache.class) { if (isInit) { if(DEBUG) { System.err.println("TempJarCache.shutdown(): real "+(false==staticInitError)+", "+ tmpFileCache.getTempDir()); } isInit = false; if(!staticInitError) { nativeLibMap.clear(); nativeLibMap = null; nativeLibJars.clear(); nativeLibJars = null; classFileJars.clear(); classFileJars = null; resourceFileJars.clear(); resourceFileJars = null; tmpFileCache.destroy(); tmpFileCache = null; } } } } } */ private static boolean isInitializedImpl() { if (!isInit) { // volatile: ok synchronized (TempJarCache.class) { if (!isInit) { return false; } } } return true; } /** * @param forExecutables if {@code true}, method also tests whether the underlying cache is suitable to load native libraries or launch executables * @return true if this class has been properly initialized, ie. is in use. Otherwise returns false. */ public static boolean isInitialized(final boolean forExecutables) { return isInitializedImpl() && !staticInitError && ( !forExecutables || staticTempIsExecutable ); } /** * @param forExecutables if {@code true}, method also tests whether the underlying cache is suitable to load native libraries or launch executables */ /* package */ static void checkInitialized(final boolean forExecutables) { if(!isInitializedImpl()) { throw new JogampRuntimeException("initSingleton() has to be called first."); } if(staticInitError) { throw new JogampRuntimeException("initSingleton() failed."); } if( forExecutables && !staticTempIsExecutable ) { throw new JogampRuntimeException("TempJarCache folder not suitable for executables"); } } /** * @return the underlying {@link TempFileCache} * @throws JogampRuntimeException if not {@link #isInitialized(boolean) isInitialized(false)} */ public static TempFileCache getTempFileCache() { checkInitialized(false); return tmpFileCache; } /** * @param jarUri * @param exp * @return * @throws IOException * @throws JogampRuntimeException if not {@link #isInitialized(boolean) isInitialized(false)} */ public synchronized static boolean checkNativeLibs(final Uri jarUri, final LoadState exp) throws IOException { checkInitialized(false); if(null == jarUri) { throw new IllegalArgumentException("jarUri is null"); } return testLoadState(nativeLibJars.get(jarUri), exp); } /** * @param jarUri * @param exp * @return * @throws IOException * @throws JogampRuntimeException if not {@link #isInitialized(boolean) isInitialized(false)} */ public synchronized static boolean checkClasses(final Uri jarUri, final LoadState exp) throws IOException { checkInitialized(false); if(null == jarUri) { throw new IllegalArgumentException("jarUri is null"); } return testLoadState(classFileJars.get(jarUri), exp); } /** * * @param jarUri * @param exp * @return * @throws IOException * @throws JogampRuntimeException if not {@link #isInitialized(boolean) isInitialized(false)} */ public synchronized static boolean checkResources(final Uri jarUri, final LoadState exp) throws IOException { checkInitialized(false); if(null == jarUri) { throw new IllegalArgumentException("jarUri is null"); } return testLoadState(resourceFileJars.get(jarUri), exp); } /** * Adds native libraries, if not yet added. * * @param certClass if class is certified, the JarFile entries needs to have the same certificate * @param jarUri * @param nativeLibraryPath if not null, only extracts native libraries within this path. * @return true if native libraries were added or previously loaded from given jarUri, otherwise false * @throws IOException if thejarUri
could not be loaded or a previous load attempt failed
* @throws SecurityException
* @throws URISyntaxException
* @throws IllegalArgumentException
* @throws JogampRuntimeException if not {@link #isInitialized(boolean) isInitialized(true)}
*/
public synchronized static final boolean addNativeLibs(final Class> certClass, final Uri jarUri, final String nativeLibraryPath) throws IOException, SecurityException, IllegalArgumentException, URISyntaxException {
checkInitialized(true);
final LoadState nativeLibJarsLS = nativeLibJars.get(jarUri);
if( !testLoadState(nativeLibJarsLS, LoadState.LOOKED_UP) ) {
nativeLibJars.put(jarUri, LoadState.LOOKED_UP);
final JarFile jarFile = JarUtil.getJarFile(jarUri);
if(DEBUG) {
System.err.println("TempJarCache: addNativeLibs: "+jarUri+": nativeJar "+jarFile.getName()+" (NEW)");
}
validateCertificates(certClass, jarFile);
final int num = JarUtil.extract(tmpFileCache.getTempDir(), nativeLibMap, jarFile, nativeLibraryPath, true, false, false);
nativeLibJars.put(jarUri, LoadState.LOADED);
return num > 0;
} else if( testLoadState(nativeLibJarsLS, LoadState.LOADED) ) {
if(DEBUG) {
System.err.println("TempJarCache: addNativeLibs: "+jarUri+": nativeJar "+jarUri+" (REUSE)");
}
return true;
}
throw new IOException("TempJarCache: addNativeLibs: "+jarUri+", previous load attempt failed");
}
/**
* Adds native classes, if not yet added.
*
* TODO class access pending
* needs Classloader.defineClass(..) access, ie. own derivation - will do when needed ..
*
* @param certClass if class is certified, the JarFile entries needs to have the same certificate
* @param jarUri
* @throws IOException if the jarUri
could not be loaded or a previous load attempt failed
* @throws SecurityException
* @throws URISyntaxException
* @throws IllegalArgumentException
* @throws JogampRuntimeException if not {@link #isInitialized(boolean) isInitialized(false)}
*/
public synchronized static final void addClasses(final Class> certClass, final Uri jarUri) throws IOException, SecurityException, IllegalArgumentException, URISyntaxException {
checkInitialized(false);
final LoadState classFileJarsLS = classFileJars.get(jarUri);
if( !testLoadState(classFileJarsLS, LoadState.LOOKED_UP) ) {
classFileJars.put(jarUri, LoadState.LOOKED_UP);
final JarFile jarFile = JarUtil.getJarFile(jarUri);
if(DEBUG) {
System.err.println("TempJarCache: addClasses: "+jarUri+": nativeJar "+jarFile.getName());
}
validateCertificates(certClass, jarFile);
JarUtil.extract(tmpFileCache.getTempDir(), null, jarFile,
null /* nativeLibraryPath */, false, true, false);
classFileJars.put(jarUri, LoadState.LOADED);
} else if( !testLoadState(classFileJarsLS, LoadState.LOADED) ) {
throw new IOException("TempJarCache: addClasses: "+jarUri+", previous load attempt failed");
}
}
/**
* Adds native resources, if not yet added.
*
* @param certClass if class is certified, the JarFile entries needs to have the same certificate
* @param jarUri
* @return
* @throws IOException if the jarUri
could not be loaded or a previous load attempt failed
* @throws SecurityException
* @throws URISyntaxException
* @throws IllegalArgumentException
* @throws JogampRuntimeException if not {@link #isInitialized(boolean) isInitialized(false)}
*/
public synchronized static final void addResources(final Class> certClass, final Uri jarUri) throws IOException, SecurityException, IllegalArgumentException, URISyntaxException {
checkInitialized(false);
final LoadState resourceFileJarsLS = resourceFileJars.get(jarUri);
if( !testLoadState(resourceFileJarsLS, LoadState.LOOKED_UP) ) {
resourceFileJars.put(jarUri, LoadState.LOOKED_UP);
final JarFile jarFile = JarUtil.getJarFile(jarUri);
if(DEBUG) {
System.err.println("TempJarCache: addResources: "+jarUri+": nativeJar "+jarFile.getName());
}
validateCertificates(certClass, jarFile);
JarUtil.extract(tmpFileCache.getTempDir(), null, jarFile,
null /* nativeLibraryPath */, false, false, true);
resourceFileJars.put(jarUri, LoadState.LOADED);
} else if( !testLoadState(resourceFileJarsLS, LoadState.LOADED) ) {
throw new IOException("TempJarCache: addResources: "+jarUri+", previous load attempt failed");
}
}
/**
* Adds all types, native libraries, class files and other files (resources)
* if not yet added.
*
* TODO class access pending
* needs Classloader.defineClass(..) access, ie. own derivation - will do when needed ..
*
* @param certClass if class is certified, the JarFile entries needs to have the same certificate
* @param jarUri
* @throws IOException if the jarUri
could not be loaded or a previous load attempt failed
* @throws SecurityException
* @throws URISyntaxException
* @throws IllegalArgumentException
* @throws JogampRuntimeException if not {@link #isInitialized(boolean) isInitialized(false)}
*/
public synchronized static final void addAll(final Class> certClass, final Uri jarUri) throws IOException, SecurityException, IllegalArgumentException, URISyntaxException {
checkInitialized(false);
if(null == jarUri) {
throw new IllegalArgumentException("jarUri is null");
}
final LoadState nativeLibJarsLS = nativeLibJars.get(jarUri);
final LoadState classFileJarsLS = classFileJars.get(jarUri);
final LoadState resourceFileJarsLS = resourceFileJars.get(jarUri);
if( !testLoadState(nativeLibJarsLS, LoadState.LOOKED_UP) ||
!testLoadState(classFileJarsLS, LoadState.LOOKED_UP) ||
!testLoadState(resourceFileJarsLS, LoadState.LOOKED_UP) ) {
final boolean extractNativeLibraries = staticTempIsExecutable && !testLoadState(nativeLibJarsLS, LoadState.LOADED);
final boolean extractClassFiles = !testLoadState(classFileJarsLS, LoadState.LOADED);
final boolean extractOtherFiles = !testLoadState(resourceFileJarsLS, LoadState.LOOKED_UP);
// mark looked-up (those who are not loaded)
if(extractNativeLibraries) {
nativeLibJars.put(jarUri, LoadState.LOOKED_UP);
}
if(extractClassFiles) {
classFileJars.put(jarUri, LoadState.LOOKED_UP);
}
if(extractOtherFiles) {
resourceFileJars.put(jarUri, LoadState.LOOKED_UP);
}
final JarFile jarFile = JarUtil.getJarFile(jarUri);
if(DEBUG) {
System.err.println("TempJarCache: addAll: "+jarUri+": nativeJar "+jarFile.getName());
}
validateCertificates(certClass, jarFile);
JarUtil.extract(tmpFileCache.getTempDir(), nativeLibMap, jarFile,
null /* nativeLibraryPath */, extractNativeLibraries, extractClassFiles, extractOtherFiles);
// mark loaded (those were just loaded)
if(extractNativeLibraries) {
nativeLibJars.put(jarUri, LoadState.LOADED);
}
if(extractClassFiles) {
classFileJars.put(jarUri, LoadState.LOADED);
}
if(extractOtherFiles) {
resourceFileJars.put(jarUri, LoadState.LOADED);
}
} else if( !testLoadState(nativeLibJarsLS, LoadState.LOADED) ||
!testLoadState(classFileJarsLS, LoadState.LOADED) ||
!testLoadState(resourceFileJarsLS, LoadState.LOADED) ) {
throw new IOException("TempJarCache: addAll: "+jarUri+", previous load attempt failed");
}
}
/**
* If {@link #isInitialized(boolean) isInitialized(true)} is false due to lack of executable support only,
* this method always returns false.
* @param libName
* @return the found native library path within this cache or null if not found
* @throws JogampRuntimeException if not {@link #isInitialized(boolean) isInitialized(false)}
*/
public synchronized static final String findLibrary(final String libName) {
checkInitialized(false);
if( !staticTempIsExecutable ) {
return null;
}
// try with mapped library basename first
String path = nativeLibMap.get(libName);
if(null == path) {
// if valid library name, try absolute path in temp-dir
if(null != NativeLibrary.isValidNativeLibraryName(libName, false)) {
final File f = new File(tmpFileCache.getTempDir(), libName);
if(f.exists()) {
path = f.getAbsolutePath();
}
}
}
return path;
}
/** TODO class access pending
* needs Classloader.defineClass(..) access, ie. own derivation - will do when needed ..
public static Class> findClass(String name, ClassLoader cl) throws IOException, ClassFormatError {
checkInitialized();
final File f = new File(nativeTmpFileCache.getTempDir(), IOUtil.getClassFileName(name));
if(f.exists()) {
Class.forName(fname, initialize, loader)
URL url = new URL(f.getAbsolutePath());
byte[] b = IOUtil.copyStream2ByteArray(new BufferedInputStream( url.openStream() ));
MyClassLoader mcl = new MyClassLoader(cl);
return mcl.defineClass(name, b, 0, b.length);
}
return null;
} */
/**
* Similar to {@link ClassLoader#getResource(String)}.
* @param name
* @return
* @throws JogampRuntimeException if not {@link #isInitialized(boolean) isInitialized(false)}
*/
public synchronized static final String findResource(final String name) {
checkInitialized(false);
final File f = new File(tmpFileCache.getTempDir(), name);
if(f.exists()) {
return f.getAbsolutePath();
}
return null;
}
/**
* Similar to {@link ClassLoader#getResource(String)}.
* @param name
* @return
* @throws URISyntaxException
* @throws JogampRuntimeException if not {@link #isInitialized(boolean) isInitialized(false)}
*/
public synchronized static final Uri getResourceUri(final String name) throws URISyntaxException {
checkInitialized(false);
final File f = new File(tmpFileCache.getTempDir(), name);
if(f.exists()) {
return Uri.valueOf(f);
}
return null;
}
private static void validateCertificates(final Class> certClass, final JarFile jarFile) throws IOException, SecurityException {
if(null == certClass) {
throw new IllegalArgumentException("certClass is null");
}
final Certificate[] rootCerts = SecurityUtil.getCerts(certClass);
if( null != rootCerts ) {
// Only validate the jarFile's certs with ours, if we have any.
// Otherwise we may run uncertified JARs (application).
// In case one tries to run uncertified JARs, the wrapping applet/JNLP
// SecurityManager will kick in and throw a SecurityException.
JarUtil.validateCertificates(rootCerts, jarFile);
if(DEBUG) {
System.err.println("TempJarCache: validateCertificates: OK - Matching rootCerts in given class "+certClass.getName()+", nativeJar "+jarFile.getName());
}
} else if(DEBUG) {
System.err.println("TempJarCache: validateCertificates: OK - No rootCerts in given class "+certClass.getName()+", nativeJar "+jarFile.getName());
}
}
}