001 /******************************************************************************* 002 * Copyright (C) 2009-2011 FuseSource Corp. 003 * Copyright (c) 2000, 2009 IBM Corporation and others. 004 * 005 * All rights reserved. This program and the accompanying materials 006 * are made available under the terms of the Eclipse Public License v1.0 007 * which accompanies this distribution, and is available at 008 * http://www.eclipse.org/legal/epl-v10.html 009 *******************************************************************************/ 010 package org.fusesource.hawtjni.runtime; 011 012 import java.io.File; 013 import java.io.FileOutputStream; 014 import java.io.IOException; 015 import java.io.InputStream; 016 import java.net.MalformedURLException; 017 import java.net.URL; 018 import java.util.ArrayList; 019 import java.util.regex.Pattern; 020 021 /** 022 * Used to optionally extract and load a JNI library. 023 * 024 * It will search for the library in order at the following locations: 025 * <ol> 026 * <li> in the custom library path: If the "library.${name}.path" System property is set to a directory 027 * <ol> 028 * <li> "${name}-${version}" if the version can be determined. 029 * <li> "${name}" 030 * </ol> 031 * <li> system library path: This is where the JVM looks for JNI libraries by default. 032 * <ol> 033 * <li> "${name}-${version}" if the version can be determined. 034 * <li> "${name}" 035 * </ol> 036 * <li> classpath path: If the JNI library can be found on the classpath, it will get extracted 037 * and and then loaded. This way you can embed your JNI libraries into your packaged JAR files. 038 * They are looked up as resources in this order: 039 * <ol> 040 * <li> "META-INF/native/${platform}/${library}" : Store your library here if you want to embed more 041 * than one platform JNI library in the jar. 042 * <li> "META-INF/native/${library}": Store your library here if your JAR is only going to embedding one 043 * platform library. 044 * </ol> 045 * The file extraction is attempted until it succeeds in the following directories. 046 * <ol> 047 * <li> The directory pointed to by the "library.${name}.path" System property (if set) 048 * <li> a temporary directory (uses the "java.io.tmpdir" System property) 049 * </ol> 050 * </ol> 051 * 052 * where: 053 * <ul> 054 * <li>"${name}" is the name of library 055 * <li>"${version}" is the value of "library.${name}.version" System property if set. 056 * Otherwise it is set to the ImplementationVersion property of the JAR's Manifest</li> 057 * <li>"${os}" is your operating system, for example "osx", "linux", or "windows"</li> 058 * <li>"${bit-model}" is "64" if the JVM process is a 64 bit process, otherwise it's "32" if the 059 * JVM is a 32 bit process</li> 060 * <li>"${platform}" is "${os}${bit-model}", for example "linux32" or "osx64" </li> 061 * <li>"${library}": is the normal jni library name for the platform. For example "${name}.dll" on 062 * windows, "lib${name}.jnilib" on OS X, and "lib${name}.so" on linux</li> 063 * </ul> 064 * 065 * @author <a href="http://hiramchirino.com">Hiram Chirino</a> 066 */ 067 public class Library { 068 069 static final String SLASH = System.getProperty("file.separator"); 070 071 final private String name; 072 final private String version; 073 final private ClassLoader classLoader; 074 private boolean loaded; 075 076 public Library(String name) { 077 this(name, null, null); 078 } 079 080 public Library(String name, Class<?> clazz) { 081 this(name, version(clazz), clazz.getClassLoader()); 082 } 083 084 public Library(String name, String version) { 085 this(name, version, null); 086 } 087 088 public Library(String name, String version, ClassLoader classLoader) { 089 if( name == null ) { 090 throw new IllegalArgumentException("name cannot be null"); 091 } 092 this.name = name; 093 this.version = version; 094 this.classLoader= classLoader; 095 } 096 097 private static String version(Class<?> clazz) { 098 try { 099 return clazz.getPackage().getImplementationVersion(); 100 } catch (Throwable e) { 101 } 102 return null; 103 } 104 105 public static String getOperatingSystem() { 106 String name = System.getProperty("os.name").toLowerCase().trim(); 107 if( name.startsWith("linux") ) { 108 return "linux"; 109 } 110 if( name.startsWith("mac os x") ) { 111 return "osx"; 112 } 113 if( name.startsWith("win") ) { 114 return "windows"; 115 } 116 return name.replaceAll("\\W+", "_"); 117 118 } 119 120 public static String getPlatform() { 121 return getOperatingSystem()+getBitModel(); 122 } 123 124 public static int getBitModel() { 125 String prop = System.getProperty("sun.arch.data.model"); 126 if (prop == null) { 127 prop = System.getProperty("com.ibm.vm.bitmode"); 128 } 129 if( prop!=null ) { 130 return Integer.parseInt(prop); 131 } 132 return -1; // we don't know.. 133 } 134 135 /** 136 * 137 */ 138 synchronized public void load() { 139 if( loaded ) { 140 return; 141 } 142 doLoad(); 143 loaded = true; 144 } 145 146 private void doLoad() { 147 /* Perhaps a custom version is specified */ 148 String version = System.getProperty("library."+name+".version"); 149 if (version == null) { 150 version = this.version; 151 } 152 ArrayList<String> errors = new ArrayList<String>(); 153 154 /* Try loading library from a custom library path */ 155 String customPath = System.getProperty("library."+name+".path"); 156 if (customPath != null) { 157 if( version!=null && load(errors, file(customPath, map(name + "-" + version))) ) 158 return; 159 if( load(errors, file(customPath, map(name))) ) 160 return; 161 } 162 163 /* Try loading library from java library path */ 164 if( version!=null && load(errors, name + getBitModel() + "-" + version) ) 165 return; 166 if( version!=null && load(errors, name + "-" + version) ) 167 return; 168 if( load(errors, name ) ) 169 return; 170 171 172 /* Try extracting the library from the jar */ 173 if( classLoader!=null ) { 174 if( exractAndLoad(errors, version, customPath, getPlatformSpecifcResourcePath()) ) 175 return; 176 if( exractAndLoad(errors, version, customPath, getOperatingSystemSpecifcResourcePath()) ) 177 return; 178 // For the simpler case where only 1 platform lib is getting packed into the jar 179 if( exractAndLoad(errors, version, customPath, getResorucePath()) ) 180 return; 181 } 182 183 /* Failed to find the library */ 184 throw new UnsatisfiedLinkError("Could not load library. Reasons: " + errors.toString()); 185 } 186 187 final public String getOperatingSystemSpecifcResourcePath() { 188 return getPlatformSpecifcResourcePath(getOperatingSystem()); 189 } 190 final public String getPlatformSpecifcResourcePath() { 191 return getPlatformSpecifcResourcePath(getPlatform()); 192 } 193 final public String getPlatformSpecifcResourcePath(String platform) { 194 return "META-INF/native/"+platform+"/"+map(name); 195 } 196 197 final public String getResorucePath() { 198 return "META-INF/native/"+map(name); 199 } 200 201 final public String getLibraryFileName() { 202 return map(name); 203 } 204 205 206 private boolean exractAndLoad(ArrayList<String> errors, String version, String customPath, String resourcePath) { 207 URL resource = classLoader.getResource(resourcePath); 208 if( resource !=null ) { 209 210 String libName = name + "-" + getBitModel(); 211 if( version !=null) { 212 libName += "-" + version; 213 } 214 215 if( customPath!=null ) { 216 // Try to extract it to the custom path... 217 File target = file(customPath, map(libName)); 218 if( extract(errors, resource, target) ) { 219 if( load(errors, target) ) { 220 return true; 221 } 222 } 223 } 224 225 // Fall back to extracting to the tmp dir 226 customPath = System.getProperty("java.io.tmpdir"); 227 File target = file(customPath, map(libName)); 228 if( extract(errors, resource, target) ) { 229 if( load(errors, target) ) { 230 return true; 231 } 232 } 233 } 234 return false; 235 } 236 237 private File file(String ...paths) { 238 File rc = null ; 239 for (String path : paths) { 240 if( rc == null ) { 241 rc = new File(path); 242 } else { 243 rc = new File(rc, path); 244 } 245 } 246 return rc; 247 } 248 249 private String map(String libName) { 250 /* 251 * libraries in the Macintosh use the extension .jnilib but the some 252 * VMs map to .dylib. 253 */ 254 libName = System.mapLibraryName(libName); 255 String ext = ".dylib"; 256 if (libName.endsWith(ext)) { 257 libName = libName.substring(0, libName.length() - ext.length()) + ".jnilib"; 258 } 259 return libName; 260 } 261 262 private boolean extract(ArrayList<String> errors, URL source, File target) { 263 FileOutputStream os = null; 264 InputStream is = null; 265 boolean extracting = false; 266 try { 267 if (!target.exists() || isStale(source, target) ) { 268 is = source.openStream(); 269 if (is != null) { 270 byte[] buffer = new byte[4096]; 271 os = new FileOutputStream(target); 272 extracting = true; 273 int read; 274 while ((read = is.read(buffer)) != -1) { 275 os.write(buffer, 0, read); 276 } 277 os.close(); 278 is.close(); 279 chmod("755", target); 280 } 281 } 282 } catch (Throwable e) { 283 try { 284 if (os != null) 285 os.close(); 286 } catch (IOException e1) { 287 } 288 try { 289 if (is != null) 290 is.close(); 291 } catch (IOException e1) { 292 } 293 if (extracting && target.exists()) 294 target.delete(); 295 errors.add(e.getMessage()); 296 return false; 297 } 298 return true; 299 } 300 301 private boolean isStale(URL source, File target) { 302 303 if( source.getProtocol().equals("jar") ) { 304 // unwrap the jar protocol... 305 try { 306 String parts[] = source.getFile().split(Pattern.quote("!")); 307 source = new URL(parts[0]); 308 } catch (MalformedURLException e) { 309 return false; 310 } 311 } 312 313 File sourceFile=null; 314 if( source.getProtocol().equals("file") ) { 315 sourceFile = new File(source.getFile()); 316 } 317 if( sourceFile!=null && sourceFile.exists() ) { 318 if( sourceFile.lastModified() > target.lastModified() ) { 319 return true; 320 } 321 } 322 return false; 323 } 324 325 private void chmod(String permision, File path) { 326 if (getPlatform().startsWith("windows")) 327 return; 328 try { 329 Runtime.getRuntime().exec(new String[] { "chmod", permision, path.getCanonicalPath() }).waitFor(); 330 } catch (Throwable e) { 331 } 332 } 333 334 private boolean load(ArrayList<String> errors, File lib) { 335 try { 336 System.load(lib.getPath()); 337 return true; 338 } catch (UnsatisfiedLinkError e) { 339 errors.add(e.getMessage()); 340 } 341 return false; 342 } 343 344 private boolean load(ArrayList<String> errors, String lib) { 345 try { 346 System.loadLibrary(lib); 347 return true; 348 } catch (UnsatisfiedLinkError e) { 349 errors.add(e.getMessage()); 350 } 351 return false; 352 } 353 354 }