001 /*
002 * Copyright 2011 The Kuali Foundation.
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.kfs.sys.batch.service.impl;
017
018 import java.util.ArrayList;
019 import java.util.Arrays;
020 import java.util.Calendar;
021 import java.util.Date;
022 import java.util.HashMap;
023 import java.util.List;
024 import java.util.Map;
025
026 import org.apache.commons.lang.StringUtils;
027 import org.apache.log4j.Logger;
028 import org.kuali.kfs.sys.KFSConstants;
029 import org.kuali.kfs.sys.batch.BatchJobStatus;
030 import org.kuali.kfs.sys.batch.BatchSpringContext;
031 import org.kuali.kfs.sys.batch.Job;
032 import org.kuali.kfs.sys.batch.JobDescriptor;
033 import org.kuali.kfs.sys.batch.JobListener;
034 import org.kuali.kfs.sys.batch.ScheduleStep;
035 import org.kuali.kfs.sys.batch.SimpleTriggerDescriptor;
036 import org.kuali.kfs.sys.batch.Step;
037 import org.kuali.kfs.sys.batch.service.SchedulerService;
038 import org.kuali.kfs.sys.context.SpringContext;
039 import org.kuali.kfs.sys.service.BatchModuleService;
040 import org.kuali.kfs.sys.service.impl.KfsModuleServiceImpl;
041 import org.kuali.rice.kns.service.DateTimeService;
042 import org.kuali.rice.kns.service.KualiModuleService;
043 import org.kuali.rice.kns.service.MailService;
044 import org.kuali.rice.kns.service.ModuleService;
045 import org.kuali.rice.kns.service.ParameterService;
046 import org.quartz.JobDetail;
047 import org.quartz.JobExecutionContext;
048 import org.quartz.ObjectAlreadyExistsException;
049 import org.quartz.Scheduler;
050 import org.quartz.SchedulerException;
051 import org.quartz.Trigger;
052 import org.quartz.UnableToInterruptJobException;
053 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
054 import org.springframework.transaction.annotation.Transactional;
055
056 @Transactional
057 public class SchedulerServiceImpl implements SchedulerService {
058 private static final Logger LOG = Logger.getLogger(SchedulerServiceImpl.class);
059 protected static final String SCHEDULE_JOB_NAME = "scheduleJob";
060 public static final String JOB_STATUS_PARAMETER = "status";
061 protected static final String SOFT_DEPENDENCY_CODE = "softDependency";
062 protected static final String HARD_DEPENDENCY_CODE = "hardDependency";
063
064 private Scheduler scheduler;
065 private JobListener jobListener;
066 private KualiModuleService kualiModuleService;
067 private ParameterService parameterService;
068 private DateTimeService dateTimeService;
069 private MailService mailService;
070 /**
071 * 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)
072 */
073 protected Map<String, JobDescriptor> externalizedJobDescriptors;
074
075 protected static final List<String> jobStatuses = new ArrayList<String>();
076
077 static {
078 jobStatuses.add(SCHEDULED_JOB_STATUS_CODE);
079 jobStatuses.add(SUCCEEDED_JOB_STATUS_CODE);
080 jobStatuses.add(CANCELLED_JOB_STATUS_CODE);
081 jobStatuses.add(RUNNING_JOB_STATUS_CODE);
082 jobStatuses.add(FAILED_JOB_STATUS_CODE);
083 }
084
085 public SchedulerServiceImpl() {
086 externalizedJobDescriptors = new HashMap<String, JobDescriptor>();
087 }
088
089 /**
090 * @see org.kuali.kfs.sys.batch.service.SchedulerService#initialize()
091 */
092 public void initialize() {
093 LOG.info("Initializing the schedule");
094 jobListener.setSchedulerService(this);
095 try {
096 scheduler.addGlobalJobListener(jobListener);
097 } catch (SchedulerException e) {
098 throw new RuntimeException("SchedulerServiceImpl encountered an exception when trying to register the global job listener", e);
099 }
100 for (ModuleService moduleService : kualiModuleService.getInstalledModuleServices()) {
101 initializeJobsForModule(moduleService);
102 initializeTriggersForModule(moduleService);
103 }
104 }
105 /**
106 * Initializes all of the jobs into Quartz for the given ModuleService
107 * @param moduleService the ModuleService implementation to initalize jobs for
108 */
109 protected void initializeJobsForModule(ModuleService moduleService) {
110 if ( LOG.isInfoEnabled() ) {
111 LOG.info("Loading scheduled jobs for: " + moduleService.getModuleConfiguration().getNamespaceCode());
112 }
113 JobDescriptor jobDescriptor;
114 if ( moduleService.getModuleConfiguration().getJobNames() != null ) {
115 for (String jobName : moduleService.getModuleConfiguration().getJobNames()) {
116 try {
117 if (moduleService instanceof BatchModuleService && ((BatchModuleService) moduleService).isExternalJob(jobName)) {
118 jobDescriptor = new JobDescriptor();
119 jobDescriptor.setBeanName(jobName);
120 jobDescriptor.setGroup(SCHEDULED_GROUP);
121 jobDescriptor.setDurable(false);
122 externalizedJobDescriptors.put(jobName, jobDescriptor);
123 }
124 else {
125 jobDescriptor = BatchSpringContext.getJobDescriptor(jobName);
126 }
127 jobDescriptor.setNamespaceCode(moduleService.getModuleConfiguration().getNamespaceCode());
128 loadJob(jobDescriptor);
129 } catch (NoSuchBeanDefinitionException ex) {
130 LOG.error("unable to find job bean definition for job: " + ex.getBeanName());
131 } catch ( Exception ex ) {
132 LOG.error( "Unable to install " + jobName + " job into scheduler.", ex );
133 }
134 }
135 }
136 }
137
138 /**
139 * Loops through all the triggers associated with the given module service, adding each trigger to Quartz
140 * @param moduleService the ModuleService instance to initialize triggers for
141 */
142 protected void initializeTriggersForModule(ModuleService moduleService) {
143 if ( moduleService.getModuleConfiguration().getTriggerNames() != null ) {
144 for (String triggerName : moduleService.getModuleConfiguration().getTriggerNames()) {
145 try {
146 addTrigger(BatchSpringContext.getTriggerDescriptor(triggerName).getTrigger());
147 } catch (NoSuchBeanDefinitionException ex) {
148 LOG.error("unable to find trigger definition: " + ex.getBeanName());
149 } catch ( Exception ex ) {
150 LOG.error( "Unable to install " + triggerName + " trigger into scheduler.", ex );
151 }
152 }
153 }
154 }
155
156
157 protected void loadJob(JobDescriptor jobDescriptor) {
158 JobDetail jobDetail = jobDescriptor.getJobDetail();
159 addJob(jobDetail);
160 if (SCHEDULED_GROUP.equals(jobDetail.getGroup())) {
161 jobDetail.setGroup(UNSCHEDULED_GROUP);
162 addJob(jobDetail);
163 }
164 }
165
166 /**
167 * @see org.kuali.kfs.sys.batch.service.SchedulerService#initializeJob(java.lang.String,org.kuali.kfs.sys.batch.Job)
168 */
169 public void initializeJob(String jobName, Job job) {
170 job.setSchedulerService(this);
171 job.setParameterService(parameterService);
172 job.setSteps(BatchSpringContext.getJobDescriptor(jobName).getSteps());
173 job.setDateTimeService(dateTimeService);
174 }
175
176 /**
177 * @see org.kuali.kfs.sys.batch.service.SchedulerService#hasIncompleteJob()
178 */
179 public boolean hasIncompleteJob() {
180 try {
181 StringBuffer log = new StringBuffer("The schedule has incomplete jobs.");
182 boolean hasIncompleteJob = false;
183 for (String scheduledJobName : scheduler.getJobNames(SCHEDULED_GROUP)) {
184 JobDetail scheduledJobDetail = getScheduledJobDetail(scheduledJobName);
185 boolean jobIsIncomplete = isIncomplete(scheduledJobDetail);
186 if (jobIsIncomplete) {
187 log.append("\n\t").append(scheduledJobDetail.getFullName());
188 hasIncompleteJob = true;
189 }
190 }
191 if (hasIncompleteJob) {
192 LOG.info(log);
193 }
194 return hasIncompleteJob;
195 }
196 catch (SchedulerException e) {
197 throw new RuntimeException("Caught exception while getting list of jobs to check for incompletes", e);
198 }
199 }
200
201 protected boolean isIncomplete(JobDetail scheduledJobDetail) {
202 if ( scheduledJobDetail == null ) {
203 return false;
204 }
205 try {
206 if (!SCHEDULE_JOB_NAME.equals(scheduledJobDetail.getName()) && (isPending(scheduledJobDetail) || isScheduled(scheduledJobDetail))) {
207 Trigger[] triggersOfJob = scheduler.getTriggersOfJob(scheduledJobDetail.getName(), SCHEDULED_GROUP);
208 if (triggersOfJob.length > 0) {
209 for (int triggerIndex = 0; triggerIndex < triggersOfJob.length; triggerIndex++) {
210 if (triggersOfJob[triggerIndex].getNextFireTime() != null && !isPastScheduleCutoffTime(dateTimeService.getCalendar(triggersOfJob[triggerIndex].getNextFireTime()), false)) {
211 return true;
212 }
213 }
214 }
215 else {
216 return true;
217 }
218 }
219 return false;
220 }
221 catch (SchedulerException e) {
222 throw new RuntimeException("Caught exception while checking job for completeness: " + scheduledJobDetail.getFullName(), e);
223 }
224 }
225
226 /**
227 * @see org.kuali.kfs.sys.batch.service.SchedulerService#isPastScheduleCutoffTime()
228 */
229 public boolean isPastScheduleCutoffTime() {
230 return isPastScheduleCutoffTime(dateTimeService.getCurrentCalendar(), true);
231 }
232
233 protected boolean isPastScheduleCutoffTime(Calendar dateTime, boolean log) {
234 try {
235 Date scheduleCutoffTimeTemp = scheduler.getTriggersOfJob(SCHEDULE_JOB_NAME, SCHEDULED_GROUP)[0].getPreviousFireTime();
236 Calendar scheduleCutoffTime;
237 if (scheduleCutoffTimeTemp == null) {
238 scheduleCutoffTime = dateTimeService.getCurrentCalendar();
239 }
240 else {
241 scheduleCutoffTime = dateTimeService.getCalendar(scheduleCutoffTimeTemp);
242 }
243 String[] scheduleStepCutoffTime = StringUtils.split(parameterService.getParameterValue(ScheduleStep.class, KFSConstants.SystemGroupParameterNames.BATCH_SCHEDULE_CUTOFF_TIME), ":");
244 scheduleCutoffTime.set(Calendar.HOUR, Integer.parseInt(scheduleStepCutoffTime[0]));
245 scheduleCutoffTime.set(Calendar.MINUTE, Integer.parseInt(scheduleStepCutoffTime[1]));
246 scheduleCutoffTime.set(Calendar.SECOND, Integer.parseInt(scheduleStepCutoffTime[2]));
247 if ("AM".equals(scheduleStepCutoffTime[3].trim())) {
248 scheduleCutoffTime.set(Calendar.AM_PM, Calendar.AM);
249 }
250 else {
251 scheduleCutoffTime.set(Calendar.AM_PM, Calendar.PM);
252 }
253 if (parameterService.getIndicatorParameter(ScheduleStep.class, KFSConstants.SystemGroupParameterNames.BATCH_SCHEDULE_CUTOFF_TIME_IS_NEXT_DAY)) {
254 scheduleCutoffTime.add(Calendar.DAY_OF_YEAR, 1);
255 }
256 boolean isPastScheduleCutoffTime = dateTime.after(scheduleCutoffTime);
257 if (log) {
258 LOG.info(new StringBuffer("isPastScheduleCutoffTime=").append(isPastScheduleCutoffTime).append(" : ").append(dateTimeService.toDateTimeString(dateTime.getTime())).append(" / ").append(dateTimeService.toDateTimeString(scheduleCutoffTime.getTime())));
259 }
260 return isPastScheduleCutoffTime;
261 }
262 catch (NumberFormatException e) {
263 throw new RuntimeException("Caught exception while checking whether we've exceeded the schedule cutoff time", e);
264 }
265 catch (SchedulerException e) {
266 throw new RuntimeException("Caught exception while checking whether we've exceeded the schedule cutoff time", e);
267 }
268 }
269
270 /**
271 * @see org.kuali.kfs.sys.batch.service.SchedulerService#processWaitingJobs()
272 */
273 public void processWaitingJobs() {
274 try {
275 for (String scheduledJobName : scheduler.getJobNames(SCHEDULED_GROUP)) {
276 JobDetail jobDetail = getScheduledJobDetail(scheduledJobName);
277 if (isPending(jobDetail)) {
278 if (shouldScheduleJob(jobDetail)) {
279 scheduleJob(SCHEDULED_GROUP, scheduledJobName, 0, 0, new Date(), null);
280 }
281 if (shouldCancelJob(jobDetail)) {
282 updateStatus(SCHEDULED_GROUP, scheduledJobName, CANCELLED_JOB_STATUS_CODE);
283 }
284 }
285 }
286 }
287 catch (SchedulerException e) {
288 throw new RuntimeException("Caught exception while trying processing waiting jobs", e);
289 }
290 }
291
292 /**
293 * @see org.kuali.kfs.sys.batch.service.SchedulerService#logScheduleResults()
294 */
295 public void logScheduleResults() {
296 StringBuffer scheduleResults = new StringBuffer("The schedule completed.");
297 try {
298 for (String scheduledJobName : scheduler.getJobNames(SCHEDULED_GROUP)) {
299 JobDetail jobDetail = getScheduledJobDetail(scheduledJobName);
300 if ( jobDetail != null && !SCHEDULE_JOB_NAME.equals(jobDetail.getName())) {
301 scheduleResults.append("\n\t").append(jobDetail.getName()).append("=").append(getStatus(jobDetail));
302 }
303 }
304 }
305 catch (SchedulerException e) {
306 throw new RuntimeException("Caught exception while trying to logs schedule results", e);
307 }
308 LOG.info(scheduleResults);
309 }
310
311 /**
312 * @see org.kuali.kfs.sys.batch.service.SchedulerService#shouldNotRun(org.quartz.JobDetail)
313 */
314 public boolean shouldNotRun(JobDetail jobDetail) {
315 if (SCHEDULED_GROUP.equals(jobDetail.getGroup())) {
316 if (isCancelled(jobDetail)) {
317 if ( LOG.isInfoEnabled() ) {
318 LOG.info("Telling listener not to run job, because it has been cancelled: " + jobDetail.getName());
319 }
320 return true;
321 }
322 else {
323 for (String dependencyJobName : getJobDependencies(jobDetail.getName()).keySet()) {
324 if (!isDependencySatisfiedPositively(jobDetail, getScheduledJobDetail(dependencyJobName))) {
325 if ( LOG.isInfoEnabled() ) {
326 LOG.info("Telling listener not to run job, because a dependency has not been satisfied positively: "+jobDetail.getName()+" (dependency job = "+dependencyJobName+")");
327 }
328 return true;
329 }
330 }
331 }
332 }
333 return false;
334 }
335
336 /**
337 * @see org.kuali.kfs.sys.batch.service.SchedulerService#updateStatus(org.quartz.JobDetail,java.lang.String jobStatus)
338 */
339 public void updateStatus(JobDetail jobDetail, String jobStatus) {
340 if ( LOG.isInfoEnabled() ) {
341 LOG.info("Updating status of job: "+jobDetail.getName()+"="+jobStatus);
342 }
343 jobDetail.getJobDataMap().put(JOB_STATUS_PARAMETER, jobStatus);
344 }
345
346 public void runJob(String jobName, String requestorEmailAddress) {
347 runJob(jobName, 0, 0, new Date(), requestorEmailAddress);
348 }
349
350 public void runJob(String jobName, int startStep, int stopStep, Date startTime, String requestorEmailAddress) {
351 runJob(UNSCHEDULED_GROUP, jobName, startStep, stopStep, startTime, requestorEmailAddress);
352 }
353
354 public void runJob(String groupName, String jobName, int startStep, int stopStep, Date jobStartTime, String requestorEmailAddress) {
355 if ( LOG.isInfoEnabled() ) {
356 LOG.info("Executing user initiated job: " + groupName + "." + jobName + " (startStep=" + startStep + " / stopStep=" + stopStep + " / startTime=" + jobStartTime + " / requestorEmailAddress=" + requestorEmailAddress + ")");
357 }
358
359 try {
360 JobDetail jobDetail = scheduler.getJobDetail(jobName, groupName);
361 scheduleJob(groupName, jobName, startStep, stopStep, jobStartTime, requestorEmailAddress);
362 }
363 catch (SchedulerException ex) {
364 throw new RuntimeException("Unable to run a job directly", ex);
365 }
366 }
367
368 public void runStep(String groupName, String jobName, String stepName, Date startTime, String requestorEmailAddress) {
369 if ( LOG.isInfoEnabled() ) {
370 LOG.info("Executing user initiated step: " + stepName + " / requestorEmailAddress=" + requestorEmailAddress);
371 }
372
373 // abort if the step is already running
374 if (isJobRunning(jobName)) {
375 LOG.warn("Attempt to run job already executing, aborting");
376 return;
377 }
378 int stepNum = 1;
379 boolean stepFound = false;
380 BatchJobStatus job = getJob(groupName, jobName);
381 for (Step step : job.getSteps()) {
382 if (step.getName().equals(stepName)) {
383 stepFound = true;
384 break;
385 }
386 stepNum++;
387 }
388 if (stepFound) {
389 runJob(groupName, jobName, stepNum, stepNum, startTime, requestorEmailAddress);
390 }
391 else {
392 LOG.warn("Unable to find step " + stepName + " in job " + groupName + "." + jobName);
393 }
394 }
395
396 public boolean isJobRunning(String jobName) {
397 List<JobExecutionContext> runningJobs = getRunningJobs();
398 for (JobExecutionContext jobCtx : runningJobs) {
399 if (jobCtx.getJobDetail().getName().equals(jobName)) {
400 return true;
401 }
402 }
403 return false;
404 }
405
406 protected void addJob(JobDetail jobDetail) {
407 try {
408 if ( LOG.isInfoEnabled() ) {
409 LOG.info("Adding job: " + jobDetail.getFullName());
410 }
411 scheduler.addJob(jobDetail, true);
412 }
413 catch (SchedulerException e) {
414 throw new RuntimeException("Caught exception while adding job: " + jobDetail.getFullName(), e);
415 }
416 }
417
418 protected void addTrigger(Trigger trigger) {
419 try {
420 if (UNSCHEDULED_GROUP.equals(trigger.getGroup())) {
421 LOG.error("Triggers should not be specified for jobs in the unscheduled group - not adding trigger: " + trigger.getName());
422 }
423 else {
424 LOG.info("Adding trigger: " + trigger.getName());
425 try {
426 scheduler.scheduleJob(trigger);
427 }
428 catch (ObjectAlreadyExistsException ex) {
429 }
430 }
431 }
432 catch (SchedulerException e) {
433 throw new RuntimeException("Caught exception while adding trigger: " + trigger.getFullName(), e);
434 }
435 }
436
437 protected void scheduleJob(String groupName, String jobName, int startStep, int endStep, Date startTime, String requestorEmailAddress) {
438 try {
439 updateStatus(groupName, jobName, SchedulerService.SCHEDULED_JOB_STATUS_CODE);
440 SimpleTriggerDescriptor trigger = new SimpleTriggerDescriptor(jobName, groupName, jobName, dateTimeService);
441 trigger.setStartTime(startTime);
442 Trigger qTrigger = trigger.getTrigger();
443 qTrigger.getJobDataMap().put(JobListener.REQUESTOR_EMAIL_ADDRESS_KEY, requestorEmailAddress);
444 qTrigger.getJobDataMap().put(Job.JOB_RUN_START_STEP, String.valueOf(startStep));
445 qTrigger.getJobDataMap().put(Job.JOB_RUN_END_STEP, String.valueOf(endStep));
446 for (Trigger oldTrigger : scheduler.getTriggersOfJob(jobName, groupName)) {
447 scheduler.unscheduleJob(oldTrigger.getName(), groupName);
448 }
449 scheduler.scheduleJob(qTrigger);
450 }
451 catch (SchedulerException e) {
452 throw new RuntimeException("Caught exception while scheduling job: " + jobName, e);
453 }
454 }
455
456 protected boolean shouldScheduleJob(JobDetail jobDetail) {
457 try {
458 if (scheduler.getTriggersOfJob(jobDetail.getName(), SCHEDULED_GROUP).length > 0) {
459 return false;
460 }
461 for (String dependencyJobName : getJobDependencies(jobDetail.getName()).keySet()) {
462 JobDetail dependencyJobDetail = getScheduledJobDetail(dependencyJobName);
463 if ( dependencyJobDetail == null ) {
464 LOG.error( "Unable to get JobDetail for dependency of " + jobDetail.getName() + " : " + dependencyJobName );
465 return false;
466 }
467 if (!isDependencySatisfiedPositively(jobDetail, dependencyJobDetail)) {
468 return false;
469 }
470 }
471 }
472 catch (SchedulerException se) {
473 throw new RuntimeException("Caught scheduler exception while determining whether to schedule job: " + jobDetail.getName(), se);
474 }
475 return true;
476 }
477
478 protected boolean shouldCancelJob(JobDetail jobDetail) {
479 if ( jobDetail == null ) {
480 return true;
481 }
482 for (String dependencyJobName : getJobDependencies(jobDetail.getName()).keySet()) {
483 JobDetail dependencyJobDetail = getScheduledJobDetail(dependencyJobName);
484 if (isDependencySatisfiedNegatively(jobDetail, dependencyJobDetail)) {
485 return true;
486 }
487 }
488 return false;
489 }
490
491 protected boolean isDependencySatisfiedPositively(JobDetail dependentJobDetail, JobDetail dependencyJobDetail) {
492 if ( dependentJobDetail == null || dependencyJobDetail == null ) {
493 return false;
494 }
495 return isSucceeded(dependencyJobDetail) || ((isFailed(dependencyJobDetail) || isCancelled(dependencyJobDetail)) && isSoftDependency(dependentJobDetail.getName(), dependencyJobDetail.getName()));
496 }
497
498 protected boolean isDependencySatisfiedNegatively(JobDetail dependentJobDetail, JobDetail dependencyJobDetail) {
499 if ( dependentJobDetail == null || dependencyJobDetail == null ) {
500 return true;
501 }
502 return (isFailed(dependencyJobDetail) || isCancelled(dependencyJobDetail)) && !isSoftDependency(dependentJobDetail.getName(), dependencyJobDetail.getName());
503 }
504
505 protected boolean isSoftDependency(String dependentJobName, String dependencyJobName) {
506 return SOFT_DEPENDENCY_CODE.equals(getJobDependencies(dependentJobName).get(dependencyJobName));
507 }
508
509 protected Map<String, String> getJobDependencies(String jobName) {
510 return BatchSpringContext.getJobDescriptor(jobName).getDependencies();
511 }
512
513 protected boolean isPending(JobDetail jobDetail) {
514 return getStatus(jobDetail) == null;
515 }
516
517 protected boolean isScheduled(JobDetail jobDetail) {
518 return SCHEDULED_JOB_STATUS_CODE.equals(getStatus(jobDetail));
519 }
520
521 protected boolean isSucceeded(JobDetail jobDetail) {
522 return SUCCEEDED_JOB_STATUS_CODE.equals(getStatus(jobDetail));
523 }
524
525 protected boolean isFailed(JobDetail jobDetail) {
526 return FAILED_JOB_STATUS_CODE.equals(getStatus(jobDetail));
527 }
528
529 protected boolean isCancelled(JobDetail jobDetail) {
530 return CANCELLED_JOB_STATUS_CODE.equals(getStatus(jobDetail));
531 }
532
533 public String getStatus(JobDetail jobDetail) {
534 if ( jobDetail == null ) {
535 return FAILED_JOB_STATUS_CODE;
536 }
537 KfsModuleServiceImpl moduleService = (KfsModuleServiceImpl)
538 SpringContext.getBean(KualiModuleService.class).getResponsibleModuleServiceForJob(jobDetail.getName());
539 //If the module service has status information for a job, get the status from it
540 //else get status from job detail data map
541 return (moduleService!=null && moduleService.isExternalJob(jobDetail.getName()))
542 ? moduleService.getExternalJobStatus(jobDetail.getName())
543 : jobDetail.getJobDataMap().getString(SchedulerServiceImpl.JOB_STATUS_PARAMETER);
544 }
545
546 protected JobDetail getScheduledJobDetail(String jobName) {
547 try {
548 JobDetail jobDetail = scheduler.getJobDetail(jobName, SCHEDULED_GROUP);
549 if ( jobDetail == null ) {
550 LOG.error( "Unable to obtain the job details for the scheduled version of: " + jobName );
551 }
552 return jobDetail;
553 }
554 catch (SchedulerException e) {
555 throw new RuntimeException("Caught scheduler exception while getting job detail: " + jobName, e);
556 }
557 }
558
559 /**
560 * Sets the scheduler attribute value.
561 *
562 * @param scheduler The scheduler to set.
563 */
564 public void setScheduler(Scheduler scheduler) {
565 this.scheduler = scheduler;
566 }
567
568 public void setParameterService(ParameterService parameterService) {
569 this.parameterService = parameterService;
570 }
571
572 /**
573 * Sets the dateTimeService attribute value.
574 *
575 * @param dateTimeService The dateTimeService to set.
576 */
577 public void setDateTimeService(DateTimeService dateTimeService) {
578 this.dateTimeService = dateTimeService;
579 }
580
581 /**
582 * Sets the moduleService attribute value.
583 *
584 * @param moduleService The moduleService to set.
585 */
586 public void setKualiModuleService(KualiModuleService moduleService) {
587 this.kualiModuleService = moduleService;
588 }
589
590 /**
591 * Sets the jobListener attribute value.
592 *
593 * @param jobListener The jobListener to set.
594 */
595 public void setJobListener(JobListener jobListener) {
596 this.jobListener = jobListener;
597 }
598
599 public List<BatchJobStatus> getJobs() {
600 ArrayList<BatchJobStatus> jobs = new ArrayList<BatchJobStatus>();
601 try {
602 for (String jobGroup : scheduler.getJobGroupNames()) {
603 for (String jobName : scheduler.getJobNames(jobGroup)) {
604 try {
605 JobDescriptor jobDescriptor = retrieveJobDescriptor(jobName);
606 JobDetail jobDetail = scheduler.getJobDetail(jobName, jobGroup);
607 jobs.add(new BatchJobStatus(jobDescriptor, jobDetail));
608 }
609 catch (NoSuchBeanDefinitionException ex) {
610 // do nothing, ignore jobs not defined in spring
611 LOG.warn("Attempt to find bean " + jobGroup + "." + jobName + " failed - not in Spring context");
612 }
613 }
614 }
615 }
616 catch (SchedulerException ex) {
617 throw new RuntimeException("Exception while obtaining job list", ex);
618 }
619 return jobs;
620 }
621
622 public BatchJobStatus getJob(String groupName, String jobName) {
623 for (BatchJobStatus job : getJobs()) {
624 if (job.getName().equals(jobName) && job.getGroup().equals(groupName)) {
625 return job;
626 }
627 }
628 return null;
629 }
630
631 public List<BatchJobStatus> getJobs(String groupName) {
632 ArrayList<BatchJobStatus> jobs = new ArrayList<BatchJobStatus>();
633 try {
634 for (String jobName : scheduler.getJobNames(groupName)) {
635 try {
636 JobDescriptor jobDescriptor = retrieveJobDescriptor(jobName);
637 JobDetail jobDetail = scheduler.getJobDetail(jobName, groupName);
638 jobs.add(new BatchJobStatus(jobDescriptor, jobDetail));
639 }
640 catch (NoSuchBeanDefinitionException ex) {
641 // do nothing, ignore jobs not defined in spring
642 LOG.warn("Attempt to find bean " + groupName + "." + jobName + " failed - not in Spring context");
643 }
644 }
645 }
646 catch (SchedulerException ex) {
647 throw new RuntimeException("Exception while obtaining job list", ex);
648 }
649 return jobs;
650 }
651
652 public List<JobExecutionContext> getRunningJobs() {
653 try {
654 List<JobExecutionContext> jobContexts = scheduler.getCurrentlyExecutingJobs();
655 return jobContexts;
656 }
657 catch (SchedulerException ex) {
658 throw new RuntimeException("Unable to get list of running jobs.", ex);
659 }
660 }
661
662 protected void updateStatus(String groupName, String jobName, String jobStatus) {
663 try {
664 JobDetail jobDetail = scheduler.getJobDetail(jobName, groupName);
665 updateStatus(jobDetail, jobStatus);
666 scheduler.addJob(jobDetail, true);
667 }
668 catch (SchedulerException e) {
669 throw new RuntimeException(new StringBuffer("Caught scheduler exception while updating job status: ").append(jobName).append(", ").append(jobStatus).toString(), e);
670 }
671 }
672
673 public void removeScheduled(String jobName) {
674 try {
675 scheduler.deleteJob(jobName, SCHEDULED_GROUP);
676 }
677 catch (SchedulerException ex) {
678 throw new RuntimeException("Unable to remove scheduled job: " + jobName, ex);
679 }
680 }
681
682 public void addScheduled(JobDetail job) {
683 try {
684 job.setGroup(SCHEDULED_GROUP);
685 scheduler.addJob(job, true);
686 }
687 catch (SchedulerException ex) {
688 throw new RuntimeException("Unable to add job to scheduled group: " + job.getName(), ex);
689 }
690 }
691
692 public void addUnscheduled(JobDetail job) {
693 try {
694 job.setGroup(UNSCHEDULED_GROUP);
695 scheduler.addJob(job, true);
696 }
697 catch (SchedulerException ex) {
698 throw new RuntimeException("Unable to add job to unscheduled group: " + job.getName(), ex);
699 }
700 }
701
702 public List<String> getSchedulerGroups() {
703 try {
704 return Arrays.asList(scheduler.getJobGroupNames());
705 }
706 catch (SchedulerException ex) {
707 throw new RuntimeException("Exception while obtaining job list", ex);
708 }
709 }
710
711 public List<String> getJobStatuses() {
712 return jobStatuses;
713 }
714
715 public void interruptJob(String jobName) {
716 List<JobExecutionContext> runningJobs = getRunningJobs();
717 for (JobExecutionContext jobCtx : runningJobs) {
718 if (jobName.equals(jobCtx.getJobDetail().getName())) {
719 // if so...
720 try {
721 ((Job) jobCtx.getJobInstance()).interrupt();
722 }
723 catch (UnableToInterruptJobException ex) {
724 LOG.warn("Unable to perform job interrupt", ex);
725 }
726 break;
727 }
728 }
729
730 }
731
732 public Date getNextStartTime(BatchJobStatus job) {
733 try {
734 Trigger[] triggers = scheduler.getTriggersOfJob(job.getName(), job.getGroup());
735 Date nextDate = new Date(Long.MAX_VALUE);
736 for (Trigger trigger : triggers) {
737 if (trigger.getNextFireTime().getTime() < nextDate.getTime()) {
738 nextDate = trigger.getNextFireTime();
739 }
740 }
741 if (nextDate.getTime() == Long.MAX_VALUE) {
742 nextDate = null;
743 }
744 return nextDate;
745 }
746 catch (SchedulerException ex) {
747
748 }
749 return null;
750 }
751
752 public Date getNextStartTime(String groupName, String jobName) {
753 BatchJobStatus job = getJob(groupName, jobName);
754
755 return getNextStartTime(job);
756 }
757
758 public void setMailService(MailService mailService) {
759 this.mailService = mailService;
760 }
761
762 protected JobDescriptor retrieveJobDescriptor(String jobName) {
763 if (externalizedJobDescriptors.containsKey(jobName)) {
764 return externalizedJobDescriptors.get(jobName);
765 }
766 return BatchSpringContext.getJobDescriptor(jobName);
767 }
768
769 public void reinitializeScheduledJobs() {
770 try {
771 for (String scheduledJobName : scheduler.getJobNames(SCHEDULED_GROUP)) {
772 if (scheduler.getTriggersOfJob(scheduledJobName, SCHEDULED_GROUP).length == 0) {
773 // jobs that have their own triggers will not be reinited
774 updateStatus(SCHEDULED_GROUP, scheduledJobName, null);
775 }
776 }
777 }
778 catch (Exception e) {
779 LOG.error("Error occurred while trying to reinitialize jobs", e);
780 }
781 }
782 }