snappy-java/src/main/java/org/xerial/snappy/SnappyLoader.java

351 lines
13 KiB
Java
Executable File

/*--------------------------------------------------------------------------
* Copyright 2011 Taro L. Saito
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*--------------------------------------------------------------------------*/
//--------------------------------------
// snappy-java Project
//
// SnappyLoader.java
// Since: 2011/03/29
//
// $URL$
// $Author$
//--------------------------------------
package org.xerial.snappy;
import java.io.*;
import java.net.URL;
import java.util.Enumeration;
import java.util.Properties;
import java.util.UUID;
/**
* <b>Internal only - Do not use this class.</b> This class loads a native
* library of snappy-java (snappyjava.dll, libsnappy.so, etc.) according to the
* user platform (<i>os.name</i> and <i>os.arch</i>). The natively compiled
* libraries bundled to snappy-java contain the codes of the original snappy and
* JNI programs to access Snappy.
*
* In default, no configuration is required to use snappy-java, but you can load
* your own native library created by 'make native' command.
*
* This SnappyLoader searches for native libraries (snappyjava.dll,
* libsnappy.so, etc.) in the following order:
* <ol>
* <li>If system property <i>org.xerial.snappy.use.systemlib</i> is set to true,
* lookup folders specified by <i>java.lib.path</i> system property (This is the
* default path that JVM searches for native libraries)
* <li>(System property: <i>org.xerial.snappy.lib.path</i>)/(System property:
* <i>org.xerial.lib.name</i>)
* <li>One of the libraries embedded in snappy-java-(version).jar extracted into
* (System property: <i>java.io.tempdir</i>). If
* <i>org.xerial.snappy.tempdir</i> is set, use this folder instead of
* <i>java.io.tempdir</i>.
* </ol>
*
* <p>
* If you do not want to use folder <i>java.io.tempdir</i>, set the System
* property <i>org.xerial.snappy.tempdir</i>. For example, to use
* <i>/tmp/leo</i> as a temporary folder to copy native libraries, use -D option
* of JVM:
*
* <pre>
* <code>
* java -Dorg.xerial.snappy.tempdir="/tmp/leo" ...
* </code>
* </pre>
*
* </p>
*
* @author leo
*
*/
public class SnappyLoader
{
public static final String SNAPPY_SYSTEM_PROPERTIES_FILE = "org-xerial-snappy.properties";
public static final String KEY_SNAPPY_LIB_PATH = "org.xerial.snappy.lib.path";
public static final String KEY_SNAPPY_LIB_NAME = "org.xerial.snappy.lib.name";
public static final String KEY_SNAPPY_TEMPDIR = "org.xerial.snappy.tempdir";
public static final String KEY_SNAPPY_USE_SYSTEMLIB = "org.xerial.snappy.use.systemlib";
public static final String KEY_SNAPPY_DISABLE_BUNDLED_LIBS = "org.xerial.snappy.disable.bundled.libs"; // Depreciated, but preserved for backward compatibility
private static volatile boolean isLoaded = false;
private static volatile SnappyNative api = null;
private static File nativeLibFile = null;
static void cleanUpExtractedNativeLib() {
if(nativeLibFile != null && nativeLibFile.exists())
nativeLibFile.delete();
}
/**
* Set the api instance.
*
* @param nativeCode
*/
static synchronized void setApi(SnappyNative nativeCode)
{
api = nativeCode;
}
/**
* load system properties when configuration file of the name
* {@link #SNAPPY_SYSTEM_PROPERTIES_FILE} is found
*/
private static void loadSnappySystemProperties() {
try {
InputStream is = Thread.currentThread().getContextClassLoader()
.getResourceAsStream(SNAPPY_SYSTEM_PROPERTIES_FILE);
if (is == null)
return; // no configuration file is found
// Load property file
Properties props = new Properties();
props.load(is);
is.close();
Enumeration< ? > names = props.propertyNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
if (name.startsWith("org.xerial.snappy.")) {
if (System.getProperty(name) == null) {
System.setProperty(name, props.getProperty(name));
}
}
}
}
catch (Throwable ex) {
System.err.println("Could not load '" + SNAPPY_SYSTEM_PROPERTIES_FILE + "' from classpath: "
+ ex.toString());
}
}
static {
loadSnappySystemProperties();
}
static synchronized SnappyNative load()
{
if (api != null)
return api;
try {
loadNativeLibrary();
setApi(new SnappyNative());
isLoaded = true;
}
catch (Exception e) {
e.printStackTrace();
throw new SnappyError(SnappyErrorCode.FAILED_TO_LOAD_NATIVE_LIBRARY, e.getMessage());
}
return api;
}
/**
* Load a native library of snappy-java
*/
private static void loadNativeLibrary() {
nativeLibFile = findNativeLibrary();
if (nativeLibFile != null) {
// Load extracted or specified snappyjava native library.
System.load(nativeLibFile.getAbsolutePath());
}
else {
// Load preinstalled snappyjava (in the path -Djava.library.path)
System.loadLibrary("snappyjava");
}
}
private static boolean contentsEquals(InputStream in1, InputStream in2) throws IOException {
if(!(in1 instanceof BufferedInputStream)) {
in1 = new BufferedInputStream(in1);
}
if(!(in2 instanceof BufferedInputStream)) {
in2 = new BufferedInputStream(in2);
}
int ch = in1.read();
while(ch != -1) {
int ch2 = in2.read();
if(ch != ch2)
return false;
ch = in1.read();
}
int ch2 = in2.read();
return ch2 == -1;
}
/**
* Extract the specified library file to the target folder
*
* @param libFolderForCurrentOS
* @param libraryFileName
* @param targetFolder
* @return
*/
private static File extractLibraryFile(String libFolderForCurrentOS, String libraryFileName, String targetFolder) {
String nativeLibraryFilePath = libFolderForCurrentOS + "/" + libraryFileName;
// 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);
try {
// Extract a native library file into the target directory
InputStream reader = SnappyLoader.class.getResourceAsStream(nativeLibraryFilePath);
FileOutputStream writer = new FileOutputStream(extractedLibFile);
try {
byte[] buffer = new byte[8192];
int bytesRead = 0;
while ((bytesRead = reader.read(buffer)) != -1) {
writer.write(buffer, 0, bytesRead);
}
}
finally {
// Delete the extracted lib file on JVM exit.
extractedLibFile.deleteOnExit();
if(writer != null)
writer.close();
if(reader != null)
reader.close();
}
// Set executable (x) flag to enable Java to load the native library
extractedLibFile.setReadable(true);
extractedLibFile.setWritable(true, true);
extractedLibFile.setExecutable(true);
// Check whether the contents are properly copied from the resource folder
{
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) {
e.printStackTrace(System.err);
return null;
}
}
static File findNativeLibrary() {
boolean useSystemLib = Boolean.parseBoolean(System.getProperty(KEY_SNAPPY_USE_SYSTEMLIB, "false"));
boolean disabledBundledLibs = Boolean
.parseBoolean(System.getProperty(KEY_SNAPPY_DISABLE_BUNDLED_LIBS, "false"));
if (useSystemLib || disabledBundledLibs)
return null; // Use a pre-installed libsnappyjava
// Try to load the library in org.xerial.snappy.lib.path */
String snappyNativeLibraryPath = System.getProperty(KEY_SNAPPY_LIB_PATH);
String snappyNativeLibraryName = System.getProperty(KEY_SNAPPY_LIB_NAME);
// Resolve the library file name with a suffix (e.g., dll, .so, etc.)
if (snappyNativeLibraryName == null)
snappyNativeLibraryName = System.mapLibraryName("snappyjava");
if (snappyNativeLibraryPath != null) {
File nativeLib = new File(snappyNativeLibraryPath, snappyNativeLibraryName);
if (nativeLib.exists())
return nativeLib;
}
// Load an OS-dependent native library inside a jar file
snappyNativeLibraryPath = "/org/xerial/snappy/native/" + OSInfo.getNativeLibFolderPathForCurrentOS();
boolean hasNativeLib = hasResource(snappyNativeLibraryPath + "/" + snappyNativeLibraryName);
if(!hasNativeLib) {
if(OSInfo.getOSName().equals("Mac")) {
// Fix for openjdk7 for Mac
String altName = "libsnappyjava.jnilib";
if(hasResource(snappyNativeLibraryPath + "/" + altName)) {
snappyNativeLibraryName = altName;
hasNativeLib = true;
}
}
}
if(!hasNativeLib) {
String errorMessage = String.format("no native library is found for os.name=%s and os.arch=%s", OSInfo.getOSName(), OSInfo.getArchName());
throw new SnappyError(SnappyErrorCode.FAILED_TO_LOAD_NATIVE_LIBRARY, errorMessage);
}
// Temporary folder for the native lib. Use the value of org.xerial.snappy.tempdir or java.io.tmpdir
File tempFolder = new File(System.getProperty(KEY_SNAPPY_TEMPDIR, System.getProperty("java.io.tmpdir")));
if (!tempFolder.exists()) {
tempFolder.mkdir();
}
// Extract and load a native library inside the jar file
return extractLibraryFile(snappyNativeLibraryPath, snappyNativeLibraryName, tempFolder.getAbsolutePath());
}
private static boolean hasResource(String path) {
return SnappyLoader.class.getResource(path) != null;
}
/**
* 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
* jar.
*
* @return the version string
*/
public static String getVersion() {
URL versionFile = SnappyLoader.class
.getResource("/META-INF/maven/org.xerial.snappy/snappy-java/pom.properties");
if (versionFile == null)
versionFile = SnappyLoader.class.getResource("/org/xerial/snappy/VERSION");
String version = "unknown";
try {
if (versionFile != null) {
Properties versionData = new Properties();
versionData.load(versionFile.openStream());
version = versionData.getProperty("version", version);
if (version.equals("unknown"))
version = versionData.getProperty("VERSION", version);
version = version.trim().replaceAll("[^0-9M\\.]", "");
}
}
catch (IOException e) {
System.err.println(e);
}
return version;
}
}