001 /******************************************************************************* 002 * Copyright (c) 2009 Progress Software, Inc. 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 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 String getPlatform() { 121 return getOperatingSystem()+getBitModel(); 122 } 123 124 protected 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 + "-" + version) ) 165 return; 166 if( load(errors, name ) ) 167 return; 168 169 170 /* Try extracting the library from the jar */ 171 if( classLoader!=null ) { 172 if( exractAndLoad(errors, version, customPath, getPlatformSpecifcResourcePath()) ) 173 return; 174 if( exractAndLoad(errors, version, customPath, getOperatingSystemSpecifcResourcePath()) ) 175 return; 176 // For the simpler case where only 1 platform lib is getting packed into the jar 177 if( exractAndLoad(errors, version, customPath, getResorucePath()) ) 178 return; 179 } 180 181 /* Failed to find the library */ 182 throw new UnsatisfiedLinkError("Could not load library. Reasons: " + errors.toString()); 183 } 184 185 final public String getOperatingSystemSpecifcResourcePath() { 186 return getPlatformSpecifcResourcePath(getOperatingSystem()); 187 } 188 final public String getPlatformSpecifcResourcePath() { 189 return getPlatformSpecifcResourcePath(getPlatform()); 190 } 191 final public String getPlatformSpecifcResourcePath(String platform) { 192 return "META-INF/native/"+platform+"/"+map(name); 193 } 194 195 final public String getResorucePath() { 196 return "META-INF/native/"+map(name); 197 } 198 199 final public String getLibraryFileName() { 200 return map(name); 201 } 202 203 204 private boolean exractAndLoad(ArrayList<String> errors, String version, String customPath, String resourcePath) { 205 URL resource = classLoader.getResource(resourcePath); 206 if( resource !=null ) { 207 208 String libName = name; 209 if( version !=null) { 210 libName += "-" + version; 211 } 212 213 if( customPath!=null ) { 214 // Try to extract it to the custom path... 215 File target = file(customPath, map(libName)); 216 if( extract(errors, resource, target) ) { 217 if( load(errors, target) ) { 218 return true; 219 } 220 } 221 } 222 223 // Fall back to extracting to the tmp dir 224 customPath = System.getProperty("java.io.tmpdir"); 225 File target = file(customPath, map(libName)); 226 if( extract(errors, resource, target) ) { 227 if( load(errors, target) ) { 228 return true; 229 } 230 } 231 } 232 return false; 233 } 234 235 private File file(String ...paths) { 236 File rc = null ; 237 for (String path : paths) { 238 if( rc == null ) { 239 rc = new File(path); 240 } else { 241 rc = new File(rc, path); 242 } 243 } 244 return rc; 245 } 246 247 private String map(String libName) { 248 /* 249 * libraries in the Macintosh use the extension .jnilib but the some 250 * VMs map to .dylib. 251 */ 252 libName = System.mapLibraryName(libName); 253 String ext = ".dylib"; 254 if (libName.endsWith(ext)) { 255 libName = libName.substring(0, libName.length() - ext.length()) + ".jnilib"; 256 } 257 return libName; 258 } 259 260 private boolean extract(ArrayList<String> errors, URL source, File target) { 261 FileOutputStream os = null; 262 InputStream is = null; 263 boolean extracting = false; 264 try { 265 if (!target.exists() || isStale(source, target) ) { 266 is = source.openStream(); 267 if (is != null) { 268 byte[] buffer = new byte[4096]; 269 os = new FileOutputStream(target); 270 extracting = true; 271 int read; 272 while ((read = is.read(buffer)) != -1) { 273 os.write(buffer, 0, read); 274 } 275 os.close(); 276 is.close(); 277 chmod("755", target); 278 } 279 } 280 } catch (Throwable e) { 281 try { 282 if (os != null) 283 os.close(); 284 } catch (IOException e1) { 285 } 286 try { 287 if (is != null) 288 is.close(); 289 } catch (IOException e1) { 290 } 291 if (extracting && target.exists()) 292 target.delete(); 293 errors.add(e.getMessage()); 294 return false; 295 } 296 return true; 297 } 298 299 private boolean isStale(URL source, File target) { 300 301 if( source.getProtocol().equals("jar") ) { 302 // unwrap the jar protocol... 303 try { 304 String parts[] = source.getFile().split(Pattern.quote("!")); 305 source = new URL(parts[0]); 306 } catch (MalformedURLException e) { 307 return false; 308 } 309 } 310 311 File sourceFile=null; 312 if( source.getProtocol().equals("file") ) { 313 sourceFile = new File(source.getFile()); 314 } 315 if( sourceFile!=null && sourceFile.exists() ) { 316 if( sourceFile.lastModified() > target.lastModified() ) { 317 return true; 318 } 319 } 320 return false; 321 } 322 323 private void chmod(String permision, File path) { 324 if (getPlatform().startsWith("windows")) 325 return; 326 try { 327 Runtime.getRuntime().exec(new String[] { "chmod", permision, path.getCanonicalPath() }).waitFor(); 328 } catch (Throwable e) { 329 } 330 } 331 332 private boolean load(ArrayList<String> errors, File lib) { 333 try { 334 System.load(lib.getPath()); 335 return true; 336 } catch (UnsatisfiedLinkError e) { 337 errors.add(e.getMessage()); 338 } 339 return false; 340 } 341 342 private boolean load(ArrayList<String> errors, String lib) { 343 try { 344 System.loadLibrary(lib); 345 return true; 346 } catch (UnsatisfiedLinkError e) { 347 errors.add(e.getMessage()); 348 } 349 return false; 350 } 351 352 }