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 }