View Javadoc

1   /*
2    * Copyright 2007 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.sys.batch.service.impl;
17  
18  import java.util.ArrayList;
19  import java.util.Arrays;
20  import java.util.Calendar;
21  import java.util.Collections;
22  import java.util.Date;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Map.Entry;
27  import java.util.Set;
28  
29  import org.apache.commons.lang.StringUtils;
30  import org.apache.log4j.Logger;
31  import org.kuali.ole.sys.OLEConstants;
32  import org.kuali.ole.sys.batch.BatchJobStatus;
33  import org.kuali.ole.sys.batch.BatchSpringContext;
34  import org.kuali.ole.sys.batch.Job;
35  import org.kuali.ole.sys.batch.JobDescriptor;
36  import org.kuali.ole.sys.batch.JobListener;
37  import org.kuali.ole.sys.batch.ScheduleStep;
38  import org.kuali.ole.sys.batch.SimpleTriggerDescriptor;
39  import org.kuali.ole.sys.batch.Step;
40  import org.kuali.ole.sys.batch.service.SchedulerService;
41  import org.kuali.ole.sys.context.SpringContext;
42  import org.kuali.ole.sys.service.BatchModuleService;
43  import org.kuali.ole.sys.service.impl.OleModuleServiceImpl;
44  import org.kuali.rice.core.api.datetime.DateTimeService;
45  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
46  import org.kuali.rice.krad.service.KualiModuleService;
47  import org.kuali.rice.krad.service.MailService;
48  import org.kuali.rice.krad.service.ModuleService;
49  import org.quartz.JobDetail;
50  import org.quartz.JobExecutionContext;
51  import org.quartz.ObjectAlreadyExistsException;
52  import org.quartz.Scheduler;
53  import org.quartz.SchedulerException;
54  import org.quartz.Trigger;
55  import org.quartz.UnableToInterruptJobException;
56  import org.springframework.beans.factory.NoSuchBeanDefinitionException;
57  import org.springframework.transaction.annotation.Transactional;
58  
59  @Transactional
60  public class SchedulerServiceImpl implements SchedulerService {
61      private static final Logger LOG = Logger.getLogger(SchedulerServiceImpl.class);
62      protected static final String SOFT_DEPENDENCY_CODE = "softDependency";
63      protected static final String HARD_DEPENDENCY_CODE = "hardDependency";
64  
65      protected Scheduler scheduler;
66      protected JobListener jobListener;
67      private KualiModuleService kualiModuleService;
68      protected ParameterService parameterService;
69      protected DateTimeService dateTimeService;
70      private MailService mailService;
71      /**
72       * 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)
73       */
74      protected Map<String, JobDescriptor> externalizedJobDescriptors;
75  
76      protected static final List<String> jobStatuses = new ArrayList<String>();
77  
78      static {
79          jobStatuses.add(SCHEDULED_JOB_STATUS_CODE);
80          jobStatuses.add(SUCCEEDED_JOB_STATUS_CODE);
81          jobStatuses.add(CANCELLED_JOB_STATUS_CODE);
82          jobStatuses.add(RUNNING_JOB_STATUS_CODE);
83          jobStatuses.add(FAILED_JOB_STATUS_CODE);
84      }
85  
86      public SchedulerServiceImpl() {
87          externalizedJobDescriptors = new HashMap<String, JobDescriptor>();
88      }
89      
90      /**
91       * @see org.kuali.ole.sys.batch.service.SchedulerService#initialize()
92       */
93      @Override
94      public void initialize() {
95          LOG.info("Initializing the schedule");
96          jobListener.setSchedulerService(this);
97          try {
98              scheduler.addGlobalJobListener(jobListener);
99          }
100         catch (SchedulerException e) {
101             throw new RuntimeException("SchedulerServiceImpl encountered an exception when trying to register the global job listener", e);
102         }
103         JobDescriptor jobDescriptor;
104         for (ModuleService moduleService : kualiModuleService.getInstalledModuleServices()) {
105             initializeJobsForModule(moduleService);
106             initializeTriggersForModule(moduleService);
107         }
108 
109         dropDependenciesNotScheduled();
110     }
111 
112     /**
113      * Initializes all of the jobs into Quartz for the given ModuleService
114      * @param moduleService the ModuleService implementation to initalize jobs for
115      */
116     protected void initializeJobsForModule(ModuleService moduleService) {
117         if ( LOG.isInfoEnabled() ) {
118             LOG.info("Loading scheduled jobs for: " + moduleService.getModuleConfiguration().getNamespaceCode());
119         }
120         JobDescriptor jobDescriptor;
121         if ( moduleService.getModuleConfiguration().getJobNames() != null ) { 
122             for (String jobName : moduleService.getModuleConfiguration().getJobNames()) {
123                 try {
124                     if (moduleService instanceof BatchModuleService && ((BatchModuleService) moduleService).isExternalJob(jobName)) {
125                         jobDescriptor = new JobDescriptor();
126                         jobDescriptor.setBeanName(jobName);
127                         jobDescriptor.setGroup(SCHEDULED_GROUP);
128                         jobDescriptor.setDurable(false);
129                         externalizedJobDescriptors.put(jobName, jobDescriptor);
130                     }
131                     else {
132                         jobDescriptor = BatchSpringContext.getJobDescriptor(jobName);
133                     }
134                     jobDescriptor.setNamespaceCode(moduleService.getModuleConfiguration().getNamespaceCode());
135                     loadJob(jobDescriptor);
136                 } catch (NoSuchBeanDefinitionException ex) {
137                     LOG.error("unable to find job bean definition for job: " + ex.getBeanName());
138                 } catch ( Exception ex ) {
139                     LOG.error( "Unable to install " + jobName + " job into scheduler.", ex );
140                 }
141             }
142         }
143     }
144 
145     /**
146      * Loops through all the triggers associated with the given module service, adding each trigger to Quartz
147      * @param moduleService the ModuleService instance to initialize triggers for
148      */
149     protected void initializeTriggersForModule(ModuleService moduleService) {
150         if ( moduleService.getModuleConfiguration().getTriggerNames() != null ) {
151             for (String triggerName : moduleService.getModuleConfiguration().getTriggerNames()) {
152                 try {
153                     addTrigger(BatchSpringContext.getTriggerDescriptor(triggerName).getTrigger());
154                 } catch (NoSuchBeanDefinitionException ex) {
155                     LOG.error("unable to find trigger definition: " + ex.getBeanName());
156                 } catch ( Exception ex ) {
157                     LOG.error( "Unable to install " + triggerName + " trigger into scheduler.", ex );
158                 }
159             }
160         }
161     }
162 
163 
164     protected void loadJob(JobDescriptor jobDescriptor) {
165         JobDetail jobDetail = jobDescriptor.getJobDetail();
166         addJob(jobDetail);
167         if (SCHEDULED_GROUP.equals(jobDetail.getGroup())) {
168             jobDetail.setGroup(UNSCHEDULED_GROUP);
169             addJob(jobDetail);
170         }
171     }
172 
173     /**
174      * Remove dependencies that are not scheduled. Important for modularization if some
175      * modules arn't loaded or if institutions don't schedule some dependencies
176      */
177     protected void dropDependenciesNotScheduled() {
178         try {
179             List<String> scheduledGroupJobNames = Arrays.asList(scheduler.getJobNames(SCHEDULED_GROUP));
180 
181             for (String jobName : scheduledGroupJobNames) {
182                 JobDescriptor jobDescriptor = BatchSpringContext.getJobDescriptor(jobName);
183 
184                 if (jobDescriptor != null && jobDescriptor.getDependencies() != null) {
185                     // dependenciesToBeRemoved so to avoid ConcurrentModificationException
186                     ArrayList<Entry<String, String>> dependenciesToBeRemoved = new ArrayList<Entry<String, String>>();
187                     Set<Entry<String, String>> dependenciesSet = jobDescriptor.getDependencies().entrySet();
188                     for (Entry<String, String> dependency : dependenciesSet) {
189                         String dependencyJobName = dependency.getKey();
190                         if (!scheduledGroupJobNames.contains(dependencyJobName)) {
191                             LOG.warn("Removing dependency " + dependencyJobName + " from " + jobName + " because it is not scheduled.");
192                             dependenciesToBeRemoved.add(dependency);
193                         }
194                     }
195                     dependenciesSet.removeAll(dependenciesToBeRemoved);
196                 }
197             }
198         } catch (SchedulerException e) {
199             throw new RuntimeException("Caught exception while trying to drop dependencies that are not scheduled", e);
200         }
201     }
202 
203     /**
204      * @see org.kuali.ole.sys.batch.service.SchedulerService#initializeJob(java.lang.String,org.kuali.ole.sys.batch.Job)
205      */
206     @Override
207     public void initializeJob(String jobName, Job job) {
208         job.setSchedulerService(this);
209         job.setParameterService(parameterService);
210         job.setSteps(BatchSpringContext.getJobDescriptor(jobName).getSteps());
211         job.setDateTimeService(dateTimeService);
212     }
213 
214     /**
215      * @see org.kuali.ole.sys.batch.service.SchedulerService#hasIncompleteJob()
216      */
217     @Override
218     public boolean hasIncompleteJob() {
219         StringBuilder log = new StringBuilder("The schedule has incomplete jobs.");
220         boolean hasIncompleteJob = false;
221         for (String scheduledJobName : getJobNamesForScheduleJob() ) {
222             JobDetail scheduledJobDetail = getScheduledJobDetail(scheduledJobName);
223             
224             boolean jobIsIncomplete = isIncomplete(scheduledJobDetail);
225             if (jobIsIncomplete) {
226                 log.append("\n\t").append(scheduledJobDetail.getFullName());
227                 hasIncompleteJob = true;
228             }
229         }
230         if (hasIncompleteJob) {
231             LOG.info(log);
232         }
233         return hasIncompleteJob;
234     }
235 
236     protected boolean isIncomplete(JobDetail scheduledJobDetail) {
237         if ( scheduledJobDetail == null ) {
238             return false;
239         }
240          
241         return !SCHEDULE_JOB_NAME.equals(scheduledJobDetail.getName()) && (isPending(scheduledJobDetail) || isScheduled(scheduledJobDetail));
242     }
243 
244     /**
245      * @see org.kuali.ole.sys.batch.service.SchedulerService#isPastScheduleCutoffTime()
246      */
247     @Override
248     public boolean isPastScheduleCutoffTime() {
249         return isPastScheduleCutoffTime(dateTimeService.getCurrentCalendar(), true);
250     }
251 
252     protected boolean isPastScheduleCutoffTime(Calendar dateTime, boolean log) {
253         try {
254             Date scheduleCutoffTimeTemp = scheduler.getTriggersOfJob(SCHEDULE_JOB_NAME, SCHEDULED_GROUP)[0].getPreviousFireTime();
255             Calendar scheduleCutoffTime;
256             if (scheduleCutoffTimeTemp == null) {
257                 scheduleCutoffTime = dateTimeService.getCurrentCalendar();
258             }
259             else {
260                 scheduleCutoffTime = dateTimeService.getCalendar(scheduleCutoffTimeTemp);
261             }
262             String cutoffParameter = parameterService.getParameterValueAsString(ScheduleStep.class, OLEConstants.SystemGroupParameterNames.BATCH_SCHEDULE_CUTOFF_TIME);
263             String[] scheduleStepCutoffTime = StringUtils.split(cutoffParameter, ":");
264             if ( scheduleStepCutoffTime.length != 3 && scheduleStepCutoffTime.length != 4 ) {
265                 throw new IllegalArgumentException( "Error! The " + OLEConstants.SystemGroupParameterNames.BATCH_SCHEDULE_CUTOFF_TIME + " parameter had an invalid value: " + cutoffParameter );
266             }
267             // if there are 4 components, then we have an AM/PM delimiter
268             // otherwise, assume 24-hour time
269             if ( scheduleStepCutoffTime.length == 4 ) {
270                 int hour = Integer.parseInt(scheduleStepCutoffTime[0]);
271                 // need to adjust for meaning of hour
272                 if ( hour == 12 ) {
273                     hour = 0;
274                 } else {
275                     hour--;
276             }
277                 scheduleCutoffTime.set(Calendar.HOUR, hour );
278                 if ( StringUtils.containsIgnoreCase(scheduleStepCutoffTime[3], "AM") ) {
279                     scheduleCutoffTime.set(Calendar.AM_PM, Calendar.AM);
280                 } else {
281                 scheduleCutoffTime.set(Calendar.AM_PM, Calendar.PM);
282             }
283             } else {
284                 scheduleCutoffTime.set(Calendar.HOUR_OF_DAY, Integer.parseInt(scheduleStepCutoffTime[0]));
285             }
286             scheduleCutoffTime.set(Calendar.MINUTE, Integer.parseInt(scheduleStepCutoffTime[1]));
287             scheduleCutoffTime.set(Calendar.SECOND, Integer.parseInt(scheduleStepCutoffTime[2]));
288             if (parameterService.getParameterValueAsBoolean(ScheduleStep.class, OLEConstants.SystemGroupParameterNames.BATCH_SCHEDULE_CUTOFF_TIME_IS_NEXT_DAY)) {
289                 scheduleCutoffTime.add(Calendar.DAY_OF_YEAR, 1);
290             }
291             boolean isPastScheduleCutoffTime = dateTime.after(scheduleCutoffTime);
292             if (log) {
293                 LOG.info(new StringBuilder("isPastScheduleCutoffTime=").append(isPastScheduleCutoffTime).append(" : ").append(dateTimeService.toDateTimeString(dateTime.getTime())).append(" / ").append(dateTimeService.toDateTimeString(scheduleCutoffTime.getTime())));
294             }
295             return isPastScheduleCutoffTime;
296         }
297         catch (NumberFormatException e) {
298             throw new RuntimeException("Caught exception while checking whether we've exceeded the schedule cutoff time", e);
299         }
300         catch (SchedulerException e) {
301             throw new RuntimeException("Caught exception while checking whether we've exceeded the schedule cutoff time", e);
302         }
303     }
304 
305     /**
306      * @see org.kuali.ole.sys.batch.service.SchedulerService#processWaitingJobs()
307      */
308     @Override
309     public void processWaitingJobs() {
310         for ( String scheduledJobName : getJobNamesForScheduleJob() ) {
311             JobDetail jobDetail = getScheduledJobDetail(scheduledJobName);
312             if (isPending(jobDetail)) {
313                 if (shouldScheduleJob(jobDetail)) {
314                     scheduleJob(SCHEDULED_GROUP, scheduledJobName, 0, 0, new Date(), null, Collections.singletonMap(Job.MASTER_JOB_NAME, SCHEDULE_JOB_NAME) );
315                 }
316                 if (shouldCancelJob(jobDetail)) {
317                     updateStatus(SCHEDULED_GROUP, scheduledJobName, CANCELLED_JOB_STATUS_CODE);
318                 }
319             }
320         }
321     }
322 
323     /**
324      * @see org.kuali.ole.sys.batch.service.SchedulerService#logScheduleResults()
325      */
326     @Override
327     public void logScheduleResults() {
328         StringBuilder scheduleResults = new StringBuilder("The schedule completed.");
329         for ( String scheduledJobName : getJobNamesForScheduleJob() ) {
330             JobDetail jobDetail = getScheduledJobDetail(scheduledJobName);
331             if ( jobDetail != null &&  !SCHEDULE_JOB_NAME.equals(jobDetail.getName())) {
332                 scheduleResults.append("\n\t").append(jobDetail.getName()).append("=").append(getStatus(jobDetail));
333             }
334         }
335         LOG.info(scheduleResults);
336     }
337 
338     /**
339      * @see org.kuali.ole.sys.batch.service.SchedulerService#shouldNotRun(org.quartz.JobDetail)
340      */
341     @Override
342     public boolean shouldNotRun(JobDetail jobDetail) {
343         if (SCHEDULED_GROUP.equals(jobDetail.getGroup())) {
344             if (isCancelled(jobDetail)) {
345                 if ( LOG.isInfoEnabled() ) {
346                     LOG.info("Telling listener not to run job, because it has been cancelled: " + jobDetail.getName());
347                 }
348                 return true;
349             }
350             else {
351                 for (String dependencyJobName : getJobDependencies(jobDetail.getName()).keySet()) {
352                     if (!isDependencySatisfiedPositively(jobDetail, getScheduledJobDetail(dependencyJobName))) {
353                         if ( LOG.isInfoEnabled() ) {
354                             LOG.info("Telling listener not to run job, because a dependency has not been satisfied positively: "+jobDetail.getName()+" (dependency job = "+dependencyJobName+")");
355                         }
356                         return true;
357                     }
358                 }
359             }
360         }
361         return false;
362     }
363 
364     /**
365      * @see org.kuali.ole.sys.batch.service.SchedulerService#updateStatus(org.quartz.JobDetail,java.lang.String jobStatus)
366      */
367     @Override
368     public void updateStatus(JobDetail jobDetail, String jobStatus) {
369         if ( LOG.isInfoEnabled() ) {
370             LOG.info("Updating status of job: "+jobDetail.getName()+"="+jobStatus);
371         }
372         jobDetail.getJobDataMap().put(JOB_STATUS_PARAMETER, jobStatus);
373     }
374 
375     @Override
376     public void runJob(String jobName, String requestorEmailAddress) {
377         runJob(jobName, 0, 0, new Date(), requestorEmailAddress);
378     }
379 
380     @Override
381     public void runJob(String jobName, int startStep, int stopStep, Date startTime, String requestorEmailAddress) {
382         runJob(UNSCHEDULED_GROUP, jobName, startStep, stopStep, startTime, requestorEmailAddress);
383     }
384 
385     @Override
386     public void runJob(String groupName, String jobName, int startStep, int stopStep, Date jobStartTime, String requestorEmailAddress) {
387         if ( LOG.isInfoEnabled() ) {
388             LOG.info("Executing user initiated job: " + groupName + "." + jobName + " (startStep=" + startStep + " / stopStep=" + stopStep + " / startTime=" + jobStartTime + " / requestorEmailAddress=" + requestorEmailAddress + ")");
389         }
390 
391         try {
392             JobDetail jobDetail = scheduler.getJobDetail(jobName, groupName);
393             scheduleJob(groupName, jobName, startStep, stopStep, jobStartTime, requestorEmailAddress, null);
394         }
395         catch (SchedulerException ex) {
396             throw new RuntimeException("Unable to run a job directly", ex);
397         }
398     }
399 
400     public void runStep(String groupName, String jobName, String stepName, Date startTime, String requestorEmailAddress) {
401         if ( LOG.isInfoEnabled() ) {
402             LOG.info("Executing user initiated step: " + stepName + " / requestorEmailAddress=" + requestorEmailAddress);
403         }
404 
405         // abort if the step is already running
406         if (isJobRunning(jobName)) {
407             LOG.warn("Attempt to run job already executing, aborting");
408             return;
409         }
410         int stepNum = 1;
411         boolean stepFound = false;
412         BatchJobStatus job = getJob(groupName, jobName);
413         for (Step step : job.getSteps()) {
414             if (step.getName().equals(stepName)) {
415                 stepFound = true;
416                 break;
417             }
418             stepNum++;
419         }
420         if (stepFound) {
421             runJob(groupName, jobName, stepNum, stepNum, startTime, requestorEmailAddress);
422         }
423         else {
424             LOG.warn("Unable to find step " + stepName + " in job " + groupName + "." + jobName);
425         }
426     }
427 
428     @Override
429     public boolean isJobRunning(String jobName) {
430         List<JobExecutionContext> runningJobs = getRunningJobs();
431         for (JobExecutionContext jobCtx : runningJobs) {
432             if (jobCtx.getJobDetail().getName().equals(jobName)) {
433                 return true;
434             }
435         }
436         return false;
437     }
438 
439     protected void addJob(JobDetail jobDetail) {
440         try {
441             if ( LOG.isInfoEnabled() ) {
442                 LOG.info("Adding job: " + jobDetail.getFullName());
443             }
444             scheduler.addJob(jobDetail, true);
445         }
446         catch (SchedulerException e) {
447             throw new RuntimeException("Caught exception while adding job: " + jobDetail.getFullName(), e);
448         }
449     }
450 
451     protected void addTrigger(Trigger trigger) {
452         try {
453             if (UNSCHEDULED_GROUP.equals(trigger.getGroup())) {
454                 LOG.error("Triggers should not be specified for jobs in the unscheduled group - not adding trigger: " + trigger.getName());
455             }
456             else {
457                 LOG.info("Adding trigger: " + trigger.getName());
458                 try {
459                     scheduler.scheduleJob(trigger);
460                 }
461                 catch (ObjectAlreadyExistsException ex) {
462                 }
463             }
464         }
465         catch (SchedulerException e) {
466             throw new RuntimeException("Caught exception while adding trigger: " + trigger.getFullName(), e);
467         }
468     }
469 
470     protected void scheduleJob(String groupName, String jobName, int startStep, int endStep, Date startTime, String requestorEmailAddress, Map<String,String> additionalJobData ) {
471         try {
472             updateStatus(groupName, jobName, SchedulerService.SCHEDULED_JOB_STATUS_CODE);
473             SimpleTriggerDescriptor trigger = new SimpleTriggerDescriptor(jobName, groupName, jobName, dateTimeService);
474             trigger.setStartTime(startTime);
475             Trigger qTrigger = trigger.getTrigger();
476             qTrigger.getJobDataMap().put(JobListener.REQUESTOR_EMAIL_ADDRESS_KEY, requestorEmailAddress);
477             qTrigger.getJobDataMap().put(Job.JOB_RUN_START_STEP, String.valueOf(startStep));
478             qTrigger.getJobDataMap().put(Job.JOB_RUN_END_STEP, String.valueOf(endStep));
479             if ( additionalJobData != null ) {
480                 qTrigger.getJobDataMap().putAll(additionalJobData);
481             }
482             for (Trigger oldTrigger : scheduler.getTriggersOfJob(jobName, groupName)) {
483                 scheduler.unscheduleJob(oldTrigger.getName(), groupName);
484             }
485             scheduler.scheduleJob(qTrigger);
486         }
487         catch (SchedulerException e) {
488             throw new RuntimeException("Caught exception while scheduling job: " + jobName, e);
489         }
490     }
491 
492     protected boolean shouldScheduleJob(JobDetail jobDetail) {
493         try {
494             if (scheduler.getTriggersOfJob(jobDetail.getName(), SCHEDULED_GROUP).length > 0) {
495                 return false;
496             }
497             for (String dependencyJobName : getJobDependencies(jobDetail.getName()).keySet()) {
498                 JobDetail dependencyJobDetail = getScheduledJobDetail(dependencyJobName);
499                 if ( dependencyJobDetail == null ) {
500                     LOG.error( "Unable to get JobDetail for dependency of " + jobDetail.getName() + " : " + dependencyJobName );
501                     return false;
502                 }
503                 if (!isDependencySatisfiedPositively(jobDetail, dependencyJobDetail)) {
504                     return false;
505                 }
506             }
507         }
508         catch (SchedulerException se) {
509             throw new RuntimeException("Caught scheduler exception while determining whether to schedule job: " + jobDetail.getName(), se);
510         }
511         return true;
512     }
513 
514     protected boolean shouldCancelJob(JobDetail jobDetail) {
515         LOG.info("shouldCancelJob:::::: " + jobDetail.getFullName());
516         if ( jobDetail == null ) {
517             return true;
518         }
519         for (String dependencyJobName : getJobDependencies(jobDetail.getName()).keySet()) {
520             LOG.info("dependencyJobName:::::" + dependencyJobName);
521             JobDetail dependencyJobDetail = getScheduledJobDetail(dependencyJobName);
522             if (isDependencySatisfiedNegatively(jobDetail, dependencyJobDetail)) {
523                 return true;
524             }
525         }
526         return false;
527     }
528 
529     protected boolean isDependencySatisfiedPositively(JobDetail dependentJobDetail, JobDetail dependencyJobDetail) {
530         if ( dependentJobDetail == null || dependencyJobDetail == null ) {
531             return false;
532         }
533         return isSucceeded(dependencyJobDetail) || ((isFailed(dependencyJobDetail) || isCancelled(dependencyJobDetail)) && isSoftDependency(dependentJobDetail.getName(), dependencyJobDetail.getName()));
534     }
535 
536     protected boolean isDependencySatisfiedNegatively(JobDetail dependentJobDetail, JobDetail dependencyJobDetail) {
537        LOG.info("isDependencySatisfiedNegatively::::  dependentJobDetail::: " + dependencyJobDetail.getFullName() + " dependencyJobDetail    " + dependencyJobDetail.getFullName() );
538         if ( dependentJobDetail == null || dependencyJobDetail == null ) {
539             return true;
540         }
541         return (isFailed(dependencyJobDetail) || isCancelled(dependencyJobDetail)) && !isSoftDependency(dependentJobDetail.getName(), dependencyJobDetail.getName());
542     }
543 
544     protected boolean isSoftDependency(String dependentJobName, String dependencyJobName) {
545         return SOFT_DEPENDENCY_CODE.equals(getJobDependencies(dependentJobName).get(dependencyJobName));
546     }
547 
548     protected Map<String, String> getJobDependencies(String jobName) {
549         LOG.info("getJobDependencies:::: for job " + jobName);
550         return BatchSpringContext.getJobDescriptor(jobName).getDependencies();
551     }
552 
553     protected boolean isPending(JobDetail jobDetail) {
554         return getStatus(jobDetail) == null;
555     }
556 
557     protected boolean isScheduled(JobDetail jobDetail) {
558         return SCHEDULED_JOB_STATUS_CODE.equals(getStatus(jobDetail));
559     }
560 
561     protected boolean isSucceeded(JobDetail jobDetail) {
562         return SUCCEEDED_JOB_STATUS_CODE.equals(getStatus(jobDetail));
563     }
564 
565     protected boolean isFailed(JobDetail jobDetail) {
566         return FAILED_JOB_STATUS_CODE.equals(getStatus(jobDetail));
567     }
568 
569     protected boolean isCancelled(JobDetail jobDetail) {
570         return CANCELLED_JOB_STATUS_CODE.equals(getStatus(jobDetail));
571     }
572 
573     @Override
574     public String getStatus(JobDetail jobDetail) {
575         if ( jobDetail == null ) {
576             return FAILED_JOB_STATUS_CODE;
577         }
578         OleModuleServiceImpl moduleService = (OleModuleServiceImpl)
579             SpringContext.getBean(KualiModuleService.class).getResponsibleModuleServiceForJob(jobDetail.getName());
580         //If the module service has status information for a job, get the status from it
581         //else get status from job detail data map 
582         return (moduleService!=null && moduleService.isExternalJob(jobDetail.getName()))
583                     ? moduleService.getExternalJobStatus(jobDetail.getName())
584                     : jobDetail.getJobDataMap().getString(SchedulerServiceImpl.JOB_STATUS_PARAMETER);
585     }
586 
587     protected JobDetail getScheduledJobDetail(String jobName) {
588         LOG.info("getScheduledJobDetail ::::::: " + jobName);
589         try {
590             JobDetail jobDetail = scheduler.getJobDetail(jobName, SCHEDULED_GROUP);
591             if ( jobDetail == null ) {
592                 LOG.error( "Unable to obtain the job details for the scheduled version of: " + jobName );
593             }
594             return jobDetail;
595         }
596         catch (SchedulerException e) {
597             throw new RuntimeException("Caught scheduler exception while getting job detail: " + jobName, e);
598         }
599     }
600 
601     /**
602      * Sets the scheduler attribute value.
603      * 
604      * @param scheduler The scheduler to set.
605      */
606     @Override
607     public void setScheduler(Scheduler scheduler) {
608         this.scheduler = scheduler;
609     }
610 
611     public void setParameterService(ParameterService parameterService) {
612         this.parameterService = parameterService;
613     }
614 
615     /**
616      * Sets the dateTimeService attribute value.
617      * 
618      * @param dateTimeService The dateTimeService to set.
619      */
620     public void setDateTimeService(DateTimeService dateTimeService) {
621         this.dateTimeService = dateTimeService;
622     }
623 
624     /**
625      * Sets the moduleService attribute value.
626      * 
627      * @param moduleService The moduleService to set.
628      */
629     public void setKualiModuleService(KualiModuleService moduleService) {
630         this.kualiModuleService = moduleService;
631     }
632 
633     /**
634      * Sets the jobListener attribute value.
635      * 
636      * @param jobListener The jobListener to set.
637      */
638     public void setJobListener(JobListener jobListener) {
639         this.jobListener = jobListener;
640     }
641 
642     @Override
643     public List<BatchJobStatus> getJobs() {
644         ArrayList<BatchJobStatus> jobs = new ArrayList<BatchJobStatus>();
645         try {
646             for (String jobGroup : scheduler.getJobGroupNames()) {
647                 for (String jobName : scheduler.getJobNames(jobGroup)) {
648                     try {
649                         JobDescriptor jobDescriptor = retrieveJobDescriptor(jobName);
650                         JobDetail jobDetail = scheduler.getJobDetail(jobName, jobGroup);
651                         jobs.add(new BatchJobStatus(jobDescriptor, jobDetail));
652                     }
653                     catch (NoSuchBeanDefinitionException ex) {
654                         // do nothing, ignore jobs not defined in spring
655                         LOG.warn("Attempt to find bean " + jobGroup + "." + jobName + " failed - not in Spring context");
656                     }
657                 }
658             }
659         }
660         catch (SchedulerException ex) {
661             throw new RuntimeException("Exception while obtaining job list", ex);
662         }
663         return jobs;
664     }
665 
666     @Override
667     public BatchJobStatus getJob(String groupName, String jobName) {
668         for (BatchJobStatus job : getJobs()) {
669             if (job.getName().equals(jobName) && job.getGroup().equals(groupName)) {
670                 return job;
671             }
672         }
673         return null;
674     }
675 
676     @Override
677     public List<BatchJobStatus> getJobs(String groupName) {
678         ArrayList<BatchJobStatus> jobs = new ArrayList<BatchJobStatus>();
679         try {
680             for (String jobName : scheduler.getJobNames(groupName)) {
681                 try {
682                     JobDescriptor jobDescriptor = retrieveJobDescriptor(jobName);
683                     JobDetail jobDetail = scheduler.getJobDetail(jobName, groupName);
684                     jobs.add(new BatchJobStatus(jobDescriptor, jobDetail));
685                 }
686                 catch (NoSuchBeanDefinitionException ex) {
687                     // do nothing, ignore jobs not defined in spring
688                     LOG.warn("Attempt to find bean " + groupName + "." + jobName + " failed - not in Spring context");
689                 }
690             }
691         }
692         catch (SchedulerException ex) {
693             throw new RuntimeException("Exception while obtaining job list", ex);
694         }
695         return jobs;
696     }
697 
698     @Override
699     public List<JobExecutionContext> getRunningJobs() {
700         try {
701             List<JobExecutionContext> jobContexts = scheduler.getCurrentlyExecutingJobs();
702             return jobContexts;
703         }
704         catch (SchedulerException ex) {
705             throw new RuntimeException("Unable to get list of running jobs.", ex);
706         }
707     }
708 
709     protected void updateStatus(String groupName, String jobName, String jobStatus) {
710         try {
711             JobDetail jobDetail = scheduler.getJobDetail(jobName, groupName);
712             updateStatus(jobDetail, jobStatus);
713             scheduler.addJob(jobDetail, true);
714         }
715         catch (SchedulerException e) {
716             throw new RuntimeException(new StringBuilder("Caught scheduler exception while updating job status: ").append(jobName).append(", ").append(jobStatus).toString(), e);
717         }
718     }
719 
720     @Override
721     public void removeScheduled(String jobName) {
722         try {
723             scheduler.deleteJob(jobName, SCHEDULED_GROUP);
724         }
725         catch (SchedulerException ex) {
726             throw new RuntimeException("Unable to remove scheduled job: " + jobName, ex);
727         }
728     }
729 
730     @Override
731     public void addScheduled(JobDetail job) {
732         try {
733             job.setGroup(SCHEDULED_GROUP);
734             scheduler.addJob(job, true);
735         }
736         catch (SchedulerException ex) {
737             throw new RuntimeException("Unable to add job to scheduled group: " + job.getName(), ex);
738         }
739     }
740 
741     @Override
742     public void addUnscheduled(JobDetail job) {
743         try {
744             job.setGroup(UNSCHEDULED_GROUP);
745             scheduler.addJob(job, true);
746         }
747         catch (SchedulerException ex) {
748             throw new RuntimeException("Unable to add job to unscheduled group: " + job.getName(), ex);
749         }
750     }
751 
752     @Override
753     public List<String> getSchedulerGroups() {
754         try {
755             return Arrays.asList(scheduler.getJobGroupNames());
756         }
757         catch (SchedulerException ex) {
758             throw new RuntimeException("Exception while obtaining job list", ex);
759         }
760     }
761 
762     @Override
763     public List<String> getJobStatuses() {
764         return jobStatuses;
765     }
766 
767     @Override
768     public void interruptJob(String jobName) {
769         List<JobExecutionContext> runningJobs = getRunningJobs();
770         for (JobExecutionContext jobCtx : runningJobs) {
771             if (jobName.equals(jobCtx.getJobDetail().getName())) {
772                 // if so...
773                 try {
774                     ((Job) jobCtx.getJobInstance()).interrupt();
775                 }
776                 catch (UnableToInterruptJobException ex) {
777                     LOG.warn("Unable to perform job interrupt", ex);
778                 }
779                 break;
780             }
781         }
782 
783     }
784 
785     @Override
786     public Date getNextStartTime(BatchJobStatus job) {
787         try {
788             Trigger[] triggers = scheduler.getTriggersOfJob(job.getName(), job.getGroup());
789             Date nextDate = new Date(Long.MAX_VALUE);
790             for (Trigger trigger : triggers) {
791                 if (trigger.getNextFireTime() != null){
792                 if (trigger.getNextFireTime().getTime() < nextDate.getTime()) {
793                     nextDate = trigger.getNextFireTime();
794                 }
795             }
796             }
797             if (nextDate.getTime() == Long.MAX_VALUE) {
798                 nextDate = null;
799             }
800             return nextDate;
801         }
802         catch (SchedulerException ex) {
803 
804         }
805         return null;
806     }
807 
808     @Override
809     public Date getNextStartTime(String groupName, String jobName) {
810         BatchJobStatus job = getJob(groupName, jobName);
811 
812         return getNextStartTime(job);
813     }
814 
815     public void setMailService(MailService mailService) {
816         this.mailService = mailService;
817     }
818     
819     protected JobDescriptor retrieveJobDescriptor(String jobName) {
820         if (externalizedJobDescriptors.containsKey(jobName)) {
821             return externalizedJobDescriptors.get(jobName);
822         }
823         return BatchSpringContext.getJobDescriptor(jobName);
824     }
825     
826     ThreadLocal<List<String>> jobNamesForScheduleJob = new ThreadLocal<List<String>>();
827     
828     protected List<String> getJobNamesForScheduleJob() {
829         List<String> jobNames = new ArrayList<String>();
830         try {
831             for (String scheduledJobName : scheduler.getJobNames(SCHEDULED_GROUP)) {
832                 if (scheduler.getTriggersOfJob(scheduledJobName, SCHEDULED_GROUP).length == 0) {
833                     // jobs that have their own triggers will not be included in the master scheduleJob
834                     jobNames.add( scheduledJobName );
835                 }
836             }
837         } catch (Exception ex) {
838             LOG.error("Error occurred while initializing job name list", ex);
839         }
840         return jobNames;
841     }
842     
843     @Override
844     public void reinitializeScheduledJobs() {
845         try {
846             for (String scheduledJobName : getJobNamesForScheduleJob() ) {
847                 updateStatus(SCHEDULED_GROUP, scheduledJobName, null);
848             }
849         } catch (Exception e) {
850             LOG.error("Error occurred while trying to reinitialize jobs", e);
851         }
852     }
853 }