/*
* 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.util.Collection;
import java.util.Collections;
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.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.Scheduler;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.plugin.PluginKey;
import org.rhq.enterprise.server.scheduler.EnhancedScheduler;
import org.rhq.enterprise.server.xmlschema.CronScheduleType;
import org.rhq.enterprise.server.xmlschema.PeriodicScheduleType;
import org.rhq.enterprise.server.xmlschema.ScheduledJobDefinition;
/**
* The abstract superclass for all plugin containers of the different {@link ServerPluginType plugin types}.
*
* @author John Mazzitelli
*/
public abstract class AbstractTypeServerPluginContainer {
private final Log log = LogFactory.getLog(this.getClass());
private final MasterServerPluginContainer master;
private ServerPluginManager pluginManager;
/**
* Maps the time (in epoch milliseconds) when plugins were loaded in this plugin container.
*/
private final Map<PluginKey, Long> loadedTimestamps;
/**
* Instantiates the plugin container. All subclasses must support this and only this
* constructor.
*
* @param master the master plugin container that is creating this instance.
*/
public AbstractTypeServerPluginContainer(MasterServerPluginContainer master) {
this.master = master;
this.loadedTimestamps = Collections.synchronizedMap(new HashMap<PluginKey, Long>());
}
/**
* Each plugin container will tell the master which plugins it can support via this method; this
* method returns the type of plugin that the plugin container can process. Only one
* plugin container can support a plugin type.
*
* @return the type of plugin that this plugin container instance can support
*/
public abstract ServerPluginType getSupportedServerPluginType();
/**
* Returns the master plugin container that is responsible for managing this instance.
*
* @return this plugin container's master
*/
public MasterServerPluginContainer getMasterServerPluginContainer() {
return this.master;
}
/**
* Returns the object that manages the plugins.
*
* @return the plugin manager for this container
*/
public ServerPluginManager getPluginManager() {
return this.pluginManager;
}
/**
* Determines if the given plugin is loaded in the plugin container.
* The plugin may be loaded but not enabled.
*
* @param pluginKey
*
* @return <code>true</code> if the plugin is loaded in this plugin container; <code>false</code> otherwise
*/
public boolean isPluginLoaded(PluginKey pluginKey) {
return this.pluginManager.isPluginLoaded(pluginKey.getPluginName());
}
/**
* Determines if the given plugin is enabled in the plugin container.
* <code>true</code> implies the plugin is also loaded. If <code>false</code> is returned,
* it is either because the plugin is loaded but disabled, or the plugin is just
* not loaded. Use {@link #isPluginLoaded(PluginKey)} to know if the plugin is loaded or not.
*
* @param pluginKey
*
* @return <code>true</code> if the plugin is enabled in this plugin container; <code>false</code> otherwise
*/
public boolean isPluginEnabled(PluginKey pluginKey) {
return this.pluginManager.isPluginEnabled(pluginKey.getPluginName());
}
/**
* Given a plugin key, this returns the time (in epoch milliseconds) when that plugin was loaded into
* this plugin container.
*
* @param pluginKey identifies the plugin whose load time is to be returned
* @return the epoch millis timestamp when the plugin was loaded; <code>null</code> if the plugin is not loaded
*/
public Long getPluginLoadTime(PluginKey pluginKey) {
return this.loadedTimestamps.get(pluginKey);
}
/**
* The initialize method that prepares the plugin container. This should get the plugin
* container ready to accept plugins.
*
* Subclasses are free to perform additional tasks by overriding this method.
*
* @throws Exception if the plugin container failed to initialize for some reason
*/
public synchronized void initialize() throws Exception {
log.debug("Server plugin container initializing");
this.pluginManager = createPluginManager();
this.pluginManager.initialize();
}
/**
* This method informs the plugin container that all of its plugins have been loaded.
* Once this is called, the plugin container can assume all plugins that it will
* ever know about have been {@link #loadPlugin(ServerPluginEnvironment, boolean)} loaded}.
*/
public synchronized void start() {
log.debug("Server plugin container starting");
this.pluginManager.startPlugins();
return;
}
/**
* This will inform the plugin container that it must stop doing its work. Once called,
* the plugin container must assume that soon it will be asked to {@link #shutdown()}.
*/
public synchronized void stop() {
log.debug("Server plugin container stopping");
this.pluginManager.stopPlugins();
return;
}
/**
* The shutdown method that will stop and unload all plugins.
*
* Subclasses are free to perform additional tasks by overriding this method.
*/
public synchronized void shutdown() {
log.debug("Server plugin container shutting down");
if (this.pluginManager != null) {
Collection<ServerPluginEnvironment> envs = this.pluginManager.getPluginEnvironments();
for (ServerPluginEnvironment env : envs) {
try {
unloadPlugin(env.getPluginKey());
} catch (Exception e) {
this.log.warn("Failed to unload plugin [" + env.getPluginKey().getPluginName() + "].", e);
}
}
try {
this.pluginManager.shutdown();
} finally {
this.pluginManager = null;
}
}
return;
}
/**
* Informs the plugin container that it has a plugin that it must begin to start managing.
*
* @param env the plugin environment, including the plugin jar and its descriptor
* @param enabled <code>true</code> if the plugin should be initialized; <code>false</code> if
* the plugin's existence should be noted but it should not be initialized or started
*
* @throws Exception if failed to load the plugin
*/
public synchronized void loadPlugin(ServerPluginEnvironment env, boolean enabled) throws Exception {
if (this.pluginManager != null) {
this.pluginManager.loadPlugin(env, enabled);
this.loadedTimestamps.put(env.getPluginKey(), System.currentTimeMillis());
} else {
throw new Exception("Cannot load a plugin; plugin container is not initialized yet");
}
}
/**
* Informs the plugin container that a plugin should be unloaded and any of its resources
* should be released.
*
* @param pluginKey identifies the plugin that should be shutdown
*
* @throws Exception if failed to unload the plugin
*/
public synchronized void unloadPlugin(PluginKey pluginKey) throws Exception {
if (this.pluginManager != null) {
this.pluginManager.unloadPlugin(pluginKey.getPluginName(), false);
this.loadedTimestamps.remove(pluginKey);
} else {
throw new Exception("Cannot unload a plugin; plugin container has been shutdown");
}
}
/**
* Informs the plugin container that a plugin should be reloaded and any of its resources
* should be started if being enabled.
*
* @param pluginKey identifies the plugin that should be reloaded
* @param enabled indicates if the plugin should be enabled or disabled after being loaded
*
* @throws Exception if failed to unload the plugin
*/
public synchronized void reloadPlugin(PluginKey pluginKey, boolean enabled) throws Exception {
if (this.pluginManager != null) {
this.pluginManager.reloadPlugin(pluginKey.getPluginName(), enabled);
this.loadedTimestamps.put(pluginKey, System.currentTimeMillis());
} else {
throw new Exception("Cannot reload a plugin; plugin container has been shutdown");
}
}
/**
* If a plugin has scheduled jobs, this method will schedule them now.
* This particular method implementation schedules the global jobs as defined in the
* plugin descriptors.
*
* Subclasses are free to extend this method to schedule additional plugin jobs, but must
* ensure they call this method so the global scheduled jobs get added to the scheduler.
*
* Note that this is separate from the {@link #start()} method because it is possible that
* the plugin container has been started before the scheduler has. In this case, the caller
* must wait for the scheduler to be started before this method is called to schedule jobs.
*
* @throws Exception if failed to schedule jobs
*/
public synchronized void scheduleAllPluginJobs() throws Exception {
if (this.pluginManager != null) {
for (ServerPluginEnvironment pluginEnv : this.pluginManager.getPluginEnvironments()) {
schedulePluginJobs(pluginEnv.getPluginKey());
}
} else {
throw new Exception("Cannot schedule plugins jobs; plugin container is not initialized yet");
}
return;
}
public synchronized void schedulePluginJobs(PluginKey pluginKey) throws Exception {
if (this.pluginManager != null) {
try {
String pluginName = pluginKey.getPluginName();
if (this.pluginManager.isPluginEnabled(pluginName)) {
ServerPluginEnvironment pluginEnv = this.pluginManager.getPluginEnvironment(pluginName);
if (pluginEnv != null) {
ServerPluginContext serverPluginContext = this.pluginManager.getServerPluginContext(pluginEnv);
List<ScheduledJobDefinition> jobs = serverPluginContext.getSchedules();
if (jobs != null) {
for (ScheduledJobDefinition job : jobs) {
try {
scheduleJob(job, pluginKey);
} catch (Throwable t) {
log.warn("Failed to schedule job [" + job + "] for server plugin [" + pluginKey
+ "]", t);
}
}
}
} else {
log.warn("Failed to get server plugin env for [" + pluginKey + "]; cannot schedule jobs");
}
}
} catch (Throwable t) {
log.warn("Failed to get scheduled jobs for server plugin [" + pluginKey + "]", t);
}
} else {
throw new Exception("Cannot schedule plugins jobs for server plugin [" + pluginKey
+ "]; plugin container is not initialized yet");
}
return;
}
/**
* Unschedules any plugin jobs that are currently scheduled for the named plugin.
*
* Subclasses are free to extend this method to unschedule those additional plugin jobs
* they created, but must ensure they call this method so the global scheduled jobs get
* removed from the scheduler.
*
* Note that this is separate from the {@link #stop()} method because we never want
* to unschedule jobs since other plugin containers on other servers may be running
* and able to process the jobs. This method should only be called when a plugin
* is being disabled or removed.
*
* @param pluginKey
* @throws Exception if failed to unschedule jobs
*/
public void unschedulePluginJobs(PluginKey pluginKey) throws Exception {
// note: all jobs for a plugin are placed in the same group, where the group name is the plugin name
String groupName = pluginKey.getPluginName();
EnhancedScheduler clusteredScheduler = getMasterServerPluginContainer().getClusteredScheduler();
EnhancedScheduler nonclusteredScheduler = getMasterServerPluginContainer().getNonClusteredScheduler();
for (Scheduler scheduler : new Scheduler[] { clusteredScheduler, nonclusteredScheduler }) {
String[] jobNames = scheduler.getJobNames(groupName);
if (jobNames != null) {
for (String jobName : jobNames) {
boolean deleted = scheduler.deleteJob(jobName, groupName);
if (deleted) {
log.info("Job [" + jobName + "] for plugin [" + pluginKey + "] has been unscheduled!");
} else {
log.warn("Job [" + jobName + "] for plugin [" + pluginKey + "] failed to be unscheduled!");
}
}
}
}
return;
}
/**
* Invokes a control operation on a given plugin and returns the results. This method blocks until
* the plugin component completes the invocation.
*
* @param pluginKey identifies the plugin whose control operation is to be invoked
* @param controlName identifies the name of the control operation to invoke
* @param params parameters to pass to the control operation; may be <code>null</code>
* @return the results of the invocation
*
* @throws Exception if failed to obtain the plugin component and invoke the control. This usually means an
* abnormal error occurred - if the control operation merely failed to do what it needed to do,
* the error will be reported in the returned results, not as a thrown exception.
*/
public ControlResults invokePluginControl(PluginKey pluginKey, String controlName, Configuration params)
throws Exception {
if (this.pluginManager != null) {
String pluginName = pluginKey.getPluginName();
if (this.pluginManager.isPluginEnabled(pluginName)) {
ServerPluginEnvironment pluginEnv = this.pluginManager.getPluginEnvironment(pluginName);
if (pluginEnv != null) {
ServerPluginComponent pluginComponent = this.pluginManager.getServerPluginComponent(pluginName);
if (pluginComponent != null) {
log.debug("Invoking control [" + controlName + "] on server plugin [" + pluginKey + "]");
ClassLoader originalContextClassLoader = Thread.currentThread().getContextClassLoader();
try {
ControlFacet controlFacet = (ControlFacet) pluginComponent; // let it throw ClassCastException when appropriate
Thread.currentThread().setContextClassLoader(pluginEnv.getPluginClassLoader());
ControlResults results = controlFacet.invoke(controlName, params);
return results;
} catch (Throwable t) {
throw new Exception("Failed to invoke control operation [" + controlName
+ "] for server plugin [" + pluginKey + "]", t);
} finally {
Thread.currentThread().setContextClassLoader(originalContextClassLoader);
}
} else {
throw new Exception("Cannot invoke control operation [" + controlName + "] for server plugin ["
+ pluginKey + "]; failed to get server plugin component");
}
} else {
throw new Exception("Cannot invoke control operation [" + controlName + "] for server plugin ["
+ pluginKey + "]; failed to get server plugin environment");
}
} else {
throw new Exception("Cannot invoke control operation [" + controlName + "] for server plugin ["
+ pluginKey + "]; plugin is not enabled");
}
} else {
throw new Exception("Cannot invoke control operation [" + controlName + "] for server plugin [" + pluginKey
+ "]; plugin container is not initialized yet");
}
}
/**
* This will be called when its time for this plugin container to create its plugin manager.
* Subclasses are free to override this if they need their own specialized plugin manager.
*
* @return the plugin manager for use by this plugin container
*/
protected ServerPluginManager createPluginManager() {
return new ServerPluginManager(this);
}
/**
* Returns the logger that can be used to log messages. A convienence object so all
* subclasses don't have to explicitly declare and create their own.
*
* @return this instance's logger object
*/
protected Log getLog() {
return this.log;
}
/**
* Schedules a job for periodic execution. Note that if the <code>schedule</code> indicates the
* job is not enabled, this method returns immediately as a no-op.
*
* @param schedule instructs how the job should be scheduled
* @param pluginKey the key of the plugin scheduling the job
*
* @throws Exception if failed to schedule the job
*/
protected void scheduleJob(ScheduledJobDefinition schedule, PluginKey pluginKey) throws Exception {
if (!schedule.isEnabled()) {
return;
}
String groupName = pluginKey.getPluginName();
boolean rescheduleIfExists = true; // just in case the parameters change, we'll always want to reschedule it if it exists
boolean isVolatile = true; // if plugin is removed, this allows for the schedule to go away upon restart automatically
// determine which quartz job class we should be using, based on the concurrency needs of the schedule
Class<? extends Job> jobClass;
if (schedule.getScheduleType().isConcurrent()) {
jobClass = ConcurrentJobWrapper.class;
} else {
jobClass = StatefulJobWrapper.class;
}
// build the data map for the job, setting some values we need, plus adding the callback data for the plugin itself
JobDataMap jobData = new JobDataMap();
jobData.put(AbstractJobWrapper.DATAMAP_PLUGIN_NAME, pluginKey.getPluginName());
jobData.put(AbstractJobWrapper.DATAMAP_PLUGIN_TYPE, pluginKey.getPluginType());
jobData.put(AbstractJobWrapper.DATAMAP_JOB_ID, schedule.getJobId());
jobData.put(AbstractJobWrapper.DATAMAP_SCHEDULE_TYPE, schedule.getScheduleType().getTypeName());
jobData.put(AbstractJobWrapper.DATAMAP_SCHEDULE_TRIGGER, schedule.getScheduleType().getScheduleTrigger());
jobData.putAsString(AbstractJobWrapper.DATAMAP_IS_CONCURRENT, schedule.getScheduleType().isConcurrent());
jobData.putAsString(AbstractJobWrapper.DATAMAP_IS_CLUSTERED, schedule.getScheduleType().isClustered());
jobData.put(AbstractJobWrapper.DATAMAP_JOB_METHOD_NAME, schedule.getMethodName());
if (schedule.getClassName() != null) {
jobData.put(AbstractJobWrapper.DATAMAP_JOB_CLASS, schedule.getClassName());
}
if (schedule.getCallbackData() != null) {
jobData.putAll(schedule.getCallbackData());
}
// schedule the job now
EnhancedScheduler scheduler;
if (schedule.getScheduleType().isClustered()) {
scheduler = getMasterServerPluginContainer().getClusteredScheduler();
} else {
scheduler = getMasterServerPluginContainer().getNonClusteredScheduler();
}
if (schedule.getScheduleType() instanceof CronScheduleType) {
String cronExpression = ((CronScheduleType) schedule.getScheduleType()).getCronExpression();
log.info("Scheduling server plugin cron job: jobName=" + schedule.getJobId() + ", groupName=" + groupName
+ ", jobClass=" + jobClass + ", cron=" + cronExpression);
scheduler.scheduleCronJob(schedule.getJobId(), groupName, jobData, jobClass, rescheduleIfExists,
isVolatile, cronExpression);
} else {
long initialDelay = 10000L; // arbitrary - wait a small bit of time before triggering the job
long interval = ((PeriodicScheduleType) schedule.getScheduleType()).getPeriod();
log.info("Scheduling server plugin periodic job: jobName=" + schedule.getJobId() + ", groupName="
+ groupName + ", jobClass=" + jobClass + ", interval=" + interval);
scheduler.scheduleRepeatingJob(schedule.getJobId(), groupName, jobData, jobClass, rescheduleIfExists,
isVolatile, initialDelay, interval);
}
return;
}
}