/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.sys.batch.service.impl; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.batch.BatchJobStatus; import org.kuali.kfs.sys.batch.BatchSpringContext; import org.kuali.kfs.sys.batch.Job; import org.kuali.kfs.sys.batch.JobDescriptor; import org.kuali.kfs.sys.batch.JobListener; import org.kuali.kfs.sys.batch.ScheduleStep; import org.kuali.kfs.sys.batch.SimpleTriggerDescriptor; import org.kuali.kfs.sys.batch.Step; import org.kuali.kfs.sys.batch.service.SchedulerService; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.service.BatchModuleService; import org.kuali.kfs.sys.service.impl.KfsModuleServiceImpl; import org.kuali.rice.core.api.datetime.DateTimeService; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.krad.service.KualiModuleService; import org.kuali.rice.krad.service.MailService; import org.kuali.rice.krad.service.ModuleService; import org.quartz.CronExpression; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.ObjectAlreadyExistsException; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.Trigger; import org.quartz.UnableToInterruptJobException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.transaction.annotation.Transactional; @Transactional public class SchedulerServiceImpl implements SchedulerService { private static final Logger LOG = Logger.getLogger(SchedulerServiceImpl.class); protected static final String SOFT_DEPENDENCY_CODE = "softDependency"; protected static final String HARD_DEPENDENCY_CODE = "hardDependency"; private Scheduler scheduler; private JobListener jobListener; private KualiModuleService kualiModuleService; private ParameterService parameterService; private DateTimeService dateTimeService; private MailService mailService; /** * Holds a list of job name to job descriptor mappings for those jobs that are externalized (i.e. the module service is responsible for reporting their status) */ protected Map<String, JobDescriptor> externalizedJobDescriptors; protected static final List<String> jobStatuses = new ArrayList<String>(); static { jobStatuses.add(SCHEDULED_JOB_STATUS_CODE); jobStatuses.add(SUCCEEDED_JOB_STATUS_CODE); jobStatuses.add(CANCELLED_JOB_STATUS_CODE); jobStatuses.add(RUNNING_JOB_STATUS_CODE); jobStatuses.add(FAILED_JOB_STATUS_CODE); } public SchedulerServiceImpl() { externalizedJobDescriptors = new HashMap<String, JobDescriptor>(); } /** * @see org.kuali.kfs.sys.batch.service.SchedulerService#initialize() */ @Override public void initialize() { LOG.info("Initializing the schedule"); jobListener.setSchedulerService(this); try { scheduler.addGlobalJobListener(jobListener); } catch (SchedulerException e) { throw new RuntimeException("SchedulerServiceImpl encountered an exception when trying to register the global job listener", e); } JobDescriptor jobDescriptor; for (ModuleService moduleService : kualiModuleService.getInstalledModuleServices()) { initializeJobsForModule(moduleService); initializeTriggersForModule(moduleService); } dropDependenciesNotScheduled(); } /** * Initializes all of the jobs into Quartz for the given ModuleService * @param moduleService the ModuleService implementation to initalize jobs for */ protected void initializeJobsForModule(ModuleService moduleService) { if ( LOG.isInfoEnabled() ) { LOG.info("Loading scheduled jobs for: " + moduleService.getModuleConfiguration().getNamespaceCode()); } JobDescriptor jobDescriptor; if ( moduleService.getModuleConfiguration().getJobNames() != null ) { for (String jobName : moduleService.getModuleConfiguration().getJobNames()) { try { if (moduleService instanceof BatchModuleService && ((BatchModuleService) moduleService).isExternalJob(jobName)) { jobDescriptor = new JobDescriptor(); jobDescriptor.setBeanName(jobName); jobDescriptor.setGroup(SCHEDULED_GROUP); jobDescriptor.setDurable(false); externalizedJobDescriptors.put(jobName, jobDescriptor); } else { jobDescriptor = BatchSpringContext.getJobDescriptor(jobName); } jobDescriptor.setNamespaceCode(moduleService.getModuleConfiguration().getNamespaceCode()); loadJob(jobDescriptor); } catch (NoSuchBeanDefinitionException ex) { LOG.error("unable to find job bean definition for job: " + ex.getBeanName()); } catch ( Exception ex ) { LOG.error( "Unable to install " + jobName + " job into scheduler.", ex ); } } } } /** * Loops through all the triggers associated with the given module service, adding each trigger to Quartz * @param moduleService the ModuleService instance to initialize triggers for */ protected void initializeTriggersForModule(ModuleService moduleService) { if ( moduleService.getModuleConfiguration().getTriggerNames() != null ) { for (String triggerName : moduleService.getModuleConfiguration().getTriggerNames()) { try { addTrigger(BatchSpringContext.getTriggerDescriptor(triggerName).getTrigger()); } catch (NoSuchBeanDefinitionException ex) { LOG.error("unable to find trigger definition: " + ex.getBeanName()); } catch ( Exception ex ) { LOG.error( "Unable to install " + triggerName + " trigger into scheduler.", ex ); } } } } protected void loadJob(JobDescriptor jobDescriptor) { JobDetail jobDetail = jobDescriptor.getJobDetail(); addJob(jobDetail); if (SCHEDULED_GROUP.equals(jobDetail.getGroup())) { jobDetail.setGroup(UNSCHEDULED_GROUP); addJob(jobDetail); } } /** * Remove dependencies that are not scheduled. Important for modularization if some * modules arn't loaded or if institutions don't schedule some dependencies */ protected void dropDependenciesNotScheduled() { try { List<String> scheduledGroupJobNames = Arrays.asList(scheduler.getJobNames(SCHEDULED_GROUP)); for (String jobName : scheduledGroupJobNames) { JobDescriptor jobDescriptor = BatchSpringContext.getJobDescriptor(jobName); if (jobDescriptor != null && jobDescriptor.getDependencies() != null) { // dependenciesToBeRemoved so to avoid ConcurrentModificationException ArrayList<Entry<String, String>> dependenciesToBeRemoved = new ArrayList<Entry<String, String>>(); Set<Entry<String, String>> dependenciesSet = jobDescriptor.getDependencies().entrySet(); for (Entry<String, String> dependency : dependenciesSet) { String dependencyJobName = dependency.getKey(); if (!scheduledGroupJobNames.contains(dependencyJobName)) { LOG.warn("Removing dependency " + dependencyJobName + " from " + jobName + " because it is not scheduled."); dependenciesToBeRemoved.add(dependency); } } dependenciesSet.removeAll(dependenciesToBeRemoved); } } } catch (SchedulerException e) { throw new RuntimeException("Caught exception while trying to drop dependencies that are not scheduled", e); } } /** * @see org.kuali.kfs.sys.batch.service.SchedulerService#initializeJob(java.lang.String,org.kuali.kfs.sys.batch.Job) */ @Override public void initializeJob(String jobName, Job job) { job.setSchedulerService(this); job.setParameterService(parameterService); job.setSteps(BatchSpringContext.getJobDescriptor(jobName).getSteps()); job.setDateTimeService(dateTimeService); } /** * @see org.kuali.kfs.sys.batch.service.SchedulerService#hasIncompleteJob() */ @Override public boolean hasIncompleteJob() { StringBuilder log = new StringBuilder("The schedule has incomplete jobs."); boolean hasIncompleteJob = false; for (String scheduledJobName : getJobNamesForScheduleJob() ) { JobDetail scheduledJobDetail = getScheduledJobDetail(scheduledJobName); boolean jobIsIncomplete = isIncomplete(scheduledJobDetail); if (jobIsIncomplete) { log.append("\n\t").append(scheduledJobDetail.getFullName()); hasIncompleteJob = true; } } if (hasIncompleteJob) { LOG.info(log); } return hasIncompleteJob; } protected boolean isIncomplete(JobDetail scheduledJobDetail) { if ( scheduledJobDetail == null ) { return false; } return !SCHEDULE_JOB_NAME.equals(scheduledJobDetail.getName()) && (isPending(scheduledJobDetail) || isScheduled(scheduledJobDetail)); } /** * @see org.kuali.kfs.sys.batch.service.SchedulerService#isPastScheduleCutoffTime() */ @Override public boolean isPastScheduleCutoffTime() { return isPastScheduleCutoffTime(dateTimeService.getCurrentCalendar(), true); } protected boolean isPastScheduleCutoffTime(Calendar dateTime, boolean log) { try { Date scheduleCutoffTimeTemp = scheduler.getTriggersOfJob(SCHEDULE_JOB_NAME, SCHEDULED_GROUP)[0].getPreviousFireTime(); Calendar scheduleCutoffTime; if (scheduleCutoffTimeTemp == null) { scheduleCutoffTime = dateTimeService.getCurrentCalendar(); } else { scheduleCutoffTime = dateTimeService.getCalendar(scheduleCutoffTimeTemp); } String cutoffParameter = parameterService.getParameterValueAsString(ScheduleStep.class, KFSConstants.SystemGroupParameterNames.BATCH_SCHEDULE_CUTOFF_TIME); String[] scheduleStepCutoffTime = StringUtils.split(cutoffParameter, ":"); if ( scheduleStepCutoffTime.length != 3 && scheduleStepCutoffTime.length != 4 ) { throw new IllegalArgumentException( "Error! The " + KFSConstants.SystemGroupParameterNames.BATCH_SCHEDULE_CUTOFF_TIME + " parameter had an invalid value: " + cutoffParameter ); } // if there are 4 components, then we have an AM/PM delimiter // otherwise, assume 24-hour time if ( scheduleStepCutoffTime.length == 4 ) { int hour = Integer.parseInt(scheduleStepCutoffTime[0]); // need to adjust for meaning of hour if ( hour == 12 ) { hour = 0; } else { hour--; } scheduleCutoffTime.set(Calendar.HOUR, hour ); if ( StringUtils.containsIgnoreCase(scheduleStepCutoffTime[3], "AM") ) { scheduleCutoffTime.set(Calendar.AM_PM, Calendar.AM); } else { scheduleCutoffTime.set(Calendar.AM_PM, Calendar.PM); } } else { scheduleCutoffTime.set(Calendar.HOUR_OF_DAY, Integer.parseInt(scheduleStepCutoffTime[0])); } scheduleCutoffTime.set(Calendar.MINUTE, Integer.parseInt(scheduleStepCutoffTime[1])); scheduleCutoffTime.set(Calendar.SECOND, Integer.parseInt(scheduleStepCutoffTime[2])); if (parameterService.getParameterValueAsBoolean(ScheduleStep.class, KFSConstants.SystemGroupParameterNames.BATCH_SCHEDULE_CUTOFF_TIME_IS_NEXT_DAY)) { scheduleCutoffTime.add(Calendar.DAY_OF_YEAR, 1); } boolean isPastScheduleCutoffTime = dateTime.after(scheduleCutoffTime); if (log) { LOG.info(new StringBuilder("isPastScheduleCutoffTime=").append(isPastScheduleCutoffTime).append(" : ").append(dateTimeService.toDateTimeString(dateTime.getTime())).append(" / ").append(dateTimeService.toDateTimeString(scheduleCutoffTime.getTime()))); } return isPastScheduleCutoffTime; } catch (NumberFormatException e) { throw new RuntimeException("Caught exception while checking whether we've exceeded the schedule cutoff time", e); } catch (SchedulerException e) { throw new RuntimeException("Caught exception while checking whether we've exceeded the schedule cutoff time", e); } } /** * @see org.kuali.kfs.sys.batch.service.SchedulerService#processWaitingJobs() */ @Override public void processWaitingJobs() { for ( String scheduledJobName : getJobNamesForScheduleJob() ) { JobDetail jobDetail = getScheduledJobDetail(scheduledJobName); if (isPending(jobDetail)) { if (shouldScheduleJob(jobDetail)) { scheduleJob(SCHEDULED_GROUP, scheduledJobName, 0, 0, new Date(), null, Collections.singletonMap(Job.MASTER_JOB_NAME, SCHEDULE_JOB_NAME) ); } if (shouldCancelJob(jobDetail)) { updateStatus(SCHEDULED_GROUP, scheduledJobName, CANCELLED_JOB_STATUS_CODE); } } } } /** * @see org.kuali.kfs.sys.batch.service.SchedulerService#logScheduleResults() */ @Override public void logScheduleResults() { StringBuilder scheduleResults = new StringBuilder("The schedule completed."); for ( String scheduledJobName : getJobNamesForScheduleJob() ) { JobDetail jobDetail = getScheduledJobDetail(scheduledJobName); if ( jobDetail != null && !SCHEDULE_JOB_NAME.equals(jobDetail.getName())) { scheduleResults.append("\n\t").append(jobDetail.getName()).append("=").append(getStatus(jobDetail)); } } LOG.info(scheduleResults); } /** * @see org.kuali.kfs.sys.batch.service.SchedulerService#shouldNotRun(org.quartz.JobDetail) */ @Override public boolean shouldNotRun(JobDetail jobDetail) { if (SCHEDULED_GROUP.equals(jobDetail.getGroup())) { if (isCancelled(jobDetail)) { if ( LOG.isInfoEnabled() ) { LOG.info("Telling listener not to run job, because it has been cancelled: " + jobDetail.getName()); } return true; } else { for (String dependencyJobName : getJobDependencies(jobDetail.getName()).keySet()) { if (!isDependencySatisfiedPositively(jobDetail, getScheduledJobDetail(dependencyJobName))) { if ( LOG.isInfoEnabled() ) { LOG.info("Telling listener not to run job, because a dependency has not been satisfied positively: "+jobDetail.getName()+" (dependency job = "+dependencyJobName+")"); } return true; } } } } return false; } /** * @see org.kuali.kfs.sys.batch.service.SchedulerService#updateStatus(org.quartz.JobDetail,java.lang.String jobStatus) */ @Override public void updateStatus(JobDetail jobDetail, String jobStatus) { if ( LOG.isInfoEnabled() ) { LOG.info("Updating status of job: "+jobDetail.getName()+"="+jobStatus); } jobDetail.getJobDataMap().put(JOB_STATUS_PARAMETER, jobStatus); } @Override public void runJob(String jobName, String requestorEmailAddress) { runJob(jobName, 0, 0, new Date(), requestorEmailAddress); } @Override public void runJob(String jobName, int startStep, int stopStep, Date startTime, String requestorEmailAddress) { runJob(UNSCHEDULED_GROUP, jobName, startStep, stopStep, startTime, requestorEmailAddress); } @Override public void runJob(String groupName, String jobName, int startStep, int stopStep, Date jobStartTime, String requestorEmailAddress) { if ( LOG.isInfoEnabled() ) { LOG.info("Executing user initiated job: " + groupName + "." + jobName + " (startStep=" + startStep + " / stopStep=" + stopStep + " / startTime=" + jobStartTime + " / requestorEmailAddress=" + requestorEmailAddress + ")"); } try { JobDetail jobDetail = scheduler.getJobDetail(jobName, groupName); scheduleJob(groupName, jobName, startStep, stopStep, jobStartTime, requestorEmailAddress, null); } catch (SchedulerException ex) { throw new RuntimeException("Unable to run a job directly", ex); } } public void runStep(String groupName, String jobName, String stepName, Date startTime, String requestorEmailAddress) { if ( LOG.isInfoEnabled() ) { LOG.info("Executing user initiated step: " + stepName + " / requestorEmailAddress=" + requestorEmailAddress); } // abort if the step is already running if (isJobRunning(jobName)) { LOG.warn("Attempt to run job already executing, aborting"); return; } int stepNum = 1; boolean stepFound = false; BatchJobStatus job = getJob(groupName, jobName); for (Step step : job.getSteps()) { if (step.getName().equals(stepName)) { stepFound = true; break; } stepNum++; } if (stepFound) { runJob(groupName, jobName, stepNum, stepNum, startTime, requestorEmailAddress); } else { LOG.warn("Unable to find step " + stepName + " in job " + groupName + "." + jobName); } } @Override public boolean isJobRunning(String jobName) { List<JobExecutionContext> runningJobs = getRunningJobs(); for (JobExecutionContext jobCtx : runningJobs) { if (jobCtx.getJobDetail().getName().equals(jobName)) { return true; } } return false; } protected void addJob(JobDetail jobDetail) { try { if ( LOG.isInfoEnabled() ) { LOG.info("Adding job: " + jobDetail.getFullName()); } scheduler.addJob(jobDetail, true); } catch (SchedulerException e) { throw new RuntimeException("Caught exception while adding job: " + jobDetail.getFullName(), e); } } protected void addTrigger(Trigger trigger) { try { if (UNSCHEDULED_GROUP.equals(trigger.getGroup())) { LOG.error("Triggers should not be specified for jobs in the unscheduled group - not adding trigger: " + trigger.getName()); } else { LOG.info("Adding trigger: " + trigger.getName()); try { scheduler.scheduleJob(trigger); } catch (ObjectAlreadyExistsException ex) { } } } catch (SchedulerException e) { throw new RuntimeException("Caught exception while adding trigger: " + trigger.getFullName(), e); } } protected void scheduleJob(String groupName, String jobName, int startStep, int endStep, Date startTime, String requestorEmailAddress, Map<String,String> additionalJobData ) { try { updateStatus(groupName, jobName, SchedulerService.SCHEDULED_JOB_STATUS_CODE); SimpleTriggerDescriptor trigger = new SimpleTriggerDescriptor(jobName, groupName, jobName, dateTimeService); trigger.setStartTime(startTime); Trigger qTrigger = trigger.getTrigger(); qTrigger.getJobDataMap().put(JobListener.REQUESTOR_EMAIL_ADDRESS_KEY, requestorEmailAddress); qTrigger.getJobDataMap().put(Job.JOB_RUN_START_STEP, String.valueOf(startStep)); qTrigger.getJobDataMap().put(Job.JOB_RUN_END_STEP, String.valueOf(endStep)); if ( additionalJobData != null ) { qTrigger.getJobDataMap().putAll(additionalJobData); } for (Trigger oldTrigger : scheduler.getTriggersOfJob(jobName, groupName)) { scheduler.unscheduleJob(oldTrigger.getName(), groupName); } scheduler.scheduleJob(qTrigger); } catch (SchedulerException e) { throw new RuntimeException("Caught exception while scheduling job: " + jobName, e); } } protected boolean shouldScheduleJob(JobDetail jobDetail) { try { if (scheduler.getTriggersOfJob(jobDetail.getName(), SCHEDULED_GROUP).length > 0) { return false; } for (String dependencyJobName : getJobDependencies(jobDetail.getName()).keySet()) { JobDetail dependencyJobDetail = getScheduledJobDetail(dependencyJobName); if ( dependencyJobDetail == null ) { LOG.error( "Unable to get JobDetail for dependency of " + jobDetail.getName() + " : " + dependencyJobName ); return false; } if (!isDependencySatisfiedPositively(jobDetail, dependencyJobDetail)) { return false; } } } catch (SchedulerException se) { throw new RuntimeException("Caught scheduler exception while determining whether to schedule job: " + jobDetail.getName(), se); } return true; } protected boolean shouldCancelJob(JobDetail jobDetail) { LOG.info("shouldCancelJob:::::: " + jobDetail.getFullName()); if ( jobDetail == null ) { return true; } for (String dependencyJobName : getJobDependencies(jobDetail.getName()).keySet()) { LOG.info("dependencyJobName:::::" + dependencyJobName); JobDetail dependencyJobDetail = getScheduledJobDetail(dependencyJobName); if (isDependencySatisfiedNegatively(jobDetail, dependencyJobDetail)) { return true; } } return false; } protected boolean isDependencySatisfiedPositively(JobDetail dependentJobDetail, JobDetail dependencyJobDetail) { if ( dependentJobDetail == null || dependencyJobDetail == null ) { return false; } return isSucceeded(dependencyJobDetail) || ((isFailed(dependencyJobDetail) || isCancelled(dependencyJobDetail)) && isSoftDependency(dependentJobDetail.getName(), dependencyJobDetail.getName())); } protected boolean isDependencySatisfiedNegatively(JobDetail dependentJobDetail, JobDetail dependencyJobDetail) { LOG.info("isDependencySatisfiedNegatively:::: dependentJobDetail::: " + dependencyJobDetail.getFullName() + " dependencyJobDetail " + dependencyJobDetail.getFullName() ); if ( dependentJobDetail == null || dependencyJobDetail == null ) { return true; } return (isFailed(dependencyJobDetail) || isCancelled(dependencyJobDetail)) && !isSoftDependency(dependentJobDetail.getName(), dependencyJobDetail.getName()); } protected boolean isSoftDependency(String dependentJobName, String dependencyJobName) { return SOFT_DEPENDENCY_CODE.equals(getJobDependencies(dependentJobName).get(dependencyJobName)); } protected Map<String, String> getJobDependencies(String jobName) { LOG.info("getJobDependencies:::: for job " + jobName); return BatchSpringContext.getJobDescriptor(jobName).getDependencies(); } protected boolean isPending(JobDetail jobDetail) { return getStatus(jobDetail) == null; } protected boolean isScheduled(JobDetail jobDetail) { return SCHEDULED_JOB_STATUS_CODE.equals(getStatus(jobDetail)); } protected boolean isSucceeded(JobDetail jobDetail) { return SUCCEEDED_JOB_STATUS_CODE.equals(getStatus(jobDetail)); } protected boolean isFailed(JobDetail jobDetail) { return FAILED_JOB_STATUS_CODE.equals(getStatus(jobDetail)); } protected boolean isCancelled(JobDetail jobDetail) { return CANCELLED_JOB_STATUS_CODE.equals(getStatus(jobDetail)); } @Override public String getStatus(JobDetail jobDetail) { if ( jobDetail == null ) { return FAILED_JOB_STATUS_CODE; } KfsModuleServiceImpl moduleService = (KfsModuleServiceImpl) SpringContext.getBean(KualiModuleService.class).getResponsibleModuleServiceForJob(jobDetail.getName()); //If the module service has status information for a job, get the status from it //else get status from job detail data map return (moduleService!=null && moduleService.isExternalJob(jobDetail.getName())) ? moduleService.getExternalJobStatus(jobDetail.getName()) : jobDetail.getJobDataMap().getString(SchedulerServiceImpl.JOB_STATUS_PARAMETER); } protected JobDetail getScheduledJobDetail(String jobName) { LOG.info("getScheduledJobDetail ::::::: " + jobName); try { JobDetail jobDetail = scheduler.getJobDetail(jobName, SCHEDULED_GROUP); if ( jobDetail == null ) { LOG.error( "Unable to obtain the job details for the scheduled version of: " + jobName ); } return jobDetail; } catch (SchedulerException e) { throw new RuntimeException("Caught scheduler exception while getting job detail: " + jobName, e); } } /** * Sets the scheduler attribute value. * * @param scheduler The scheduler to set. */ @Override public void setScheduler(Scheduler scheduler) { this.scheduler = scheduler; } public void setParameterService(ParameterService parameterService) { this.parameterService = parameterService; } /** * Sets the dateTimeService attribute value. * * @param dateTimeService The dateTimeService to set. */ public void setDateTimeService(DateTimeService dateTimeService) { this.dateTimeService = dateTimeService; } /** * Sets the moduleService attribute value. * * @param moduleService The moduleService to set. */ public void setKualiModuleService(KualiModuleService moduleService) { this.kualiModuleService = moduleService; } /** * Sets the jobListener attribute value. * * @param jobListener The jobListener to set. */ public void setJobListener(JobListener jobListener) { this.jobListener = jobListener; } @Override public List<BatchJobStatus> getJobs() { ArrayList<BatchJobStatus> jobs = new ArrayList<BatchJobStatus>(); try { for (String jobGroup : scheduler.getJobGroupNames()) { for (String jobName : scheduler.getJobNames(jobGroup)) { try { JobDescriptor jobDescriptor = retrieveJobDescriptor(jobName); JobDetail jobDetail = scheduler.getJobDetail(jobName, jobGroup); jobs.add(new BatchJobStatus(jobDescriptor, jobDetail)); } catch (NoSuchBeanDefinitionException ex) { // do nothing, ignore jobs not defined in spring LOG.warn("Attempt to find bean " + jobGroup + "." + jobName + " failed - not in Spring context"); } } } } catch (SchedulerException ex) { throw new RuntimeException("Exception while obtaining job list", ex); } return jobs; } @Override public BatchJobStatus getJob(String groupName, String jobName) { for (BatchJobStatus job : getJobs()) { if (job.getName().equals(jobName) && job.getGroup().equals(groupName)) { return job; } } return null; } @Override public List<BatchJobStatus> getJobs(String groupName) { ArrayList<BatchJobStatus> jobs = new ArrayList<BatchJobStatus>(); try { for (String jobName : scheduler.getJobNames(groupName)) { try { JobDescriptor jobDescriptor = retrieveJobDescriptor(jobName); JobDetail jobDetail = scheduler.getJobDetail(jobName, groupName); jobs.add(new BatchJobStatus(jobDescriptor, jobDetail)); } catch (NoSuchBeanDefinitionException ex) { // do nothing, ignore jobs not defined in spring LOG.warn("Attempt to find bean " + groupName + "." + jobName + " failed - not in Spring context"); } } } catch (SchedulerException ex) { throw new RuntimeException("Exception while obtaining job list", ex); } return jobs; } @Override public List<JobExecutionContext> getRunningJobs() { try { List<JobExecutionContext> jobContexts = scheduler.getCurrentlyExecutingJobs(); return jobContexts; } catch (SchedulerException ex) { throw new RuntimeException("Unable to get list of running jobs.", ex); } } protected void updateStatus(String groupName, String jobName, String jobStatus) { try { JobDetail jobDetail = scheduler.getJobDetail(jobName, groupName); updateStatus(jobDetail, jobStatus); scheduler.addJob(jobDetail, true); } catch (SchedulerException e) { throw new RuntimeException(new StringBuilder("Caught scheduler exception while updating job status: ").append(jobName).append(", ").append(jobStatus).toString(), e); } } @Override public void removeScheduled(String jobName) { try { scheduler.deleteJob(jobName, SCHEDULED_GROUP); } catch (SchedulerException ex) { throw new RuntimeException("Unable to remove scheduled job: " + jobName, ex); } } @Override public void addScheduled(JobDetail job) { try { job.setGroup(SCHEDULED_GROUP); scheduler.addJob(job, true); } catch (SchedulerException ex) { throw new RuntimeException("Unable to add job to scheduled group: " + job.getName(), ex); } } @Override public void addUnscheduled(JobDetail job) { try { job.setGroup(UNSCHEDULED_GROUP); scheduler.addJob(job, true); } catch (SchedulerException ex) { throw new RuntimeException("Unable to add job to unscheduled group: " + job.getName(), ex); } } @Override public List<String> getSchedulerGroups() { try { return Arrays.asList(scheduler.getJobGroupNames()); } catch (SchedulerException ex) { throw new RuntimeException("Exception while obtaining job list", ex); } } @Override public List<String> getJobStatuses() { return jobStatuses; } @Override public void interruptJob(String jobName) { List<JobExecutionContext> runningJobs = getRunningJobs(); for (JobExecutionContext jobCtx : runningJobs) { if (jobName.equals(jobCtx.getJobDetail().getName())) { // if so... try { ((Job) jobCtx.getJobInstance()).interrupt(); } catch (UnableToInterruptJobException ex) { LOG.warn("Unable to perform job interrupt", ex); } break; } } } @Override public Date getNextStartTime(BatchJobStatus job) { try { Trigger[] triggers = scheduler.getTriggersOfJob(job.getName(), job.getGroup()); Date nextDate = new Date(Long.MAX_VALUE); for (Trigger trigger : triggers) { if (trigger.getNextFireTime() != null){ if (trigger.getNextFireTime().getTime() < nextDate.getTime()) { nextDate = trigger.getNextFireTime(); } } } if (nextDate.getTime() == Long.MAX_VALUE) { nextDate = null; } return nextDate; } catch (SchedulerException ex) { } return null; } @Override public Date getNextStartTime(String groupName, String jobName) { BatchJobStatus job = getJob(groupName, jobName); return getNextStartTime(job); } public void setMailService(MailService mailService) { this.mailService = mailService; } protected JobDescriptor retrieveJobDescriptor(String jobName) { if (externalizedJobDescriptors.containsKey(jobName)) { return externalizedJobDescriptors.get(jobName); } return BatchSpringContext.getJobDescriptor(jobName); } ThreadLocal<List<String>> jobNamesForScheduleJob = new ThreadLocal<List<String>>(); protected List<String> getJobNamesForScheduleJob() { List<String> jobNames = new ArrayList<String>(); try { for (String scheduledJobName : scheduler.getJobNames(SCHEDULED_GROUP)) { if (scheduler.getTriggersOfJob(scheduledJobName, SCHEDULED_GROUP).length == 0) { // jobs that have their own triggers will not be included in the master scheduleJob jobNames.add( scheduledJobName ); } } } catch (Exception ex) { LOG.error("Error occurred while initializing job name list", ex); } return jobNames; } @Override public void reinitializeScheduledJobs() { try { for (String scheduledJobName : getJobNamesForScheduleJob() ) { updateStatus(SCHEDULED_GROUP, scheduledJobName, null); } } catch (Exception e) { LOG.error("Error occurred while trying to reinitialize jobs", e); } } @Override public boolean cronConditionMet(String cronExpressionString) { boolean cronConditionMet = false; CronExpression cronExpression; try { cronExpression = new CronExpression(cronExpressionString); Date currentDate = dateTimeService.getCurrentDate(); Date validTimeAfter = cronExpression.getNextValidTimeAfter(dateTimeService.getCurrentDate()); if (validTimeAfter != null) { String cronDate = dateTimeService.toString(validTimeAfter, KFSConstants.MONTH_DAY_YEAR_DATE_FORMAT); if (cronDate.equals(dateTimeService.toString(currentDate, KFSConstants.MONTH_DAY_YEAR_DATE_FORMAT))){ cronConditionMet = true; } } else { LOG.error("Null date returned when calling CronExpression.nextValidTimeAfter() for cronExpression: " + cronExpressionString); } } catch (ParseException ex) { LOG.error("Error parsing cronExpression: " + cronExpressionString, ex); } return cronConditionMet; } }