diff options
Diffstat (limited to 'src/java/com/jogamp/common/util')
-rw-r--r-- | src/java/com/jogamp/common/util/JarUtil.java | 347 | ||||
-rw-r--r-- | src/java/com/jogamp/common/util/cache/TempFileCache.java | 480 | ||||
-rw-r--r-- | src/java/com/jogamp/common/util/cache/TempJarCache.java | 224 |
3 files changed, 1051 insertions, 0 deletions
diff --git a/src/java/com/jogamp/common/util/JarUtil.java b/src/java/com/jogamp/common/util/JarUtil.java new file mode 100644 index 0000000..a2e6be5 --- /dev/null +++ b/src/java/com/jogamp/common/util/JarUtil.java @@ -0,0 +1,347 @@ +/** + * 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; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.security.AccessController; +import java.security.cert.Certificate; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import com.jogamp.common.os.NativeLibrary; + +import jogamp.common.Debug; + +public class JarUtil { + private static final boolean VERBOSE = Debug.isPropertyDefined("jogamp.debug.JARUtil", true, AccessController.getContext()); + + /** + * @param clazzBinName com.jogamp.common.util.cache.TempJarCache + * @param cl + * @return jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/ + * @throws IOException + * @see {@link IOUtil#getClassURL(String, ClassLoader)} + */ + public static URL getJarURL(String clazzBinName, ClassLoader cl) throws IOException { + URL url = IOUtil.getClassURL(clazzBinName, cl); + if(null != url) { + String urlS = url.toExternalForm(); + // from + // jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/com/jogamp/common/util/cache/TempJarCache.class + // to + // jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/ + urlS = urlS.substring(0, urlS.lastIndexOf('!')+2); // include !/ + return new URL(urlS); + } + return null; + } + + /** + * + * @param jarURL jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/com/jogamp/common/util/cache/TempJarCache.class + * @return file:/usr/local/projects/JOGL/gluegen/build-x86_64/ + * @throws IOException + */ + public static URL getJarURLDirname(URL jarURL) throws IOException { + String urlS = jarURL.toExternalForm(); + // from + // jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/com/jogamp/common/util/cache/TempJarCache.class + // to + // jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar + urlS = urlS.substring(0, urlS.lastIndexOf('!')); // exclude !/ + + // from + // jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar + // to + // file:/usr/local/projects/JOGL/gluegen/build-x86_64/ + urlS = urlS.substring(4, urlS.lastIndexOf('/')+1); // include / exclude jar: + return new URL(urlS); + } + + /** + * + * @param baseUrl file:/usr/local/projects/JOGL/gluegen/build-x86_64/ + * @param jarFileName gluegen-rt.jar + * @return jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/ + * @throws IOException + */ + public static URL getJarURL(URL baseUrl, String jarFileName) throws IOException { + return new URL("jar:"+baseUrl.toExternalForm()+jarFileName+"!/"); + } + + /** + * + * @param clazzBinName com.jogamp.common.util.cache.TempJarCache + * @param cl domain + * @return JarFile containing the named class within the given ClassLoader + * @throws IOException + * @see {@link #getJarURL(String, ClassLoader)} + */ + public static JarFile getJarFile(String clazzBinName, ClassLoader cl) throws IOException { + return getJarFile(getJarURL(clazzBinName, cl), cl); + } + + /** + * + * @param jarURL jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/ + * @param cl domain + * @return JarFile as named by URL within the given ClassLoader + * @throws IOException + */ + public static JarFile getJarFile(URL jarUrl, ClassLoader cl) throws IOException { + if(null != jarUrl) { + URLConnection urlc = jarUrl.openConnection(); + if(urlc instanceof JarURLConnection) { + JarURLConnection jarConnection = (JarURLConnection)jarUrl.openConnection(); + JarFile jarFile = jarConnection.getJarFile(); + return jarFile; + } + } + return null; + } + + /** + * Return a map from native-lib-base-name to entry-name. + */ + public static Map<String, String> getNativeLibNames(JarFile jarFile) { + if (VERBOSE) { + System.err.println("getNativeLibNames: "+jarFile); + } + + Map<String,String> nameMap = new HashMap<String, String>(); + Enumeration<JarEntry> entries = jarFile.entries(); + + while (entries.hasMoreElements()) { + final JarEntry entry = entries.nextElement(); + final String entryName = entry.getName(); + final String baseName = NativeLibrary.isValidNativeLibraryName(entryName, false); + + if(null != baseName) { + nameMap.put(baseName, entryName); + } + } + + return nameMap; + } + + /** + * Extract the files of the given jar file. + * <p> + * If <code>extractNativeLibraries</code> is true, + * native libraries are added to the given <code>nativeLibMap</code> + * with the base name to temp file location.<br> + * A file is identified as a native library, + * if it's name complies with the running platform's native library naming scheme.<br> + * Root entries are favored over non root entries in case of naming collisions.<br> + * Example on a Unix like machine:<br> + * <pre> + * mylib.jar!/sub1/libsour.so -> sour (mapped, unique name) + * mylib.jar!/sub1/libsweet.so (dropped, root entry favored) + * mylib.jar!/libsweet.so -> sweet (mapped, root entry favored) + * mylib.jar!/sweet.dll -> (dropped, not a unix library name) + * </pre> + * </p> + * <p> + * In order to be compatible with Java Web Start, we need + * to extract all root entries from the jar file.<br> + * In this case, set all flags to true <code>extractNativeLibraries </code>. + * <code>extractClassFiles</code>, <code>extractOtherFiles</code>. + * </p> + * + * @param dest + * @param nativeLibMap + * @param jarFile + * @param deepDirectoryTraversal + * @param extractNativeLibraries + * @param extractClassFiles + * @param extractOtherFiles + * @return + * @throws IOException + */ + public static int extract(File dest, Map<String, String> nativeLibMap, + JarFile jarFile, + boolean extractNativeLibraries, + boolean extractClassFiles, + boolean extractOtherFiles) throws IOException { + + if (VERBOSE) { + System.err.println("extractNativeLibs: "+jarFile.getName()+" -> "+dest+ + ", extractNativeLibraries "+extractNativeLibraries+ + ", extractClassFiles"+extractClassFiles+ + ", extractOtherFiles "+extractOtherFiles); + } + int num = 0; + + Enumeration<JarEntry> entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = (JarEntry) entries.nextElement(); + String entryName = entry.getName(); + + // Match entries with correct prefix and suffix (ignoring case) + final String libBaseName = NativeLibrary.isValidNativeLibraryName(entryName, false); + final boolean isNativeLib = null != libBaseName; + if(isNativeLib && !extractNativeLibraries) { + if (VERBOSE) { + System.err.println("JarEntry : " + entryName + " native-lib skipped"); + } + continue; + } + + final boolean isClassFile = entryName.endsWith(".class"); + if(isClassFile && !extractClassFiles) { + if (VERBOSE) { + System.err.println("JarEntry : " + entryName + " class-file skipped"); + } + continue; + } + + if(!isNativeLib && !isClassFile && !extractOtherFiles) { + if (VERBOSE) { + System.err.println("JarEntry : " + entryName + " other-file skipped"); + } + continue; + } + + boolean isDir = entryName.endsWith("/"); + + boolean isRootEntry = entryName.indexOf('/') == -1 && + entryName.indexOf(File.separatorChar) == -1; + + // strip prefix & suffix + final File destFile = new File(dest, entryName); + if(isDir) { + destFile.mkdir(); + if (VERBOSE) { + System.err.println("MKDIR: " + entryName + " -> " + destFile ); + } + } else { + final InputStream in = new BufferedInputStream(jarFile.getInputStream(entry)); + final OutputStream out = new BufferedOutputStream(new FileOutputStream(destFile)); + int numBytes = -1; + try { + numBytes = IOUtil.copyStream2Stream(in, out, -1); + } finally { + in.close(); + out.close(); + } + boolean addedAsNativeLib = false; + if (numBytes>0) { + num++; + if (isNativeLib && ( isRootEntry || !nativeLibMap.containsKey(libBaseName) ) ) { + nativeLibMap.put(libBaseName, destFile.getAbsolutePath()); + addedAsNativeLib = true; + } + } + if (VERBOSE) { + System.err.println("EXTRACT["+num+"]: [" + libBaseName + " -> ] " + entryName + " -> " + destFile + ": "+numBytes+" bytes, addedAsNativeLib: "+addedAsNativeLib); + } + } + } + return num; + } + + /** + * Validate the certificates for each native Lib in the jar file. + * Throws an IOException if any certificate is not valid. + * <pre> + Certificate[] appletLauncherCerts = Something.class.getProtectionDomain(). + getCodeSource().getCertificates(); + </pre> + */ + public static void validateCertificates(Certificate[] appletLauncherCerts, JarFile jarFile) + throws IOException { + + if (VERBOSE) { + System.err.println("validateCertificates:"); + } + + byte[] buf = new byte[1000]; + Enumeration<JarEntry> entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = (JarEntry) entries.nextElement(); + String entryName = entry.getName(); + + if (VERBOSE) { + System.err.println("Validate JarEntry : " + entryName); + } + + if (!checkNativeCertificates(appletLauncherCerts, jarFile, entry, buf)) { + throw new IOException("Cannot validate certificate for " + entryName); + } + } + + } + + /** + * Check the certificates with the ones in the jar file + * (all must match). + */ + private static boolean checkNativeCertificates(Certificate[] launchedCerts, + JarFile jar, JarEntry entry, byte[] buf) throws IOException { + + // API states that we must read all of the data from the entry's + // InputStream in order to be able to get its certificates + + InputStream is = jar.getInputStream(entry); + while (is.read(buf) > 0) { } + is.close(); + + if (launchedCerts == null || launchedCerts.length == 0) { + throw new RuntimeException("Null certificates passed"); + } + + // Get the certificates for the JAR entry + Certificate[] nativeCerts = entry.getCertificates(); + if (nativeCerts == null || nativeCerts.length == 0) { + return false; + } + + int checked = 0; + for (int i = 0; i < launchedCerts.length; i++) { + for (int j = 0; j < nativeCerts.length; j++) { + if (nativeCerts[j].equals(launchedCerts[i])){ + checked++; + break; + } + } + } + return (checked == launchedCerts.length); + } +} diff --git a/src/java/com/jogamp/common/util/cache/TempFileCache.java b/src/java/com/jogamp/common/util/cache/TempFileCache.java new file mode 100644 index 0000000..2145239 --- /dev/null +++ b/src/java/com/jogamp/common/util/cache/TempFileCache.java @@ -0,0 +1,480 @@ +/** + * 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.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.security.AccessController; + +import jogamp.common.Debug; + +public class TempFileCache { + private static final boolean VERBOSE = Debug.isPropertyDefined("jogamp.debug.TempFileCache", true, AccessController.getContext()); + + // Lifecycle: For all JVMs, ClassLoader and times. + private static final String tmpDirPrefix = "jogamp.tmp.cache"; + + // Get the value of the tmproot system property + // Lifecycle: For all JVMs and ClassLoader + private static final String tmpRootPropName = "jnlp.jogamp.tmp.cache.root"; + + // Flag indicating that we got a fatal error in the static initializer. + private static boolean staticInitError = false; + + private static File tmpBaseDir; + + // String representing the name of the temp root directory relative to the + // tmpBaseDir. Its value is "jlnNNNNN", which is the unique filename created + // by File.createTempFile() without the ".tmp" extension. + // + // Lifecycle: For all JVMs and ClassLoader + // + private static String tmpRootPropValue; + + private static File tmpRootDir; + + // Flag indicating that we got a fatal error in the initializer. + private boolean initError = false; + + private File individualTmpDir; + + static { + // Create / initialize the temp root directory, starting the Reaper + // thread to reclaim old installations if necessary. If we get an + // exception, set an error code. + try { + initTmpRoot(); + } catch (Exception ex) { + ex.printStackTrace(); + staticInitError = true; + } + } + + /** + * Documented way to kick off static initialization + * @return true is static initialization was successful + */ + public static boolean initSingleton() { + return !staticInitError; + } + + /** + * This method is called by the static initializer to create / initialize + * the temp root directory that will hold the temp directories for this + * instance of the JVM. This is done as follows: + * + * 1. Synchronize on a global lock. Note that for this purpose we will + * use System.out in the absence of a true global lock facility. + * We are careful not to hold this lock too long. + * + * 2. Check for the existence of the "jnlp.applet.launcher.tmproot" + * system property. + * + * a. If set, then some other thread in a different ClassLoader has + * already created the tmprootdir, so we just need to + * use it. The remaining steps are skipped. + * + * b. If not set, then we are the first thread in this JVM to run, + * and we need to create the the tmprootdir. + * + * 3. Create the tmprootdir, along with the appropriate locks. + * Note that we perform the operations in the following order, + * prior to creating tmprootdir itself, to work around the fact that + * the file creation and file lock steps are not atomic, and we need + * to ensure that a newly-created tmprootdir isn't reaped by a + * concurrently running JVM. + * + * create jlnNNNN.tmp using File.createTempFile() + * lock jlnNNNN.tmp + * create jlnNNNN.lck while holding the lock on the .tmp file + * lock jlnNNNN.lck + * + * Since the Reaper thread will enumerate the list of *.lck files + * before starting, we can guarantee that if there exists a *.lck file + * for an active process, then the corresponding *.tmp file is locked + * by that active process. This guarantee lets us avoid reaping an + * active process' files. + * + * 4. Set the "jnlp.applet.launcher.tmproot" system property. + * + * 5. Add a shutdown hook to cleanup jlnNNNN.lck and jlnNNNN.tmp. We + * don't actually expect that this shutdown hook will ever be called, + * but the act of doing this, ensures that the locks never get + * garbage-collected, which is necessary for correct behavior when + * the first ClassLoader is later unloaded, while subsequent Applets + * are still running. + * + * 6. Start the Reaper thread to cleanup old installations. + */ + private static void initTmpRoot() throws IOException { + if (VERBOSE) { + System.err.println("TempFileCache Static Initialization ----------------------------------------------"); + System.err.println("Thread: "+Thread.currentThread().getName()+", CL 0x"+Integer.toHexString(TempFileCache.class.getClassLoader().hashCode())); + } + + synchronized (System.out) { + + // Get the name of the tmpbase directory. + String tmpBaseName = System.getProperty("java.io.tmpdir") + File.separator + tmpDirPrefix ; + tmpBaseDir = new File(tmpBaseName); + + tmpRootPropValue = System.getProperty(tmpRootPropName); + + if (tmpRootPropValue == null) { + // Create the tmpbase directory if it doesn't already exist + tmpBaseDir.mkdir(); + if (!tmpBaseDir.isDirectory()) { + throw new IOException("Cannot create directory " + tmpBaseDir); + } + + // Create ${tmpbase}/jlnNNNN.tmp then lock the file + File tmpFile = File.createTempFile("jln", ".tmp", tmpBaseDir); + if (VERBOSE) { + System.err.println("tmpFile = " + tmpFile.getAbsolutePath()); + } + final FileOutputStream tmpOut = new FileOutputStream(tmpFile); + final FileChannel tmpChannel = tmpOut.getChannel(); + final FileLock tmpLock = tmpChannel.lock(); + + // Strip off the ".tmp" to get the name of the tmprootdir + String tmpFileName = tmpFile.getAbsolutePath(); + String tmpRootName = tmpFileName.substring(0, tmpFileName.lastIndexOf(".tmp")); + + // create ${tmpbase}/jlnNNNN.lck then lock the file + String lckFileName = tmpRootName + ".lck"; + File lckFile = new File(lckFileName); + if (VERBOSE) { + System.err.println("lckFile = " + lckFile.getAbsolutePath()); + } + lckFile.createNewFile(); + final FileOutputStream lckOut = new FileOutputStream(lckFile); + final FileChannel lckChannel = lckOut.getChannel(); + final FileLock lckLock = lckChannel.lock(); + + // Create tmprootdir + tmpRootDir = new File(tmpRootName); + if (VERBOSE) { + System.err.println("tmpRootDir = " + tmpRootDir.getAbsolutePath()); + } + if (!tmpRootDir.mkdir()) { + throw new IOException("Cannot create " + tmpRootDir); + } + + // Add shutdown hook to cleanup the OutputStream, FileChannel, + // and FileLock for the jlnNNNN.lck and jlnNNNN.lck files. + // We do this so that the locks never get garbage-collected. + Runtime.getRuntime().addShutdownHook(new Thread() { + /* @Override */ + public void run() { + // NOTE: we don't really expect that this code will ever + // be called. If it does, we will close the output + // stream, which will in turn close the channel. + // We will then release the lock. + try { + tmpOut.close(); + tmpLock.release(); + lckOut.close(); + lckLock.release(); + } catch (IOException ex) { + // Do nothing + } + } + }); + + // Set the system property... + tmpRootPropValue = tmpRootName.substring(tmpRootName.lastIndexOf(File.separator) + 1); + System.setProperty(tmpRootPropName, tmpRootPropValue); + if (VERBOSE) { + System.err.println("Setting " + tmpRootPropName + "=" + tmpRootPropValue); + } + + // Start a new Reaper thread to do stuff... + Thread reaperThread = new Thread() { + /* @Override */ + public void run() { + deleteOldTempDirs(); + } + }; + reaperThread.setName("AppletLauncher-Reaper"); + reaperThread.start(); + } else { + // Make sure that the property is not set to an illegal value + if (tmpRootPropValue.indexOf('/') >= 0 || + tmpRootPropValue.indexOf(File.separatorChar) >= 0) { + throw new IOException("Illegal value of: " + tmpRootPropName); + } + + // Set tmpRootDir = ${tmpbase}/${jnlp.applet.launcher.tmproot} + if (VERBOSE) { + System.err.println("Using existing value of: " + + tmpRootPropName + "=" + tmpRootPropValue); + } + tmpRootDir = new File(tmpBaseDir, tmpRootPropValue); + if (VERBOSE) { + System.err.println("tmpRootDir = " + tmpRootDir.getAbsolutePath()); + } + if (!tmpRootDir.isDirectory()) { + throw new IOException("Cannot access " + tmpRootDir); + } + } + } + if (VERBOSE) { + System.err.println("------------------------------------------------------------------ (static ok: "+(!staticInitError)+")"); + } + } + + /** + * Called by the Reaper thread to delete old temp directories + * Only one of these threads will run per JVM invocation. + */ + private static void deleteOldTempDirs() { + if (VERBOSE) { + System.err.println("*** Reaper: deleteOldTempDirs in " + + tmpBaseDir.getAbsolutePath()); + } + + // enumerate list of jnl*.lck files, ignore our own jlnNNNN file + final String ourLockFile = tmpRootPropValue + ".lck"; + FilenameFilter lckFilter = new FilenameFilter() { + /* @Override */ + public boolean accept(File dir, String name) { + return name.endsWith(".lck") && !name.equals(ourLockFile); + } + }; + + // For each file <file>.lck in the list we will first try to lock + // <file>.tmp if that succeeds then we will try to lock <file>.lck + // (which should always succeed unless there is a problem). If we can + // get the lock on both files, then it must be an old installation, and + // we will delete it. + String[] fileNames = tmpBaseDir.list(lckFilter); + if (fileNames != null) { + for (int i = 0; i < fileNames.length; i++) { + String lckFileName = fileNames[i]; + String tmpDirName = lckFileName.substring(0, lckFileName.lastIndexOf(".lck")); + String tmpFileName = tmpDirName + ".tmp"; + + File lckFile = new File(tmpBaseDir, lckFileName); + File tmpFile = new File(tmpBaseDir, tmpFileName); + File tmpDir = new File(tmpBaseDir, tmpDirName); + + if (lckFile.exists() && tmpFile.exists() && tmpDir.isDirectory()) { + FileOutputStream tmpOut = null; + FileChannel tmpChannel = null; + FileLock tmpLock = null; + + try { + tmpOut = new FileOutputStream(tmpFile); + tmpChannel = tmpOut.getChannel(); + tmpLock = tmpChannel.tryLock(); + } catch (Exception ex) { + // Ignore exceptions + if (VERBOSE) { + ex.printStackTrace(); + } + } + + if (tmpLock != null) { + FileOutputStream lckOut = null; + FileChannel lckChannel = null; + FileLock lckLock = null; + + try { + lckOut = new FileOutputStream(lckFile); + lckChannel = lckOut.getChannel(); + lckLock = lckChannel.tryLock(); + } catch (Exception ex) { + if (VERBOSE) { + ex.printStackTrace(); + } + } + + if (lckLock != null) { + // Recursively remove the old tmpDir and all of + // its contents + removeAll(tmpDir); + + // Close the streams and delete the .lck and .tmp + // files. Note that there is a slight race condition + // in that another process could open a stream at + // the same time we are trying to delete it, which will + // prevent deletion, but we won't worry about it, since + // the worst that will happen is we might have an + // occasional 0-byte .lck or .tmp file left around + try { + lckOut.close(); + } catch (IOException ex) { + } + lckFile.delete(); + try { + tmpOut.close(); + } catch (IOException ex) { + } + tmpFile.delete(); + } else { + try { + // Close the file and channel for the *.lck file + if (lckOut != null) { + lckOut.close(); + } + // Close the file/channel and release the lock + // on the *.tmp file + tmpOut.close(); + tmpLock.release(); + } catch (IOException ex) { + if (VERBOSE) { + ex.printStackTrace(); + } + } + } + } + } else { + if (VERBOSE) { + System.err.println(" Skipping: " + tmpDir.getAbsolutePath()); + } + } + } + } + } + + /** + * Remove the specified file or directory. If "path" is a directory, then + * recursively remove all entries, then remove the directory itself. + */ + private static void removeAll(File path) { + if (VERBOSE) { + System.err.println("removeAll(" + path + ")"); + } + + if (path.isDirectory()) { + // Recursively remove all files/directories in this directory + File[] list = path.listFiles(); + if (list != null) { + for (int i = 0; i < list.length; i++) { + removeAll(list[i]); + } + } + } + + path.delete(); + } + + public TempFileCache () { + if (VERBOSE) { + System.err.println("new TempFileCache() --------------------- (static ok: "+(!staticInitError)+")"); + System.err.println("Thread: "+Thread.currentThread().getName()+", CL 0x"+Integer.toHexString(TempFileCache.class.getClassLoader().hashCode())+", this 0x"+Integer.toHexString(hashCode())); + } + if(!staticInitError) { + try { + createTmpDir(); + } catch (Exception ex) { + ex.printStackTrace(); + initError = true; + } + } + if (VERBOSE) { + System.err.println("tempDir: "+individualTmpDir+" (ok: "+(!initError)+")"); + System.err.println("----------------------------------------------------------"); + } + } + + /** + * @return true is static and object initialization was successful + */ + public boolean isValid() { return !staticInitError && !initError; } + + /** + * Base temp directory used by TempFileCache. + * Lifecycle: For all JVMs, ClassLoader and times. + * + * This is set to: + * + * ${java.io.tmpdir}/<tmpDirPrefix> + * + * + * @return + */ + public File getBaseDir() { return tmpBaseDir; } + + /** + * Root temp directory for this JVM instance. Used to store individual + * directories. + * + * Lifecycle: For all JVMs and ClassLoader + * + * <tmpBaseDir>/<tmpRootPropValue> + * + * Use Case: Per ClassLoader files, eg. native libraries. + * + * Old temp directories are cleaned up the next time a JVM is launched that + * uses TempFileCache. + * + * + * @return + */ + public File getRootDir() { return tmpRootDir; } + + /** + * Temporary directory for individual files (eg. native libraries of one ClassLoader instance). + * The directory name is: + * + * Lifecycle: Within each JVM .. use case dependent, ie. per ClassLoader + * + * <tmpRootDir>/jlnMMMMM + * + * where jlnMMMMM is the unique filename created by File.createTempFile() + * without the ".tmp" extension. + * + * + * @return + */ + public File getTempDir() { return individualTmpDir; } + + + /** + * Create the temp directory in tmpRootDir. To do this, we create a temp + * file with a ".tmp" extension, and then create a directory of the + * same name but without the ".tmp". The temp file, directory, and all + * files in the directory will be reaped the next time this is started. + * We avoid deleteOnExit, because it doesn't work reliably. + */ + private void createTmpDir() throws IOException { + + File tmpFile = File.createTempFile("jln", ".tmp", tmpRootDir); + String tmpFileName = tmpFile.getAbsolutePath(); + String tmpDirName = tmpFileName.substring(0, tmpFileName.lastIndexOf(".tmp")); + individualTmpDir = new File(tmpDirName); + if (!individualTmpDir.mkdir()) { + throw new IOException("Cannot create " + individualTmpDir); + } + } +} diff --git a/src/java/com/jogamp/common/util/cache/TempJarCache.java b/src/java/com/jogamp/common/util/cache/TempJarCache.java new file mode 100644 index 0000000..efdf113 --- /dev/null +++ b/src/java/com/jogamp/common/util/cache/TempJarCache.java @@ -0,0 +1,224 @@ +/** + * 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.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarFile; + + +import com.jogamp.common.os.NativeLibrary; +import com.jogamp.common.util.JarUtil; + +public class 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<String, String> nativeLibMap; + + // Set of native jar files added + private static Set<JarFile> nativeLibJars; + private static Set<JarFile> classFileJars; + private static Set<JarFile> resourceFileJars; + + private static TempFileCache tmpFileCache; + + private static boolean staticInitError = false; + + static { + staticInitError = !TempFileCache.initSingleton(); + + if(!staticInitError) { + tmpFileCache = new TempFileCache(); + staticInitError = !tmpFileCache.isValid(); + } + + if(!staticInitError) { + // Initialize the collections of resources + nativeLibMap = new HashMap<String, String>(); + nativeLibJars = new HashSet<JarFile>(); + classFileJars = new HashSet<JarFile>(); + resourceFileJars = new HashSet<JarFile>(); + } + } + + /** + * Documented way to kick off static initialization + * @return true is static initialization was successful + */ + public static boolean initSingleton() { + return isValid(); + } + + /** + * @return true is static initialization was successful + */ + public static boolean isValid() { + return !staticInitError; + } + + public static TempFileCache getTempFileCache() { + return tmpFileCache; + } + + public static boolean contains(JarFile jarFile) throws IOException { + return nativeLibJars.contains(jarFile); + } + + /** + * Adds native libraries, if not yet added. + * + * @param jarFile + * @return + * @throws IOException + */ + public static boolean addNativeLibs(JarFile jarFile) throws IOException { + if(!nativeLibJars.contains(jarFile)) { + JarUtil.extract(tmpFileCache.getTempDir(), nativeLibMap, jarFile, + true, false, false); + nativeLibJars.add(jarFile); + return true; + } + return false; + } + + /** + * Adds native classes, if not yet added. + * + * TODO class access pending + * needs Classloader.defineClass(..) access, ie. own derivation - will do when needed .. + * + * @param jarFile + * @return + * @throws IOException + */ + public static boolean addClasses(JarFile jarFile) throws IOException { + if(!classFileJars.contains(jarFile)) { + JarUtil.extract(tmpFileCache.getTempDir(), null, jarFile, + false, true, false); + classFileJars.add(jarFile); + return true; + } + return false; + } + + /** + * Adds native resources, if not yet added. + * + * @param jarFile + * @return + * @throws IOException + */ + public static boolean addResources(JarFile jarFile) throws IOException { + if(!resourceFileJars.contains(jarFile)) { + JarUtil.extract(tmpFileCache.getTempDir(), null, jarFile, + false, false, true); + resourceFileJars.add(jarFile); + return true; + } + return false; + } + + /** + * 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 jarFile + * @return + * @throws IOException + */ + public static boolean addAll(JarFile jarFile) throws IOException { + if(!nativeLibJars.contains(jarFile) || + !classFileJars.contains(jarFile) || + !resourceFileJars.contains(jarFile)) { + final boolean extractNativeLibraries = !nativeLibJars.contains(jarFile); + final boolean extractClassFiles = !classFileJars.contains(jarFile); + final boolean extractOtherFiles = !resourceFileJars.contains(jarFile); + JarUtil.extract(tmpFileCache.getTempDir(), nativeLibMap, jarFile, + extractNativeLibraries, extractClassFiles, extractOtherFiles); + if(extractNativeLibraries) { + nativeLibJars.add(jarFile); + } + if(extractClassFiles) { + classFileJars.add(jarFile); + } + if(extractOtherFiles) { + resourceFileJars.add(jarFile); + } + return true; + } + return false; + } + + public static String findLibrary(String libName) { + // 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 { + 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; + } */ + + public static String findResource(String name) { + final File f = new File(tmpFileCache.getTempDir(), name); + if(f.exists()) { + return f.getAbsolutePath(); + } + return null; + } + +} |