/*
* RHQ Management Platform
* Copyright (C) 2005-2009 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.server.plugin.pc;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.simpl.RAMJobStore;
import org.quartz.simpl.SimpleThreadPool;
import org.rhq.core.domain.plugin.PluginKey;
import org.rhq.core.util.exception.ThrowableUtil;
import org.rhq.enterprise.server.plugin.pc.alert.AlertServerPluginContainer;
import org.rhq.enterprise.server.plugin.pc.bundle.BundleServerPluginContainer;
import org.rhq.enterprise.server.plugin.pc.content.ContentServerPluginContainer;
import org.rhq.enterprise.server.plugin.pc.content.PackageTypeServerPluginContainer;
import org.rhq.enterprise.server.plugin.pc.drift.DriftServerPluginContainer;
import org.rhq.enterprise.server.plugin.pc.generic.GenericServerPluginContainer;
import org.rhq.enterprise.server.scheduler.EnhancedScheduler;
import org.rhq.enterprise.server.scheduler.EnhancedSchedulerImpl;
import org.rhq.enterprise.server.util.LookupUtil;
import org.rhq.enterprise.server.xmlschema.ServerPluginDescriptorUtil;
import org.rhq.enterprise.server.xmlschema.generated.serverplugin.ServerPluginDescriptorType;
/**
* The container responsible for managing all the plugin containers for all the
* different plugin types.
*
* @author John Mazzitelli
*/
public class MasterServerPluginContainer {
private static final Log log = LogFactory.getLog(MasterServerPluginContainer.class);
/** The configuration for the master plugin container itself. */
private MasterServerPluginContainerConfiguration configuration;
/** the plugin containers for all the different types of plugins that are supported */
private Map<ServerPluginType, AbstractTypeServerPluginContainer> pluginContainers = new HashMap<ServerPluginType, AbstractTypeServerPluginContainer>();
/** the object that provides all the classloaders for all plugins */
private ClassLoaderManager classLoaderManager;
/** this is used to obtain an in-memory, non-persistent scheduler used to schedule jobs to run only within this JVM */
private SchedulerFactory nonClusteredSchedulerFactory;
/**
* Because the individual plugin containers are only managing enabled plugins (they are never told about plugins that disabled).
* this map contains the lists of plugins that are disabled so others can find out what plugins are registered but not running.
*/
private Map<ServerPluginType, List<PluginKey>> disabledPlugins = new HashMap<ServerPluginType, List<PluginKey>>();
/**
* Starts the master plugin container, which will load all plugins and begin managing them.
*
* @param config the master configuration
*/
public synchronized void initialize(MasterServerPluginContainerConfiguration config) {
try {
List<Throwable> caughtExceptions = new ArrayList<Throwable>();
log.debug("Master server plugin container is being initialized with config: " + config);
this.configuration = config;
initializeNonClusteredScheduler();
// load all server-side plugins - this just parses their descriptors and confirms they are valid server plugins
Map<URL, ? extends ServerPluginDescriptorType> plugins = preloadAllPlugins();
// create the root classloader to be used as the top classloader for all plugins
ClassLoader rootClassLoader = createRootServerPluginClassLoader();
File tmpDir = this.configuration.getTemporaryDirectory();
this.classLoaderManager = createClassLoaderManager(plugins, rootClassLoader, tmpDir);
log.debug("Created classloader manager: " + this.classLoaderManager);
// create all known child plugin containers and map them to their supported plugin types
List<AbstractTypeServerPluginContainer> pcs = createPluginContainers();
for (AbstractTypeServerPluginContainer pc : pcs) {
this.pluginContainers.put(pc.getSupportedServerPluginType(), pc);
}
log.debug("Created server plugin containers: " + this.pluginContainers.keySet());
// initialize all the plugin containers
Iterator<Map.Entry<ServerPluginType, AbstractTypeServerPluginContainer>> iterator;
iterator = this.pluginContainers.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<ServerPluginType, AbstractTypeServerPluginContainer> entry = iterator.next();
ServerPluginType pluginType = entry.getKey();
AbstractTypeServerPluginContainer pc = entry.getValue();
log.debug("Master PC is initializing server plugin container for plugin type [" + pluginType + "]");
try {
pc.initialize();
log.debug("Master PC initialized server plugin container for plugin type [" + pluginType + "]");
} catch (Throwable e) {
log.warn("Failed to initialize server plugin container for plugin type [" + pluginType + "]", e);
caughtExceptions.add(e);
iterator.remove();
}
}
// Create classloaders/environments for all plugins and load plugins into their plugin containers.
// Note that we do not care what order we load plugins - in the future we may want dependencies.
List<PluginKey> allDisabledPlugins = getDisabledPluginKeys();
for (Map.Entry<URL, ? extends ServerPluginDescriptorType> entry : plugins.entrySet()) {
URL pluginUrl = entry.getKey();
ServerPluginDescriptorType descriptor = entry.getValue();
AbstractTypeServerPluginContainer pc = getPluginContainerByDescriptor(descriptor);
if (pc != null) {
String pluginName = descriptor.getName();
ServerPluginType pluginType = new ServerPluginType(descriptor);
PluginKey pluginKey = PluginKey.createServerPluginKey(pluginType.stringify(), pluginName);
try {
ClassLoader classLoader = this.classLoaderManager.obtainServerPluginClassLoader(pluginKey);
log.debug("Pre-loading server plugin [" + pluginKey + "] from [" + pluginUrl
+ "] into its plugin container");
try {
ServerPluginEnvironment env = new ServerPluginEnvironment(pluginUrl, classLoader,
descriptor);
boolean enabled = !allDisabledPlugins.contains(pluginKey);
pc.loadPlugin(env, enabled);
log.info("Preloaded server plugin [" + pluginName + "]");
} catch (Throwable e) {
log.warn("Failed to preload server plugin [" + pluginName + "] from URL [" + pluginUrl
+ "]", e);
caughtExceptions.add(e);
}
} catch (Throwable e) {
log.warn("Failed to preload server plugin [" + pluginName
+ "]; cannot get its classloader from URL [ " + pluginUrl + "]", e);
caughtExceptions.add(e);
}
} else {
log.warn("There is no server plugin container to support plugin: " + pluginUrl);
}
}
// now that all plugins have been loaded, we need to tell all the plugin containers to start
iterator = this.pluginContainers.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<ServerPluginType, AbstractTypeServerPluginContainer> entry = iterator.next();
ServerPluginType pluginType = entry.getKey();
AbstractTypeServerPluginContainer pc = entry.getValue();
log.debug("Master PC is starting server plugin container for plugin type [" + pluginType + "]");
try {
pc.start();
log.info("Master PC started server plugin container for plugin type [" + pluginType + "]");
} catch (Throwable e) {
log.warn("Failed to start server plugin container for plugin type [" + pluginType + "]", e);
caughtExceptions.add(e);
}
}
if (caughtExceptions.isEmpty()) {
log.info("Master server plugin container has been initialized");
} else {
log.warn("Master server plugin container has been initialized but it detected some problems. "
+ "Parts of the server may not operate correctly due to these errors.");
int i = 1;
for (Throwable t : caughtExceptions) {
log.warn("Problem #" + (i++) + ": " + ThrowableUtil.getAllMessages(t));
}
}
} catch (Throwable t) {
shutdown();
log.error("Failed to initialize master plugin container! Server side plugins will not start.", t);
throw new RuntimeException(t);
}
return;
}
/**
* Stops all plugins and cleans up after them.
*/
public synchronized void shutdown() {
log.debug("Master server plugin container is being shutdown");
// stop all the plugin containers, giving them a chance to do things like stop threads they have running
for (Map.Entry<ServerPluginType, AbstractTypeServerPluginContainer> entry : this.pluginContainers.entrySet()) {
ServerPluginType pluginType = entry.getKey();
AbstractTypeServerPluginContainer pc = entry.getValue();
log.debug("Master PC is stopping server plugin container for plugin type [" + pluginType + "]");
try {
pc.stop();
log.debug("Master PC stopped server plugin container for plugin type [" + pluginType + "]");
} catch (Exception e) {
log.error("Failed to stop server plugin container for plugin type [" + pluginType + "]", e);
}
}
// shutdown all the plugin containers which in turn shuts down all their plugins.
for (Map.Entry<ServerPluginType, AbstractTypeServerPluginContainer> entry : this.pluginContainers.entrySet()) {
ServerPluginType pluginType = entry.getKey();
AbstractTypeServerPluginContainer pc = entry.getValue();
log.debug("Master PC is shutting down server plugin container for plugin type [" + pluginType + "]");
try {
pc.shutdown();
log.info("Master PC shutdown server plugin container for plugin type [" + pluginType + "]");
} catch (Exception e) {
log.error("Failed to shutdown server plugin container for plugin type [" + pluginType + "]", e);
}
}
shutdownNonClusteredScheduler();
// now shutdown the classloader manager, destroying the classloaders it created
if (this.classLoaderManager != null) {
this.classLoaderManager.shutdown();
log.debug("Shutdown classloader manager: " + this.classLoaderManager);
}
this.pluginContainers.clear();
this.disabledPlugins.clear();
this.classLoaderManager = null;
this.configuration = null;
log.info("Master server plugin container has been shutdown");
}
/**
* Loads a plugin into the appropriate plugin container.
*
* @param pluginUrl the location where the new plugin is found
* @param enabled indicates if the plugin should be enabled as soon as its loaded
* @throws Exception if the plugin's descriptor could not be parsed or could not be loaded into the plugin container
*/
public synchronized void loadPlugin(URL pluginUrl, boolean enabled) throws Exception {
ServerPluginDescriptorType descriptor = ServerPluginDescriptorUtil.loadPluginDescriptorFromUrl(pluginUrl);
ServerPluginType pluginType = new ServerPluginType(descriptor);
PluginKey pluginKey = PluginKey.createServerPluginKey(pluginType.stringify(), descriptor.getName());
this.classLoaderManager.loadPlugin(pluginUrl, descriptor);
ClassLoader classLoader = this.classLoaderManager.obtainServerPluginClassLoader(pluginKey);
log.debug("Loading server plugin [" + pluginKey + "] from [" + pluginUrl + "] into its plugin container");
try {
ServerPluginEnvironment env = new ServerPluginEnvironment(pluginUrl, classLoader, descriptor);
AbstractTypeServerPluginContainer pc = getPluginContainerByDescriptor(descriptor);
if (pc != null) {
pc.loadPlugin(env, enabled);
log.info("Loaded server plugin [" + pluginKey.getPluginName() + "]");
} else {
throw new Exception("No plugin container can load server plugin [" + pluginKey + "]");
}
} catch (Exception e) {
log.warn("Failed to load server plugin file [" + pluginUrl + "]", e);
}
return;
}
/**
* Asks that all plugin containers schedule jobs now, if needed.
* Note that this is separate from the {@link #initialize(MasterServerPluginContainerConfiguration)}
* method because it is possible that the master plugin container has been
* initialized before the scheduler is started. In this case, the caller must wait for the scheduler to
* be started before this method is called to schedule jobs.
*/
public synchronized void scheduleAllPluginJobs() {
log.debug("Master server plugin container will schedule all jobs now");
for (AbstractTypeServerPluginContainer pc : this.pluginContainers.values()) {
try {
pc.scheduleAllPluginJobs();
} catch (Exception e) {
log.error("Server plugin container for plugin type [" + pc.getSupportedServerPluginType()
+ "] failed to scheduled some or all of its jobs", e);
}
}
log.info("Master server plugin container scheduled all jobs");
return;
}
/**
* Returns the configuration that this object was initialized with. If this plugin container was not
* {@link #initialize(MasterServerPluginContainerConfiguration) initialized} or has been {@link #shutdown() shutdown},
* this will return <code>null</code>.
*
* @return the configuration
*/
public MasterServerPluginContainerConfiguration getConfiguration() {
return this.configuration;
}
/**
* Returns the manager that is responsible for created classloaders for plugins.
*
* @return classloader manager
*/
public ClassLoaderManager getClassLoaderManager() {
return this.classLoaderManager;
}
/**
* This will return all known server plugins types. These are the types of plugins
* that are supported by a server plugin container. You can obtain the server
* plugin container that manages a particular server plugin type via
* {@link #getPluginContainerByPluginType(ServerPluginType)}.
*
* @return all known server plugin types
*/
public synchronized List<ServerPluginType> getServerPluginTypes() {
return new ArrayList<ServerPluginType>(this.pluginContainers.keySet());
}
/**
* Get the plugin container of the given class. This method provides a strongly typed return value,
* based on the type of plugin container the caller wants returned.
*
* @param clazz the class name of the plugin container that the caller wants
* @return the plugin container of the given class (<code>null</code> if none found)
*/
@SuppressWarnings("unchecked")
public synchronized <T extends AbstractTypeServerPluginContainer> T getPluginContainerByClass(Class<T> clazz) {
for (AbstractTypeServerPluginContainer pc : this.pluginContainers.values()) {
if (clazz.isInstance(pc)) {
return (T) pc;
}
}
return null;
}
/**
* Given the key of a deployed plugin, this returns the plugin container that is hosting
* that plugin. If there is no plugin with the given key or that plugin is not
* loaded in any plugin container (e.g. when it is disabled), then <code>null</code> is returned.
*
* @param pluginKey
* @return the plugin container that is managing the named plugin or <code>null</code>
*/
@SuppressWarnings("unchecked")
public synchronized <T extends AbstractTypeServerPluginContainer> T getPluginContainerByPlugin(PluginKey pluginKey) {
for (AbstractTypeServerPluginContainer pc : this.pluginContainers.values()) {
try {
if (pc.getSupportedServerPluginType().equals(new ServerPluginType(pluginKey.getPluginType()))) {
if (null != pc.getPluginManager().getPluginEnvironment(pluginKey.getPluginName())) {
return (T) pc;
}
}
} catch (Exception skip) {
// should never really happen
log.error("Bad plugin key: " + pluginKey);
}
}
return null;
}
/**
* Given a server plugin type, this will return the plugin container that can manage that type of plugin.
* If the server plugin type is unknown to the master, or if the master plugin is not started, this will
* return <code>null</code>.
*
* @param pluginType the type of server plugin whose PC is to be returned
* @return a plugin container that can handle the given type of server plugin
*/
public synchronized AbstractTypeServerPluginContainer getPluginContainerByPluginType(ServerPluginType pluginType) {
AbstractTypeServerPluginContainer pc = this.pluginContainers.get(pluginType);
return pc;
}
/**
* Given a plugin's descriptor, this will return the plugin container that can manage the plugin.
*
* @param descriptor descriptor to identify a plugin whose container is to be returned
* @return a plugin container that can handle the plugin with the given descriptor
*/
protected synchronized AbstractTypeServerPluginContainer getPluginContainerByDescriptor(
ServerPluginDescriptorType descriptor) {
ServerPluginType pluginType = new ServerPluginType(descriptor.getClass());
AbstractTypeServerPluginContainer pc = getPluginContainerByPluginType(pluginType);
return pc;
}
/**
* Finds all plugins and parses their descriptors. This is only called during
* this master plugin container's {@link #initialize(MasterServerPluginContainerConfiguration) initialization}.
*
* If a plugin fails to preload, it will be ignored - other plugins will still preload.
*
* @return a map of plugins, keyed on the plugin jar URL whose values are the parsed descriptors
*
* @throws Exception on catastrophic failure. Note that if a plugin failed to load,
* that plugin will simply be ignored and no exception will be thrown
*/
protected Map<URL, ? extends ServerPluginDescriptorType> preloadAllPlugins() throws Exception {
Map<URL, ServerPluginDescriptorType> plugins;
plugins = new HashMap<URL, ServerPluginDescriptorType>();
File pluginDirectory = this.configuration.getPluginDirectory();
if (pluginDirectory != null) {
File[] pluginFiles = pluginDirectory.listFiles();
if (pluginFiles != null) {
for (File pluginFile : pluginFiles) {
if (pluginFile.getName().endsWith(".jar")) {
URL pluginUrl = pluginFile.toURI().toURL();
try {
ServerPluginDescriptorType descriptor;
descriptor = ServerPluginDescriptorUtil.loadPluginDescriptorFromUrl(pluginUrl);
if (descriptor != null) {
log.debug("pre-loaded server plugin from URL: " + pluginUrl);
plugins.put(pluginUrl, descriptor);
}
} catch (Throwable t) {
// for some reason, the plugin failed to load - it will be ignored
log.error("Plugin at [" + pluginUrl + "] could not be pre-loaded. Ignoring it.", t);
}
}
}
}
}
return plugins;
}
/**
* This will return a list of plugin keys that represent all the plugins that are to be
* disabled. If a plugin jar is found on the filesystem, its plugin key should be checked with
* this "blacklist" - if its key is found, that plugin should be disabled.
*
* @return names of "blacklisted" plugins that should not be started (i.e. loaded as a disabled plugin)
*/
protected List<PluginKey> getDisabledPluginKeys() {
List<PluginKey> disabledPlugins = LookupUtil.getServerPluginManager().getServerPluginKeysByEnabled(false);
return disabledPlugins;
}
/**
* Creates the individual plugin containers that can be used to deploy different plugin types.
*
* <p>This is protected to allow subclasses to override the PCs that are created by this service (mainly to support tests).</p>
*
* @return the new plugin containers created by this method
*/
protected List<AbstractTypeServerPluginContainer> createPluginContainers() {
ArrayList<AbstractTypeServerPluginContainer> pcs = new ArrayList<AbstractTypeServerPluginContainer>(5);
pcs.add(new GenericServerPluginContainer(this));
pcs.add(new ContentServerPluginContainer(this));
pcs.add(new AlertServerPluginContainer(this));
pcs.add(new BundleServerPluginContainer(this));
pcs.add(new PackageTypeServerPluginContainer(this));
pcs.add(new DriftServerPluginContainer(this));
return pcs;
}
/**
* Create the root classloader that will be the ancester to all plugin classloaders.
*
* @return the root server plugin classloader
*/
protected ClassLoader createRootServerPluginClassLoader() {
ClassLoader thisClassLoader = this.getClass().getClassLoader();
String classesToHideRegexStr = this.configuration.getRootServerPluginClassLoaderRegex();
RootServerPluginClassLoader root = new RootServerPluginClassLoader(null, thisClassLoader, classesToHideRegexStr);
return root;
}
/**
* Creates the manager that will be responsible for instantiating plugin classloaders.
* @param plugins maps plugin URLs with their parsed descriptors
* @param rootClassLoader the classloader at the top of the classloader hierarchy
* @param tmpDir where the classloaders can write out the jars that are embedded in the plugin jars
*
* @return the classloader manager instance
*/
protected ClassLoaderManager createClassLoaderManager(Map<URL, ? extends ServerPluginDescriptorType> plugins,
ClassLoader rootClassLoader, File tmpDir) {
return new ClassLoaderManager(plugins, rootClassLoader, tmpDir);
}
/**
* Some schedule jobs may want to run on all machines in the RHQ cluster. We can't use our
* normal persistent, clustered scheduler for these jobs. Instead, each master plugin container
* running in each RHQ server will have their own internal scheduler that can be used to run
* jobs for this purpose.
*
* @throws Exception if failed to initialize the internal scheduler
*/
protected void initializeNonClusteredScheduler() throws Exception {
Properties schedulerConfig = new Properties();
schedulerConfig.setProperty(StdSchedulerFactory.PROP_JOB_STORE_CLASS, RAMJobStore.class.getName());
schedulerConfig.setProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, "RHQServerPluginsJobs");
schedulerConfig.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getName());
schedulerConfig.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_PREFIX + ".threadCount", "5");
schedulerConfig.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_PREFIX + ".threadNamePrefix",
"RHQServerPluginsJob");
StdSchedulerFactory factory = new StdSchedulerFactory();
factory.initialize(schedulerConfig);
Scheduler scheduler = factory.getScheduler();
scheduler.start();
this.nonClusteredSchedulerFactory = factory;
return;
}
/**
* This will stop the internal, non-clustered scheduler running in the master plugin container.
* This tells all jobs to shut down.
*/
protected void shutdownNonClusteredScheduler() {
if (this.nonClusteredSchedulerFactory != null) {
try {
Scheduler scheduler = this.nonClusteredSchedulerFactory.getScheduler();
if (scheduler != null) {
scheduler.shutdown(false);
}
} catch (Exception e) {
log.warn("Failed to shutdown master plugin container nonclustered scheduler", e);
} finally {
this.nonClusteredSchedulerFactory = null;
}
}
return;
}
/**
* Some schedule jobs may want to run on all machines in the RHQ cluster. We can't use our
* normal persistent, clustered scheduler for these jobs. Instead, each master plugin container
* running in each RHQ server will have their own internal scheduler that can be used to run
* jobs for this purpose.
*
* @throws Exception if failed to obtain the internal scheduler
*
* @see #getClusteredScheduler()
*/
protected EnhancedScheduler getNonClusteredScheduler() throws Exception {
if (this.nonClusteredSchedulerFactory == null) {
throw new NullPointerException("The non-clustered scheduler has not be initialized");
}
return new EnhancedSchedulerImpl(this.nonClusteredSchedulerFactory.getScheduler());
}
/**
* Most jobs need to be scheduled using the clustered scheduler so they can be run on different
* machines in the RHQ cluster in order to balance the load across the cluster. Use this
* scheduler when scheduling those jobs.
*
* @return the clustered scheduler
*
* @see #getNonClusteredScheduler()
*/
protected EnhancedScheduler getClusteredScheduler() {
return LookupUtil.getSchedulerBean();
}
}