changeset 740:8e6aa48abeba

Extract NativeLibraryStorage class from JNLPClassLoader
author Adam Domurad <adomurad@redhat.com>
date Mon, 03 Jun 2013 10:34:36 -0400
parents 2566a700bd86
children d6f6c5524acc
files ChangeLog netx/net/sourceforge/jnlp/cache/NativeLibraryStorage.java netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java tests/netx/unit/net/sourceforge/jnlp/runtime/JNLPClassLoaderTest.java
diffstat 4 files changed, 182 insertions(+), 140 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Wed May 29 15:43:21 2013 -0400
+++ b/ChangeLog	Mon Jun 03 10:34:36 2013 -0400
@@ -1,3 +1,10 @@
+2013-06-03  Adam Domurad  <adomurad@redhat.com>
+
+	* netx/net/sourceforge/jnlp/cache/NativeLibraryStorage.java: New,
+	stores and searches for native library files that are loaded from jars.
+	* netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java: Move code
+	that handled native jar caching to NativeLibraryStorage. 
+
 2013-05-29  Adam Domurad  <adomurad@redhat.com>
 
 	* tests/netx/unit/net/sourceforge/jnlp/runtime/JNLPClassLoaderTest.java:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/cache/NativeLibraryStorage.java	Mon Jun 03 10:34:36 2013 -0400
@@ -0,0 +1,162 @@
+package net.sourceforge.jnlp.cache;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import net.sourceforge.jnlp.runtime.JNLPRuntime;
+import net.sourceforge.jnlp.util.FileUtils;
+
+/**
+ * Handles loading and access of native code loading through a JNLP application or applet.
+ * Stores native code in a temporary folder.
+ * Be sure to call cleanupTemporayFolder when finished with the object.
+ */
+public class NativeLibraryStorage {
+    private ResourceTracker tracker;
+    private List<File> nativeSearchDirectories = new ArrayList<File>();
+
+    /* Temporary directory to store native jar entries, added to our search path */
+    private File jarEntryDirectory = null;
+
+    public NativeLibraryStorage(ResourceTracker tracker) {
+        this.tracker = tracker;
+    }
+
+    /**
+     * Clean up our temporary folder if we created one.
+     */
+    public void cleanupTemporaryFolder() {
+        if (jarEntryDirectory != null) {
+            if (JNLPRuntime.isDebug()) {
+                System.out.println("Cleaning up native directory" + jarEntryDirectory.getAbsolutePath());
+            }
+            try {
+                FileUtils.recursiveDelete(jarEntryDirectory,
+                        new File(System.getProperty("java.io.tmpdir")));
+                jarEntryDirectory = null;
+            } catch (IOException e) {
+                /*
+                 * failed to delete a file in tmpdir, no big deal (as well the VM 
+                 * might be shutting down at this point so no much we can do)
+                 */
+            }
+        }
+    }
+
+    /**
+     * Adds the {@link File} to the search path of this {@link NativeLibraryStorage}
+     * when trying to find a native library
+     */
+    public void addSearchDirectory(File directory) {
+        nativeSearchDirectories.add(directory);
+    }
+
+    public List<File> getSearchDirectories() {
+        return nativeSearchDirectories;
+    }
+
+    /**
+     * Looks in the search directories for 'fileName',
+     * returning a path to the found file if it exists.
+     * Returns null otherwise.
+     */
+    public File findLibrary(String fileName) {
+        for (File dir : getSearchDirectories()) {
+            File target = new File(dir, fileName);
+            if (target.exists())
+                return target;
+        }
+        return null;
+    }
+
+    /**
+     * Search for and enable any native code contained in a JAR by copying the
+     * native files into the filesystem. Called in the security context of the
+     * classloader.
+     */
+    public void addSearchJar(URL jarLocation) {
+        if (JNLPRuntime.isDebug())
+            System.out.println("Activate native: " + jarLocation);
+
+        File localFile = tracker.getCacheFile(jarLocation);
+        if (localFile == null)
+            return;
+
+        String[] librarySuffixes = { ".so", ".dylib", ".jnilib", ".framework", ".dll" };
+
+        try {
+            JarFile jarFile = new JarFile(localFile, false);
+            Enumeration<JarEntry> entries = jarFile.entries();
+
+            while (entries.hasMoreElements()) {
+                JarEntry e = entries.nextElement();
+
+                if (e.isDirectory()) {
+                    continue;
+                }
+
+                String name = new File(e.getName()).getName();
+                boolean isLibrary = false;
+
+                for (String suffix : librarySuffixes) {
+                    if (name.endsWith(suffix)) {
+                        isLibrary = true;
+                        break;
+                    }
+                }
+                if (!isLibrary) {
+                    continue;
+                }
+
+                ensureNativeStoreDirectory();
+
+                File outFile = new File(jarEntryDirectory, name);
+                if (!outFile.isFile()) {
+                    FileUtils.createRestrictedFile(outFile, true);
+                }
+                CacheUtil.streamCopy(jarFile.getInputStream(e),
+                                     new FileOutputStream(outFile));
+            }
+
+            jarFile.close();
+        } catch (IOException ex) {
+            if (JNLPRuntime.isDebug())
+                ex.printStackTrace();
+        }
+    }
+
+    private void ensureNativeStoreDirectory() {
+        if (jarEntryDirectory == null) {
+            jarEntryDirectory = createNativeStoreDirectory();
+            addSearchDirectory(jarEntryDirectory);
+        }
+    }
+
+    /**
+     * Create a random base directory to store native code files in.
+     */
+    private static File createNativeStoreDirectory() {
+        final int rand = (int)((Math.random()*2 - 1) * Integer.MAX_VALUE);
+        File nativeDir = new File(System.getProperty("java.io.tmpdir")
+                             + File.separator + "netx-native-"
+                             + (rand & 0xFFFF));
+        File parent = nativeDir.getParentFile();
+        if (!parent.isDirectory() && !parent.mkdirs()) {
+            return null;
+        }
+
+        try {
+            FileUtils.createRestrictedDirectory(nativeDir);
+            return nativeDir;
+        } catch (IOException e) {
+            return null;
+        }
+    }
+}
--- a/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java	Wed May 29 15:43:21 2013 -0400
+++ b/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java	Mon Jun 03 10:34:36 2013 -0400
@@ -82,6 +82,7 @@
 import net.sourceforge.jnlp.Version;
 import net.sourceforge.jnlp.cache.CacheUtil;
 import net.sourceforge.jnlp.cache.IllegalResourceDescriptorException;
