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