From 1b53d59af4826218b4079659272d5ab265494215 Mon Sep 17 00:00:00 2001 From: David Mollitor Date: Tue, 20 Aug 2024 12:06:08 -0400 Subject: [PATCH] Create native SQLite loader chain --- .../NativeLibraryNotFoundException.java | 8 + .../java/org/sqlite/SQLiteJDBCLoader.java | 220 +----------------- .../bin/AbstractSQLiteNativeLoaderChain.java | 49 ++++ .../bin/DefaultSQLiteNativeLoaderChain.java | 30 +++ .../SQLiteJavaLibraryPathNativeLoader.java | 57 +++++ .../bin/SQLiteLibraryJDKNativeLoader.java | 27 +++ .../org/sqlite/bin/SQLiteNativeLoader.java | 14 ++ .../bin/SQLiteResourceNativeLoader.java | 189 +++++++++++++++ .../bin/SQLiteSystemPropertyNativeLoader.java | 58 +++++ 9 files changed, 441 insertions(+), 211 deletions(-) create mode 100644 src/main/java/org/sqlite/bin/AbstractSQLiteNativeLoaderChain.java create mode 100644 src/main/java/org/sqlite/bin/DefaultSQLiteNativeLoaderChain.java create mode 100644 src/main/java/org/sqlite/bin/SQLiteJavaLibraryPathNativeLoader.java create mode 100644 src/main/java/org/sqlite/bin/SQLiteLibraryJDKNativeLoader.java create mode 100644 src/main/java/org/sqlite/bin/SQLiteNativeLoader.java create mode 100644 src/main/java/org/sqlite/bin/SQLiteResourceNativeLoader.java create mode 100644 src/main/java/org/sqlite/bin/SQLiteSystemPropertyNativeLoader.java diff --git a/src/main/java/org/sqlite/NativeLibraryNotFoundException.java b/src/main/java/org/sqlite/NativeLibraryNotFoundException.java index 592d5c75a..40e592db4 100644 --- a/src/main/java/org/sqlite/NativeLibraryNotFoundException.java +++ b/src/main/java/org/sqlite/NativeLibraryNotFoundException.java @@ -4,4 +4,12 @@ public class NativeLibraryNotFoundException extends Exception { public NativeLibraryNotFoundException(String message) { super(message); } + + public NativeLibraryNotFoundException(Throwable cause) { + super(cause); + } + + public NativeLibraryNotFoundException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/src/main/java/org/sqlite/SQLiteJDBCLoader.java b/src/main/java/org/sqlite/SQLiteJDBCLoader.java index e054eedd3..4092cd497 100644 --- a/src/main/java/org/sqlite/SQLiteJDBCLoader.java +++ b/src/main/java/org/sqlite/SQLiteJDBCLoader.java @@ -26,24 +26,18 @@ import java.io.*; import java.net.URL; -import java.net.URLConnection; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.LinkedList; -import java.util.List; import java.util.Properties; -import java.util.UUID; import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.sqlite.util.LibraryLoaderUtil; +import org.sqlite.bin.DefaultSQLiteNativeLoaderChain; import org.sqlite.util.OSInfo; -import org.sqlite.util.StringUtils; /** * Set the system properties, org.sqlite.lib.path, org.sqlite.lib.name, appropriately so that the @@ -147,149 +141,6 @@ static String md5sum(InputStream input) throws IOException { } } - 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; - } - - /** - * Extracts and loads the specified library file to the target folder - * - * @param libFolderForCurrentOS Library path. - * @param libraryFileName Library name. - * @param targetFolder Target folder. - * @return - */ - private static boolean extractAndLoadLibraryFile( - String libFolderForCurrentOS, String libraryFileName, String targetFolder) - throws FileException { - String nativeLibraryFilePath = libFolderForCurrentOS + "/" + libraryFileName; - // Include architecture name in temporary filename in order to avoid conflicts - // when multiple JVMs with different architectures running at the same time - String uuid = UUID.randomUUID().toString(); - String extractedLibFileName = - String.format("sqlite-%s-%s-%s", getVersion(), uuid, libraryFileName); - String extractedLckFileName = extractedLibFileName + LOCK_EXT; - - Path extractedLibFile = Paths.get(targetFolder, extractedLibFileName); - Path extractedLckFile = Paths.get(targetFolder, extractedLckFileName); - - try { - // Extract a native library file into the target directory - try (InputStream reader = getResourceAsStream(nativeLibraryFilePath)) { - if (Files.notExists(extractedLckFile)) { - Files.createFile(extractedLckFile); - } - - Files.copy(reader, extractedLibFile, StandardCopyOption.REPLACE_EXISTING); - } finally { - // Delete the extracted lib file on JVM exit. - extractedLibFile.toFile().deleteOnExit(); - extractedLckFile.toFile().deleteOnExit(); - } - - // Set executable (x) flag to enable Java to load the native library - extractedLibFile.toFile().setReadable(true); - extractedLibFile.toFile().setWritable(true, true); - extractedLibFile.toFile().setExecutable(true); - - // Check whether the contents are properly copied from the resource folder - { - try (InputStream nativeIn = getResourceAsStream(nativeLibraryFilePath); - InputStream extractedLibIn = Files.newInputStream(extractedLibFile)) { - if (!contentsEquals(nativeIn, extractedLibIn)) { - throw new FileException( - String.format( - "Failed to write a native library file at %s", - extractedLibFile)); - } - } - } - return loadNativeLibrary(targetFolder, extractedLibFileName); - } catch (IOException e) { - logger.error("Unexpected IOException", e); - return false; - } - } - - // Replacement of java.lang.Class#getResourceAsStream(String) to disable sharing the resource - // stream - // in multiple class loaders and specifically to avoid - // https://bugs.openjdk.java.net/browse/JDK-8205976 - private static InputStream getResourceAsStream(String name) { - // Remove leading '/' since all our resource paths include a leading directory - // See: - // https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/Class.java#L3054 - String resolvedName = name.substring(1); - ClassLoader cl = SQLiteJDBCLoader.class.getClassLoader(); - URL url = cl.getResource(resolvedName); - if (url == null) { - return null; - } - try { - URLConnection connection = url.openConnection(); - connection.setUseCaches(false); - return connection.getInputStream(); - } catch (IOException e) { - logger.error("Could not connect", e); - return null; - } - } - - /** - * Loads native library using the given path and name of the library. - * - * @param path Path of the native library. - * @param name Name of the native library. - * @return True for successfully loading; false otherwise. - */ - private static boolean loadNativeLibrary(String path, String name) { - File libPath = new File(path, name); - if (libPath.exists()) { - - try { - System.load(new File(path, name).getAbsolutePath()); - return true; - } catch (UnsatisfiedLinkError e) { - - logger.error( - "Failed to load native library: {}. osinfo: {}", - name, - OSInfo.getNativeLibFolderPathForCurrentOS(), - e); - return false; - } - - } else { - return false; - } - } - - private static boolean loadNativeLibraryJdk() { - try { - System.loadLibrary(LibraryLoaderUtil.NATIVE_LIB_BASE_NAME); - return true; - } catch (UnsatisfiedLinkError e) { - logger.error("Failed to load native library through System.loadLibrary", e); - return false; - } - } - /** * Loads SQLite native library using given path and name of the library. * @@ -300,69 +151,16 @@ private static void loadSQLiteNativeLibrary() throws Exception { return; } - List triedPaths = new LinkedList<>(); - - // Try loading library from org.sqlite.lib.path library path */ - String sqliteNativeLibraryPath = System.getProperty("org.sqlite.lib.path"); - String sqliteNativeLibraryName = System.getProperty("org.sqlite.lib.name"); - if (sqliteNativeLibraryName == null) { - sqliteNativeLibraryName = LibraryLoaderUtil.getNativeLibName(); - } - - if (sqliteNativeLibraryPath != null) { - if (loadNativeLibrary(sqliteNativeLibraryPath, sqliteNativeLibraryName)) { - extracted = true; - return; - } else { - triedPaths.add(sqliteNativeLibraryPath); - } - } - - // Load the os-dependent library from the jar file - sqliteNativeLibraryPath = LibraryLoaderUtil.getNativeLibResourcePath(); - boolean hasNativeLib = - LibraryLoaderUtil.hasNativeLib(sqliteNativeLibraryPath, sqliteNativeLibraryName); - - if (hasNativeLib) { - // temporary library folder - String tempFolder = getTempDir().getAbsolutePath(); - // Try extracting the library from jar - if (extractAndLoadLibraryFile( - sqliteNativeLibraryPath, sqliteNativeLibraryName, tempFolder)) { - extracted = true; - return; - } else { - triedPaths.add(sqliteNativeLibraryPath); - } - } - - // As a last resort try from java.library.path - String javaLibraryPath = System.getProperty("java.library.path", ""); - for (String ldPath : javaLibraryPath.split(File.pathSeparator)) { - if (ldPath.isEmpty()) { - continue; - } - if (loadNativeLibrary(ldPath, sqliteNativeLibraryName)) { - extracted = true; - return; - } else { - triedPaths.add(ldPath); - } - } - - // As an ultimate last resort, try loading through System.loadLibrary - if (loadNativeLibraryJdk()) { + try { + DefaultSQLiteNativeLoaderChain.getInstance().loadSQLiteNative(); extracted = true; - return; + } catch (NativeLibraryNotFoundException e) { + throw new NativeLibraryNotFoundException( + String.format( + "No native library found for os.name=%s, os.arch=%s", + OSInfo.getOSName(), OSInfo.getArchName()), + e); } - - extracted = false; - throw new NativeLibraryNotFoundException( - String.format( - "No native library found for os.name=%s, os.arch=%s, paths=[%s]", - OSInfo.getOSName(), - OSInfo.getArchName(), - StringUtils.join(triedPaths, File.pathSeparator))); } @SuppressWarnings("unused") diff --git a/src/main/java/org/sqlite/bin/AbstractSQLiteNativeLoaderChain.java b/src/main/java/org/sqlite/bin/AbstractSQLiteNativeLoaderChain.java new file mode 100644 index 000000000..dcf5a6c62 --- /dev/null +++ b/src/main/java/org/sqlite/bin/AbstractSQLiteNativeLoaderChain.java @@ -0,0 +1,49 @@ +package org.sqlite.bin; + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sqlite.NativeLibraryNotFoundException; + +/** + * A {@link SQLiteNativeLoader} implementation that chains together multiple SQLite native loaders. + * When loading the SQLite native file from this loader, it calls all the loaders in the chain, in + * the original order specified, until a loader can successfully load the native library. If all of + * the loaders in the chain have been called, and none can load the library, then this class will + * throw an exception. + */ +public abstract class AbstractSQLiteNativeLoaderChain implements SQLiteNativeLoader { + + private static final Logger logger = + LoggerFactory.getLogger(AbstractSQLiteNativeLoaderChain.class); + + private final Collection chain; + + AbstractSQLiteNativeLoaderChain(Collection chain) { + this.chain = Objects.requireNonNull(chain); + } + + AbstractSQLiteNativeLoaderChain(SQLiteNativeLoader... chain) { + this(Arrays.asList(chain)); + } + + @Override + public void loadSQLiteNative() throws NativeLibraryNotFoundException { + List exceptionMessages = new LinkedList<>(); + for (SQLiteNativeLoader loader : chain) { + try { + loader.loadSQLiteNative(); + return; + } catch (Exception e) { + logger.debug("Unable to load SQLite native", e); + exceptionMessages.add(e.getMessage()); + } + } + throw new NativeLibraryNotFoundException( + "Unable to load SQLite native from chain: " + exceptionMessages); + } +} diff --git a/src/main/java/org/sqlite/bin/DefaultSQLiteNativeLoaderChain.java b/src/main/java/org/sqlite/bin/DefaultSQLiteNativeLoaderChain.java new file mode 100644 index 000000000..3dbb0267d --- /dev/null +++ b/src/main/java/org/sqlite/bin/DefaultSQLiteNativeLoaderChain.java @@ -0,0 +1,30 @@ +package org.sqlite.bin; + +/** + * A {@link SQLiteNativeLoader} implementation that looks in the following locations for the SQLite + * native library, in order. + * + *
    + *
  1. Load library from org.sqlite.lib.path library path + *
  2. Load the os-dependent library from the jar file + *
  3. Load from java.library.path + *
  4. Load through System.loadLibrary + *
+ */ +public class DefaultSQLiteNativeLoaderChain extends AbstractSQLiteNativeLoaderChain { + + private static final DefaultSQLiteNativeLoaderChain INSTANCE = + new DefaultSQLiteNativeLoaderChain(); + + DefaultSQLiteNativeLoaderChain() { + super( + new SQLiteSystemPropertyNativeLoader(), + new SQLiteResourceNativeLoader(), + new SQLiteJavaLibraryPathNativeLoader(), + new SQLiteLibraryJDKNativeLoader()); + } + + public static DefaultSQLiteNativeLoaderChain getInstance() { + return INSTANCE; + } +} diff --git a/src/main/java/org/sqlite/bin/SQLiteJavaLibraryPathNativeLoader.java b/src/main/java/org/sqlite/bin/SQLiteJavaLibraryPathNativeLoader.java new file mode 100644 index 000000000..d275d4482 --- /dev/null +++ b/src/main/java/org/sqlite/bin/SQLiteJavaLibraryPathNativeLoader.java @@ -0,0 +1,57 @@ +package org.sqlite.bin; + +import java.io.File; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sqlite.NativeLibraryNotFoundException; + +/** + * A {@link SQLiteNativeLoader} implementation that loads from java.library.path system property. + */ +public class SQLiteJavaLibraryPathNativeLoader implements SQLiteNativeLoader { + + private static final Logger logger = + LoggerFactory.getLogger(SQLiteJavaLibraryPathNativeLoader.class); + + @Override + public void loadSQLiteNative() throws NativeLibraryNotFoundException { + // try from java.library.path + String javaLibraryPath = System.getProperty("java.library.path", ""); + String sqliteNativeLibraryName = System.getProperty("org.sqlite.lib.name"); + + for (String ldPath : javaLibraryPath.split(File.pathSeparator)) { + if (ldPath.isEmpty()) { + continue; + } + if (loadNativeLibrary(ldPath, sqliteNativeLibraryName)) { + return; + } else { + throw new NativeLibraryNotFoundException("Tried path: " + ldPath); + } + } + } + + /** + * Loads native library using the given path and name of the library. + * + * @param path Path of the native library. + * @param name Name of the native library. + * @return True for successfully loading; false otherwise. + */ + private static boolean loadNativeLibrary(String path, String name) { + File libPath = new File(path, name); + if (libPath.exists()) { + + try { + System.load(new File(path, name).getAbsolutePath()); + return true; + } catch (UnsatisfiedLinkError e) { + logger.debug("Failed to load native library: {}", name, e); + return false; + } + + } else { + return false; + } + } +} diff --git a/src/main/java/org/sqlite/bin/SQLiteLibraryJDKNativeLoader.java b/src/main/java/org/sqlite/bin/SQLiteLibraryJDKNativeLoader.java new file mode 100644 index 000000000..05b0561b0 --- /dev/null +++ b/src/main/java/org/sqlite/bin/SQLiteLibraryJDKNativeLoader.java @@ -0,0 +1,27 @@ +package org.sqlite.bin; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sqlite.NativeLibraryNotFoundException; +import org.sqlite.util.LibraryLoaderUtil; + +/** + * A {@link SQLiteNativeLoader} implementation that loads through System.loadLibrary. + * + * @see System#loadLibrary(String) + */ +public class SQLiteLibraryJDKNativeLoader implements SQLiteNativeLoader { + + private static final Logger logger = + LoggerFactory.getLogger(SQLiteLibraryJDKNativeLoader.class); + + @Override + public void loadSQLiteNative() throws NativeLibraryNotFoundException { + try { + System.loadLibrary(LibraryLoaderUtil.NATIVE_LIB_BASE_NAME); + } catch (UnsatisfiedLinkError e) { + logger.debug("Failed to load native library through System.loadLibrary", e); + throw new NativeLibraryNotFoundException(e); + } + } +} diff --git a/src/main/java/org/sqlite/bin/SQLiteNativeLoader.java b/src/main/java/org/sqlite/bin/SQLiteNativeLoader.java new file mode 100644 index 000000000..7af3017cf --- /dev/null +++ b/src/main/java/org/sqlite/bin/SQLiteNativeLoader.java @@ -0,0 +1,14 @@ +package org.sqlite.bin; + +import org.sqlite.NativeLibraryNotFoundException; + +/** Loader which loads the SQLite Native binary file. */ +public interface SQLiteNativeLoader { + + /** + * Load the SQLite Native binary file. + * + * @throws NativeLibraryNotFoundException Unable to load the native binary + */ + void loadSQLiteNative() throws NativeLibraryNotFoundException; +} diff --git a/src/main/java/org/sqlite/bin/SQLiteResourceNativeLoader.java b/src/main/java/org/sqlite/bin/SQLiteResourceNativeLoader.java new file mode 100644 index 000000000..03a9bdac1 --- /dev/null +++ b/src/main/java/org/sqlite/bin/SQLiteResourceNativeLoader.java @@ -0,0 +1,189 @@ +package org.sqlite.bin; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.UUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sqlite.FileException; +import org.sqlite.NativeLibraryNotFoundException; +import org.sqlite.SQLiteJDBCLoader; +import org.sqlite.util.LibraryLoaderUtil; + +/** + * A {@link SQLiteNativeLoader} implementation that loads the os-dependent library from the jar + * file. + */ +public class SQLiteResourceNativeLoader implements SQLiteNativeLoader { + + private static final String LOCK_EXT = ".lck"; + + private static final Logger logger = LoggerFactory.getLogger(SQLiteResourceNativeLoader.class); + + @Override + public void loadSQLiteNative() throws NativeLibraryNotFoundException { + String sqliteNativeLibraryPath = LibraryLoaderUtil.getNativeLibResourcePath(); + String sqliteNativeLibraryName = LibraryLoaderUtil.getNativeLibName(); + + boolean hasNativeLib = + LibraryLoaderUtil.hasNativeLib(sqliteNativeLibraryPath, sqliteNativeLibraryName); + + if (hasNativeLib) { + // temporary library folder + String tempFolder = getTempDir().getAbsolutePath(); + // Try extracting the library from jar + try { + if (extractAndLoadLibraryFile( + sqliteNativeLibraryPath, sqliteNativeLibraryName, tempFolder)) { + return; + } else { + throw new NativeLibraryNotFoundException( + "Could not find library: " + sqliteNativeLibraryPath); + } + } catch (FileException e) { + throw new NativeLibraryNotFoundException(e); + } + } + throw new NativeLibraryNotFoundException("No native library present"); + } + + private static File getTempDir() { + return new File( + System.getProperty("org.sqlite.tmpdir", System.getProperty("java.io.tmpdir"))); + } + + /** + * Extracts and loads the specified library file to the target folder + * + * @param libFolderForCurrentOS Library path. + * @param libraryFileName Library name. + * @param targetFolder Target folder. + * @return + */ + private static boolean extractAndLoadLibraryFile( + String libFolderForCurrentOS, String libraryFileName, String targetFolder) + throws FileException { + String nativeLibraryFilePath = libFolderForCurrentOS + "/" + libraryFileName; + // Include architecture name in temporary filename in order to avoid conflicts + // when multiple JVMs with different architectures running at the same time + String uuid = UUID.randomUUID().toString(); + String extractedLibFileName = + String.format( + "sqlite-%s-%s-%s", SQLiteJDBCLoader.getVersion(), uuid, libraryFileName); + String extractedLckFileName = extractedLibFileName + LOCK_EXT; + + Path extractedLibFile = Paths.get(targetFolder, extractedLibFileName); + Path extractedLckFile = Paths.get(targetFolder, extractedLckFileName); + + try { + // Extract a native library file into the target directory + try (InputStream reader = getResourceAsStream(nativeLibraryFilePath)) { + if (Files.notExists(extractedLckFile)) { + Files.createFile(extractedLckFile); + } + + Files.copy(reader, extractedLibFile, StandardCopyOption.REPLACE_EXISTING); + } finally { + // Delete the extracted lib file on JVM exit. + extractedLibFile.toFile().deleteOnExit(); + extractedLckFile.toFile().deleteOnExit(); + } + + // Set executable (x) flag to enable Java to load the native library + extractedLibFile.toFile().setReadable(true); + extractedLibFile.toFile().setWritable(true, true); + extractedLibFile.toFile().setExecutable(true); + + // Check whether the contents are properly copied from the resource folder + try (InputStream nativeIn = getResourceAsStream(nativeLibraryFilePath); + InputStream extractedLibIn = Files.newInputStream(extractedLibFile)) { + if (!contentsEquals(nativeIn, extractedLibIn)) { + throw new FileException( + String.format( + "Failed to write a native library file at %s", + extractedLibFile)); + } + } + return loadNativeLibrary(targetFolder, extractedLibFileName); + } catch (IOException e) { + logger.info("Unexpected IOException", e); + return false; + } + } + + // Replacement of java.lang.Class#getResourceAsStream(String) to disable sharing the resource + // stream + // in multiple class loaders and specifically to avoid + // https://bugs.openjdk.java.net/browse/JDK-8205976 + private static InputStream getResourceAsStream(String name) { + // Remove leading '/' since all our resource paths include a leading directory + // See: + // https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/Class.java#L3054 + String resolvedName = name.substring(1); + ClassLoader cl = SQLiteJDBCLoader.class.getClassLoader(); + URL url = cl.getResource(resolvedName); + if (url == null) { + return null; + } + try { + URLConnection connection = url.openConnection(); + connection.setUseCaches(false); + return connection.getInputStream(); + } catch (IOException e) { + logger.info("Could not connect", e); + return null; + } + } + + /** + * Loads native library using the given path and name of the library. + * + * @param path Path of the native library. + * @param name Name of the native library. + * @return True for successfully loading; false otherwise. + */ + private static boolean loadNativeLibrary(String path, String name) { + File libPath = new File(path, name); + if (libPath.exists()) { + + try { + System.load(new File(path, name).getAbsolutePath()); + return true; + } catch (UnsatisfiedLinkError e) { + logger.debug("Failed to load native library: {}", name, e); + return false; + } + + } else { + return false; + } + } + + 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; + } +} diff --git a/src/main/java/org/sqlite/bin/SQLiteSystemPropertyNativeLoader.java b/src/main/java/org/sqlite/bin/SQLiteSystemPropertyNativeLoader.java new file mode 100644 index 000000000..d6000f91a --- /dev/null +++ b/src/main/java/org/sqlite/bin/SQLiteSystemPropertyNativeLoader.java @@ -0,0 +1,58 @@ +package org.sqlite.bin; + +import java.io.File; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sqlite.NativeLibraryNotFoundException; +import org.sqlite.util.LibraryLoaderUtil; + +/** + * A {@link SQLiteNativeLoader} implementation that loads through org.sqlite.lib.path system + * property. + */ +public class SQLiteSystemPropertyNativeLoader implements SQLiteNativeLoader { + + private static final Logger logger = + LoggerFactory.getLogger(SQLiteSystemPropertyNativeLoader.class); + + @Override + public void loadSQLiteNative() throws NativeLibraryNotFoundException { + // Try loading library from org.sqlite.lib.path library path */ + String sqliteNativeLibraryPath = System.getProperty("org.sqlite.lib.path"); + String sqliteNativeLibraryName = System.getProperty("org.sqlite.lib.name"); + if (sqliteNativeLibraryName == null) { + sqliteNativeLibraryName = LibraryLoaderUtil.getNativeLibName(); + } + + if (sqliteNativeLibraryPath != null) { + if (loadNativeLibrary(sqliteNativeLibraryPath, sqliteNativeLibraryName)) { + return; + } + } + throw new NativeLibraryNotFoundException(""); + } + + /** + * Loads native library using the given path and name of the library. + * + * @param path Path of the native library. + * @param name Name of the native library. + * @return True for successfully loading; false otherwise. + */ + private static boolean loadNativeLibrary(String path, String name) { + File libPath = new File(path, name); + if (libPath.exists()) { + + try { + System.load(new File(path, name).getAbsolutePath()); + return true; + } catch (UnsatisfiedLinkError e) { + logger.debug("Failed to load native library: {}.", name, e); + return false; + } + + } else { + return false; + } + } +}