+import net.sourceforge.jnlp.cache.NativeLibraryStorage;
 import net.sourceforge.jnlp.cache.ResourceTracker;
 import net.sourceforge.jnlp.cache.UpdatePolicy;
 import net.sourceforge.jnlp.security.AppVerifier;
@@ -127,11 +128,8 @@
      * initialization of applets that share a unique key*/
     private static Map<String, ReentrantLock> uniqueKeyToLock = new HashMap<String, ReentrantLock>();
 
-    /** the directory for native code */
-    private File nativeDir = null; // if set, some native code exists
-
-    /** a list of directories that contain native libraries */
-    private List<File> nativeDirectories = Collections.synchronizedList(new LinkedList<File>());
+    /** Provides a search path & temporary storage for native code */
+    private NativeLibraryStorage nativeLibraryStorage;
 
     /** security context */
     private AccessControlContext acc = AccessController.getContext();
@@ -231,6 +229,8 @@
         this.updatePolicy = policy;
         this.resources = file.getResources();
 
+        this.nativeLibraryStorage = new NativeLibraryStorage(tracker);
+
         this.mainClass = mainName;
 
         AppVerifier verifier;
@@ -270,21 +270,7 @@
                  * there is one). Other classloaders (parent, peers) will all
                  * cleanup things they created
                  */
-                if (nativeDir != null) {
-                    if (JNLPRuntime.isDebug()) {
-                        System.out.println("Cleaning up native directory" + nativeDir.getAbsolutePath());
-                    }
-                    try {
-                        FileUtils.recursiveDelete(nativeDir,
-                                new File(System.getProperty("java.io.tmpdir")));
-                    } catch (IOException e) {
-                        /*
-                         * failed to delete a file in tmpdir, no big deal (not
-                         * to mention that the VM is shutting down at this
-                         * point so no much we can do)
-                         */
-                    }
-                }
+                nativeLibraryStorage.cleanupTemporaryFolder();
             }
         });
     }
@@ -1349,7 +1335,7 @@
                     }
 
                     // some programs place a native library in any jar
-                    activateNative(jar);
+                    nativeLibraryStorage.addSearchJar(jar.getLocation());
                 }
 
                 return null;
