/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 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 Lesser General Public License for more details.
*
* Copyright 2007 - 2009 Pentaho Corporation. All rights reserved.
*
*/
package org.pentaho.platform.scheduler;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.platform.api.engine.IScheduledJob;
import org.pentaho.platform.api.engine.ISubscriptionScheduler;
import org.pentaho.platform.api.engine.SubscriptionSchedulerException;
import org.pentaho.platform.api.repository.ISchedule;
import org.pentaho.platform.scheduler.messages.Messages;
import org.quartz.CronExpression;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
// TODO sbarkdull, this class should be logging, possibly at the info level,
// all operations like create, update, delete, suspend, and resume, and include the time the operation occurred.
/**
* Provides the interface between the Quartz Scheduling component and the Subscription
* Subsystem
*
* @author dmoran
*
*/
public class QuartzSubscriptionScheduler implements ISubscriptionScheduler {
protected static final Log logger = LogFactory.getLog(QuartzSubscriptionScheduler.class);
public static final String GROUP_NAME = Messages.getInstance().getString("QuartzSubscriptionScheduler.GROUP_NAME");
private static final int PAUSE = 0;
private static final int RESUME = 1;
private static final int EXECUTE = 2;
private static final int DELETE = 3;
private static final String exceptionMessages[] = {
Messages.getInstance().getString("QuartzSubscriptionScheduler.USER_UNABLE_TO_PAUSE"), //$NON-NLS-1$
Messages.getInstance().getString("QuartzSubscriptionScheduler.USER_UNABLE_TO_RESUME"), //$NON-NLS-1$
Messages.getInstance().getString("QuartzSubscriptionScheduler.USER_UNABLE_TO_EXECUTE"), //$NON-NLS-1$
Messages.getInstance().getString("QuartzSubscriptionScheduler.USER_UNABLE_TO_DELETE"), //$NON-NLS-1$
};
// TODO sbarkdull, clean up the exception communication
/**
* Synchronizes The Scheduler schedule with the subscription schedule. Returns the scheduled job or null
* if the job was deleted
*
* @throws SubscriptionSchedulerException
*/
// TODO sbarkdull really need to throw a SchedulerBadCronStringException when Cron string is bad,
// will disambiguate for the client why the exception was throw
public IScheduledJob syncSchedule(final String oldScheduleReference, final ISchedule newSchedule) throws SubscriptionSchedulerException {
try {
Scheduler scheduler = QuartzSystemListener.getSchedulerInstance();
if (oldScheduleReference != null) {
scheduler.deleteJob(oldScheduleReference, QuartzSubscriptionScheduler.GROUP_NAME);
}
// Delete
if (newSchedule == null) {
return (null);
}
Trigger trigger = createTriggerFromSchedule( newSchedule );
trigger.setDescription(newSchedule.getGroup() + " : " + newSchedule.getDescription()); //$NON-NLS-1$
trigger.setMisfireInstruction(Trigger.MISFIRE_INSTRUCTION_SMART_POLICY);
// Delete just in case some old one was left lying around with this name
scheduler.deleteJob(newSchedule.getScheduleReference(), QuartzSubscriptionScheduler.GROUP_NAME);
JobDetail jobDetail = new JobDetail(newSchedule.getScheduleReference(), QuartzSubscriptionScheduler.GROUP_NAME,
QuartzSubscriptionJob.class);
jobDetail.setDescription(newSchedule.getGroup() + " : " + newSchedule.getDescription()); //$NON-NLS-1$
scheduler.scheduleJob(jobDetail, trigger);
return (new QuartzScheduledJob(trigger));
} catch (SchedulerException e ) {
throw new SubscriptionSchedulerException( Messages.getInstance().getErrorString("QuartzSubscriptionScheduler.ERROR_0421_SYNC_SCHED_FAILED0"), e ); //$NON-NLS-1$
} catch (ParseException e ) {
throw new SubscriptionSchedulerException( Messages.getInstance().getErrorString("QuartzSubscriptionScheduler.ERROR_0422_SYNC_SCHED_FAILED1", newSchedule.getCronString() ), e ); //$NON-NLS-1$
}
}
/**
* Returns a list of exception messages
*/
public List syncSchedule(final List newSchedules) throws Exception {
List exceptionList = new ArrayList();
if (newSchedules == null) {
return (exceptionList);
}
Scheduler scheduler = QuartzSystemListener.getSchedulerInstance();
HashSet jobSet = new HashSet(Arrays.asList(scheduler.getJobNames(QuartzSubscriptionScheduler.GROUP_NAME)));
// Add/modify the good schedules
for (int i = 0; i < newSchedules.size(); ++i) {
ISchedule sched = (ISchedule) newSchedules.get(i);
try {
syncSchedule(sched.getScheduleReference(), sched);
} catch (Throwable t) {
exceptionList.add(Messages.getInstance().getString(
"QuartzSubscriptionScheduler.ERROR_SCHEDULING", sched.getScheduleReference(), t.getLocalizedMessage())); //$NON-NLS-1$
}
jobSet.remove(sched.getScheduleReference());
}
// Now delete the left overs
for (Iterator it = jobSet.iterator(); it.hasNext();) {
scheduler.deleteJob((String) it.next(), QuartzSubscriptionScheduler.GROUP_NAME);
}
return (exceptionList);
}
/**
* NOTE: doesn't actually throw any checked exceptions
* @throws SchedulerException
* @throws SubscriptionSchedulerException
*/
public Map<String,IScheduledJob> getScheduledJobMap() throws SchedulerException, SubscriptionSchedulerException {
Map<String,IScheduledJob> jobMap = new HashMap<String,IScheduledJob>();
Scheduler scheduler = QuartzSystemListener.getSchedulerInstance();
String jobs[] = scheduler.getJobNames(QuartzSubscriptionScheduler.GROUP_NAME);
for (String jobName : jobs) {
Trigger t = scheduler.getTrigger(jobName, QuartzSubscriptionScheduler.GROUP_NAME);
if ( null != t ) {
jobMap.put(jobName, new QuartzScheduledJob(t));
} else {
throw new SubscriptionSchedulerException( Messages.getInstance().getErrorString("QuartzSubscriptionScheduler.ERROR_0423_GET_JOB_MAP_FAILED", jobName ) ); //$NON-NLS-1$
}
}
return (jobMap);
}
public IScheduledJob getScheduledJob(final String schedRef) throws SubscriptionSchedulerException {
Scheduler scheduler = QuartzSystemListener.getSchedulerInstance();
Trigger trigger;
try {
trigger = scheduler.getTrigger(schedRef, QuartzSubscriptionScheduler.GROUP_NAME);
} catch (SchedulerException e) {
throw new SubscriptionSchedulerException( Messages.getInstance().getErrorString("QuartzSubscriptionScheduler.ERROR_0424_FAILED_TO_GET_JOB_WITH_NAME", schedRef ), e ); //$NON-NLS-1$
}
return (new QuartzScheduledJob( trigger ));
}
public List<QuartzScheduledJob> getScheduledJobs() {
List<QuartzScheduledJob> jobList = new ArrayList<QuartzScheduledJob>();
try {
Scheduler scheduler = QuartzSystemListener.getSchedulerInstance();
String jobs[] = scheduler.getJobNames(QuartzSubscriptionScheduler.GROUP_NAME);
for (String element : jobs) {
jobList.add(new QuartzScheduledJob(scheduler.getTrigger(element, QuartzSubscriptionScheduler.GROUP_NAME)));
}
} catch (SchedulerException se) {
QuartzSubscriptionScheduler.logger.error(null, se);
} catch (Exception e) {
QuartzSubscriptionScheduler.logger.error(null, e);
}
return (jobList);
}
/**
*
* @param cmd String the command
* @param triggerName String the name of the trigger to apply the command to
* @return
* @throws Exception
*/
private IScheduledJob doJob(final int cmd, final String triggerName) throws Exception {
Scheduler scheduler = QuartzSystemListener.getSchedulerInstance();
Trigger trigger = scheduler.getTrigger(triggerName, QuartzSubscriptionScheduler.GROUP_NAME);
if (trigger == null) {
throw new Exception(QuartzSubscriptionScheduler.exceptionMessages[cmd] + triggerName);
}
switch (cmd) {
case PAUSE: {
scheduler.pauseJob(triggerName, QuartzSubscriptionScheduler.GROUP_NAME);
break;
}
case RESUME: {
scheduler.resumeJob(triggerName, QuartzSubscriptionScheduler.GROUP_NAME);
break;
}
case EXECUTE: {
scheduler.triggerJob(triggerName, QuartzSubscriptionScheduler.GROUP_NAME);
break;
}
case DELETE: {
logger.error( Messages.getInstance().getErrorString("QuartzSubscriptionScheduler.ERROR_0425_FAILED_TO_DELETE_SCHEDULE", triggerName ) ); //$NON-NLS-1$
scheduler.deleteJob(triggerName, QuartzSubscriptionScheduler.GROUP_NAME);
break;
}
default:
return (null);
}
return (new QuartzScheduledJob(trigger));
}
public IScheduledJob pauseJob(final String jobName) throws Exception {
return (doJob(QuartzSubscriptionScheduler.PAUSE, jobName));
}
public IScheduledJob resumeJob(final String jobName) throws Exception {
return (doJob(QuartzSubscriptionScheduler.RESUME, jobName));
}
public IScheduledJob executeJob(final String jobName) throws Exception {
return (doJob(QuartzSubscriptionScheduler.EXECUTE, jobName));
}
public IScheduledJob deleteJob(final String triggerName) throws Exception {
return (doJob(QuartzSubscriptionScheduler.DELETE, triggerName));
}
public IScheduledJob scheduleJob(final ISchedule schedule) throws Exception {
if (schedule == null) {
return (null);
}
return (syncSchedule(schedule.getScheduleReference(), schedule));
}
public int getSchedulerState() throws Exception {
Scheduler scheduler = QuartzSystemListener.getSchedulerInstance();
if (scheduler.isInStandbyMode()) {
return (IScheduledJob.STATE_PAUSED );
} else if (scheduler.isShutdown()) {
return (IScheduledJob.STATE_NONE );
}
return (IScheduledJob.STATE_NORMAL );
}
public void pauseScheduler() throws Exception {
Scheduler scheduler = QuartzSystemListener.getSchedulerInstance();
scheduler.standby();
}
public void resumeScheduler() throws Exception {
Scheduler scheduler = QuartzSystemListener.getSchedulerInstance();
scheduler.start();
}
public String getCronSummary(final String cron) throws Exception {
return (new CronExpression(cron).getExpressionSummary());
}
// TODO belongs in a subscription schedule helper or util class
/**
* @throws ParseException if the schedule is a cron schedule, and the cron string is invalid
*/
public static Trigger createTriggerFromSchedule( ISchedule sched ) throws ParseException {
Trigger trigger = null;
if ( sched.isCronSchedule() ) {
trigger = new CronTrigger( sched.getScheduleReference(), QuartzSubscriptionScheduler.GROUP_NAME,
sched.getCronString() );
} else if ( sched.isRepeatSchedule() ) {
int repeatCount = null == sched.getRepeatCount()
? SimpleTrigger.REPEAT_INDEFINITELY
: sched.getRepeatCount();
trigger = new SimpleTrigger( sched.getScheduleReference(), QuartzSubscriptionScheduler.GROUP_NAME,
repeatCount, sched.getRepeatInterval() );
} else {
throw new IllegalStateException( Messages.getInstance().getErrorString("QuartzSubscriptionScheduler.ERROR_0420_MISSING_CRON_AND_REPEAT_INTERVAL", sched.getId() ) ); //$NON-NLS-1$
}
if ( null != sched.getStartDate() ) {
trigger.setStartTime( sched.getStartDate() );
}
if ( null != sched.getEndDate() ) {
trigger.setEndTime( sched.getEndDate() );
}
return trigger;
}
}