/**
* NOTE: This copyright does *not* cover user programs that use Hyperic
* program services by normal system calls through the application
* program interfaces provided as part of the Hyperic Plug-in Development
* Kit or the Hyperic Client Development Kit - this is merely considered
* normal use of the program, and does *not* fall under the heading of
* "derived work".
*
* Copyright (C) [2004-2010], VMware, Inc.
* This file is part of Hyperic.
*
* Hyperic is free software; you can redistribute it and/or modify
* it under the terms version 2 of the GNU General Public License 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 for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*
*/
package org.hyperic.hq.product;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.hq.agent.AgentConfig;
import org.hyperic.hq.common.shared.ProductProperties;
import org.hyperic.hq.product.pluginxml.PluginData;
import org.hyperic.util.PluginLoader;
import org.hyperic.util.PluginLoaderException;
import org.hyperic.util.StringUtil;
import org.hyperic.util.config.ConfigResponse;
import org.hyperic.util.config.ConfigSchema;
import org.hyperic.util.security.MD5;
/**
* This class is a manager for ProductPlugin implementations and is also a
* manager of plugin managers.
*/
public class ProductPluginManager
extends PluginManager {
public static final String PROP_PDK_DIR = AgentConfig.PDK_DIR_KEY;
private static final String PROP_PDK_PLUGINS_DIR = AgentConfig.PDK_PLUGIN_DIR_KEY;
private static final String PROP_PDK_WORK_DIR = AgentConfig.PDK_WORK_DIR_KEY;
// this is really verbose and not very helpful
static final boolean DEBUG_LIFECYCLE = false;
private static final String PLUGIN_STUB_NAME = "org.hyperic.hq.product.ProductPluginXML";
private static final String PLUGIN_STUB = "org/hyperic/hq/product/ProductPluginXML.stub";
// absolute must-have
private static final String SYSTEM_PLUGIN = "system";
private static final String[] BASE_PLUGINS = { "netservices", // many
// plugins
// depend on
// this
"sqlquery", // for sql:
// metrics
};
// support plugins loaded before product plugins
private static final String[] PLUGIN_SUPPORT_DIRS = { "scripting" };
private boolean registerTypes = false;
private boolean client;
private HashMap<String, PluginManager> managers = new HashMap<String, PluginManager>();
private Map<String, Map<String, TypeInfo>> types = new HashMap<String, Map<String, TypeInfo>>();
private HashMap includePlugins = null;
private HashMap excludePlugins = null;
private Log log = LogFactory.getLog(this.getClass().getName());
private byte[] pluginStub = null;
private int pluginStubLength = 0;
private static final File HQ_DIR = new File(System.getProperty("user.home"), ".hq");
public static final File PLUGIN_PROPERTIES_FILE = new File(HQ_DIR, "plugin.properties");
public static final String PROPERTY_PREFIX = "hq.plugins.";
private static final Map<String, String> JAVA_VERSIONS = new HashMap<String, String>();
// java.class.version.major -> java.version
static {
JAVA_VERSIONS.put("48", "1.4");
JAVA_VERSIONS.put("49", "1.5");
JAVA_VERSIONS.put("50", "1.6");
JAVA_VERSIONS.put("51", "1.7");
}
private MeasurementPluginManager mpm;
private ControlPluginManager cpm;
private AutoinventoryPluginManager apm;
private RtPluginManager rpm;
private LogTrackPluginManager ltpm;
private ConfigTrackPluginManager ctpm;
private LiveDataPluginManager ldpm;
public ProductPluginManager() {
this(System.getProperties());
}
/**
* If true creates a mapping of the ProductPlugin TypeInfos, only needed on
* the server side.
*/
public void setRegisterTypes(boolean registerTypes) {
this.registerTypes = registerTypes;
}
public boolean getRegisterTypes() {
return this.registerTypes;
}
public static String getPropertyKey(String plugin, String key) {
return PROPERTY_PREFIX + plugin + "." + key;
}
// ignore failures. if you want to check for errors, do it yourself.
private static Properties getFileProperties(File file) {
Properties props = new Properties();
FileInputStream is = null;
try {
is = new FileInputStream(file);
props.load(is);
} catch (IOException e) {
} finally {
if (is != null) {
try {
is.close();
} catch (IOException ie) {
}
}
}
return props;
}
public ProductPluginManager(File file) {
this(getFileProperties(file));
}
public ProductPluginManager(Properties props) {
super(props);
}
public String getName() {
return ProductPlugin.TYPE_PRODUCT;
}
/**
* Derive plugin name from file name-plugin.ext
*/
public static String getNameFromFile(String file) {
String name = new File(file).getName();
int ix = name.indexOf("-plugin.");
if (ix != -1) {
return name.substring(0, ix);
} else {
return null;
}
}
// assumes type names are unique across plugins,
// which they should be. if needed we could index on
// product name too.
/**
* Find TypeInfo for the given platform and type name.
* @param platform The platform name, e.g. "Linux"
* @param name The type name, e.g. "Apache 2.0"
*/
public TypeInfo getTypeInfo(String platform, String name) {
Map<String, TypeInfo> platforms = this.types.get(platform);
if (platforms == null) {
return null;
}
return platforms.get(name);
}
/**
* Find TypeInfo's accross all platforms for the given type name.
* @param name The type name, e.g. "Apache 2.0"
* @return map<platformName, typeInfo> the type per platform
*/
public Map<String, TypeInfo> getTypeInfo(String typeName) {
Map<String, TypeInfo> typeInfos = Collections.emptyMap();
if((null != typeName) && (typeName.length() > 0)) {
typeInfos = new HashMap<String, TypeInfo>(10);
final Map<String, Map<String, TypeInfo>> types = getTypes();
for(final Entry<String, Map<String, TypeInfo>> platform:types.entrySet()) {
final TypeInfo typeInfo = platform.getValue().get(typeName);
if (null != typeInfo)
typeInfos.put(platform.getKey(), typeInfo);
}
}
return typeInfos;
}
public Map<String, Map<String, TypeInfo>> getTypes() {
return Collections.unmodifiableMap(types);
}
protected void setTypeInfo(String platform, String name, TypeInfo info) {
Map<String, TypeInfo> platforms = this.types.get(platform);
if (platforms == null) {
platforms = new HashMap<String, TypeInfo>();
this.types.put(platform, platforms);
}
platforms.put(name, info);
}
// XXX we could just cache lookups in getTypeInfo
// instead of mapping everything.
/**
* Create a mapping of product plugin TypeInfos.
* @see #getTypeInfo
* @see #registerPluginJar(String jarName)
*/
private void registerTypeInfo(TypeInfo[] types) {
if (types == null) {
return;
}
for (int i = 0; i < types.length; i++) {
TypeInfo type = types[i];
String[] platforms = type.getPlatformTypes();
for (int j = 0; j < platforms.length; j++) {
setTypeInfo(platforms[j], type.getName(), type);
}
}
}
private String[] getPluginNames(String plugins) {
if (plugins == null) {
return null;
}
List<String> names = StringUtil.explode(plugins, ",");
return names.toArray(new String[0]);
}
private void initPluginFilters() {
HashMap<String, Integer> basePlugins = new HashMap<String, Integer>();
basePlugins.put(SYSTEM_PLUGIN, // must-have
new Integer(TypeInfo.TYPE_PLATFORM));
String[] defaultPlugins;
String base = getProperty("plugins.base");
if (base == null) {
defaultPlugins = BASE_PLUGINS;
} else {
defaultPlugins = getPluginNames(base);
}
for (int i = 0; i < defaultPlugins.length; i++) {
basePlugins.put(defaultPlugins[i], new Integer(TypeInfo.TYPE_SERVER));
}
String include = getProperty("plugins.include");
String exclude = getProperty("plugins.exclude");
if ((include != null) && (exclude != null)) {
log.warn("plugins.{include,exclude} are both defined" + ", use one or the other.");
}
if (include != null) {
this.includePlugins = new HashMap();
String[] plugins = getPluginNames(include);
for (int i = 0; i < plugins.length; i++) {
this.includePlugins.put(plugins[i], Boolean.TRUE);
}
// must-haves
this.includePlugins.putAll(basePlugins);
}
if (exclude != null) {
this.excludePlugins = new HashMap();
String[] plugins = getPluginNames(exclude);
for (int i = 0; i < plugins.length; i++) {
String name = plugins[i];
if (basePlugins.get(name) == null) {
this.excludePlugins.put(name, Boolean.TRUE);
} else {
this.log.warn("Cannot exclude " + name + " plugin, ignoring.");
}
}
}
}
private void setSystemProperties() {
String pdk = getProperty(PROP_PDK_DIR);
String workDir = getProperty(PROP_PDK_WORK_DIR, pdk + "/" + AgentConfig.WORK_DIR);
String pluginsDir = getProperty(PROP_PDK_PLUGINS_DIR, pdk + "/plugins");
if (pdk != null) {
setPdkDir(pdk);
setPdkWorkDir(workDir);
setPdkPluginsDir(pluginsDir);
log.info(PROP_PDK_DIR + "=" + getPdkDir());
} else {
String tmp = System.getProperty("java.io.tmpdir");
File work = new File(tmp + "/pdk" + workDir);
setPdkWorkDir(work.getPath());
if (!work.exists()) {
work.mkdirs();
}
}
log.info(PROP_PDK_PLUGINS_DIR + "=" + getPdkPluginsDir());
log.info(PROP_PDK_WORK_DIR + "=" + getPdkWorkDir());
}
public static String getPdkDir() {
return System.getProperty(PROP_PDK_DIR);
}
public static void setPdkDir(String dir) {
System.setProperty(PROP_PDK_DIR, dir);
}
public static String getPdkPluginsDir() {
return System.getProperty(PROP_PDK_PLUGINS_DIR);
}
public static void setPdkPluginsDir(String dir) {
System.setProperty(PROP_PDK_PLUGINS_DIR, dir);
}
public static String getPdkWorkDir() {
return System.getProperty(PROP_PDK_WORK_DIR);
}
public static void setPdkWorkDir(String dir) {
System.setProperty(PROP_PDK_WORK_DIR, dir);
}
public void init() throws PluginException {
loadProductPluginStub();
super.init(null); // null == we dont have a parent manager
Properties props = getProperties();
props.putAll(ProductProperties.getProperties());
setSystemProperties();
// not the same as platform.fqdn
String name = props.getProperty(ProductPlugin.PROP_PLATFORM_NAME);
if (name != null) {
GenericPlugin.setPlatformName(name);
}
initPluginFilters();
String pdk = getPdkDir();
if (pdk != null) {
this.client = new File(pdk, "lib").exists();
}
if (this.client) {
log.debug("Initializing in client mode " + "(pdk=" + pdk + ")");
} else {
log.debug("Initializing in server mode");
}
initPluginManagers(props);
}
protected void initPluginManagers(Properties props) throws PluginException {
this.mpm = new MeasurementPluginManager(props);
this.cpm = new ControlPluginManager(props);
this.apm = new AutoinventoryPluginManager(props);
this.rpm = new RtPluginManager(props);
this.ltpm = new LogTrackPluginManager(props);
this.ctpm = new ConfigTrackPluginManager(props);
this.ldpm = new LiveDataPluginManager(props);
PluginManager[] mgrs = { this.mpm, this.cpm, this.apm, this.rpm, this.ltpm, this.ctpm, this.ldpm, this // note
// to
// self
};
for (int i = 0; i < mgrs.length; i++) {
PluginManager mgr = mgrs[i];
mgr.init(this);
this.managers.put(mgr.getName(), mgr);
if (!this.client || DEBUG_LIFECYCLE) {
log.debug(mgr.getName() + " plugins enabled=" + isPluginTypeEnabled(mgr.getName()));
}
}
// XXX by-passing server hot-deploy
String plugins = getPdkPluginsDir();
for (int i = 0; i < PLUGIN_SUPPORT_DIRS.length; i++) {
File dir = new File(plugins, PLUGIN_SUPPORT_DIRS[i]);
if (!dir.exists()) {
continue;
}
registerPlugins(dir.getPath(), null);
}
}
public Map<String, String> getPluginVersions(String pluginDir) {
Map<String, String> rtn = new HashMap<String, String>();
for (int i = 0; i < PLUGIN_SUPPORT_DIRS.length; i++) {
File dir = new File(pluginDir, PLUGIN_SUPPORT_DIRS[i]);
if (!dir.exists()) {
continue;
}
File[] pluginFiles = listPlugins(dir);
for (final File file : pluginFiles) {
rtn.put(file.toString(), MD5.getMD5Checksum(file));
}
}
return rtn;
}
public void shutdown() throws PluginException {
synchronized (managers) {
managers.remove(getName());
for (Map.Entry<String, PluginManager> entry : managers.entrySet()) {
PluginManager manager = entry.getValue();
try {
manager.shutdown();
} catch (PluginException e) {
log.error(manager.getName() + ".shutdown() failed", e);
}
}
this.types.clear();
this.managers.clear();
}
// shutdown() all registered ProductPlugins
super.shutdown();
}
public MeasurementPluginManager getMeasurementPluginManager() {
return this.mpm;
}
public ControlPluginManager getControlPluginManager() {
return this.cpm;
}
public AutoinventoryPluginManager getAutoinventoryPluginManager() {
return this.apm;
}
public RtPluginManager getRtPluginManager() {
return this.rpm;
}
public LogTrackPluginManager getLogTrackPluginManager() {
return this.ltpm;
}
public ConfigTrackPluginManager getConfigTrackPluginManager() {
return this.ctpm;
}
public LiveDataPluginManager getLiveDataPluginManager() {
return this.ldpm;
}
public MeasurementPlugin getMeasurementPlugin(String name) {
try {
return (MeasurementPlugin) this.mpm.getPlugin(name);
} catch (PluginNotFoundException e) {
log.debug("No MeasurementPlugin found for: " + name);
return null;
}
}
public ControlPlugin getControlPlugin(String name) {
try {
return (ControlPlugin) this.cpm.getPlugin(name);
} catch (PluginNotFoundException e) {
log.debug("No ControlPlugin found for: " + name);
return null;
}
}
public ServerDetector getAutoinventoryPlugin(String name) {
try {
return (ServerDetector) this.apm.getPlugin(name);
} catch (PluginNotFoundException e) {
log.debug("No AutoinventoryPlugin found for: " + name);
return null;
}
}
public RtPlugin getRtPlugin(String name) {
try {
return (RtPlugin) this.rpm.getPlugin(name);
} catch (PluginNotFoundException e) {
log.debug("No RtPlugin found for: " + name);
return null;
}
}
public ProductPlugin getProductPlugin(String name) {
try {
return (ProductPlugin) getPlugin(name);
} catch (PluginNotFoundException e) {
log.debug("No ProductPlugin found for: " + name);
return null;
}
}
/**
* Register a plugin with the given GenericPluginManger, one instance
* per-platform of server/service types.
* @param pm The plugin manager
* @param info Plugin info from the product plugin where this plugin is
* implemented.
* @param plugin The plugin instance to register.
* @param type The resource type info for this plugin.
* @param registerTypes If true registers a plugin instance for all platform
* types (server-side), otherwise the current platform only
* (agent-side).
* @throws PluginExistsException If an instance already exists with the same
* name in the given plugin manager.
*/
void registerTypePlugin(PluginManager pm, PluginInfo info, GenericPlugin plugin, TypeInfo type)
throws PluginExistsException {
boolean register = this.registerTypes;
boolean hasServer = false;
ServiceTypeInfo service;
ServerTypeInfo server = null;
String[] platforms = null;
String thisPlatform = HypericOperatingSystem.getInstance().getName();
String name = plugin.getName();
String skipMsg = null;
if (DEBUG_LIFECYCLE) {
skipMsg = "Skipping registration of '" + name + "' " + pm.getName() + " plugin on this platform";
}
switch (type.getType()) {
case TypeInfo.TYPE_SERVER:
server = (ServerTypeInfo) type;
hasServer = true;
break;
case TypeInfo.TYPE_SERVICE:
service = (ServiceTypeInfo) type;
server = service.getServerTypeInfo();
hasServer = true;
break;
case TypeInfo.TYPE_PLATFORM:
if (((PlatformTypeInfo) type).isDevice()) {
// always register devices, so they can be serviced by
// an agent on another platform
register = true;
}
if (!register && !thisPlatform.equals(name)) {
if (DEBUG_LIFECYCLE) {
log.trace(skipMsg);
}
return;
}
default:
break;
}
if (hasServer) {
if (server.isPlatformDevice()) {
// always register server types on platform devices
register = true;
}
String[] validPlatforms = server.getValidPlatformTypes();
if (register) {
platforms = validPlatforms;
} else {
if (Arrays.asList(validPlatforms).contains(thisPlatform)) {
platforms = new String[] { thisPlatform };
} else {
if (DEBUG_LIFECYCLE) {
log.trace(skipMsg + ", validPlatforms=" + Arrays.asList(validPlatforms));
}
return;
}
}
}
try {
// XXX in the case of server/service type plugins
// if there are to TypeInfos defined with the same
// name but different platforms, first one wins
// here. this should just be for temporary compat,
// until subsystems include platform name when
// looking up a server/service type plugin.
PluginInfo gInfo = new PluginInfo(name, info);
pm.setPluginInfo(name, gInfo);
pm.registerPlugin(plugin, null);
} catch (PluginExistsException e) {
if (!hasServer) {
throw e;
}
} catch (PluginException e) {
this.log.error("registerPlugin=" + plugin.getName(), e);
}
if (!hasServer) {
return;
}
// for server/service types we register an instance of
// the plugin per-platform
for (int i = 0; i < platforms.length; i++) {
String pName = TypeBuilder.composePlatformTypeName(name, platforms[i]);
try {
pm.createPlugin(pName, plugin, null);
} catch (PluginException e) {
this.log.error("createPlugin=" + plugin.getName(), e);
}
}
}
private boolean isPluginTypeEnabled(String type) {
String typeProp = getPropertyKey(type, "disable");
return !"true".equals(getProperty(typeProp));
}
public boolean isLoadablePluginName(String name) {
if (!(name.endsWith("-plugin.jar") || name.endsWith("-plugin.xml"))) {
if (DEBUG_LIFECYCLE) {
log.debug(name + " not a loadable plugin");
}
return false;
}
name = name.substring(0, name.length() - 11);
if (this.includePlugins != null) {
if (this.includePlugins.get(name) == null) {
if (DEBUG_LIFECYCLE) {
log.debug("Skipping " + name + " (not in plugins.include)");
}
return false;
}
}
if (isExcluded(name)) {
return false;
}
return true;
}
private boolean isExcluded(String name) {
if (name.endsWith("-plugin.jar") || name.endsWith("-plugin.xml")) {
name = name.substring(0, name.length() - 11);
}
if (this.excludePlugins != null) {
if (this.excludePlugins.get(name) != null) {
if (DEBUG_LIFECYCLE) {
log.debug("Skipping " + name + " (in plugins.exclude)");
}
return true;
}
}
return false;
}
/**
* registerPluginJar() without mapping types.
* @param jarName The name of the jar file on disk.
* @see #registerPluginJar(String jarName,ClassLoader resourceLoader)
*/
public PluginInfo registerPluginJar(String jarName) throws PluginException, PluginExistsException {
return registerPluginJar(jarName, null);
}
private void loadProductPluginStub() throws PluginException {
this.pluginStub = new byte[1024];
ClassLoader loader = this.getClass().getClassLoader();
InputStream is = null;
try {
is = loader.getResourceAsStream(PLUGIN_STUB);
if (is == null) {
throw new PluginException("Unable to find: " + PLUGIN_STUB);
}
this.pluginStubLength = is.read(this.pluginStub);
} catch (IOException e) {
throw new PluginException("Unable to read: " + PLUGIN_STUB + ": " + e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
}
private File[] listPlugins(File dir) {
File[] plugins = dir.listFiles();
return plugins;
}
private String unsupportedClassVersionMessage(String msg) {
final String ex = "Unsupported major.minor version ";
int ix;
if ((ix = msg.indexOf(ex)) > -1) {
ix += ex.length();
String major = msg.substring(ix, ix + 2);
String jre = (String) JAVA_VERSIONS.get(major);
if (jre == null) {
return msg;
}
return "requires JRE " + jre + " or higher";
} else {
return msg;
}
}
public Collection<PluginInfo> getAllPluginInfoDirectFromFileSystem(String path) {
final Collection<PluginInfo> rtn = new ArrayList<PluginInfo>();
final List<String> dirs = StringUtil.explode(path, File.pathSeparator);
for (final String d : dirs) {
final File dir = new File(d);
if (!dir.exists() || !dir.isDirectory()) {
continue;
}
File[] plugins = dir.listFiles();
for (File plugin : plugins) {
String name = plugin.getName();
if (name.endsWith("-plugin.jar") || name.endsWith("-plugin.xml")) {
rtn.add(new PluginInfo(plugin, "NONE"));
}
}
}
return rtn;
}
public Collection<PluginInfo> registerPlugins(String path, Collection<PluginInfo> excludes) {
Collection<PluginInfo> rtn = new ArrayList<PluginInfo>();
List<String> dirs = StringUtil.explode(path, File.pathSeparator);
for (int i = 0; i < dirs.size(); i++) {
File dir = new File(dirs.get(i));
if (!dir.exists()) {
log.warn("register plugins: " + dir + " does not exist");
continue;
}
if (!dir.isDirectory()) {
log.warn("register plugins: " + dir + " not a directory");
continue;
}
File[] plugins = listPlugins(dir);
Collection<PluginInfo> pluginInfo = register(Arrays.asList(plugins), excludes);
rtn.addAll(pluginInfo);
}
return rtn;
}
private Collection<PluginInfo> register(Collection<File> plugins, Collection<PluginInfo> excludes) {
Collection<PluginInfo> rtn = new ArrayList<PluginInfo>();
for (File plugin : plugins) {
String name = plugin.getName();
if (!isLoadablePluginName(name)) {
if (isExcluded(name) && excludes != null && plugin.exists() && !plugin.isDirectory()) {
PluginInfo info = new PluginInfo(plugin, "EXCLUDED");
excludes.add(info);
}
continue;
}
log.info("Loading plugin: " + name + " (" + plugin.getParent() + ")");
try {
PluginInfo info = null;
if ((info = registerPluginJar(plugin.getAbsolutePath())) != null) {
rtn.add(info);
}
} catch (UnsupportedClassVersionError e) {
log.info("Cannot load " + name + ": " + unsupportedClassVersionMessage(e.getMessage()));
} catch (PluginExistsException e) {
log.debug("Plugin " + name + " already exists.");
} catch (PluginException e) {
// ...we're unable to register this particular plugin, log it and press on...
log.error("A problem occured while registering plugin [" + name + "]", e);
}
}
return rtn;
}
private void addClassPath(PluginLoader loader, String path) throws PluginException {
try {
loader.addURL(path);
} catch (PluginLoaderException e) {
throw new PluginException(e.getMessage());
}
}
private void addClassPath(PluginLoader loader, String name, String[] classpath) throws PluginException {
if (classpath.length == 0) {
return;
}
String pdkDir = getPdkDir();
String pdkPluginsDir = getPdkPluginsDir();
for (int i = 0; i < classpath.length; i++) {
String path = classpath[i];
if(path.startsWith("pdk/plugins/") && pdkPluginsDir !=null) {
path = pdkPluginsDir + "/" + path.substring(12);
}else if (path.startsWith("pdk/") && (pdkDir != null)) {
path = pdkDir + "/" + path.substring(3);
}
addClassPath(loader, path);
}
}
private void logPluginManifest(String jarName) {
if (log.isDebugEnabled()) {
URL url;
try {
url = new URL("jar", "", "file:" + jarName + "!/");
JarURLConnection jarConn = (JarURLConnection) url.openConnection();
Map attributeMap = jarConn.getManifest().getMainAttributes();
if (!attributeMap.isEmpty()) {
StringBuilder manifestLog = new StringBuilder("\n--- Manifest entries for: " + url.toString() +
" ---\n");
Iterator iter = attributeMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
manifestLog.append(entry.getKey() + " - " + entry.getValue() + "\n");
}
log.debug(manifestLog.toString());
}
} catch (Exception e) {
log.debug("Manifest retrieval had an exception (continuing): " + e.getMessage());
}
}
}
/**
* Load a product plugin jar. Registers the product plugin, as defined by
* the Main-Class jar manifest attribute which must be a class which
* implements the ProductPlugin interface. Registers plugins supported for
* each plugin type (control, measurement, responsetime) as returned by the
* ProductPlugin.getPlugin method.
* @param jarName The name of the jar file on disk.
* @param resourceLoader ClassLoader used to find jar resources.
* @return The name of the product plugin as returned by
* ProductPlugin.getName.
* @see org.hyperic.hq.product.ProductPlugin
*/
public PluginInfo registerPluginJar(String jarName, ClassLoader resourceLoader) throws PluginException,
PluginExistsException {
ProductPlugin plugin = null;
Class<?> pluginClass = null;
PluginData data;
String defaultPluginName = getNameFromFile(jarName);
try {
PluginLoader loader = PluginLoader.create(jarName, this.getClass().getClassLoader());
logPluginManifest(jarName);
PluginLoader.setClassLoader(loader);
ClassLoader dataLoader;
if (resourceLoader != null) {
dataLoader = resourceLoader;
} else {
dataLoader = loader;
}
data = PluginData.getInstance(this, dataLoader, jarName);
String[] classpath = ProductPlugin.getDataClassPath(data);
addClassPath(loader, jarName, classpath);
if (jarName.endsWith(".jar")) {
String pdk = getPdkWorkDir();
ClientPluginDeployer deployer = new ClientPluginDeployer(pdk, defaultPluginName);
List jars = deployer.unpackJar(jarName);
//if (this.isClient) {
loader.addURLs(jars);
//}
}
String implName = data.getPlugin(ProductPlugin.TYPE_PRODUCT, ProductPlugin.TYPE_PRODUCT);
if (implName == null) {
pluginClass = loader.loadPlugin(PLUGIN_STUB_NAME, this.pluginStub, this.pluginStubLength);
} else {
pluginClass = ProductPlugin.getPluginClass(PluginLoader.getClassLoader(), data, implName, jarName);
}
if (pluginClass == null) {
throw new PluginException("Class [" + implName + "] not found " + "via classloader=[" +
PluginLoader.getClassLoader());
}
plugin = (ProductPlugin) pluginClass.newInstance();
plugin.data = data;
// there are 3 ways to set the product name:
// - legacy ProductPlugin.setName()
// - <product name="foo"> in hq-plugin.xml
// - default to name of the plugin file minus "-plugin.{xml,jar}"
// we try all three and make sure plugin.name and data.name
// are both set with the same value.
String pluginName = plugin.getName(); // legacy
if (pluginName == null) {
pluginName = data.getName(); // hq-plugin.xml
}
if (pluginName == null) {
pluginName = defaultPluginName;
if (pluginName == null) {
throw new PluginException("Malformed name for: " + jarName);
}
}
if (data.getName() == null) {
data.setName(pluginName);
}
if (plugin.getName() == null) {
plugin.setName(pluginName);
}
if (plugin.getPluginVersion() == null) {
plugin.setPluginVersion(data.getVersion());
}
if (this.client && (implName != null)) {
// already added the classpath, but the impl may override/adjust
String[] pluginClasspath = plugin.getClassPath(this);
addClassPath(loader, plugin.getName(), pluginClasspath);
}
PluginInfo info = new PluginInfo(plugin, jarName);
// e.g. for finding hq-plugin.xml
// when deployed on server
// resourceLoader != plugin.getClass().getClassLoader()
if (resourceLoader == null) {
resourceLoader = plugin.getClass().getClassLoader();
}
info.resourceLoader = resourceLoader;
setPluginInfo(pluginName, info);
registerPlugin(plugin, null);
TypeInfo[] types = plugin.getTypes();
if (types == null) {
this.log.error(pluginName + ".getTypes returned null");
return null;
}
addPluginTypes(types, plugin);
return info;
} catch (PluginException e) {
throw e;
} catch (Exception e) {
throw new PluginException(e.getMessage(), e);
} finally {
if (plugin != null) {
PluginLoader.resetClassLoader(plugin);
}
}
}
public void addPluginTypes(TypeInfo[] types, ProductPlugin plugin) throws PluginExistsException {
PluginInfo info = getPluginInfo(plugin.getName());
if (this.registerTypes) {
registerTypeInfo(types);
}
for (int i = 0; i < ProductPlugin.TYPES.length; i++) {
String type = ProductPlugin.TYPES[i];
if (type.equals(ProductPlugin.TYPE_PRODUCT))
continue;
if (!isPluginTypeEnabled(type)) {
continue;
}
PluginManager pm = (PluginManager) managers.get(type);
for (int j = 0; j < types.length; j++) {
GenericPlugin gPlugin;
String typeName = types[j].getName();
gPlugin = plugin.getPlugin(type, types[j]);
if (gPlugin == null) {
if (DEBUG_LIFECYCLE) {
log.debug(plugin.getName() + " does not implement " + type + " for type=" + typeName);
}
continue;
}
gPlugin.data = plugin.data;
gPlugin.setName(typeName);
gPlugin.setTypeInfo(types[j]);
if (DEBUG_LIFECYCLE) {
log.debug(plugin.getName() + " implements " + type + " for type=" + typeName);
}
registerTypePlugin(pm, info, gPlugin, types[j]);
}
}
}
public void removePluginTypes(List<TypeInfo> typeInfos) {
for (int i = 0; i < ProductPlugin.TYPES.length; i++) {
String type = ProductPlugin.TYPES[i];
if (type.equals(ProductPlugin.TYPE_PRODUCT))
continue;
if (!isPluginTypeEnabled(type)) {
continue;
}
PluginManager pm = (PluginManager) managers.get(type);
Map<String, GenericPlugin> plugins = pm.getPlugins();
Set<String> pluginsToDelete = new HashSet<String>();
for (GenericPlugin gPlugin : plugins.values()) {
if (typeInfos.contains(gPlugin.getTypeInfo())) {
pluginsToDelete.add(gPlugin.getName());
}
}
for (String pluginToDelete : pluginsToDelete) {
try {
pm.removePlugin(pluginToDelete);
} catch (PluginNotFoundException e) {
log.warn("Error attempting to remove a plugin that does not exist");
} catch (PluginException e) {
log
.warn("Error removing a plugin while updating type metadata. " +
"This could cause a PluginExistsException later when an attempt is made to re-register the plugin. Cause: " +
e.getMessage());
}
}
}
}
private void removeManagerPlugins(PluginManager mgr, String jarName) throws PluginException {
Map<String, GenericPlugin> mPlugins = mgr.getPlugins();
// cannot use keySet().iterator() else
// ConcurrentModificationException is thrown during removePlugin()
String[] keys = mPlugins.keySet().toArray(new String[0]);
for (int i = 0; i < keys.length; i++) {
String name = keys[i];
PluginInfo info = mgr.getPluginInfo(name);
if (info == null) {
String msg = "no plugin info found for " + mgr.getName() + " plugin " + name;
throw new PluginException(msg);
}
// XXX: should prolly check more than jar basename
// but then again, they live in the same directory
// so jar basename should be unique.
if (info.jar.equals(jarName)) {
try {
mgr.removePlugin(name);
} catch (PluginNotFoundException e) {
e.printStackTrace();
}
}
}
}
public void removePluginJar(String jarName) throws PluginException {
String fileName = new File(jarName).getName();
for (Map.Entry<String, PluginManager> entry : this.managers.entrySet()) {
PluginManager manager = entry.getValue();
removeManagerPlugins(manager, fileName);
}
}
public void updatePluginJar(String jarName) throws PluginException {
removePluginJar(jarName);
try {
registerPluginJar(jarName);
} catch (PluginExistsException e) {
// should not happen if removePluginJar was a success
throw new PluginException(e);
}
}
public PluginManager getPluginManager(String type) throws PluginException {
PluginManager mgr = this.managers.get(type);
if (mgr == null) {
// XXX PluginManagerNotFoundException
throw new PluginException();
}
return mgr;
}
public void setProperty(String key, String value) {
for (Map.Entry<String, PluginManager> entry : this.managers.entrySet()) {
PluginManager manager = entry.getValue();
manager.getProperties().setProperty(key, value);
}
}
public boolean isClient() {
return this.client;
}
/**
* As opposed to other plugins, product plugins
* are fetched by plugin name
*/
@Override
public ConfigSchema getConfigSchema(String pluginName,
String platformName,
String typeName,
TypeInfo info,
ConfigResponse config)
throws PluginNotFoundException {
return getConfigSchema(pluginName, info, config);
}
}