@@ -1360,114 +1346,14 @@
     }
 
     /**
-     * Search for and enable any native code contained in a JAR by copying the
-     * native files into the filesystem. Called in the security context of the
-     * classloader.
-     */
-    protected void activateNative(JARDesc jar) {
-        if (JNLPRuntime.isDebug())
-            System.out.println("Activate native: " + jar.getLocation());
-
-        File localFile = tracker.getCacheFile(jar.getLocation());
-        if (localFile == null)
-            return;
-
-        String[] librarySuffixes = { ".so", ".dylib", ".jnilib", ".framework", ".dll" };
-
-        try {
-            JarFile jarFile = new JarFile(localFile, false);
-            Enumeration<JarEntry> entries = jarFile.entries();
-
-            while (entries.hasMoreElements()) {
-                JarEntry e = entries.nextElement();
-
-                if (e.isDirectory()) {
-                    continue;
-                }
-
-                String name = new File(e.getName()).getName();
-                boolean isLibrary = false;
-
-                for (String suffix : librarySuffixes) {
-                    if (name.endsWith(suffix)) {
-                        isLibrary = true;
-                        break;
-                    }
-                }
-                if (!isLibrary) {
-                    continue;
-                }
-
-                if (nativeDir == null)
-                    nativeDir = getNativeDir();
-
-                File outFile = new File(nativeDir, name);
-                if (!outFile.isFile()) {
-                    FileUtils.createRestrictedFile(outFile, true);
-                }
-                CacheUtil.streamCopy(jarFile.getInputStream(e),
-                                     new FileOutputStream(outFile));
-
-            }
-            jarFile.close();
-        } catch (IOException ex) {
-            if (JNLPRuntime.isDebug())
-                ex.printStackTrace();
-        }
-    }
-
-    /**
-     * Return the base directory to store native code files in.
-     * This method does not need to return the same directory across
-     * calls.
-     */
-    protected File getNativeDir() {
-        final int rand = (int)((Math.random()*2 - 1) * Integer.MAX_VALUE);
-        nativeDir = new File(System.getProperty("java.io.tmpdir")
-                             + File.separator + "netx-native-"
-                             + (rand & 0xFFFF));
-        File parent = nativeDir.getParentFile();
-        if (!parent.isDirectory() && !parent.mkdirs()) {
-            return null;
-        }
-
-        try {
-            FileUtils.createRestrictedDirectory(nativeDir);
-            // add this new native directory to the search path
-            addNativeDirectory(nativeDir);
-            return nativeDir;
-        } catch (IOException e) {
-            return null;
-        }
-    }
-
-    /**
-     * Adds the {@link File} to the search path of this {@link JNLPClassLoader}
-     * when trying to find a native library
-     */
-    protected void addNativeDirectory(File nativeDirectory) {
-        nativeDirectories.add(nativeDirectory);
-    }
-
-    /**
-     * Returns a list of all directories in the search path of the current classloader
-     * when it tires to find a native library.
-     * @return a list of directories in the search path for native libraries
-     */
-    protected List<File> getNativeDirectories() {
-        return nativeDirectories;
-    }
-
-    /**
      * Return the absolute path to the native library.
      */
     protected String findLibrary(String lib) {
         String syslib = System.mapLibraryName(lib);
+        File libFile = nativeLibraryStorage.findLibrary(syslib);
 
-        for (File dir : getNativeDirectories()) {
-            File target = new File(dir, syslib);
-            if (target.exists())
-                return target.toString();
+        if (libFile != null) {
+            return libFile.toString();
         }
 
         String result = super.findLibrary(lib);
@@ -2044,8 +1930,9 @@
         addToCodeBaseLoader(extLoader.file.getCodeBase());
 
         // native search paths
-        for (File nativeDirectory : extLoader.getNativeDirectories())
-            addNativeDirectory(nativeDirectory);
+        for (File nativeDirectory : extLoader.nativeLibraryStorage.getSearchDirectories()) {
+            nativeLibraryStorage.addSearchDirectory(nativeDirectory);
+        }
 
         // security descriptors
         for (URL key : extLoader.jarLocationSecurityMap.keySet()) {
--- a/tests/netx/unit/net/sourceforge/jnlp/runtime/JNLPClassLoaderTest.java	Wed May 29 15:43:21 2013 -0400
+++ b/tests/netx/unit/net/sourceforge/jnlp/runtime/JNLPClassLoaderTest.java	Mon Jun 03 10:34:36 2013 -0400
@@ -150,20 +150,6 @@
         });
 
     }
-
-    /* Note: Only does file leak testing for now, but more testing could be added. */
-    @Test
-    public void activateNativeFileLeakTest() throws Exception {
-        final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(createTempJar("test.jar"));
-        final JNLPClassLoader classLoader = new JNLPClassLoader(jnlpFile, UpdatePolicy.ALWAYS);
-
-        assertNoFileLeak( new Runnable () {
-            @Override
-            public void run() {
-                    classLoader.activateNative(jnlpFile.jarDesc);
-            }
-        });
-    }
     
     @Test
     public void getMainClassNameTest() throws Exception {