View Javadoc

1   package org.rpi.os;
2   
3   import java.io.File;
4   import java.lang.reflect.Field;
5   import java.net.URL;
6   import java.security.AccessController;
7   import java.security.PrivilegedAction;
8   import java.util.ArrayList;
9   import java.util.Arrays;
10  import java.util.List;
11  
12  import org.apache.log4j.Logger;
13  import org.rpi.utils.Utils;
14  import com.pi4j.io.gpio.GpioController;
15  import net.xeoh.plugins.base.impl.PluginManagerFactory;
16  import net.xeoh.plugins.base.PluginManager;
17  
18  public class OSManager {
19  
20  	private static Logger log = Logger.getLogger(OSManager.class);
21  	private boolean bRaspi = false;
22  	private PluginManager pm = null;
23  	private boolean bUsedPi4J = false;
24  	private static OSManager instance = null;
25  
26  	private static final String OHNET_LIB_DIR = "/mediaplayer_lib/ohNet";
27  
28  	public static OSManager getInstance() {
29  		if (instance == null) {
30  			instance = new OSManager();
31  		}
32  		return instance;
33  	}
34  
35  	protected OSManager() {
36  		log.debug("Initializing OSManager");
37  		setJavaPath();
38  		if (isRaspi()) {
39  			log.debug("This is a Raspi so Attempt to initialize Pi4J");
40  			// initPi4J();
41  		}
42  	}
43  
44  	// private void initPi4J()
45  	// {
46  	// try
47  	// {
48  	// setGpio(GpioFactory.getInstance());
49  	// }
50  	// catch(Exception e)
51  	// {
52  	// log.error("Error Initializing Pi4J",e);
53  	// }
54  	// }
55  
56  	/**
57       * Add the library path to the ohNet.so file.
58       *
59  	 * Not clever enough to work out how to override ClassLoader functionality,
60  	 * so using this nice trick instead..
61  	 * 
62  	 * @param pathToAdd
63       *
64  	 * @throws NoSuchFieldException
65  	 * @throws SecurityException
66  	 * @throws IllegalArgumentException
67  	 * @throws IllegalAccessException
68  	 */
69  	public void addLibraryPath(String pathToAdd) throws Exception {
70  		log.debug("Adding Path: " + pathToAdd);
71  		Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths");
72  		usrPathsField.setAccessible(true);
73  
74  		String[] paths = (String[]) usrPathsField.get(null);
75  
76  		for (String path : paths)
77  			if (path.equals(pathToAdd))
78  				return;
79  
80  		String[] newPaths = Arrays.copyOf(paths, paths.length + 1);
81  		newPaths[newPaths.length - 1] = pathToAdd;
82  		usrPathsField.set(null, newPaths);
83  	}
84  
85  	/**
86  	 * Set the Path to the ohNetxx.so files
87  	 */
88  	private void setJavaPath() {
89          String fullPath = constructLibraryPath();
90          if (!Utils.isEmpty(fullPath)) {
91              try {
92                  addLibraryPath(fullPath);
93              } catch (Exception e) {
94                 log.error("Cannot add library path", e);
95              }
96          }
97  	}
98  
99      /**
100      * Constructs the additional library path.
101      *
102      * public to avoid problems for unit tests.
103      *
104      * @return
105      */
106     public String constructLibraryPath() {
107         String fullPath = null;
108 
109         String class_name = this.getClass().getName();
110         log.debug("Find Class, ClassName: " + class_name);
111         String path = getFilePath(this.getClass(), true);
112 
113         if (path.endsWith("/")) {
114             path = path.substring(0, (path.length() - 1));
115             log.debug("Path ended with '/'. Updated Path to be: " + path);
116         } else {
117             log.debug("Path did not end with '/': " + path);
118         }
119 
120         fullPath = path + getOhnetLibDir();
121 
122         log.warn("Using fullPath " + fullPath);
123 
124         return fullPath;
125     }
126 
127     /**
128      * retrieves the path suffix for the ohNet.so files (suffix in this case means all path elements inclusive
129      * /mediaplayer_lib).
130      *
131      * E.g. you will receive mediaplayer_lib/ohNet/linux/amd64, if you are running on an amd64 system.
132      *
133      * The default is OHNET_LIB_DIR + "/default". If your system is not recognized, you are still able to copy your
134      * applicable libs to this directory and use the mediaplayer.
135      *
136      * @return
137      */
138     public String getOhnetLibDir() {
139 
140         String path_suffix = OHNET_LIB_DIR + "/default";
141         log.debug("Path of this File is: " + path_suffix);
142 
143         String os = System.getProperty("os.name").toUpperCase();
144         log.debug("OS Name: " + os);
145 
146         if (os.startsWith("WINDOWS")) {
147             log.debug("Windows OS");
148             String osPathName = "windows";
149             String osArch = System.getProperty("os.arch");
150 
151             String architecture = "x86";
152             if (osArch.endsWith("64")) {
153                 architecture = "x64";
154             }
155 
156             path_suffix = OHNET_LIB_DIR + "/" + osPathName + "/" + architecture;
157         } else if (os.startsWith("LINUX")) {
158             String osPathName = "linux";
159 
160             String arch = System.getProperty("os.arch").toUpperCase();
161             if (arch.startsWith("ARM")) {
162                 String osArch = "arm";
163 
164                 log.debug("Its an ARM device, now check, which revision");
165                 try {
166                     String armVersion = getReadElfTag("Tag_CPU_arch");
167 
168                     if (armVersion == null) {
169                         log.error("Cannot determine ARM version...");
170                         osArch = "UNKNOWN";
171                     } else if (armVersion.equals("v5")) {
172                         osArch = osArch + "v5sf";
173                     } else if (armVersion.equals("v6")) {
174                         // we believe that a v6 arm is always a raspi (could
175                         // be a pogoplug...)
176                         log.debug("We think this is a Raspi");
177                         setRaspi(true);
178                         if (isHardFloat()) {
179                             osArch = osArch + "v6hf";
180                         } else {
181                             osArch = osArch + "v6sf";
182                         }
183                     } else if (armVersion.equals("v7")) {
184                         osArch = osArch + "v7";
185                     } else {
186                         log.error("Unknown ARM version...(" + armVersion + ")");
187                         osArch = "UNKNOWN";
188                     }
189 
190                     if (!osArch.equals("UNKNOWN"))  {
191                         path_suffix = OHNET_LIB_DIR + "/" + osPathName + "/" + osArch;
192                     }
193                 } catch (Exception e) {
194                     log.debug("Error Determining ARM OS Type: ", e);
195                 }
196             } else if (arch.startsWith("I386")) {
197                 String version = System.getProperty("os.version");
198                 log.debug("OS is Linux, and arch is  " + arch + ". Version is: " + version);
199                 path_suffix = OHNET_LIB_DIR + "/" + osPathName + "/x86";
200             } else if (arch.startsWith("AMD64")) {
201                 String version = System.getProperty("os.version");
202                 log.debug("OS is Linux, and arch is " + arch + ". Version is: " + version);
203                 path_suffix = OHNET_LIB_DIR + "/" + osPathName + "/amd64";
204             }
205         }
206 
207         return path_suffix;
208     }
209 
210 	/***
211 	 * Get the Path of this ClassFile and/or the path of the current JAR, which should be basically the same! No?
212 	 * Returned to the old method because the new method did not work when a plugin requested the path.
213 	 * Getting LCD.xml from Directory: /C:/Keep/git/repository/MediaPlayer/com.upnp.mediaplayer/bin
214 	 * Instead of
215 	 * /C:/Keep/git/repository/MediaPlayer/com.upnp.mediaplayer/bin/org/rpi/plugin/lcddisplay/
216      *
217      * For unit testing or for other special circumstances a system property "mediaplayer.core.home"
218      * variable is used. If this variable is set, the value of this variable is returned instead of the
219      * path of the current class.
220      *
221 	 * @return
222 	 */
223 //	public synchronized String getFilePath(Class mClass, boolean bUseFullNamePath) {
224 //        String path = mClass.getProtectionDomain().getCodeSource().getLocation().getPath();
225 //        String retValue = path.substring(0, path.lastIndexOf("/"));
226 //        log.debug("Returning Path of Class: " + mClass.getName() + " " + retValue);
227 //        return retValue;
228 //	}
229 	
230 	public synchronized String getFilePath(Class mClass, boolean bUseFullNamePath) {
231         // read core.home environment variable
232         String home = (String)System.getProperties().get("mediaplayer.core.home");
233         if (!Utils.isEmpty(home)) {
234             return home;
235         }
236 
237 		String className = mClass.getName();
238 		if (!className.startsWith("/")) {
239 			className = "/" + className;
240 		}
241 		className = className.replace('.', '/');
242 		className = className + ".class";
243 		log.debug("Find Class, Full ClassName: " + className);
244 		String[] splits = className.split("/");
245 		String properName = splits[splits.length - 1];
246 		log.debug("Find Class, Proper ClassName: " + properName);
247 		URL classUrl = mClass.getResource(className);
248 		if (classUrl != null) {
249 			String temp = classUrl.getFile();
250 			log.debug("Find Class, ClassURL: " + temp);
251 			if (temp.startsWith("file:")) {
252 				temp = temp.substring(5);
253 			}
254 
255 			if (temp.toUpperCase().contains(".JAR!")) {
256 				log.debug("Find Class, This is a JarFile: " + temp);
257 				String[] parts = temp.split("/");
258 				String jar_path = "";
259 				for (String part : parts) {
260 					if (!part.toUpperCase().endsWith(".JAR!")) {
261 						jar_path += part + "/";
262 					} else {
263 						log.debug("Find File: Returning JarPath: " + jar_path);
264 						return jar_path;
265 					}
266 				}
267 			} else {
268 				log.debug("Find Class, This is NOT a Jar File: " + temp);
269 				if (temp.endsWith(className)) {
270 					if (bUseFullNamePath) {
271 						temp = temp.substring(0, (temp.length() - className.length()));
272 					} else {
273 						temp = temp.substring(0, (temp.length() - properName.length()));
274 					}
275 				}
276 			}
277 			log.debug("Find File: Returning FilePath: " + temp);
278 			return temp;
279 		} else {
280 			log.debug("Find Class, URL Not Found");
281 			return "\nClass '" + className + "' not found in \n'" + System.getProperty("java.class.path") + "'";
282 		}
283 	}
284 
285 	/***
286 	 * Load the Plugins
287 	 */
288 	public void loadPlugins() {
289 		try {
290 			log.info("Start of LoadPlugins");
291 			pm = PluginManagerFactory.createPluginManager();
292 			List<File> files = listFiles("plugins");
293 			if (files == null)
294 				return;
295 			for (File file : files) {
296 				try {
297 					if (file.getName().toUpperCase().endsWith(".JAR")) {
298 						log.debug("Attempt to Load Plugin: " + file.getName() + " " + file.toURI());
299 						pm.addPluginsFrom(file.toURI());
300 					}
301 				} catch (Exception e) {
302 					log.error("Unable to load Plugins", e);
303 				}
304 			}
305 			log.info("End of LoadPlugins");
306 		} catch (Exception e) {
307 			log.error("Error Loading Plugins");
308 		}
309 	}
310 
311 	/***
312 	 * List all the files in this directory and sub directories.
313 	 * 
314 	 * @param directoryName
315 	 * @return
316 	 */
317 	private List<File> listFiles(String directoryName) {
318 		File directory = new File(directoryName);
319 		List<File> resultList = new ArrayList<File>();
320 		File[] fList = directory.listFiles();
321 		if (fList == null)
322 			return resultList;
323 		resultList.addAll(Arrays.asList(fList));
324 		for (File file : fList) {
325 			if (file.isFile()) {
326 			} else if (file.isDirectory()) {
327 				resultList.addAll(listFiles(file.getAbsolutePath()));
328 			}
329 		}
330 		return resultList;
331 	}
332 
333 	/**
334 	 * Is this a Raspberry Pi
335 	 * 
336 	 * @return
337 	 */
338 	public boolean isRaspi() {
339 		return bRaspi;
340 	}
341 
342 	private void setRaspi(boolean bRaspi) {
343 		this.bRaspi = bRaspi;
344 	}
345 
346     /**
347 	 * Is this a SoftFloat Raspberry Pi
348 	 * 
349 	 * @return
350 	 */
351 	public boolean isSoftFloat() {
352 		return !isHardFloat();
353 	}
354 
355 	/**
356 	 * Tidy up..
357 	 */
358 	public void dispose() {
359 		try {
360 			if (pm != null) {
361 				pm.shutdown();
362 			}
363 		} catch (Exception e) {
364 			log.error("Error closing PluginManager", e);
365 		}
366 		try {
367 			if (bUsedPi4J)
368 				Pi4JManager.getInstance().dispose();
369 		} catch (Exception e) {
370 			log.error("Error closing pi4j", e);
371 		}
372 	}
373 
374 	public GpioController getGpio() {
375 		bUsedPi4J = true;
376 		return Pi4JManager.getInstance().getGpio();
377 	}
378 
379 // the following is taken fully from pi4j (https://github.com/Pi4J/pi4j/blob/master/pi4j-core/src/main/java/com/pi4j/system/SystemInfo.java)
380 // we should get rid of this dependency, but right now it does work nicely
381 
382     /*
383      * this method was partially derived from :: (project) jogamp / (developer) sgothel
384      * https://github.com/sgothel/gluegen/blob/master/src/java/jogamp/common/os/PlatformPropsImpl.java#L160
385      * https://github.com/sgothel/gluegen/blob/master/LICENSE.txt
386      *
387      */
388     public  boolean isHardFloat() {
389 
390         return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
391             ArrayList<String> gnueabihf = new ArrayList<String>();
392            public Boolean run() {
393            	gnueabihf.add("gnueabihf");
394            	gnueabihf.add("armhf");
395                if (Utils.containsString(System.getProperty("sun.boot.library.path"), gnueabihf) ||
396                		Utils.containsString(System.getProperty("java.library.path"), gnueabihf) ||
397                		Utils.containsString(System.getProperty("java.home"), gnueabihf) ||
398                        getBashVersionInfo().contains("gnueabihf") ||
399                        hasReadElfTag("Tag_ABI_HardFP_use")) {
400             	   log.debug("This is a HardFloat");
401                    return true; //
402                }
403                log.debug("This is a HardFloat");
404                return false;
405            }
406        });
407     }
408     
409 
410 
411     /*
412      * taken from https://github.com/Pi4J/pi4j/blob/master/pi4j-core/src/main/java/com/pi4j/system/SystemInfo.java
413      *
414      * this method will to obtain the version info string from the 'bash' program
415      * (this method is used to help determine the HARD-FLOAT / SOFT-FLOAT ABI of the system)
416      */
417     private static String getBashVersionInfo() {
418         String versionInfo = "";
419         try {
420             String result[] = Utils.execute("bash --version");
421             for(String line : result) {
422                 if(!line.isEmpty()) {
423                     versionInfo = line; // return only first output line of version info
424                     break;
425                 }
426             }
427         }
428         catch(Exception e)
429         {
430         	log.error("Error Executing bash --version", e);
431         }
432         //catch (IOException ioe) { ioe.printStackTrace(); }
433         //catch (InterruptedException ie) { ie.printStackTrace(); }
434         return versionInfo;
435     }
436 
437     /*
438      * this method will determine if a specified tag exists from the elf info in the '/proc/self/exe' program
439      * (this method is used to help determine the HARD-FLOAT / SOFT-FLOAT ABI of the system)
440      */
441     private static boolean hasReadElfTag(String tag) {
442         String tagValue = getReadElfTag(tag);
443         if(tagValue != null && !tagValue.isEmpty())
444             return true;
445         return false;
446     }
447 
448     /*
449      * this method will obtain a specified tag value from the elf info in the '/proc/self/exe' program
450      * (this method is used to help determine the HARD-FLOAT / SOFT-FLOAT ABI of the system)
451      */
452     private static String getReadElfTag(String tag) {
453         String tagValue = null;
454         try {
455             String result[] = Utils.execute("/usr/bin/readelf -A /proc/self/exe");
456             if(result != null){
457                 for(String line : result) {
458                     line = line.trim();
459                     if (line.startsWith(tag) && line.contains(":")) {
460                         String lineParts[] = line.split(":", 2);
461                         if(lineParts.length > 1)
462                             tagValue = lineParts[1].trim();
463                         break;
464                     }
465                 }
466             }
467         }
468         catch(Exception e)
469         {
470         	log.error("IOException during readelf operation", e);
471         }
472         //catch (IOException ioe) {
473         //    log.error("IOException during readelf operation", ioe);
474         //}
475         //catch (InterruptedException ie) {
476         //    log.error("InterruptedEx during readelf operation", ie);
477 
478         //}
479         return tagValue;
480     }
481 
482 
483 }