/*
* RHQ Management Platform
* Copyright (C) 2005-2008 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.enterprise.server.plugin.pc;
import java.io.File;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.maven.artifact.versioning.ComparableVersion;
import org.rhq.core.domain.plugin.PluginKey;
import org.rhq.enterprise.server.xmlschema.ServerPluginDescriptorMetadataParser;
import org.rhq.enterprise.server.xmlschema.ServerPluginDescriptorUtil;
import org.rhq.enterprise.server.xmlschema.generated.serverplugin.ServerPluginDescriptorType;
/**
* A utility to test that a set of server plugins are valid.
* This has a main() in which you can pass a list of serverplugin jar filenames
* as arguments. If you do not pass arguments, you can set a system
* property {@link #SYSPROP_VALIDATE_SERVERPLUGINS} whose value is a comma-separated
* list of server plugin jar filenames. If you do not set that system property
* and you do not pass arguments, this will look for any and all plugin descriptors
* it can find in jar files found in the class's classloader and validate all
* of those plugin jars.
*
* @author John Mazzitelli
*/
public class ServerPluginValidatorUtil {
// this is a system property that can contain comma-separated list of plugin jar filenames
private static final String SYSPROP_VALIDATE_SERVERPLUGINS = "rhq.test.serverplugins";
private static final Log LOG = LogFactory.getLog(ServerPluginValidatorUtil.class);
private static final String PLUGIN_DESCRIPTOR_PATH = "META-INF/rhq-serverplugin.xml";
/**
* See {@link #validatePlugins(String[])} for information on what plugins get validated.
*
* The last line this will output will be "!OK!" and exit with an exit code of 0 if everything is OK.
* The last line this will output will be "!FAILURE!" and exit with an exit code of 1 if one or more plugins failed validation.
*
* @param args 0 or more plugin jar file paths
*/
public static void main(String[] args) {
try {
if (new ServerPluginValidatorUtil().validatePlugins(args)) {
System.out.println("!OK!");
System.exit(0);
} else {
System.out.println("!FAILED!");
System.exit(1);
}
} catch (Exception e) {
System.out.println("!FAILED!");
System.out.println("Could not complete server plugin validation due to the following exception:");
e.printStackTrace(System.out);
System.exit(1);
}
}
/**
* If one or more argument strings are provided, they will be assumed to be paths to the plugin jars to validate.
* If no arguments are passed, but the system property {@link #SYSPROP_VALIDATE_SERVERPLUGINS} is set, its value
* is assumed to be a comma-separated list of plugin filenames.
* If no args are passed in, this class's classloader will be used to find the plugins to validate.
*
* @param pluginFileNames 0 or more plugin jar file paths
*
* @return 0 if all plugins validated successfully, 1 on error.
*
* @throws Exception on some error that caused the validation to abort
*/
public boolean validatePlugins(String[] pluginFileNames) throws Exception {
List<URL> jars;
try {
if (pluginFileNames != null && pluginFileNames.length > 0) {
jars = new ArrayList<URL>(pluginFileNames.length);
for (String arg : pluginFileNames) {
URL jarUrl = new File(arg).toURI().toURL();
jars.add(jarUrl);
LOG.info("Plugin jar: " + jarUrl);
}
} else {
String sysprop = System.getProperty(SYSPROP_VALIDATE_SERVERPLUGINS);
if (sysprop != null && !sysprop.startsWith("$")) {
String[] serverplugins = sysprop.split(",");
jars = new ArrayList<URL>(serverplugins.length);
for (String serverplugin : serverplugins) {
URL jarUrl = new File(serverplugin).toURI().toURL();
jars.add(jarUrl);
LOG.info("Plugin jar: " + jarUrl);
}
} else {
jars = findPluginJars();
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
boolean status = validatePlugins(jars);
return status;
}
public boolean validatePlugins(List<URL> jars) throws Exception {
boolean success = true; // assume all goes well; we'll set this to false if we hit any error
ClassLoaderManager classloaderManager = null;
try {
Map<URL, ServerPluginDescriptorType> descriptors = new HashMap<URL, ServerPluginDescriptorType>();
for (URL jar : jars) {
try {
LOG.info("Parsing server plugin [" + jar + "]");
ServerPluginDescriptorType descriptor = ServerPluginDescriptorUtil.loadPluginDescriptorFromUrl(jar);
descriptors.put(jar, descriptor);
} catch (Exception e) {
LOG.error("Failed to parse descriptor from plugin [" + jar + "]", e);
}
}
// make sure we successfully processed all the plugins that are in our finder
boolean sizesMatch = (descriptors.size() == jars.size());
if (!sizesMatch) {
success = false;
LOG.error("Only [" + descriptors.size() + "] out of [" + jars.size()
+ "] plugin descriptors are valid.");
} else {
LOG.info("All [" + jars.size() + "] plugin descriptors are valid.");
}
classloaderManager = new ClassLoaderManager(descriptors, getClass().getClassLoader(), null);
// examine all the resource types defined in all plugins and validate some things about them
for (Map.Entry<URL, ServerPluginDescriptorType> entry : descriptors.entrySet()) {
URL pluginUrl = entry.getKey();
ServerPluginDescriptorType descriptor = entry.getValue();
String pluginName = descriptor.getName();
if (pluginName == null) {
LOG.error("No plugin name in [" + pluginUrl + "]");
success = false;
continue;
} else {
LOG.info("Validating plugin [" + pluginName + "] from [" + pluginUrl + "]");
}
ServerPluginType pluginType = new ServerPluginType(descriptor);
PluginKey pluginKey = PluginKey.createServerPluginKey(pluginType.stringify(), pluginName);
ClassLoader classloader = classloaderManager.obtainServerPluginClassLoader(pluginKey);
ServerPluginEnvironment env = new ServerPluginEnvironment(pluginUrl, classloader, descriptor);
success = success && validatePluginComponentClass(env);
success = success && validatePluginVersion(env);
success = success && validatePluginConfiguration(env);
success = success && validateScheduledJobs(env);
// now see if there is specific validation to be done based on the type of plugin
ServerPluginValidator validator = getValidator(pluginType);
if (validator != null) {
boolean validatorStatus = validator.validate(env);
if (!validatorStatus) {
LOG.error(pluginType.toString() + " validator detected a problem in plugin ["
+ env.getPluginUrl() + "]");
}
success = success && validatorStatus;
}
}
} finally {
// do any cleanup here
if (classloaderManager != null) {
classloaderManager.shutdown();
}
}
return success;
}
private boolean validateScheduledJobs(ServerPluginEnvironment env) {
boolean success = true;
try {
ServerPluginDescriptorMetadataParser.getScheduledJobs(env.getPluginDescriptor());
} catch (Exception e) {
LOG.error("Invalid scheduled jobs for plugin [" + env.getPluginUrl() + "]", e);
success = false;
}
return success;
}
private boolean validatePluginConfiguration(ServerPluginEnvironment env) {
boolean success = true;
try {
ServerPluginDescriptorMetadataParser.getPluginConfigurationDefinition(env.getPluginDescriptor());
} catch (Exception e) {
LOG.error("Invalid plugin configuration for plugin [" + env.getPluginUrl() + "]", e);
success = false;
}
return success;
}
private boolean validatePluginVersion(ServerPluginEnvironment env) {
boolean success = true;
try {
File pluginFile = new File(env.getPluginUrl().toURI());
ComparableVersion version;
version = ServerPluginDescriptorUtil.getPluginVersion(pluginFile, env.getPluginDescriptor());
if (version == null) {
throw new NullPointerException("version is null");
}
} catch (Exception e) {
LOG.error("Invalid version for plugin [" + env.getPluginUrl() + "]", e);
success = false;
}
return success;
}
private boolean validatePluginComponentClass(ServerPluginEnvironment env) {
boolean success = true;
String clazz = ServerPluginDescriptorMetadataParser.getPluginComponentClassName(env.getPluginDescriptor());
// the plugin component is optional - null is allowed
if (clazz != null) {
try {
Class<?> componentClazz = Class.forName(clazz, false, env.getPluginClassLoader());
if (!ServerPluginComponent.class.isAssignableFrom(componentClazz)) {
success = false;
LOG.error("Component class [" + clazz + "] from plugin [" + env.getPluginUrl()
+ "] does not implement " + ServerPluginComponent.class);
}
} catch (Exception e) {
success = false;
LOG.error("Component class [" + clazz + "] from plugin [" + env.getPluginUrl()
+ "] could not be loaded", e);
}
}
return success;
}
private List<URL> findPluginJars() throws Exception {
List<URL> retUrls = new ArrayList<URL>();
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> descriptorUrls = classloader.getResources(PLUGIN_DESCRIPTOR_PATH);
while (descriptorUrls.hasMoreElements()) {
URL descriptorUrl = descriptorUrls.nextElement();
URLConnection connection = descriptorUrl.openConnection();
if (connection instanceof JarURLConnection) {
URL jarUrl = ((JarURLConnection) connection).getJarFileURL();
retUrls.add(jarUrl);
LOG.info("Found plugin jar: " + jarUrl);
} else {
LOG.warn("Found a plugin descriptor outside of a jar, skipping: " + descriptorUrl);
}
}
return retUrls;
}
private ServerPluginValidator getValidator(ServerPluginType pluginType) throws Exception {
String pkg = ServerPluginValidator.class.getPackage().getName();
String simpleName = pluginType.getDescriptorType().getSimpleName().replaceAll("DescriptorType$", "");
String subpkg = simpleName.replaceAll("Plugin$", "").toLowerCase();
String className = pkg + '.' + subpkg + '.' + simpleName + "Validator";
ServerPluginValidator validator = null;
Class<?> clazz = null;
try {
clazz = Class.forName(className);
} catch (Exception ignore) {
}
if (clazz != null && ServerPluginValidator.class.isAssignableFrom(clazz)) {
validator = (ServerPluginValidator) clazz.newInstance();
}
return validator;
}
}