/* * 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.net.URL; import java.util.Collections; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.rhq.core.clientapi.agent.PluginContainerException; import org.rhq.core.clientapi.agent.metadata.PluginMetadataManager; import org.rhq.core.clientapi.agent.metadata.ResourceTypeNotEnabledException; import org.rhq.core.domain.measurement.AvailabilityType; import org.rhq.core.domain.resource.Resource; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.pc.ContainerService; import org.rhq.core.pc.inventory.InventoryManager; import org.rhq.core.pc.inventory.ResourceContainer; import org.rhq.core.pluginapi.inventory.ClassLoaderFacet; import org.rhq.core.pluginapi.inventory.ResourceComponent; import org.rhq.core.pluginapi.inventory.ResourceContext; import org.rhq.core.pluginapi.inventory.ResourceDiscoveryCallback; import org.rhq.core.pluginapi.inventory.ResourceDiscoveryComponent; import org.rhq.core.pluginapi.upgrade.ResourceUpgradeCallback; /** * This class builds and lifecycles the various plugin components for use by the other services. * * @author Greg Hinkle * @author John Mazzitelli */ @SuppressWarnings("unchecked") public class PluginComponentFactory implements ContainerService { private static final Log log = LogFactory.getLog(PluginComponentFactory.class); private final InventoryManager inventoryManager; private final PluginManager pluginManager; /** * Constructs a new instance. */ public PluginComponentFactory(InventoryManager inventoryManager, PluginManager pluginManager) { this.inventoryManager = inventoryManager; this.pluginManager = pluginManager; } /** * This will create a new {@link ResourceDiscoveryComponent} instance that can be used to discover and create * {@link Resource}s of the given <code>resourceType</code>. The new discovery component will be loaded in the * plugin classloader that belongs to the plugin responsible for handling that specific resource type. * * @param resourceType the type of resource that is to be discovered * @param parentResourceContainer represents the parent resource for any newly discovered resources that may be found * by the discovery component that is created by this method. This can be <code>null</code>, * but ONLY in the case when the resourceType is that of a root platform type. * @return a new discover component loaded in the proper plugin classloader that can discover resources of the given * type * * @throws PluginContainerException if failed to create the discovery component instance * @throws ResourceTypeNotEnabledException if the resource type has been disabled or ignored - no management of the resource is allowed */ public ResourceDiscoveryComponent getDiscoveryComponent(ResourceType resourceType, ResourceContainer parentResourceContainer) throws PluginContainerException, ResourceTypeNotEnabledException { // This is an exception for PC unit tests which use a fake platform type. if (resourceType.equals(PluginMetadataManager.TEST_PLATFORM_TYPE)) { return null; } PluginMetadataManager metadataManager = pluginManager.getMetadataManager(); String className = metadataManager.getDiscoveryClass(resourceType); String typeName = resourceType.getName(); String pluginName = resourceType.getPlugin(); if (log.isDebugEnabled()) { log.debug("Creating discovery component [" + className + "] for resource type [" + typeName + ']'); } ClassLoader classLoader = getDiscoveryComponentClassLoader(parentResourceContainer, pluginName); ResourceDiscoveryComponent discoveryComponent = (ResourceDiscoveryComponent) instantiateClass(classLoader, className); if (log.isDebugEnabled()) { log.debug("Created discovery component [" + className + "] for resource type [" + typeName + ']'); } return discoveryComponent; } public ClassLoader getDiscoveryComponentClassLoader(ResourceContainer parentResourceContainer, String pluginName) throws PluginContainerException { // Determine what classloader to use to load the discovery component class. If the parent resource for newly // discovered resources is the root platform (or if the discovered resource is going TO BE the root platform), // we can just use the plugin classloader. If discovered resources will be children of a top level server or // of a low-level resource, the classloader will be that of the discovery plugin but will have a parent // classloader that is the classloader of the parent resource in order for the discovery component to talk // to its parent resource using connection classes provided by the parent resource classloader. ClassLoaderManager classLoaderMgr = pluginManager.getClassLoaderManager(); ClassLoader classLoader; if (parentResourceContainer == null || inventoryManager.getPlatform().equals(parentResourceContainer.getResource())) { classLoader = classLoaderMgr.obtainPluginClassLoader(pluginName); } else { ClassLoader parentClassLoader = parentResourceContainer.getResourceClassLoader(); // only create if plugins are different, otherwise, use parent classloader as is if (pluginName.equals(parentResourceContainer.getResource().getResourceType().getPlugin())) { classLoader = parentClassLoader; } else { classLoader = classLoaderMgr.obtainDiscoveryClassLoader(pluginName, parentClassLoader); } } return classLoader; } /** * This will create a new {@link ResourceComponent} instance that will represent the given {@link Resource}. * The new component will be loaded in the proper plugin classloader based on its specific resource type. * * @param resource the resource that the component will wrap * * @return a new resource component loaded in the proper plugin classloader * * @throws PluginContainerException if failed to create the component instance * @throws ResourceTypeNotEnabledException if the resource type has been disabled or ignored - no management of the resource is allowed */ public ResourceComponent buildResourceComponent(Resource resource) throws PluginContainerException, ResourceTypeNotEnabledException { ResourceType resourceType = resource.getResourceType(); if (PluginMetadataManager.TEST_PLATFORM_TYPE.equals(resourceType)) { return new ResourceComponent() { public AvailabilityType getAvailability() { return AvailabilityType.UP; } public void start(ResourceContext context) { } public void stop() { } }; } String className = pluginManager.getMetadataManager().getComponentClass(resourceType); ResourceContainer resourceContainer = inventoryManager.getResourceContainer(resource); if (resourceContainer == null) { throw new PluginContainerException("Resource container not found for " + resource + " - cannot create ResourceComponent."); } ClassLoader resourceClassloader = resourceContainer.getResourceClassLoader(); if (resourceClassloader == null) { throw new PluginContainerException("Resource classLoader not found for " + resource + " - cannot create ResourceComponent."); } ResourceComponent component = (ResourceComponent) instantiateClass(resourceClassloader, className); if (log.isDebugEnabled()) { log.debug("Created resource component [" + className + "] of resource type [" + resourceType + ']'); } return component; } /** * Given a resource, this will return the appropriate classloader for that resource. * If no classloader has been created for it yet, one will be created by this method. * * @param resource the resource whose classloader is to be returned (and possibly created if needed) * * @return the resource's classloader * * @throws PluginContainerException if the resource's classloader could not be created */ public ClassLoader getResourceClassloader(Resource resource) throws PluginContainerException { try { ClassLoaderManager classLoaderMgr = pluginManager.getClassLoaderManager(); ResourceType resourceType = resource.getResourceType(); // supports tests if (resourceType.equals(PluginMetadataManager.TEST_PLATFORM_TYPE)) { return Thread.currentThread().getContextClassLoader(); } // information about the resource's parent Resource parentResource = resource.getParentResource(); ResourceContainer parentContainer; if (parentResource != null) { parentContainer = inventoryManager.getResourceContainer(parentResource); if (parentContainer == null) { throw new PluginContainerException("Missing container for parent " + parentResource + " of " + resource + "."); } } else if (resource.equals(inventoryManager.getPlatform())) { // the given resource is our top platform resource - just use its plugin classloader return classLoaderMgr.obtainPluginClassLoader(resourceType.getPlugin()); } else { throw new PluginContainerException("Missing parent resource for resource=" + resource); } // get the classloader the resource should use List<URL> additionalJars = (classLoaderMgr.isCreateResourceClassLoaders()) ? askDiscoveryComponentForAdditionalClasspathUrls(resource, parentContainer) : Collections.<URL>emptyList(); ClassLoader cl = classLoaderMgr.obtainResourceClassLoader(resource, parentContainer, additionalJars); return cl; } catch (Throwable t) { throw new PluginContainerException("Failed to obtain classloader for resource: " + resource, t); } } /** * This will create a new {@link ResourceDiscoveryCallback} instance that can be used to process * details of newly discovered resources. * * @return a new discovery callback loaded in the proper classloader * @throws PluginContainerException if failed to create the discovery callback instance */ public ResourceDiscoveryCallback getDiscoveryCallback(String pluginName, String callbackClassName) throws PluginContainerException { // same classloader as plugin discovery component would use - with null parent, its just the plugin classloader ResourceDiscoveryCallback callback = instantiateInDiscoveryComponentClassLoader(pluginName, callbackClassName, ResourceDiscoveryCallback.class); if (log.isDebugEnabled()) { log.debug("Created discovery callback [" + callbackClassName + "] for plugin [" + pluginName + ']'); } return callback; } /** * This will create a new {@link ResourceUpgradeCallback} instance that can be used to process * details of upgraded resources. * * @return a new resource upgrade callback loaded in the proper classloader * @throws PluginContainerException if failed to create the discovery callback instance */ public <T extends ResourceComponent<?>> ResourceUpgradeCallback<T> getResourceUpgradeCallback(String pluginName, String callbackClassName) throws PluginContainerException { // same classloader as plugin discovery component would use - with null parent, its just the plugin classloader ResourceUpgradeCallback callback = instantiateInDiscoveryComponentClassLoader(pluginName, callbackClassName, ResourceUpgradeCallback.class); if (log.isDebugEnabled()) { log.debug("Created resource upgrade callback [" + callbackClassName + "] for plugin [" + pluginName + ']'); } return callback; } private <T> T instantiateInDiscoveryComponentClassLoader(String pluginName, String className, Class<T> facetType) throws PluginContainerException { ClassLoader classLoader = getDiscoveryComponentClassLoader(null, pluginName); return facetType.cast(instantiateClass(classLoader, className)); } private List<URL> askDiscoveryComponentForAdditionalClasspathUrls(Resource resource, ResourceContainer parentContainer) throws Throwable { List<URL> additionalJars = null; ResourceDiscoveryComponent discoveryComponent = getDiscoveryComponent(resource.getResourceType(), parentContainer); if (discoveryComponent != null && discoveryComponent instanceof ClassLoaderFacet) { additionalJars = inventoryManager.invokeDiscoveryComponentClassLoaderFacet(resource, discoveryComponent, parentContainer); } return additionalJars; } /** * This will load the class definition of <code>className</code> the given <code>classLoader</code>. * * @param loader the classloader where the component is to be loaded from * @param className the class name of the resource component to be instantiated * * @return the new object of type <code>className</code> that was loaded via the classloader * * @throws PluginContainerException if the class could not be instantiated for some reason */ private Object instantiateClass(ClassLoader loader, String className) throws PluginContainerException { if (log.isDebugEnabled()) { log.debug("Loading class [" + className + "] via classloader [" + loader + ']'); } if (loader == null) { throw new PluginContainerException("Cannot load class [" + className + "] with null classloader"); } try { Class<?> clazz = Class.forName(className, true, loader); if (log.isDebugEnabled()) { log.debug("Loaded class [" + clazz + "] from classloader [" + loader + ']'); } return clazz.newInstance(); } catch (InstantiationException e) { throw new PluginContainerException("Could not instantiate plugin class [" + className + "] from classloader [" + loader + "]", e); } catch (IllegalAccessException e) { throw new PluginContainerException("Could not access plugin class [" + className + "] from classloader [" + loader + "]", e); } catch (ClassNotFoundException e) { throw new PluginContainerException("Could not find plugin class [" + className + "] from classloader [" + loader + "]", e); } catch (NullPointerException npe) { throw new PluginContainerException("Plugin class was 'null' using loader [" + loader + "]", npe); } } /** * Clears our cache of discovery components. * * @see ContainerService#shutdown() */ public void shutdown() { } }