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