mirror of
https://github.com/xerial/snappy-java.git
synced 2025-07-08 06:34:34 +02:00
Attach UUID to the extracted native library file
This commit is contained in:
parent
d7263cc36d
commit
06f007a084
@ -25,17 +25,13 @@
|
|||||||
package org.xerial.snappy;
|
package org.xerial.snappy;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.security.DigestInputStream;
|
import java.security.DigestInputStream;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.ProtectionDomain;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <b>Internal only - Do not use this class.</b> This class loads a native
|
* <b>Internal only - Do not use this class.</b> This class loads a native
|
||||||
@ -136,92 +132,13 @@ public class SnappyLoader
|
|||||||
loadSnappySystemProperties();
|
loadSnappySystemProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ClassLoader getRootClassLoader() {
|
static synchronized Object load()
|
||||||
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.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* (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.
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* (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.
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* 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()
|
|
||||||
{
|
{
|
||||||
if (api != null)
|
if (api != null)
|
||||||
return api;
|
return api;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!hasInjectedNativeLoader()) {
|
loadNativeLibrary();
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoaded = true;
|
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.
|
// 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
|
* Load a native library of snappy-java
|
||||||
*
|
|
||||||
* @return native code loader class initialized in the root class loader
|
|
||||||
*/
|
*/
|
||||||
private static Class< ? > injectSnappyNativeLoader() {
|
private static void loadNativeLibrary() {
|
||||||
|
|
||||||
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<byte[]> preloadClassByteCode = new ArrayList<byte[]>(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");
|
|
||||||
|
|
||||||
File nativeLib = findNativeLibrary();
|
File nativeLib = findNativeLibrary();
|
||||||
if (nativeLib != null) {
|
if (nativeLib != null) {
|
||||||
// Load extracted or specified snappyjava native library.
|
// Load extracted or specified snappyjava native library.
|
||||||
Method loadMethod = loaderClass.getDeclaredMethod("load", new Class[] { String.class });
|
System.load(nativeLib.getAbsolutePath());
|
||||||
loadMethod.invoke(null, nativeLib.getAbsolutePath());
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Load preinstalled snappyjava (in the path -Djava.library.path)
|
// Load preinstalled snappyjava (in the path -Djava.library.path)
|
||||||
Method loadMethod = loaderClass.getDeclaredMethod("loadLibrary", new Class[] { String.class });
|
System.loadLibrary("snappyjava");
|
||||||
loadMethod.invoke(null, "snappyjava");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes the MD5 value of the input stream
|
* 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) {
|
private static File extractLibraryFile(String libFolderForCurrentOS, String libraryFileName, String targetFolder) {
|
||||||
String nativeLibraryFilePath = libFolderForCurrentOS + "/" + libraryFileName;
|
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 {
|
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
|
// Extract a native library file into the target directory
|
||||||
InputStream reader = SnappyLoader.class.getResourceAsStream(nativeLibraryFilePath);
|
InputStream reader = SnappyLoader.class.getResourceAsStream(nativeLibraryFilePath);
|
||||||
@ -435,6 +263,22 @@ public class SnappyLoader
|
|||||||
catch (Throwable e) {}
|
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);
|
return new File(targetFolder, extractedLibFileName);
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
@ -485,7 +329,7 @@ public class SnappyLoader
|
|||||||
throw new SnappyError(SnappyErrorCode.FAILED_TO_LOAD_NATIVE_LIBRARY, errorMessage);
|
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,
|
String tempFolder = new File(System.getProperty(KEY_SNAPPY_TEMPDIR,
|
||||||
System.getProperty("java.io.tmpdir"))).getAbsolutePath();
|
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.
|
* 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
|
* This version data is used as a suffix of a dll file extracted from the
|
||||||
|
Loading…
x
Reference in New Issue
Block a user