001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache 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 * https://www.apache.org/licenses/LICENSE-2.0 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 017package org.springframework.scheduling.quartz; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.LinkedList; 022import java.util.List; 023import java.util.Map; 024 025import org.apache.commons.logging.Log; 026import org.apache.commons.logging.LogFactory; 027import org.quartz.Calendar; 028import org.quartz.JobDetail; 029import org.quartz.JobListener; 030import org.quartz.ListenerManager; 031import org.quartz.ObjectAlreadyExistsException; 032import org.quartz.Scheduler; 033import org.quartz.SchedulerException; 034import org.quartz.SchedulerListener; 035import org.quartz.Trigger; 036import org.quartz.TriggerListener; 037import org.quartz.spi.ClassLoadHelper; 038import org.quartz.xml.XMLSchedulingDataProcessor; 039 040import org.springframework.context.ResourceLoaderAware; 041import org.springframework.core.io.ResourceLoader; 042import org.springframework.transaction.PlatformTransactionManager; 043import org.springframework.transaction.TransactionException; 044import org.springframework.transaction.TransactionStatus; 045import org.springframework.transaction.support.DefaultTransactionDefinition; 046 047/** 048 * Common base class for accessing a Quartz Scheduler, i.e. for registering jobs, 049 * triggers and listeners on a {@link org.quartz.Scheduler} instance. 050 * 051 * <p>For concrete usage, check out the {@link SchedulerFactoryBean} and 052 * {@link SchedulerAccessorBean} classes. 053 * 054 * <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1. 055 * 056 * @author Juergen Hoeller 057 * @author Stephane Nicoll 058 * @since 2.5.6 059 */ 060public abstract class SchedulerAccessor implements ResourceLoaderAware { 061 062 protected final Log logger = LogFactory.getLog(getClass()); 063 064 private boolean overwriteExistingJobs = false; 065 066 private String[] jobSchedulingDataLocations; 067 068 private List<JobDetail> jobDetails; 069 070 private Map<String, Calendar> calendars; 071 072 private List<Trigger> triggers; 073 074 private SchedulerListener[] schedulerListeners; 075 076 private JobListener[] globalJobListeners; 077 078 private TriggerListener[] globalTriggerListeners; 079 080 private PlatformTransactionManager transactionManager; 081 082 protected ResourceLoader resourceLoader; 083 084 085 /** 086 * Set whether any jobs defined on this SchedulerFactoryBean should overwrite 087 * existing job definitions. Default is "false", to not overwrite already 088 * registered jobs that have been read in from a persistent job store. 089 */ 090 public void setOverwriteExistingJobs(boolean overwriteExistingJobs) { 091 this.overwriteExistingJobs = overwriteExistingJobs; 092 } 093 094 /** 095 * Set the location of a Quartz job definition XML file that follows the 096 * "job_scheduling_data_1_5" XSD or better. Can be specified to automatically 097 * register jobs that are defined in such a file, possibly in addition 098 * to jobs defined directly on this SchedulerFactoryBean. 099 * @see org.quartz.xml.XMLSchedulingDataProcessor 100 */ 101 public void setJobSchedulingDataLocation(String jobSchedulingDataLocation) { 102 this.jobSchedulingDataLocations = new String[] {jobSchedulingDataLocation}; 103 } 104 105 /** 106 * Set the locations of Quartz job definition XML files that follow the 107 * "job_scheduling_data_1_5" XSD or better. Can be specified to automatically 108 * register jobs that are defined in such files, possibly in addition 109 * to jobs defined directly on this SchedulerFactoryBean. 110 * @see org.quartz.xml.XMLSchedulingDataProcessor 111 */ 112 public void setJobSchedulingDataLocations(String... jobSchedulingDataLocations) { 113 this.jobSchedulingDataLocations = jobSchedulingDataLocations; 114 } 115 116 /** 117 * Register a list of JobDetail objects with the Scheduler that 118 * this FactoryBean creates, to be referenced by Triggers. 119 * <p>This is not necessary when a Trigger determines the JobDetail 120 * itself: In this case, the JobDetail will be implicitly registered 121 * in combination with the Trigger. 122 * @see #setTriggers 123 * @see org.quartz.JobDetail 124 */ 125 public void setJobDetails(JobDetail... jobDetails) { 126 // Use modifiable ArrayList here, to allow for further adding of 127 // JobDetail objects during autodetection of JobDetail-aware Triggers. 128 this.jobDetails = new ArrayList<JobDetail>(Arrays.asList(jobDetails)); 129 } 130 131 /** 132 * Register a list of Quartz Calendar objects with the Scheduler 133 * that this FactoryBean creates, to be referenced by Triggers. 134 * @param calendars a Map with calendar names as keys as Calendar 135 * objects as values 136 * @see org.quartz.Calendar 137 */ 138 public void setCalendars(Map<String, Calendar> calendars) { 139 this.calendars = calendars; 140 } 141 142 /** 143 * Register a list of Trigger objects with the Scheduler that 144 * this FactoryBean creates. 145 * <p>If the Trigger determines the corresponding JobDetail itself, 146 * the job will be automatically registered with the Scheduler. 147 * Else, the respective JobDetail needs to be registered via the 148 * "jobDetails" property of this FactoryBean. 149 * @see #setJobDetails 150 * @see org.quartz.JobDetail 151 */ 152 public void setTriggers(Trigger... triggers) { 153 this.triggers = Arrays.asList(triggers); 154 } 155 156 /** 157 * Specify Quartz SchedulerListeners to be registered with the Scheduler. 158 */ 159 public void setSchedulerListeners(SchedulerListener... schedulerListeners) { 160 this.schedulerListeners = schedulerListeners; 161 } 162 163 /** 164 * Specify global Quartz JobListeners to be registered with the Scheduler. 165 * Such JobListeners will apply to all Jobs in the Scheduler. 166 */ 167 public void setGlobalJobListeners(JobListener... globalJobListeners) { 168 this.globalJobListeners = globalJobListeners; 169 } 170 171 /** 172 * Specify global Quartz TriggerListeners to be registered with the Scheduler. 173 * Such TriggerListeners will apply to all Triggers in the Scheduler. 174 */ 175 public void setGlobalTriggerListeners(TriggerListener... globalTriggerListeners) { 176 this.globalTriggerListeners = globalTriggerListeners; 177 } 178 179 /** 180 * Set the transaction manager to be used for registering jobs and triggers 181 * that are defined by this SchedulerFactoryBean. Default is none; setting 182 * this only makes sense when specifying a DataSource for the Scheduler. 183 */ 184 public void setTransactionManager(PlatformTransactionManager transactionManager) { 185 this.transactionManager = transactionManager; 186 } 187 188 @Override 189 public void setResourceLoader(ResourceLoader resourceLoader) { 190 this.resourceLoader = resourceLoader; 191 } 192 193 194 /** 195 * Register jobs and triggers (within a transaction, if possible). 196 */ 197 protected void registerJobsAndTriggers() throws SchedulerException { 198 TransactionStatus transactionStatus = null; 199 if (this.transactionManager != null) { 200 transactionStatus = this.transactionManager.getTransaction(new DefaultTransactionDefinition()); 201 } 202 203 try { 204 if (this.jobSchedulingDataLocations != null) { 205 ClassLoadHelper clh = new ResourceLoaderClassLoadHelper(this.resourceLoader); 206 clh.initialize(); 207 XMLSchedulingDataProcessor dataProcessor = new XMLSchedulingDataProcessor(clh); 208 for (String location : this.jobSchedulingDataLocations) { 209 dataProcessor.processFileAndScheduleJobs(location, getScheduler()); 210 } 211 } 212 213 // Register JobDetails. 214 if (this.jobDetails != null) { 215 for (JobDetail jobDetail : this.jobDetails) { 216 addJobToScheduler(jobDetail); 217 } 218 } 219 else { 220 // Create empty list for easier checks when registering triggers. 221 this.jobDetails = new LinkedList<JobDetail>(); 222 } 223 224 // Register Calendars. 225 if (this.calendars != null) { 226 for (String calendarName : this.calendars.keySet()) { 227 Calendar calendar = this.calendars.get(calendarName); 228 getScheduler().addCalendar(calendarName, calendar, true, true); 229 } 230 } 231 232 // Register Triggers. 233 if (this.triggers != null) { 234 for (Trigger trigger : this.triggers) { 235 addTriggerToScheduler(trigger); 236 } 237 } 238 } 239 240 catch (Throwable ex) { 241 if (transactionStatus != null) { 242 try { 243 this.transactionManager.rollback(transactionStatus); 244 } 245 catch (TransactionException tex) { 246 logger.error("Job registration exception overridden by rollback exception", ex); 247 throw tex; 248 } 249 } 250 if (ex instanceof SchedulerException) { 251 throw (SchedulerException) ex; 252 } 253 if (ex instanceof Exception) { 254 throw new SchedulerException("Registration of jobs and triggers failed: " + ex.getMessage(), ex); 255 } 256 throw new SchedulerException("Registration of jobs and triggers failed: " + ex.getMessage()); 257 } 258 259 if (transactionStatus != null) { 260 this.transactionManager.commit(transactionStatus); 261 } 262 } 263 264 /** 265 * Add the given job to the Scheduler, if it doesn't already exist. 266 * Overwrites the job in any case if "overwriteExistingJobs" is set. 267 * @param jobDetail the job to add 268 * @return {@code true} if the job was actually added, 269 * {@code false} if it already existed before 270 * @see #setOverwriteExistingJobs 271 */ 272 private boolean addJobToScheduler(JobDetail jobDetail) throws SchedulerException { 273 if (this.overwriteExistingJobs || getScheduler().getJobDetail(jobDetail.getKey()) == null) { 274 getScheduler().addJob(jobDetail, true); 275 return true; 276 } 277 else { 278 return false; 279 } 280 } 281 282 /** 283 * Add the given trigger to the Scheduler, if it doesn't already exist. 284 * Overwrites the trigger in any case if "overwriteExistingJobs" is set. 285 * @param trigger the trigger to add 286 * @return {@code true} if the trigger was actually added, 287 * {@code false} if it already existed before 288 * @see #setOverwriteExistingJobs 289 */ 290 private boolean addTriggerToScheduler(Trigger trigger) throws SchedulerException { 291 boolean triggerExists = (getScheduler().getTrigger(trigger.getKey()) != null); 292 if (triggerExists && !this.overwriteExistingJobs) { 293 return false; 294 } 295 296 // Check if the Trigger is aware of an associated JobDetail. 297 JobDetail jobDetail = (JobDetail) trigger.getJobDataMap().remove("jobDetail"); 298 if (triggerExists) { 299 if (jobDetail != null && !this.jobDetails.contains(jobDetail) && addJobToScheduler(jobDetail)) { 300 this.jobDetails.add(jobDetail); 301 } 302 try { 303 getScheduler().rescheduleJob(trigger.getKey(), trigger); 304 } 305 catch (ObjectAlreadyExistsException ex) { 306 if (logger.isDebugEnabled()) { 307 logger.debug("Unexpectedly encountered existing trigger on rescheduling, assumably due to " + 308 "cluster race condition: " + ex.getMessage() + " - can safely be ignored"); 309 } 310 } 311 } 312 else { 313 try { 314 if (jobDetail != null && !this.jobDetails.contains(jobDetail) && 315 (this.overwriteExistingJobs || getScheduler().getJobDetail(jobDetail.getKey()) == null)) { 316 getScheduler().scheduleJob(jobDetail, trigger); 317 this.jobDetails.add(jobDetail); 318 } 319 else { 320 getScheduler().scheduleJob(trigger); 321 } 322 } 323 catch (ObjectAlreadyExistsException ex) { 324 if (logger.isDebugEnabled()) { 325 logger.debug("Unexpectedly encountered existing trigger on job scheduling, assumably due to " + 326 "cluster race condition: " + ex.getMessage() + " - can safely be ignored"); 327 } 328 if (this.overwriteExistingJobs) { 329 getScheduler().rescheduleJob(trigger.getKey(), trigger); 330 } 331 } 332 } 333 return true; 334 } 335 336 /** 337 * Register all specified listeners with the Scheduler. 338 */ 339 protected void registerListeners() throws SchedulerException { 340 ListenerManager listenerManager = getScheduler().getListenerManager(); 341 if (this.schedulerListeners != null) { 342 for (SchedulerListener listener : this.schedulerListeners) { 343 listenerManager.addSchedulerListener(listener); 344 } 345 } 346 if (this.globalJobListeners != null) { 347 for (JobListener listener : this.globalJobListeners) { 348 listenerManager.addJobListener(listener); 349 } 350 } 351 if (this.globalTriggerListeners != null) { 352 for (TriggerListener listener : this.globalTriggerListeners) { 353 listenerManager.addTriggerListener(listener); 354 } 355 } 356 } 357 358 359 /** 360 * Template method that determines the Scheduler to operate on. 361 * To be implemented by subclasses. 362 */ 363 protected abstract Scheduler getScheduler(); 364 365}