001package com.pi4j.util; 002 003/* 004 * #%L 005 * ********************************************************************** 006 * ORGANIZATION : Pi4J 007 * PROJECT : Pi4J :: Java Library (Core) 008 * FILENAME : NativeLibraryLoader.java 009 * 010 * This file is part of the Pi4J project. More information about 011 * this project can be found here: https://www.pi4j.com/ 012 * ********************************************************************** 013 * %% 014 * Copyright (C) 2012 - 2021 Pi4J 015 * %% 016 * This program is free software: you can redistribute it and/or modify 017 * it under the terms of the GNU Lesser General Public License as 018 * published by the Free Software Foundation, either version 3 of the 019 * License, or (at your option) any later version. 020 * 021 * This program is distributed in the hope that it will be useful, 022 * but WITHOUT ANY WARRANTY; without even the implied warranty of 023 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 024 * GNU General Lesser Public License for more details. 025 * 026 * You should have received a copy of the GNU General Lesser Public 027 * License along with this program. If not, see 028 * <http://www.gnu.org/licenses/lgpl-3.0.html>. 029 * #L% 030 */ 031 032import com.pi4j.platform.Platform; 033 034import java.io.File; 035import java.io.FileNotFoundException; 036import java.io.IOException; 037import java.io.InputStream; 038import java.net.URISyntaxException; 039import java.nio.file.Files; 040import java.nio.file.Path; 041import java.nio.file.Paths; 042import java.nio.file.StandardCopyOption; 043import java.util.Set; 044import java.util.TreeSet; 045import java.util.logging.ConsoleHandler; 046import java.util.logging.FileHandler; 047import java.util.logging.Level; 048import java.util.logging.Logger; 049 050public class NativeLibraryLoader { 051 052 private static final Set<String> loadedLibraries = new TreeSet<>(); 053 private static final Logger logger = Logger.getLogger(NativeLibraryLoader.class.getName()); 054 private static boolean initialized; 055 056 // private constructor 057 private NativeLibraryLoader() { 058 // forbid object construction 059 } 060 061 public static synchronized void load(String fileName, String libName) { 062 // check for debug property; if found enable all logging levels 063 if (!initialized) { 064 initialized = true; 065 if (System.getProperty("pi4j.debug") != null) { 066 logger.setLevel(Level.ALL); 067 try { 068 // create an appending file handler 069 FileHandler fileHandler = new FileHandler("pi4j.log"); 070 fileHandler.setLevel(Level.ALL); 071 ConsoleHandler consoleHandler = new ConsoleHandler(); 072 consoleHandler.setLevel(Level.ALL); 073 074 // add to the desired loggers 075 logger.addHandler(fileHandler); 076 logger.addHandler(consoleHandler); 077 } catch (IOException e) { 078 System.err.println("Unable to setup logging to debug. No logging will be done. Error: "); 079 e.printStackTrace(); 080 } 081 } 082 } 083 084 // first, make sure that this library has not already been previously loaded 085 if (loadedLibraries.contains(fileName)) { 086 logger.fine("Library [" + fileName + "] has already been loaded; no need to load again."); 087 return; 088 } 089 090 // cache loaded library 091 loadedLibraries.add(fileName); 092 093 // determine if there is an overriding library path defined for native libraries 094 String libPath = System.getProperty("pi4j.library.path"); 095 if(StringUtil.isNotNullOrEmpty(libPath, true)) { 096 097 // if the overriding library path is set to "system", then attempt to use the system resolved library paths 098 if (libPath.equalsIgnoreCase("system")) { 099 logger.fine("Attempting to load library using {pi4j.library.path} system resolved library name: [" + libName + "]"); 100 try { 101 // load library from JVM system library path; based on library name 102 System.loadLibrary(libName); 103 } catch (Exception ex) { 104 //throw this error 105 throw new UnsatisfiedLinkError("Pi4J was unable load the native library [" + 106 libName + "] from the system defined library path. The system property 'pi4j.library.path' is defined as [" + 107 libPath + "]. You can alternatively define the 'pi4j.library.path' " + 108 "system property to override this behavior and specify an absolute library path." + 109 "; UNDERLYING EXCEPTION: [" + ex.getClass().getName() + "]=" + ex.getMessage()); 110 } 111 } 112 113 // if the overriding library path is set to "local", then attempt to use the JAR local path to resolve library 114 else if (libPath.equalsIgnoreCase("local")) { 115 // get local directory path of JAR file 116 try { 117 libPath = NativeLibraryLoader.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath(); 118 } catch (URISyntaxException e) { 119 logger.severe(e.getMessage()); 120 libPath = "."; 121 } 122 // build path based on lib directory and lib filename 123 String path = Paths.get(libPath, fileName).toString(); 124 logger.fine("Attempting to load library using {pi4j.library.path} defined path: [" + path + "]"); 125 try { 126 // load library from local path of this JAR file 127 System.load(path); 128 } catch (Exception ex) { 129 //throw this error 130 throw new UnsatisfiedLinkError("Pi4J was unable load the native library [" + 131 libName + "] from the user defined library path. The system property 'pi4j.library.path' is defined as [" + 132 libPath + "]. Please make sure the defined the 'pi4j.library.path' " + 133 "system property contains the correct absolute library path." + 134 "; UNDERLYING EXCEPTION: [" + ex.getClass().getName() + "]=" + ex.getMessage()); 135 } 136 } 137 138 // if the overriding library path is set to something else, then attempt to use the defined path to resolve library 139 else { 140 // build path based on lib directory and lib filename 141 String path = Paths.get(libPath, fileName).toString(); 142 logger.fine("Attempting to load library using {pi4j.library.path} defined path: [" + path + "]"); 143 try { 144 // load library from user defined absolute path provided via pi4j.library.path} 145 System.load(path); 146 } catch (Exception ex) { 147 //throw this error 148 throw new UnsatisfiedLinkError("Pi4J was unable load the native library [" + 149 libName + "] from the user defined library path. The system property 'pi4j.library.path' is defined as [" + 150 libPath + "]. Please make sure the defined the 'pi4j.library.path' " + 151 "system property contains the correct absolute library path." + 152 "; UNDERLYING EXCEPTION: [" + ex.getClass().getName() + "]=" + ex.getMessage()); 153 } 154 } 155 } 156 // if there is no overriding library path defined, then attempt to load native library from embedded resource 157 else { 158 // 159 // path = /lib/{platform}/{linking:static|dynamic}/{filename} 160 // 161 String platform = System.getProperty("pi4j.platform", Platform.RASPBERRYPI.getId()); 162 163 // NOTE: As of 2018-04-23, Pi4J no longer includes 164 // a statically linked wiringPi lib for the Raspberry Pi platform. The 165 // default linking for the Raspberry Pi platform should always be "dynamic" 166 String linking = System.getProperty("pi4j.linking", 167 platform.equalsIgnoreCase(Platform.RASPBERRYPI.getId()) ? "dynamic" : "static"); 168 169 String path = "/lib/" + platform + "/" + linking + "/" + fileName; 170 logger.fine("Attempting to load [" + fileName + "] using path: [" + path + "]"); 171 try { 172 loadLibraryFromClasspath(path); 173 logger.fine("Library [" + fileName + "] loaded successfully using embedded resource file: [" + path + "]"); 174 } catch (Exception | UnsatisfiedLinkError e) { 175 logger.log(Level.SEVERE, "Unable to load [" + fileName + "] using path: [" + path + "]", e); 176 // either way, we did what we could, no need to remove now the library from the loaded libraries 177 // since we run inside one VM and nothing could possibly change, so there is no point in 178 // trying out this logic again 179 } 180 } 181 } 182 183 /** 184 * Loads library from classpath 185 * 186 * The file from classpath is copied into system temporary directory and then loaded. The temporary file is 187 * deleted after exiting. Method uses String as filename because the pathname is 188 * "abstract", not system-dependent. 189 * 190 * @param path 191 * The file path in classpath as an absolute path, e.g. /package/File.ext (could be inside jar) 192 * @throws IOException 193 * If temporary file creation or read/write operation fails 194 * @throws IllegalArgumentException 195 * If source file (param path) does not exist 196 * @throws IllegalArgumentException 197 * If the path is not absolute or if the filename is shorter than three characters (restriction 198 * of {@see File#createTempFile(java.lang.String, java.lang.String)}). 199 */ 200 public static void loadLibraryFromClasspath(String path) throws IOException { 201 Path inputPath = Paths.get(path); 202 203 if (!inputPath.isAbsolute()) { 204 throw new IllegalArgumentException("The path has to be absolute, but found: " + inputPath); 205 } 206 207 String fileNameFull = inputPath.getFileName().toString(); 208 int dotIndex = fileNameFull.indexOf('.'); 209 if (dotIndex < 0 || dotIndex >= fileNameFull.length() - 1) { 210 throw new IllegalArgumentException("The path has to end with a file name and extension, but found: " + fileNameFull); 211 } 212 213 String fileName = fileNameFull.substring(0, dotIndex); 214 String extension = fileNameFull.substring(dotIndex); 215 216 Path target = Files.createTempFile(fileName, extension); 217 File targetFile = target.toFile(); 218 targetFile.deleteOnExit(); 219 220 try (InputStream source = NativeLibraryLoader.class.getResourceAsStream(inputPath.toString())) { 221 if (source == null) { 222 throw new FileNotFoundException("File " + inputPath + " was not found in classpath."); 223 } 224 Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); 225 } 226 // Finally, load the library 227 System.load(target.toAbsolutePath().toString()); 228 } 229}