/*
* RHQ Management Platform
* Copyright (C) 2005-2014 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.rhq.core.system;
import java.io.File;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.template.TemplateEngine;
import org.rhq.core.util.exception.ThrowableUtil;
/**
* Builds {@link SystemInfo} objects based on the native operating system the VM is running on.
*
* @author John Mazzitelli
*/
public class SystemInfoFactory {
/**
* All template engine replacement variables that this factory's template engine will know about
* will be prefixed with this string.
*/
public static final String TOKEN_PREFIX = "rhq.system.";
private static final Log LOG = LogFactory.getLog(SystemInfoFactory.class);
private static final String NATIVE_LIBRARY_CLASS_NAME = "org.hyperic.sigar.Sigar";
private static boolean nativeLibraryLoadable;
private static Throwable nativeLibraryLoadThrowable;
private static boolean disabled;
private static boolean initialized = false;
private static final ThreadFactory threadFactory = new ThreadFactory() {
final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
// There's no need to block the JVM while waiting for a process completion or pumping process streams
t.setDaemon(true);
t.setName("systeminfo-" + threadNumber.getAndIncrement());
return t;
}
};
/**
* Used to execute tasks watching for process completion or pumping process streams within the plugin container.
* This should be 'final' but shutdown() is not really final either.
*/
private static ExecutorService executor = Executors.newCachedThreadPool(threadFactory);
private static SystemInfo javaSystemInfo = new JavaSystemInfo(executor);
private static SystemInfo cachedSystemInfo;
// to avoid having this class need the native JNI classes at compile or load time, we'll build up
// the small amount of methods/constants we need to call via reflection and store them here
private static Map<NativeApi, AccessibleObject> nativeApis = new HashMap<NativeApi, AccessibleObject>();
// these are keys to the nativeApis map - they are names of SIGAR's static methods and constants we'll be calling
private enum NativeApi {
load, VERSION_STRING, BUILD_DATE, NATIVE_VERSION_STRING, NATIVE_BUILD_DATE
}
/**
* If the native system is both {@link #isNativeSystemInfoAvailable() available} and
* {@link #isNativeSystemInfoDisabled() enabled}, this will return the native system's version string. Otherwise, a
* generic Java version message is returned.
*
* @return native system version string
*/
public static synchronized String getNativeSystemInfoVersion() {
String version = null;
Throwable error = null;
initialize(); // make sure we've loaded the native libraries, if appropriate
if (!isNativeSystemInfoDisabled() && isNativeSystemInfoAvailable()) {
try {
version = "Version=" + invokeApi(NativeApi.VERSION_STRING) + " (" + invokeApi(NativeApi.BUILD_DATE)
+ "); Native version=" + invokeApi(NativeApi.NATIVE_VERSION_STRING) + " ("
+ invokeApi(NativeApi.NATIVE_BUILD_DATE) + ")";
} catch (Throwable t) {
error = t;
}
}
if (version == null) {
version = "Native system not supported - Java version is " + System.getProperty("java.version");
if (error != null) {
version += " : " + error;
}
}
return version;
}
/**
* This will tell the factory to not {@link #createSystemInfo() create} any native objects and to not load the
* native libraries - even if the native library {@link #isNativeSystemInfoAvailable() is available}.
*/
public static synchronized void disableNativeSystemInfo() {
// if we are switching, clear the cached system info so we'll get a new one later
if (!disabled) {
cachedSystemInfo = null;
}
disabled = true;
}
/**
* This will allow the factory to load the native libraries and {@link #createSystemInfo() create} native objects.
* Note that this will have no effect if there are no native libraries
* {@link #isNativeSystemInfoAvailable() available}.
*/
public static synchronized void enableNativeSystemInfo() {
// if we are switching, clear the cached system info so we'll get a new one later
if (disabled) {
cachedSystemInfo = null;
}
disabled = false;
}
/**
* Returns <code>true</code> if this factory was told to {@link #disableNativeSystemInfo() disable} the native
* layer. This only indicates if it was disabled; this has nothing to do with whether or not the native libraries
* {@link #isNativeSystemInfoAvailable() actually exist and are available}.
*
* @return <code>true</code> if the native layer is disabled
*/
public static synchronized boolean isNativeSystemInfoDisabled() {
return disabled;
}
/**
* If there is a native library available for the JVM's platform/operating system, <code>true</code> is returned. If
* the JVM's platform does not have native libraries available, <code>false</code> is returned (in which case, the
* only {@link SystemInfo} implementation that is available is {@link JavaSystemInfo}).
*
* @return <code>true</code> if there are native library APIs available for the JVM platform
* @see JavaSystemInfo
*/
public static boolean isNativeSystemInfoAvailable() {
initialize();
return nativeLibraryLoadable;
}
/**
* Returns a Throwable describing why the native library failed to initialize, or null if the native library
* initialized successfully
*
* @return a Throwable describing why the native library failed to initialize, or null if the native library
* initialized successfully
*/
public static Throwable getNativeLibraryLoadThrowable() {
return nativeLibraryLoadThrowable;
}
/**
* This returns <code>true</code> iff the native libraries have actually been initialized. This will return <code>
* false</code> if this factory has been {@link #disableNativeSystemInfo() disabled} from the start (i.e. prior to
* the first call to {@link #createSystemInfo()} or {@link #getNativeSystemInfoVersion()}). It will also return
* <code>false</code> after the native libraries have been {@link #shutdown()}.
*
* @return <code>true</code> if the native libraries have been loaded and initialized
*/
public static synchronized boolean isNativeSystemInfoInitialized() {
return initialized;
}
/**
* Returns the appropriate {@link SystemInfo} implementation based on the platform/operating system the JVM is
* running on.
*
* @return a {@link NativeSystemInfo} implementation or a {@link JavaSystemInfo} if the native libraries are
* {@link #isNativeSystemInfoAvailable() not available for the platform} or have been
* {@link #disableNativeSystemInfo() disabled}.
*/
public static synchronized SystemInfo createSystemInfo() {
if (cachedSystemInfo == null) {
initialize(); // make sure we've loaded the native libraries, if appropriate
SystemInfo nativePlatform = null;
if (!isNativeSystemInfoDisabled() && isNativeSystemInfoAvailable()) {
// we could use SIGAR here, but this should be enough
if (System.getProperty("os.name").toLowerCase().indexOf("windows") > -1) {
nativePlatform = new WindowsNativeSystemInfo();
} else {
// we either don't know what OS it is or we don't have a specific native subclass for it;
// but we know we have a native library for it! so just create the generic NativePlatform to represent it.
nativePlatform = new NativeSystemInfo();
}
}
if (nativePlatform == null) {
nativePlatform = javaSystemInfo;
}
cachedSystemInfo = nativePlatform;
}
return cachedSystemInfo;
}
/**
* Under some circumstances, you may want to force this factory to provide a Java-only {@link SystemInfo}
* implementation, regardless of whether the factory has {@link #disableNativeSystemInfo() disabled} the native
* layer or not. In the cases when you absolutely, positively have to have a non-native, Java-only implementation,
* you can use this method.
*
* @return Java-only {@link SystemInfo} implementation
*/
public static SystemInfo createJavaSystemInfo() {
return javaSystemInfo;
}
/**
* This method is reserved for those objects (like shutdown hooks) that are aware of the VM going down - at which
* time it would be appropriate to give the native libraries a chance to clean up after themselves.
*/
public static synchronized void shutdown() {
// Unclear if this should be 'shutdownNow()' or not
// This is used to execute sub processes; so it seems polite to wait for completion
executor.shutdown();
// This is needed by tests
executor = Executors.newCachedThreadPool(threadFactory);
javaSystemInfo = new JavaSystemInfo(executor);
if (initialized) {
// initialized is only ever set to true if the native layer was actually initialized
// we don't want or need to check enabled/disabled here; we could have disabled after
// the native library was initialized
// Does not look like there are any static SIGAR methods that need to be called at shutdown;
// Sigar.finalize() takes care of instance cleanup
// try
// {
// Sigar.XXXshutdownXXX();
// }
// catch ( Throwable t )
// {
// }
initialized = false;
cachedSystemInfo = null;
}
}
/**
* This will initialize the native layer, if applicable. If the native layer was already initialized, this does
* nothing and returns immediately.
*/
private static synchronized void initialize() {
if (!initialized) {
if (!isNativeSystemInfoDisabled()) {
try {
Class<?> clazz = Class.forName(NATIVE_LIBRARY_CLASS_NAME);
nativeApis.put(NativeApi.load, clazz.getMethod(NativeApi.load.name(), new Class[0]));
nativeApis.put(NativeApi.VERSION_STRING, clazz.getField(NativeApi.VERSION_STRING.name()));
nativeApis.put(NativeApi.BUILD_DATE, clazz.getField(NativeApi.BUILD_DATE.name()));
nativeApis.put(NativeApi.NATIVE_VERSION_STRING, clazz.getField(NativeApi.NATIVE_VERSION_STRING.name()));
nativeApis.put(NativeApi.NATIVE_BUILD_DATE, clazz.getField(NativeApi.NATIVE_BUILD_DATE.name()));
invokeApi(NativeApi.load);
nativeLibraryLoadable = true;
initialized = true; // we only set this to true when we actually initialized the native layer
} catch (Throwable t) {
// We don't have the JNI classes (sigar.jar) and/or the native shared library (e.g. libsigar-amd64-linux.so).
// This might be expected (e.g. the admin console WAR (Embedded Jopr) does not include SIGAR), so don't log
// anything, but store the Throwable, so callers can log the cause when appropriate.
nativeLibraryLoadable = false;
nativeLibraryLoadThrowable = t;
LOG.warn("Native library not available on this platform: " + ThrowableUtil.getAllMessages(t));
LOG.debug("Native library failure stack trace follows: ", t);
disabled = true; // automatically disable native system info if the native library is not loadable
}
}
}
return;
}
/**
* In order for this class to not have any compile or load time dependencies on the SIGAR jar, use this method to
* invoke static methods on SIGAR's main class. This allows applications that want to use this factory but be able
* to work even if an exception occurs when loading SIGAR.
*
* @param api the static method to invoke or a constant name whose value is to be retrieved
* @param args optional set of arguments to pass to the static method (may be <code>null</code>)
* @return the return object of the call to the static method or the value of the static constant
* @throws Throwable if failed to invoke the static method or get the static constant value for any reason (Java JNI
* jar not available, native library not available, etc)
*/
private static final Object invokeApi(NativeApi api, Object... args) throws Throwable {
AccessibleObject accessibleObject = nativeApis.get(api);
if (accessibleObject instanceof Method) {
return ((Method) accessibleObject).invoke(null, args);
} else {
return ((Field) accessibleObject).get(null);
}
}
/**
* The native libraries are located under a root directory - this returns that root directory. It is assumed this
* root directory is located in the same directory where the SIGAR Java API jar file is found. This is probably not
* needed anymore. However, it may be useful in the future, so I'll leave it here. SIGAR has something else that is
* related - the system property "org.hyperic.sigar.path" points to the directory where the native libraries can be
* found.
*
* @return the root directory
* @throws Exception if the root directory could not be determined or does not exist
*/
private static String findNativeLibrariesRootDirectory() throws Exception {
String rootDir = null;
File jniLocation = null;
URL jarLocation;
if (isNativeSystemInfoAvailable()) {
rootDir = System.getProperty("rhq.native-libraries-root-directory");
if (rootDir == null) {
jarLocation = Class.forName(NATIVE_LIBRARY_CLASS_NAME).getProtectionDomain().getCodeSource()
.getLocation();
if (jarLocation != null) {
// the root directory where all JNI libraries are located is the
// same directory as where the SIGAR jar is found
jniLocation = new File(jarLocation.toURI()).getParentFile();
}
if ((jniLocation != null) && jniLocation.exists()) {
rootDir = jniLocation.getAbsolutePath();
} else {
throw new Exception("Native JNI libraries cannot be found: " + "jar-location=[" + jarLocation
+ "], jni-location=[" + jniLocation + "]");
}
}
}
LOG.debug("Root directory to the native library is: " + rootDir);
return rootDir;
}
/**
* Prevent instantiation.
*/
private SystemInfoFactory() {
}
public static TemplateEngine fetchTemplateEngine() {
try {
SystemInfo systemInfo = createSystemInfo();
Map<String, String> tokens = new HashMap<String, String>();
// support several standard Java system properties
String[] syspropsToSupport = new String[] { "java.io.tmpdir", "file.separator", "line.separator",
"path.separator", "java.home", "java.version", "user.timezone", "user.region", "user.country",
"user.language" };
for (String sysprop : syspropsToSupport) {
tokens.put(TOKEN_PREFIX + "sysprop." + sysprop, System.getProperty(sysprop, sysprop)); // default is the name itself, just to show it wasn't there
}
tokens.put(TOKEN_PREFIX + "hostname", systemInfo.getHostname());
tokens.put(TOKEN_PREFIX + "os.name", systemInfo.getOperatingSystemName());
tokens.put(TOKEN_PREFIX + "os.version", systemInfo.getOperatingSystemVersion());
tokens.put(TOKEN_PREFIX + "os.type", systemInfo.getOperatingSystemType().toString());
tokens.put(TOKEN_PREFIX + "architecture", systemInfo.getSystemArchitecture());
try {
tokens.put(TOKEN_PREFIX + "cpu.count", Integer.toString(systemInfo.getNumberOfCpus()));
} catch (Exception e) {
tokens.put(TOKEN_PREFIX + "cpu.count", "?");
}
List<NetworkAdapterInfo> allNetworkAdapters = null;
try {
allNetworkAdapters = systemInfo.getAllNetworkAdapters();
} catch (Exception e) {
}
if (allNetworkAdapters != null) {
for (NetworkAdapterInfo networkAdapter : allNetworkAdapters) {
String key = TOKEN_PREFIX + "interfaces." + networkAdapter.getName();
tokens.put(key + ".mac", networkAdapter.getMacAddressString());
tokens.put(key + ".type", networkAdapter.getType());
tokens.put(key + ".flags", networkAdapter.getAllFlags());
if (!networkAdapter.getUnicastAddresses().isEmpty()) {
tokens.put(key + ".address", networkAdapter.getUnicastAddresses().get(0).getHostAddress());
}
if (!networkAdapter.getMulticastAddresses().isEmpty()) {
tokens.put(key + ".multicast.address", networkAdapter.getMulticastAddresses().get(0)
.getHostAddress());
}
}
}
// create a base IP address - this one is known to java and should always exist no matter what platform we are on
try {
try {
tokens.put(TOKEN_PREFIX + "interfaces.java.address", InetAddress
.getByName(systemInfo.getHostname()).getHostAddress());
} catch (Exception e) {
tokens.put(TOKEN_PREFIX + "interfaces.java.address", InetAddress.getLocalHost().getHostAddress());
}
} catch (Exception e2) {
}
TemplateEngine templateEngine = new TemplateEngine(tokens);
return templateEngine;
} catch (Exception e) {
// some rare exception occurred that we didn't expect, rather than blow up entirely, just don't provide any values
return new TemplateEngine(new HashMap<String, String>());
}
}
}