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         Map<String, String> defaultMap = new HashMap();
551         if (BatchSpringContext.getJobDescriptor(jobName) != null && BatchSpringContext.getJobDescriptor(jobName).getDependencies() != null) {
552             return BatchSpringContext.getJobDescriptor(jobName).getDependencies();
553         } else {
554             return defaultMap;
555         }
556     }
557 
558     protected boolean isPending(JobDetail jobDetail) {
559         return getStatus(jobDetail) == null;
560     }
561 
562     protected boolean isScheduled(JobDetail jobDetail) {
563         return SCHEDULED_JOB_STATUS_CODE.equals(getStatus(jobDetail));
564     }
565 
566     protected boolean isSucceeded(JobDetail jobDetail) {
567         return SUCCEEDED_JOB_STATUS_CODE.equals(getStatus(jobDetail));
568     }
569 
570     protected boolean isFailed(JobDetail jobDetail) {
571         return FAILED_JOB_STATUS_CODE.equals(getStatus(jobDetail));
572     }
573 
574     protected boolean isCancelled(JobDetail jobDetail) {
575         return CANCELLED_JOB_STATUS_CODE.equals(getStatus(jobDetail));
576     }
577 
578     @Override
579     public String getStatus(JobDetail jobDetail) {
580         if ( jobDetail == null ) {
581             return FAILED_JOB_STATUS_CODE;
582         }
583         OleModuleServiceImpl moduleService = (OleModuleServiceImpl)
584             SpringContext.getBean(KualiModuleService.class).getResponsibleModuleServiceForJob(jobDetail.getName());
585         //If the module service has status information for a job, get the status from it
586         //else get status from job detail data map 
587         return (moduleService!=null && moduleService.isExternalJob(jobDetail.getName()))
588                     ? moduleService.getExternalJobStatus(jobDetail.getName())
589                     : jobDetail.getJobDataMap().getString(SchedulerServiceImpl.JOB_STATUS_PARAMETER);
590     }
591 
592     protected JobDetail getScheduledJobDetail(String jobName) {
593         LOG.info("getScheduledJobDetail ::::::: " + jobName);
594         try {
595             JobDetail jobDetail = scheduler.getJobDetail(jobName, SCHEDULED_GROUP);
596             if ( jobDetail == null ) {
597                 LOG.error( "Unable to obtain the job details for the scheduled version of: " + jobName );
598             }
599             return jobDetail;
600         }
601         catch (SchedulerException e) {
602             throw new RuntimeException("Caught scheduler exception while getting job detail: " + jobName, e);
603         }
604     }
605 
606     /**
607      * Sets the scheduler attribute value.
608      * 
609      * @param scheduler The scheduler to set.
610      */
611     @Override
612     public void setScheduler(Scheduler scheduler) {
613         this.scheduler = scheduler;
614     }
615 
616     public void setParameterService(ParameterService parameterService) {
617         this.parameterService = parameterService;
618     }
619 
620     /**
621      * Sets the dateTimeService attribute value.
622      * 
623      * @param dateTimeService The dateTimeService to set.
624      */
625     public void setDateTimeService(DateTimeService dateTimeService) {
626         this.dateTimeService = dateTimeService;
627     }
628 
629     /**
630      * Sets the moduleService attribute value.
631      * 
632      * @param moduleService The moduleService to set.
633      */
634     public void setKualiModuleService(KualiModuleService moduleService) {
635         this.kualiModuleService = moduleService;
636     }
637 
638     /**
639      * Sets the jobListener attribute value.
640      * 
641      * @param jobListener The jobListener to set.
642      */
643     public void setJobListener(JobListener jobListener) {
644         this.jobListener = jobListener;
645     }
646 
647     @Override
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 = retrieveJobDescriptor(jobName);
655                         JobDetail jobDetail = scheduler.getJobDetail(jobName, jobGroup);
656                         jobs.add(new BatchJobStatus(jobDescriptor, jobDetail));
657                     }
658                     catch (NoSuchBeanDefinitionException ex) {
659                         // do nothing, ignore jobs not defined in spring
660                         LOG.warn("Attempt to find bean " + jobGroup + "." + jobName + " failed - not in Spring context");
661                     }
662                 }
663             }
664         }
665         catch (SchedulerException ex) {
666             throw new RuntimeException("Exception while obtaining job list", ex);
667         }
668         return jobs;
669     }
670 
671     @Override
672     public BatchJobStatus getJob(String groupName, String jobName) {
673         for (BatchJobStatus job : getJobs()) {
674             if (job.getName().equals(jobName) && job.getGroup().equals(groupName)) {
675                 return job;
676             }
677         }
678         return null;
679     }
680 
681     @Override
682     public List<BatchJobStatus> getJobs(String groupName) {
683         ArrayList<BatchJobStatus> jobs = new ArrayList<BatchJobStatus>();
684         try {
685             for (String jobName : scheduler.getJobNames(groupName)) {
686                 try {
687                     JobDescriptor jobDescriptor = retrieveJobDescriptor(jobName);
688                     JobDetail jobDetail = scheduler.getJobDetail(jobName, groupName);
689                     jobs.add(new BatchJobStatus(jobDescriptor, jobDetail));
690                 }
691                 catch (NoSuchBeanDefinitionException ex) {
692                     // do nothing, ignore jobs not defined in spring
693                     LOG.warn("Attempt to find bean " + groupName + "." + jobName + " failed - not in Spring context");
694                 }
695             }
696         }
697         catch (SchedulerException ex) {
698             throw new RuntimeException("Exception while obtaining job list", ex);
699         }
700         return jobs;
701     }
702 
703     @Override
704     public List<JobExecutionContext> getRunningJobs() {
705         try {
706             List<JobExecutionContext> jobContexts = scheduler.getCurrentlyExecutingJobs();
707             return jobContexts;
708         }
709         catch (SchedulerException ex) {
710             throw new RuntimeException("Unable to get list of running jobs.", ex);
711         }
712     }
713 
714     protected void updateStatus(String groupName, String jobName, String jobStatus) {
715         try {
716             JobDetail jobDetail = scheduler.getJobDetail(jobName, groupName);
717             updateStatus(jobDetail, jobStatus);
718             scheduler.addJob(jobDetail, true);
719         }
720         catch (SchedulerException e) {
721             throw new RuntimeException(new StringBuilder("Caught scheduler exception while updating job status: ").append(jobName).append(", ").append(jobStatus).toString(), e);
722         }
723     }
724 
725     @Override
726     public void removeScheduled(String jobName) {
727         try {
728             scheduler.deleteJob(jobName, SCHEDULED_GROUP);
729         }
730         catch (SchedulerException ex) {
731             throw new RuntimeException("Unable to remove scheduled job: " + jobName, ex);
732         }
733     }
734 
735     @Override
736     public void addScheduled(JobDetail job) {
737         try {
738             job.setGroup(SCHEDULED_GROUP);
739             scheduler.addJob(job, true);
740         }
741         catch (SchedulerException ex) {
742             throw new RuntimeException("Unable to add job to scheduled group: " + job.getName(), ex);
743         }
744     }
745 
746     @Override
747     public void addUnscheduled(JobDetail job) {
748         try {
749             job.setGroup(UNSCHEDULED_GROUP);
750             scheduler.addJob(job, true);
751         }
752         catch (SchedulerException ex) {
753             throw new RuntimeException("Unable to add job to unscheduled group: " + job.getName(), ex);
754         }
755     }
756 
757     @Override
758     public List<String> getSchedulerGroups() {
759         try {
760             return Arrays.asList(scheduler.getJobGroupNames());
761         }
762         catch (SchedulerException ex) {
763             throw new RuntimeException("Exception while obtaining job list", ex);
764         }
765     }
766 
767     @Override
768     public List<String> getJobStatuses() {
769         return jobStatuses;
770     }
771 
772     @Override
773     public void interruptJob(String jobName) {
774         List<JobExecutionContext> runningJobs = getRunningJobs();
775         for (JobExecutionContext jobCtx : runningJobs) {
776             if (jobName.equals(jobCtx.getJobDetail().getName())) {
777                 // if so...
778                 try {
779                     ((Job) jobCtx.getJobInstance()).interrupt();
780                 }
781                 catch (UnableToInterruptJobException ex) {
782                     LOG.warn("Unable to perform job interrupt", ex);
783                 }
784                 break;
785             }
786         }
787 
788     }
789 
790     @Override
791     public Date getNextStartTime(BatchJobStatus job) {
792         try {
793             Trigger[] triggers = scheduler.getTriggersOfJob(job.getName(), job.getGroup());
794             Date nextDate = new Date(Long.MAX_VALUE);
795             for (Trigger trigger : triggers) {
796                 if (trigger.getNextFireTime() != null){
797                 if (trigger.getNextFireTime().getTime() < nextDate.getTime()) {
798                     nextDate = trigger.getNextFireTime();
799                 }
800             }
801             }
802             if (nextDate.getTime() == Long.MAX_VALUE) {
803                 nextDate = null;
804             }
805             return nextDate;
806         }
807         catch (SchedulerException ex) {
808 
809         }
810         return null;
811     }
812 
813     @Override
814     public Date getNextStartTime(String groupName, String jobName) {
815         BatchJobStatus job = getJob(groupName, jobName);
816 
817         return getNextStartTime(job);
818     }
819 
820     public void setMailService(MailService mailService) {
821         this.mailService = mailService;
822     }
823     
824     protected JobDescriptor retrieveJobDescriptor(String jobName) {
825         if (externalizedJobDescriptors.containsKey(jobName)) {
826             return externalizedJobDescriptors.get(jobName);
827         }
828         return BatchSpringContext.getJobDescriptor(jobName);
829     }
830     
831     ThreadLocal<List<String>> jobNamesForScheduleJob = new ThreadLocal<List<String>>();
832     
833     protected List<String> getJobNamesForScheduleJob() {
834         List<String> jobNames = new ArrayList<String>();
835         try {
836             for (String scheduledJobName : scheduler.getJobNames(SCHEDULED_GROUP)) {
837                 if (scheduler.getTriggersOfJob(scheduledJobName, SCHEDULED_GROUP).length == 0) {
838                     // jobs that have their own triggers will not be included in the master scheduleJob
839                     jobNames.add( scheduledJobName );
840                 }
841             }
842         } catch (Exception ex) {
843             LOG.error("Error occurred while initializing job name list", ex);
844         }
845         return jobNames;
846     }
847     
848     @Override
849     public void reinitializeScheduledJobs() {
850         try {
851             for (String scheduledJobName : getJobNamesForScheduleJob() ) {
852                 updateStatus(SCHEDULED_GROUP, scheduledJobName, null);
853             }
854         } catch (Exception e) {
855             LOG.error("Error occurred while trying to reinitialize jobs", e);
856         }
857     }
858 }