001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package hivemall.xgboost.utils; 020 021import java.io.File; 022import java.io.FileInputStream; 023import java.io.FileOutputStream; 024import java.io.IOException; 025import java.io.InputStream; 026import java.io.OutputStream; 027import java.lang.reflect.Field; 028import java.util.UUID; 029 030import javax.annotation.Nonnull; 031 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034 035public final class NativeLibLoader { 036 private static final Log logger = LogFactory.getLog(NativeLibLoader.class); 037 038 private static final String keyUserDefinedLib = "hivemall.xgboost.lib"; 039 private static final String libPath = "/lib/"; 040 041 private static boolean initialized = false; 042 043 // Try to load a native library if it exists 044 public static synchronized void initXGBoost() { 045 if (!initialized) { 046 // Since a user-defined native library has a top priority, 047 // we first check if it is defined or not. 048 final String userDefinedLib = System.getProperty(keyUserDefinedLib); 049 if (userDefinedLib == null) { 050 tryLoadNativeLibFromResource("xgboost4j"); 051 } else { 052 tryLoadNativeLib(userDefinedLib); 053 } 054 initialized = true; 055 } 056 } 057 058 private static boolean hasResource(String path) { 059 return NativeLibLoader.class.getResource(path) != null; 060 } 061 062 @Nonnull 063 private static String getOSName() { 064 return System.getProperty("os.name"); 065 } 066 067 private static void tryLoadNativeLibFromResource(final String libName) { 068 // Resolve the library file name with a suffix (e.g., dll, .so, etc.) 069 String resolvedLibName = System.mapLibraryName(libName); 070 071 if (!hasResource(libPath + resolvedLibName)) { 072 if (!getOSName().equals("Mac")) { 073 return; 074 } 075 // Fix for openjdk7 for Mac 076 // A detail of this workaround can be found in https://github.com/xerial/snappy-java/issues/6 077 resolvedLibName = "lib" + libName + ".jnilib"; 078 if (hasResource(libPath + resolvedLibName)) { 079 return; 080 } 081 } 082 try { 083 File tempFile = createTempFileFromResource(resolvedLibName, 084 NativeLibLoader.class.getResourceAsStream(libPath + resolvedLibName)); 085 logger.info("Copyed the native library in JAR as " + tempFile.getAbsolutePath()); 086 addLibraryPath(tempFile.getParent()); 087 } catch (Exception e) { 088 // Simply ignore it here 089 logger.info(e.getMessage()); 090 } 091 } 092 093 private static void tryLoadNativeLib(final String userDefinedLib) { 094 final File userDefinedLibFile = new File(userDefinedLib); 095 if (!userDefinedLibFile.exists()) { 096 logger.warn(userDefinedLib + " not found"); 097 } else { 098 try { 099 File tempFile = createTempFileFromResource(userDefinedLibFile.getName(), 100 new FileInputStream(userDefinedLibFile.getAbsolutePath())); 101 logger.info( 102 "Copyed the user-defined native library as " + tempFile.getAbsolutePath()); 103 addLibraryPath(tempFile.getParent()); 104 } catch (Exception e) { 105 // Simply ignore it here 106 logger.warn(e.getMessage()); 107 } 108 } 109 } 110 111 @Nonnull 112 private static String getPrefix(@Nonnull String fileName) { 113 int point = fileName.lastIndexOf("."); 114 if (point != -1) { 115 return fileName.substring(0, point); 116 } 117 return fileName; 118 } 119 120 /** 121 * Create a temp file that copies the resource from current JAR archive. 122 * 123 * @param libName Library name with a suffix 124 * @param is Input stream to the native library 125 * @return The created temp file 126 * @throws IOException 127 * @throws IllegalArgumentException 128 */ 129 static File createTempFileFromResource(String libName, InputStream is) 130 throws IOException, IllegalArgumentException { 131 // Create a temporary folder with a random number for the native lib 132 final String uuid = UUID.randomUUID().toString(); 133 final File tempFolder = new File(System.getProperty("java.io.tmpdir"), 134 String.format("%s-%s", getPrefix(libName), uuid)); 135 if (!tempFolder.exists()) { 136 boolean created = tempFolder.mkdirs(); 137 if (!created) { 138 throw new IOException("Failed to create a temporary folder for the native lib"); 139 } 140 } 141 142 // Prepare buffer for data copying 143 final byte[] buffer = new byte[8192]; 144 int readBytes; 145 146 // Open output stream and copy the native library into the temporary one 147 File extractedLibFile = new File(tempFolder.getAbsolutePath(), libName); 148 final OutputStream os = new FileOutputStream(extractedLibFile); 149 try { 150 while ((readBytes = is.read(buffer)) != -1) { 151 os.write(buffer, 0, readBytes); 152 } 153 } finally { 154 // If read/write fails, close streams safely before throwing an exception 155 os.close(); 156 is.close(); 157 } 158 return extractedLibFile; 159 } 160 161 /** 162 * Add libPath to java.library.path, then native library in libPath would be load properly. 163 * 164 * @param libPath library path 165 * @throws IOException exception 166 */ 167 private static void addLibraryPath(String libPath) throws IOException { 168 try { 169 final Field field = ClassLoader.class.getDeclaredField("usr_paths"); 170 field.setAccessible(true); 171 final String[] paths = (String[]) field.get(null); 172 for (String path : paths) { 173 if (libPath.equals(path)) { 174 return; 175 } 176 } 177 final String[] tmp = new String[paths.length + 1]; 178 System.arraycopy(paths, 0, tmp, 0, paths.length); 179 tmp[paths.length] = libPath; 180 field.set(null, tmp); 181 } catch (IllegalAccessException e) { 182 logger.error(e.getMessage()); 183 throw new IOException("Failed to get permissions to set library path"); 184 } catch (NoSuchFieldException e) { 185 logger.error(e.getMessage()); 186 throw new IOException("Failed to get field handle to set library path"); 187 } 188 } 189 190}