/* * 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 as published by * the Free Software Foundation version 2 of the License. * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package org.rhq.core.pc.plugin; import java.io.File; import java.net.JarURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.Enumeration; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.rhq.core.clientapi.agent.metadata.PluginMetadataManager; import org.rhq.core.clientapi.agent.metadata.ResourceTypeNotEnabledException; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.pc.PluginContainerConfiguration; import org.rhq.core.pluginapi.bundle.BundleFacet; import org.rhq.core.pluginapi.configuration.ConfigurationFacet; import org.rhq.core.pluginapi.content.ContentFacet; import org.rhq.core.pluginapi.inventory.CreateChildResourceFacet; import org.rhq.core.pluginapi.inventory.DeleteResourceFacet; import org.rhq.core.pluginapi.inventory.ManualAddFacet; import org.rhq.core.pluginapi.inventory.ResourceComponent; import org.rhq.core.pluginapi.inventory.ResourceDiscoveryComponent; import org.rhq.core.pluginapi.measurement.MeasurementFacet; import org.rhq.core.pluginapi.operation.OperationFacet; import org.rhq.core.pluginapi.plugin.PluginLifecycleListener; /** * A utility to test a set of plugins are valid. * * @author John Mazzitelli */ public class PluginValidator { private static final Log LOG = LogFactory.getLog(PluginValidator.class); private static final String PLUGIN_DESCRIPTOR_PATH = "META-INF/rhq-plugin.xml"; static boolean interactive = false; /** * If no args are passed in, the current thread's classloader will be used to find the plugins to validate. * If one or more argument strings are provided, they will be assumed to be paths to the plugin jars to validate * (in which case the thread's classloader will be ignored and not searched for plugins). * * If one argument is '-i'. Warnings and Errors will also be printed to stdout and stderr, which helps developers * to find issues when they run the Plugin validator as a standalone tool. * * 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) { SimplePluginFinder finder; try { if (args.length > 0) { finder = new SimplePluginFinder(); for (String arg : args) { if (arg.equals("-i")) { interactive = true; continue; } URL jarUrl = new File(arg).toURI().toURL(); finder.addUrl(jarUrl); LOG.info("Plugin jar: " + jarUrl); } } else { finder = findPluginJars(); } } catch (Exception e) { throw new RuntimeException(e); } if (validatePlugins(finder)) { System.out.println("!OK!"); System.exit(0); } else { System.out.println("!FAILED!"); System.exit(1); } } public static boolean validatePlugins(PluginFinder finder) { PluginContainerConfiguration configuration = new PluginContainerConfiguration(); configuration.setPluginFinder(finder); configuration.setTemporaryDirectory(new File(System.getProperty("java.io.tmpdir"))); configuration.setDataDirectory(new File(System.getProperty("java.io.tmpdir"))); PluginManager manager = new PluginManager(configuration, new PluginLifecycleListenerManagerImpl()); boolean success = true; // assume all goes well; we'll set this to false if we hit any error try { // make sure we successfully processed all the plugins that are in our finder boolean sizesMatch = (manager.getPlugins().size() == finder.findPlugins().size()); if (!sizesMatch) { success = false; errorLog("Only [" + manager.getPlugins().size() + "] out of [" + finder.findPlugins().size() + "] plugin descriptors are valid."); } else { LOG.info("All [" + finder.findPlugins().size() + "] plugin descriptors are valid."); } PluginMetadataManager metadataManager = manager.getMetadataManager(); // examine all the resource types defined in all plugins and validate some things about them for (ResourceType resourceType : metadataManager.getAllTypes()) { PluginEnvironment pluginEnvironment = manager.getPlugin(resourceType.getPlugin()); LOG.info("Validating resource type [" + resourceType.getName() + "] from plugin [" + resourceType.getPlugin() + "]..."); // make sure the component class was specified and can be loaded by the plugin classloader success = success && validateResourceComponentClass(metadataManager, resourceType, pluginEnvironment); // if the optional discovery class was specified, make sure it can be loaded by the plugin classloader success = success && validateResourceDiscoveryComponentClass(metadataManager, resourceType, pluginEnvironment); // TODO removed from success calculation as it is not clear if the element // is really needed and it makes the build fail right now (descriptor is already // removed at this point, so the call will fail // validatePluginLifecycleListenerClass(metadataManager, resourceType, pluginEnvironment); } } finally { manager.shutdown(); } return success; } private static boolean validateResourceComponentClass(PluginMetadataManager metadataManager, ResourceType resourceType, PluginEnvironment pluginEnvironment) { boolean success = true; String componentClass; try { componentClass = metadataManager.getComponentClass(resourceType); } catch (ResourceTypeNotEnabledException rtne) { componentClass = null; success = false; errorLog(rtne.toString()); } if (componentClass == null) { success = false; errorLog("Missing component class in resource type [" + resourceType.getName() + "] from plugin [" + resourceType.getPlugin() + "]"); } else { try { Class componentClazz = Class.forName(componentClass, false, pluginEnvironment.getPluginClassLoader()); if (!ResourceComponent.class.isAssignableFrom(componentClazz)) { success = false; errorLog("Component class [" + componentClass + "] for resource type [" + resourceType.getName() + "] from plugin [" + resourceType.getPlugin() + "] does not implement " + ResourceComponent.class); } if (!resourceType.getMetricDefinitions().isEmpty() && !MeasurementFacet.class.isAssignableFrom(componentClazz)) { success = false; errorLog("Component class [" + componentClass + "] for resource type [" + resourceType.getName() + "] from plugin [" + resourceType.getPlugin() + "] does not support the measurement collection facet but defines metrics."); } if (!resourceType.getOperationDefinitions().isEmpty() && !OperationFacet.class.isAssignableFrom(componentClazz)) { success = false; errorLog("Component class [" + componentClass + "] for resource type [" + resourceType.getName() + "] from plugin [" + resourceType.getPlugin() + "] does not support the operations facet but defines operations."); } if (resourceType.getBundleType() != null && !BundleFacet.class.isAssignableFrom(componentClazz)) { success = false; errorLog("Component class [" + componentClass + "] for resource type [" + resourceType.getName() + "] from plugin [" + resourceType.getPlugin() + "] does not support the bundle facet but defines a bundle type."); } if (!resourceType.getPackageTypes().isEmpty() && !ContentFacet.class.isAssignableFrom(componentClazz)) { success = false; errorLog("Component class [" + componentClass + "] for resource type [" + resourceType.getName() + "] from plugin [" + resourceType.getPlugin() + "] does not support the content management facet but defines package types."); } if (resourceType.getResourceConfigurationDefinition() != null && !ConfigurationFacet.class.isAssignableFrom(componentClazz)) { success = false; errorLog("Component class [" + componentClass + "] for resource type [" + resourceType.getName() + "] from plugin [" + resourceType.getPlugin() + "] does not support the configuration facet but defines resource configuration."); } boolean hasCreatableChild = false; for (ResourceType childResourceType : resourceType.getChildResourceTypes()) { if (childResourceType.isCreatable()) { hasCreatableChild = true; break; } } if (hasCreatableChild && !CreateChildResourceFacet.class.isAssignableFrom(componentClazz)) { success = false; errorLog("Component class [" + componentClass + "] for resource type [" + resourceType.getName() + "] from plugin [" + resourceType.getPlugin() + "] does not support the child creation facet but has metadata saying it can."); } if (resourceType.isDeletable() && !DeleteResourceFacet.class.isAssignableFrom(componentClazz)) { success = false; errorLog("Component class [" + componentClass + "] for resource type [" + resourceType.getName() + "] from plugin [" + resourceType.getPlugin() + "] does not support delete resource facet but has metadata saying it can delete children."); } } catch (Exception e) { success = false; errorLog("Cannot find component class [" + componentClass + "] for resource type [" + resourceType.getName() + "] from plugin [" + resourceType.getPlugin() + "]."); } } return success; } private static boolean validateResourceDiscoveryComponentClass(PluginMetadataManager metadataManager, ResourceType resourceType, PluginEnvironment pluginEnvironment) { boolean success = true; String discoveryClass; try { discoveryClass = metadataManager.getDiscoveryClass(resourceType); } catch (ResourceTypeNotEnabledException rtne) { discoveryClass = null; success = false; errorLog(rtne.toString()); } if (discoveryClass != null) { try { Class discoveryClazz = Class.forName(discoveryClass, false, pluginEnvironment.getPluginClassLoader()); if (!ResourceDiscoveryComponent.class.isAssignableFrom(discoveryClazz)) { success = false; errorLog("Discovery class [" + discoveryClass + "] for resource type [" + resourceType.getName() + "] from plugin [" + resourceType.getPlugin() + "] does not implement " + ResourceDiscoveryComponent.class); } if (resourceType.isSupportsManualAdd() && !ManualAddFacet.class.isAssignableFrom(discoveryClazz)) { warnLog("Discovery class [" + discoveryClass + "] for resource type [" + resourceType.getName() + "] from plugin [" + resourceType.getPlugin() + "] does not implement " + ManualAddFacet.class + " - implementing manual-add in discoverResources() is deprecated."); } } catch (Exception e) { success = false; errorLog("Cannot find discovery class [" + discoveryClass + "] for resource type [" + resourceType.getName() + "] from plugin [" + resourceType.getPlugin() + "]."); } } return success; } private static boolean validatePluginLifecycleListenerClass(PluginMetadataManager metadataManager, ResourceType resourceType, PluginEnvironment pluginEnvironment) { boolean success = true; String overseerClass = metadataManager.getPluginLifecycleListenerClass(resourceType.getPlugin()); if (overseerClass != null) { try { Class overseerClazz = Class.forName(overseerClass, false, pluginEnvironment.getPluginClassLoader()); if (!PluginLifecycleListener.class.isAssignableFrom(overseerClazz)) { success = false; errorLog("Plugin Lifecycle Listener class [" + overseerClass + "] for plugin [" + resourceType.getPlugin() + "] does not implement " + PluginLifecycleListener.class); } } catch (Exception e) { success = false; errorLog("Cannot find Plugin Lifecycle Listener class [" + overseerClass + "] for plugin [" + resourceType.getPlugin() + "]."); } } return success; } private static SimplePluginFinder findPluginJars() throws Exception { SimplePluginFinder pluginFinder = new SimplePluginFinder(); 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(); pluginFinder.addUrl(jarUrl); LOG.info("Found plugin jar: " + jarUrl); } else { warnLog("Found a plugin descriptor outside of a jar, skipping: " + descriptorUrl); } } return pluginFinder; } private static void errorLog(String errorMessage) { LOG.error(errorMessage); if (interactive) System.err.println(errorMessage); } private static void warnLog(String warningMessage) { LOG.warn(warningMessage); if (interactive) System.out.println(warningMessage); } }