001/* 002 * Copyright 2007 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.ole.sys.batch.service.impl; 017 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.Calendar; 021import java.util.Collections; 022import java.util.Date; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026import java.util.Map.Entry; 027import java.util.Set; 028 029import org.apache.commons.lang.StringUtils; 030import org.apache.log4j.Logger; 031import org.kuali.ole.sys.OLEConstants; 032import org.kuali.ole.sys.batch.BatchJobStatus; 033import org.kuali.ole.sys.batch.BatchSpringContext; 034import org.kuali.ole.sys.batch.Job; 035import org.kuali.ole.sys.batch.JobDescriptor; 036import org.kuali.ole.sys.batch.JobListener; 037import org.kuali.ole.sys.batch.ScheduleStep; 038import org.kuali.ole.sys.batch.SimpleTriggerDescriptor; 039import org.kuali.ole.sys.batch.Step; 040import org.kuali.ole.sys.batch.service.SchedulerService; 041import org.kuali.ole.sys.context.SpringContext; 042import org.kuali.ole.sys.service.BatchModuleService; 043import org.kuali.ole.sys.service.impl.OleModuleServiceImpl; 044import org.kuali.rice.core.api.datetime.DateTimeService; 045import org.kuali.rice.coreservice.framework.parameter.ParameterService; 046import org.kuali.rice.krad.service.KualiModuleService; 047import org.kuali.rice.krad.service.MailService; 048import org.kuali.rice.krad.service.ModuleService; 049import org.quartz.JobDetail; 050import org.quartz.JobExecutionContext; 051import org.quartz.ObjectAlreadyExistsException; 052import org.quartz.Scheduler; 053import org.quartz.SchedulerException; 054import org.quartz.Trigger; 055import org.quartz.UnableToInterruptJobException; 056import org.springframework.beans.factory.NoSuchBeanDefinitionException; 057import org.springframework.transaction.annotation.Transactional; 058 059@Transactional 060public class SchedulerServiceImpl implements SchedulerService { 061 private static final Logger LOG = Logger.getLogger(SchedulerServiceImpl.class); 062 protected static final String SOFT_DEPENDENCY_CODE = "softDependency"; 063 protected static final String HARD_DEPENDENCY_CODE = "hardDependency"; 064 065 protected Scheduler scheduler; 066 protected JobListener jobListener; 067 private KualiModuleService kualiModuleService; 068 protected ParameterService parameterService; 069 protected DateTimeService dateTimeService; 070 private MailService mailService; 071 /** 072 * 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) 073 */ 074 protected Map<String, JobDescriptor> externalizedJobDescriptors; 075 076 protected static final List<String> jobStatuses = new ArrayList<String>(); 077 078 static { 079 jobStatuses.add(SCHEDULED_JOB_STATUS_CODE); 080 jobStatuses.add(SUCCEEDED_JOB_STATUS_CODE); 081 jobStatuses.add(CANCELLED_JOB_STATUS_CODE); 082 jobStatuses.add(RUNNING_JOB_STATUS_CODE); 083 jobStatuses.add(FAILED_JOB_STATUS_CODE); 084 } 085 086 public SchedulerServiceImpl() { 087 externalizedJobDescriptors = new HashMap<String, JobDescriptor>(); 088 } 089 090 /** 091 * @see org.kuali.ole.sys.batch.service.SchedulerService#initialize() 092 */ 093 @Override 094 public void initialize() { 095 LOG.info("Initializing the schedule"); 096 jobListener.setSchedulerService(this); 097 try { 098 scheduler.addGlobalJobListener(jobListener); 099 } 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}