001/** 002 * Copyright 2005-2017 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.rice.kew.impl.stuck; 017 018import org.kuali.rice.core.api.config.property.RuntimeConfig; 019import org.kuali.rice.core.api.config.property.RuntimeConfigSet; 020import org.quartz.CronScheduleBuilder; 021import org.quartz.JobBuilder; 022import org.quartz.JobDetail; 023import org.quartz.JobKey; 024import org.quartz.Scheduler; 025import org.quartz.SchedulerException; 026import org.quartz.Trigger; 027import org.quartz.TriggerBuilder; 028import org.slf4j.Logger; 029import org.slf4j.LoggerFactory; 030import org.springframework.beans.factory.annotation.Required; 031import org.springframework.context.ApplicationListener; 032import org.springframework.context.event.ContextRefreshedEvent; 033 034/** 035 * @author Eric Westfall 036 */ 037public class StuckDocumentScheduler implements ApplicationListener<ContextRefreshedEvent> { 038 039 private static final Logger LOG = LoggerFactory.getLogger(StuckDocumentScheduler.class); 040 041 protected static final JobKey NOTIFICATION_JOB_KEY = JobKey.jobKey("StuckDocuments", "Notification"); 042 protected static final JobKey AUTOFIX_COLLECTOR_JOB_KEY = JobKey.jobKey("StuckDocuments", "AutofixCollector"); 043 044 private Scheduler scheduler; 045 046 private RuntimeConfig notificationEnabled; 047 private RuntimeConfig notificationCronExpression; 048 049 private RuntimeConfig autofixEnabled; 050 private RuntimeConfig autofixCronExpression; 051 private RuntimeConfig autofixQuietPeriod; 052 private RuntimeConfig autofixMaxAttempts; 053 054 @Override 055 public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { 056 achieveDesiredState(); 057 RuntimeConfigSet configSet = new RuntimeConfigSet(notificationEnabled, notificationCronExpression, autofixEnabled, autofixCronExpression, autofixQuietPeriod, autofixMaxAttempts); 058 configSet.listen(this::configChanged); 059 } 060 061 private void configChanged(RuntimeConfigSet configSet) { 062 LOG.info("StuckDocumentScheduler config was changed, rebuilding job."); 063 achieveDesiredState(); 064 } 065 066 private void achieveDesiredState() { 067 try { 068 scheduleNotificationJob(); 069 scheduleAutofixCollectorJob(); 070 } catch (SchedulerException e) { 071 throw new IllegalStateException("Scheduling failure when attempting to configure Stuck Document jobs", e); 072 } 073 } 074 075 private void scheduleNotificationJob() throws SchedulerException { 076 unscheduleJobIfExists(NOTIFICATION_JOB_KEY); 077 if (notificationEnabled.getValueAsBoolean()) { 078 JobDetail job = JobBuilder.newJob(StuckDocumentNotificationJob.class) 079 .withIdentity(NOTIFICATION_JOB_KEY).build(); 080 081 LOG.info("Stuck Documents Notification job is enabled, scheduling with cron expression " + notificationCronExpression.getValue()); 082 CronScheduleBuilder scheduleBuilder = 083 CronScheduleBuilder.cronSchedule(notificationCronExpression.getValue()) 084 .withMisfireHandlingInstructionDoNothing(); 085 Trigger trigger = TriggerBuilder.newTrigger() 086 .forJob(job) 087 .startNow() 088 .withSchedule(scheduleBuilder).build(); 089 scheduler().scheduleJob(job, trigger); 090 } else { 091 LOG.info("Stuck Documents Notification job is disabled."); 092 } 093 } 094 095 private void scheduleAutofixCollectorJob() throws SchedulerException { 096 unscheduleJobIfExists(AUTOFIX_COLLECTOR_JOB_KEY); 097 if (autofixEnabled.getValueAsBoolean()) { 098 JobDetail job = JobBuilder.newJob(AutofixCollectorJob.class) 099 .withIdentity(AUTOFIX_COLLECTOR_JOB_KEY) 100 .usingJobData(AutofixCollectorJob.AUTOFIX_QUIET_PERIOD_KEY, autofixQuietPeriod.getValueAsInteger()) 101 .usingJobData(AutofixCollectorJob.AUTOFIX_MAX_ATTEMPTS_KEY, autofixMaxAttempts.getValueAsInteger()).build(); 102 103 LOG.info("Stuck Documents Autofix job is enabled, scheduling with cron expression " + autofixCronExpression.getValue()); 104 CronScheduleBuilder scheduleBuilder = 105 CronScheduleBuilder.cronSchedule(autofixCronExpression.getValue()) 106 .withMisfireHandlingInstructionDoNothing(); 107 Trigger trigger = TriggerBuilder.newTrigger() 108 .forJob(job) 109 .startNow() 110 .withSchedule(scheduleBuilder).build(); 111 scheduler().scheduleJob(job, trigger); 112 } else { 113 LOG.info("Stuck Documents Autofix job is disabled."); 114 } 115 } 116 117 private void unscheduleJobIfExists(JobKey jobKey) throws SchedulerException { 118 if (scheduler != null && scheduler().checkExists(jobKey)) { 119 scheduler().deleteJob(jobKey); 120 } 121 } 122 123 private Scheduler scheduler() { 124 if (this.scheduler == null) { 125 throw new IllegalStateException("StuckDocumentScheduler is trying to use the Scheduler but none exists!"); 126 } 127 return this.scheduler; 128 } 129 130 /** 131 * Not marked as required because it may be null if running KSB in THIN mode. This is mostly to accomodate the way 132 * that KFS has Rice wired up in their integration tests. 133 */ 134 public void setScheduler(Scheduler scheduler) { 135 this.scheduler = scheduler; 136 } 137 138 @Required 139 public void setNotificationEnabled(RuntimeConfig notificationEnabled) { 140 this.notificationEnabled = notificationEnabled; 141 } 142 143 @Required 144 public void setNotificationCronExpression(RuntimeConfig notificationCronExpression) { 145 this.notificationCronExpression = notificationCronExpression; 146 } 147 148 @Required 149 public void setAutofixEnabled(RuntimeConfig autofixEnabled) { 150 this.autofixEnabled = autofixEnabled; 151 } 152 153 @Required 154 public void setAutofixCronExpression(RuntimeConfig autofixCronExpression) { 155 this.autofixCronExpression = autofixCronExpression; 156 } 157 158 @Required 159 public void setAutofixQuietPeriod(RuntimeConfig autofixQuietPeriod) { 160 this.autofixQuietPeriod = autofixQuietPeriod; 161 } 162 163 @Required 164 public void setAutofixMaxAttempts(RuntimeConfig autofixMaxAttempts) { 165 this.autofixMaxAttempts = autofixMaxAttempts; 166 } 167 168}