diff --git a/src/main/java/org/xerial/snappy/SnappyLoader.java b/src/main/java/org/xerial/snappy/SnappyLoader.java index 6fa066a..5f8f7f4 100755 --- a/src/main/java/org/xerial/snappy/SnappyLoader.java +++ b/src/main/java/org/xerial/snappy/SnappyLoader.java @@ -25,17 +25,13 @@ package org.xerial.snappy; import java.io.*; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.net.URL; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.ProtectionDomain; -import java.util.ArrayList; import java.util.Enumeration; -import java.util.List; import java.util.Properties; +import java.util.UUID; /** * Internal only - Do not use this class. This class loads a native @@ -136,92 +132,13 @@ public class SnappyLoader loadSnappySystemProperties(); } - private static ClassLoader getRootClassLoader() { - ClassLoader cl = Thread.currentThread().getContextClassLoader(); - while (cl.getParent() != null) { - cl = cl.getParent(); - } - return cl; - } - - private static byte[] getByteCode(String resourcePath) throws IOException { - - InputStream in = SnappyLoader.class.getResourceAsStream(resourcePath); - if (in == null) - throw new IOException(resourcePath + " is not found"); - byte[] buf = new byte[1024]; - ByteArrayOutputStream byteCodeBuf = new ByteArrayOutputStream(); - for (int readLength; (readLength = in.read(buf)) != -1;) { - byteCodeBuf.write(buf, 0, readLength); - } - in.close(); - - return byteCodeBuf.toByteArray(); - } - - public static boolean isNativeLibraryLoaded() { - return isLoaded; - } - - private static boolean hasInjectedNativeLoader() { - try { - final String nativeLoaderClassName = "org.xerial.snappy.SnappyNativeLoader"; - Class< ? > c = Class.forName(nativeLoaderClassName); - // If this native loader class is already defined, it means that another class loader already loaded the native library of snappy - return true; - } - catch (ClassNotFoundException e) { - // do loading - return false; - } - } - - /** - * Load SnappyNative and its JNI native implementation using the root class - * loader. This hack is for avoiding the JNI multi-loading issue when the - * same JNI library is loaded by different class loaders. - * - * In order to load native code in the root class loader, this method first - * inject SnappyNativeLoader class into the root class loader, because - * {@link System#load(String)} method uses the class loader of the caller - * class when loading native libraries. - * - *
-     * (root class loader) -> [SnappyNativeLoader (load JNI code), SnappyNative (has native methods), SnappyNativeAPI, SnappyErrorCode]  (injected by this method)
-     *    |
-     *    |
-     * (child class loader) -> Sees the above classes loaded by the root class loader.
-     *   Then creates SnappyNativeAPI implementation by instantiating SnappyNaitive class.
-     * 
- * - * - *
-     * (root class loader) -> [SnappyNativeLoader, SnappyNative ...]  -> native code is loaded by once in this class loader 
-     *   |   \
-     *   |    (child2 class loader)      
-     * (child1 class loader)
-     * 
-     * child1 and child2 share the same SnappyNative code loaded by the root class loader.
-     * 
- * - * Note that Java's class loader first delegates the class lookup to its - * parent class loader. So once SnappyNativeLoader is loaded by the root - * class loader, no child class loader initialize SnappyNativeLoader again. - * - * @return - */ - static synchronized Object load() + static synchronized Object load() { if (api != null) return api; try { - if (!hasInjectedNativeLoader()) { - // Inject SnappyNativeLoader (src/main/resources/org/xerial/snappy/SnappyNativeLoader.bytecode) to the root class loader - Class< ? > nativeLoader = injectSnappyNativeLoader(); - // Load the JNI code using the injected loader - loadNativeLibrary(nativeLoader); - } + loadNativeLibrary(); isLoaded = true; // Look up SnappyNative, injected to the root classloder, using reflection in order to avoid the initialization of SnappyNative class in this context class loader. @@ -237,91 +154,23 @@ public class SnappyLoader } /** - * Inject SnappyNativeLoader class to the root class loader - * - * @return native code loader class initialized in the root class loader + * Load a native library of snappy-java */ - private static Class< ? > injectSnappyNativeLoader() { - - try { - // Use parent class loader to load SnappyNative, since Tomcat, which uses different class loaders for each webapps, cannot load JNI interface twice - - final String nativeLoaderClassName = "org.xerial.snappy.SnappyNativeLoader"; - ClassLoader rootClassLoader = getRootClassLoader(); - // Load a byte code - byte[] byteCode = getByteCode("/org/xerial/snappy/SnappyNativeLoader.bytecode"); - // In addition, we need to load the other dependent classes (e.g., SnappyNative and SnappyException) using the system class loader - final String[] classesToPreload = new String[] { "org.xerial.snappy.SnappyNativeAPI", - "org.xerial.snappy.SnappyNative", "org.xerial.snappy.SnappyErrorCode" }; - List preloadClassByteCode = new ArrayList(classesToPreload.length); - for (String each : classesToPreload) { - preloadClassByteCode.add(getByteCode(String.format("/%s.class", each.replaceAll("\\.", "/")))); - } - - // Create SnappyNativeLoader class from a byte code - Class< ? > classLoader = Class.forName("java.lang.ClassLoader"); - Method defineClass = classLoader.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class, - int.class, int.class, ProtectionDomain.class }); - - ProtectionDomain pd = System.class.getProtectionDomain(); - - // ClassLoader.defineClass is a protected method, so we have to make it accessible - defineClass.setAccessible(true); - try { - // Create a new class using a ClassLoader#defineClass - defineClass.invoke(rootClassLoader, nativeLoaderClassName, byteCode, 0, byteCode.length, pd); - - // And also define dependent classes in the root class loader - for (int i = 0; i < classesToPreload.length; ++i) { - byte[] b = preloadClassByteCode.get(i); - defineClass.invoke(rootClassLoader, classesToPreload[i], b, 0, b.length, pd); - } - } - finally { - // Reset the accessibility to defineClass method - defineClass.setAccessible(false); - } - - // Load the SnappyNativeLoader class - return rootClassLoader.loadClass(nativeLoaderClassName); - - } - catch (Exception e) { - e.printStackTrace(System.err); - throw new SnappyError(SnappyErrorCode.FAILED_TO_LOAD_NATIVE_LIBRARY, e.getMessage()); - } - - } - - /** - * Load snappy-java's native code using load method of the - * SnappyNativeLoader class injected to the root class loader. - * - * @param loaderClass - * @throws SecurityException - * @throws NoSuchMethodException - * @throws IllegalArgumentException - * @throws IllegalAccessException - * @throws InvocationTargetException - */ - private static void loadNativeLibrary(Class< ? > loaderClass) throws SecurityException, NoSuchMethodException, - IllegalArgumentException, IllegalAccessException, InvocationTargetException { - if (loaderClass == null) - throw new SnappyError(SnappyErrorCode.FAILED_TO_LOAD_NATIVE_LIBRARY, "missing snappy native loader class"); + private static void loadNativeLibrary() { File nativeLib = findNativeLibrary(); if (nativeLib != null) { - // Load extracted or specified snappyjava native library. - Method loadMethod = loaderClass.getDeclaredMethod("load", new Class[] { String.class }); - loadMethod.invoke(null, nativeLib.getAbsolutePath()); + // Load extracted or specified snappyjava native library. + System.load(nativeLib.getAbsolutePath()); } else { - // Load preinstalled snappyjava (in the path -Djava.library.path) - Method loadMethod = loaderClass.getDeclaredMethod("loadLibrary", new Class[] { String.class }); - loadMethod.invoke(null, "snappyjava"); + // Load preinstalled snappyjava (in the path -Djava.library.path) + System.loadLibrary("snappyjava"); } } + + /** * Computes the MD5 value of the input stream * @@ -379,35 +228,14 @@ public class SnappyLoader */ private static File extractLibraryFile(String libFolderForCurrentOS, String libraryFileName, String targetFolder) { String nativeLibraryFilePath = libFolderForCurrentOS + "/" + libraryFileName; - final String prefix = "snappy-" + getVersion() + "-"; - String extractedLibFileName = prefix + libraryFileName; - File extractedLibFile = new File(targetFolder, extractedLibFileName); + // Attach UUID to the native library file to ensure multiple class loaders can read the libsnappy-java multiple times. + String uuid = UUID.randomUUID().toString(); + String extractedLibFileName = String.format("snappy-%s-%s-%s", getVersion(), uuid, libraryFileName); + File extractedLibFile = new File(targetFolder, extractedLibFileName); + // Delete extracted lib file on exit. + extractedLibFile.deleteOnExit(); try { - if (extractedLibFile.exists()) { - // Compare the native library contents - InputStream nativeIn = SnappyLoader.class.getResourceAsStream(nativeLibraryFilePath); - InputStream extractedLibIn = new FileInputStream(extractedLibFile); - try { - if(contentsEquals(nativeIn, extractedLibIn)) { - return new File(targetFolder, extractedLibFileName); - } - else { - // remove old native library file - boolean deletionSucceeded = extractedLibFile.delete(); - if (!deletionSucceeded) { - throw new IOException("failed to remove existing native library file: " - + extractedLibFile.getAbsolutePath()); - } - } - } - finally { - if(nativeIn != null) - nativeIn.close(); - if(extractedLibIn != null) - extractedLibIn.close(); - } - } // Extract a native library file into the target directory InputStream reader = SnappyLoader.class.getResourceAsStream(nativeLibraryFilePath); @@ -435,6 +263,22 @@ public class SnappyLoader catch (Throwable e) {} } + // Check the contents + { + InputStream nativeIn = SnappyLoader.class.getResourceAsStream(nativeLibraryFilePath); + InputStream extractedLibIn = new FileInputStream(extractedLibFile); + try { + if(!contentsEquals(nativeIn, extractedLibIn)) + throw new SnappyError(SnappyErrorCode.FAILED_TO_LOAD_NATIVE_LIBRARY, String.format("Failed to write a native library file at %s", extractedLibFile)); + } + finally { + if(nativeIn != null) + nativeIn.close(); + if(extractedLibIn != null) + extractedLibIn.close(); + } + } + return new File(targetFolder, extractedLibFileName); } catch (IOException e) { @@ -485,7 +329,7 @@ public class SnappyLoader throw new SnappyError(SnappyErrorCode.FAILED_TO_LOAD_NATIVE_LIBRARY, errorMessage); } - // Temporary library folder. Use the value of org.xerial.snappy.tempdir or java.io.tmpdir + // Temporary folder for the native lib. Use the value of org.xerial.snappy.tempdir or java.io.tmpdir String tempFolder = new File(System.getProperty(KEY_SNAPPY_TEMPDIR, System.getProperty("java.io.tmpdir"))).getAbsolutePath(); @@ -500,8 +344,6 @@ public class SnappyLoader - - /** * Get the snappy-java version by reading pom.properties embedded in jar. * This version data is used as a suffix of a dll file extracted from the