1 16 17 package org.springframework.scheduling.quartz; 18 19 import java.io.IOException ; 20 import java.util.ArrayList ; 21 import java.util.Arrays ; 22 import java.util.Iterator ; 23 import java.util.LinkedList ; 24 import java.util.List ; 25 import java.util.Map ; 26 import java.util.Properties ; 27 28 import javax.sql.DataSource ; 29 30 import org.apache.commons.logging.Log; 31 import org.apache.commons.logging.LogFactory; 32 import org.quartz.Calendar; 33 import org.quartz.JobDetail; 34 import org.quartz.JobListener; 35 import org.quartz.ObjectAlreadyExistsException; 36 import org.quartz.Scheduler; 37 import org.quartz.SchedulerException; 38 import org.quartz.SchedulerFactory; 39 import org.quartz.SchedulerListener; 40 import org.quartz.Trigger; 41 import org.quartz.TriggerListener; 42 import org.quartz.impl.StdSchedulerFactory; 43 import org.quartz.simpl.SimpleThreadPool; 44 import org.quartz.spi.JobFactory; 45 46 import org.springframework.beans.BeanUtils; 47 import org.springframework.beans.factory.DisposableBean; 48 import org.springframework.beans.factory.FactoryBean; 49 import org.springframework.beans.factory.InitializingBean; 50 import org.springframework.context.ApplicationContext; 51 import org.springframework.context.ApplicationContextAware; 52 import org.springframework.context.Lifecycle; 53 import org.springframework.core.io.Resource; 54 import org.springframework.core.io.support.PropertiesLoaderUtils; 55 import org.springframework.core.task.TaskExecutor; 56 import org.springframework.scheduling.SchedulingException; 57 import org.springframework.transaction.PlatformTransactionManager; 58 import org.springframework.transaction.TransactionException; 59 import org.springframework.transaction.TransactionStatus; 60 import org.springframework.transaction.support.DefaultTransactionDefinition; 61 import org.springframework.util.CollectionUtils; 62 63 100 public class SchedulerFactoryBean 101 implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean, Lifecycle { 102 103 public static final String PROP_THREAD_COUNT = "org.quartz.threadPool.threadCount"; 104 105 public static final int DEFAULT_THREAD_COUNT = 10; 106 107 108 private static final ThreadLocal configTimeTaskExecutorHolder = new ThreadLocal (); 109 110 private static final ThreadLocal configTimeDataSourceHolder = new ThreadLocal (); 111 112 private static final ThreadLocal configTimeNonTransactionalDataSourceHolder = new ThreadLocal (); 113 114 123 public static TaskExecutor getConfigTimeTaskExecutor() { 124 return (TaskExecutor) configTimeTaskExecutorHolder.get(); 125 } 126 127 136 public static DataSource getConfigTimeDataSource() { 137 return (DataSource ) configTimeDataSourceHolder.get(); 138 } 139 140 149 public static DataSource getConfigTimeNonTransactionalDataSource() { 150 return (DataSource ) configTimeNonTransactionalDataSourceHolder.get(); 151 } 152 153 154 protected final Log logger = LogFactory.getLog(getClass()); 155 156 157 private Class schedulerFactoryClass = StdSchedulerFactory.class; 158 159 private String schedulerName; 160 161 private Resource configLocation; 162 163 private Properties quartzProperties; 164 165 166 private TaskExecutor taskExecutor; 167 168 private DataSource dataSource; 169 170 private DataSource nonTransactionalDataSource; 171 172 private PlatformTransactionManager transactionManager; 173 174 175 private Map schedulerContextMap; 176 177 private ApplicationContext applicationContext; 178 179 private String applicationContextSchedulerContextKey; 180 181 private JobFactory jobFactory = new AdaptableJobFactory(); 182 183 184 private boolean overwriteExistingJobs = false; 185 186 private String [] jobSchedulingDataLocations; 187 188 private List jobDetails; 189 190 private Map calendars; 191 192 private List triggers; 193 194 195 private SchedulerListener[] schedulerListeners; 196 197 private JobListener[] globalJobListeners; 198 199 private JobListener[] jobListeners; 200 201 private TriggerListener[] globalTriggerListeners; 202 203 private TriggerListener[] triggerListeners; 204 205 206 private boolean autoStartup = true; 207 208 private int startupDelay = 0; 209 210 private boolean waitForJobsToCompleteOnShutdown = false; 211 212 213 private Scheduler scheduler; 214 215 216 225 public void setSchedulerFactoryClass(Class schedulerFactoryClass) { 226 if (schedulerFactoryClass == null || !SchedulerFactory.class.isAssignableFrom(schedulerFactoryClass)) { 227 throw new IllegalArgumentException ("schedulerFactoryClass must implement [org.quartz.SchedulerFactory]"); 228 } 229 this.schedulerFactoryClass = schedulerFactoryClass; 230 } 231 232 238 public void setSchedulerName(String schedulerName) { 239 this.schedulerName = schedulerName; 240 } 241 242 249 public void setConfigLocation(Resource configLocation) { 250 this.configLocation = configLocation; 251 } 252 253 259 public void setQuartzProperties(Properties quartzProperties) { 260 this.quartzProperties = quartzProperties; 261 } 262 263 264 276 public void setTaskExecutor(TaskExecutor taskExecutor) { 277 this.taskExecutor = taskExecutor; 278 } 279 280 300 public void setDataSource(DataSource dataSource) { 301 this.dataSource = dataSource; 302 } 303 304 314 public void setNonTransactionalDataSource(DataSource nonTransactionalDataSource) { 315 this.nonTransactionalDataSource = nonTransactionalDataSource; 316 } 317 318 324 public void setTransactionManager(PlatformTransactionManager transactionManager) { 325 this.transactionManager = transactionManager; 326 } 327 328 329 339 public void setSchedulerContextAsMap(Map schedulerContextAsMap) { 340 this.schedulerContextMap = schedulerContextAsMap; 341 } 342 343 public void setApplicationContext(ApplicationContext applicationContext) { 344 this.applicationContext = applicationContext; 345 } 346 347 363 public void setApplicationContextSchedulerContextKey(String applicationContextSchedulerContextKey) { 364 this.applicationContextSchedulerContextKey = applicationContextSchedulerContextKey; 365 } 366 367 379 public void setJobFactory(JobFactory jobFactory) { 380 this.jobFactory = jobFactory; 381 } 382 383 384 389 public void setOverwriteExistingJobs(boolean overwriteExistingJobs) { 390 this.overwriteExistingJobs = overwriteExistingJobs; 391 } 392 393 401 public void setJobSchedulingDataLocation(String jobSchedulingDataLocation) { 402 this.jobSchedulingDataLocations = new String [] {jobSchedulingDataLocation}; 403 } 404 405 413 public void setJobSchedulingDataLocations(String [] jobSchedulingDataLocations) { 414 this.jobSchedulingDataLocations = jobSchedulingDataLocations; 415 } 416 417 429 public void setJobDetails(JobDetail[] jobDetails) { 430 this.jobDetails = new ArrayList (Arrays.asList(jobDetails)); 433 } 434 435 443 public void setCalendars(Map calendars) { 444 this.calendars = calendars; 445 } 446 447 460 public void setTriggers(Trigger[] triggers) { 461 this.triggers = Arrays.asList(triggers); 462 } 463 464 465 468 public void setSchedulerListeners(SchedulerListener[] schedulerListeners) { 469 this.schedulerListeners = schedulerListeners; 470 } 471 472 476 public void setGlobalJobListeners(JobListener[] globalJobListeners) { 477 this.globalJobListeners = globalJobListeners; 478 } 479 480 488 public void setJobListeners(JobListener[] jobListeners) { 489 this.jobListeners = jobListeners; 490 } 491 492 496 public void setGlobalTriggerListeners(TriggerListener[] globalTriggerListeners) { 497 this.globalTriggerListeners = globalTriggerListeners; 498 } 499 500 509 public void setTriggerListeners(TriggerListener[] triggerListeners) { 510 this.triggerListeners = triggerListeners; 511 } 512 513 514 518 public void setAutoStartup(boolean autoStartup) { 519 this.autoStartup = autoStartup; 520 } 521 522 529 public void setStartupDelay(int startupDelay) { 530 this.startupDelay = startupDelay; 531 } 532 533 538 public void setWaitForJobsToCompleteOnShutdown(boolean waitForJobsToCompleteOnShutdown) { 539 this.waitForJobsToCompleteOnShutdown = waitForJobsToCompleteOnShutdown; 540 } 541 542 543 547 public void afterPropertiesSet() throws Exception { 548 if (this.dataSource == null && this.nonTransactionalDataSource != null) { 549 this.dataSource = this.nonTransactionalDataSource; 550 } 551 552 SchedulerFactory schedulerFactory = (SchedulerFactory) 554 BeanUtils.instantiateClass(this.schedulerFactoryClass); 555 556 initSchedulerFactory(schedulerFactory); 557 558 if (this.taskExecutor != null) { 559 configTimeTaskExecutorHolder.set(this.taskExecutor); 561 } 562 if (this.dataSource != null) { 563 configTimeDataSourceHolder.set(this.dataSource); 565 } 566 if (this.nonTransactionalDataSource != null) { 567 configTimeNonTransactionalDataSourceHolder.set(this.nonTransactionalDataSource); 569 } 570 571 572 try { 574 this.scheduler = createScheduler(schedulerFactory, this.schedulerName); 575 if (this.jobFactory != null) { 576 if (this.jobFactory instanceof SchedulerContextAware) { 577 ((SchedulerContextAware) this.jobFactory).setSchedulerContext(this.scheduler.getContext()); 578 } 579 this.scheduler.setJobFactory(this.jobFactory); 580 } 581 } 582 583 finally { 584 if (this.taskExecutor != null) { 585 configTimeTaskExecutorHolder.set(null); 586 } 587 if (this.dataSource != null) { 588 configTimeDataSourceHolder.set(null); 589 } 590 if (this.nonTransactionalDataSource != null) { 591 configTimeNonTransactionalDataSourceHolder.set(null); 592 } 593 } 594 595 populateSchedulerContext(); 596 597 registerListeners(); 598 599 registerJobsAndTriggers(); 600 601 if (this.autoStartup) { 603 startScheduler(this.scheduler, this.startupDelay); 604 } 605 } 606 607 608 612 private void initSchedulerFactory(SchedulerFactory schedulerFactory) 613 throws SchedulerException, IOException { 614 615 if (this.configLocation != null || this.quartzProperties != null || 616 this.dataSource != null || this.schedulerName != null || this.taskExecutor != null) { 617 618 if (!(schedulerFactory instanceof StdSchedulerFactory)) { 619 throw new IllegalArgumentException ("StdSchedulerFactory required for applying Quartz properties"); 620 } 621 622 Properties mergedProps = new Properties (); 623 624 if (this.taskExecutor != null) { 627 mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, LocalTaskExecutorThreadPool.class.getName()); 628 } 629 else { 630 mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getName()); 631 mergedProps.setProperty(PROP_THREAD_COUNT, Integer.toString(DEFAULT_THREAD_COUNT)); 632 } 633 634 if (this.configLocation != null) { 635 if (logger.isInfoEnabled()) { 636 logger.info("Loading Quartz config from [" + this.configLocation + "]"); 637 } 638 PropertiesLoaderUtils.fillProperties(mergedProps, this.configLocation); 639 } 640 641 CollectionUtils.mergePropertiesIntoMap(this.quartzProperties, mergedProps); 642 643 if (this.dataSource != null) { 644 mergedProps.put(StdSchedulerFactory.PROP_JOB_STORE_CLASS, LocalDataSourceJobStore.class.getName()); 645 } 646 647 if (this.schedulerName != null) { 649 mergedProps.put(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, this.schedulerName); 650 } 651 652 ((StdSchedulerFactory) schedulerFactory).initialize(mergedProps); 653 } 654 } 655 656 668 protected Scheduler createScheduler(SchedulerFactory schedulerFactory, String schedulerName) 669 throws SchedulerException { 670 671 return schedulerFactory.getScheduler(); 675 } 676 677 681 private void populateSchedulerContext() throws SchedulerException { 682 if (this.schedulerContextMap != null) { 684 this.scheduler.getContext().putAll(this.schedulerContextMap); 685 } 686 687 if (this.applicationContextSchedulerContextKey != null) { 689 if (this.applicationContext == null) { 690 throw new IllegalStateException ( 691 "SchedulerFactoryBean needs to be set up in an ApplicationContext " + 692 "to be able to handle an 'applicationContextSchedulerContextKey'"); 693 } 694 this.scheduler.getContext().put(this.applicationContextSchedulerContextKey, this.applicationContext); 695 } 696 } 697 698 699 702 private void registerListeners() throws SchedulerException { 703 if (this.schedulerListeners != null) { 704 for (int i = 0; i < this.schedulerListeners.length; i++) { 705 this.scheduler.addSchedulerListener(this.schedulerListeners[i]); 706 } 707 } 708 if (this.globalJobListeners != null) { 709 for (int i = 0; i < this.globalJobListeners.length; i++) { 710 this.scheduler.addGlobalJobListener(this.globalJobListeners[i]); 711 } 712 } 713 if (this.jobListeners != null) { 714 for (int i = 0; i < this.jobListeners.length; i++) { 715 this.scheduler.addJobListener(this.jobListeners[i]); 716 } 717 } 718 if (this.globalTriggerListeners != null) { 719 for (int i = 0; i < this.globalTriggerListeners.length; i++) { 720 this.scheduler.addGlobalTriggerListener(this.globalTriggerListeners[i]); 721 } 722 } 723 if (this.triggerListeners != null) { 724 for (int i = 0; i < this.triggerListeners.length; i++) { 725 this.scheduler.addTriggerListener(this.triggerListeners[i]); 726 } 727 } 728 } 729 730 733 private void registerJobsAndTriggers() throws SchedulerException { 734 TransactionStatus transactionStatus = null; 735 if (this.transactionManager != null) { 736 transactionStatus = this.transactionManager.getTransaction(new DefaultTransactionDefinition()); 737 } 738 try { 739 740 if (this.jobSchedulingDataLocations != null) { 741 ResourceJobSchedulingDataProcessor dataProcessor = new ResourceJobSchedulingDataProcessor(); 742 if (this.applicationContext != null) { 743 dataProcessor.setResourceLoader(this.applicationContext); 744 } 745 for (int i = 0; i < this.jobSchedulingDataLocations.length; i++) { 746 dataProcessor.processFileAndScheduleJobs( 747 this.jobSchedulingDataLocations[i], this.scheduler, this.overwriteExistingJobs); 748 } 749 } 750 751 if (this.jobDetails != null) { 753 for (Iterator it = this.jobDetails.iterator(); it.hasNext();) { 754 JobDetail jobDetail = (JobDetail) it.next(); 755 addJobToScheduler(jobDetail); 756 } 757 } 758 else { 759 this.jobDetails = new LinkedList (); 761 } 762 763 if (this.calendars != null) { 765 for (Iterator it = this.calendars.keySet().iterator(); it.hasNext();) { 766 String calendarName = (String ) it.next(); 767 Calendar calendar = (Calendar) this.calendars.get(calendarName); 768 this.scheduler.addCalendar(calendarName, calendar, true, true); 769 } 770 } 771 772 if (this.triggers != null) { 774 for (Iterator it = this.triggers.iterator(); it.hasNext();) { 775 Trigger trigger = (Trigger) it.next(); 776 addTriggerToScheduler(trigger); 777 } 778 } 779 } 780 781 catch (Throwable ex) { 782 if (transactionStatus != null) { 783 try { 784 this.transactionManager.rollback(transactionStatus); 785 } 786 catch (TransactionException tex) { 787 logger.error("Job registration exception overridden by rollback exception", ex); 788 throw tex; 789 } 790 } 791 if (ex instanceof SchedulerException) { 792 throw (SchedulerException) ex; 793 } 794 if (ex instanceof Exception ) { 795 throw new SchedulerException( 796 "Registration of jobs and triggers failed: " + ex.getMessage(), (Exception ) ex); 797 } 798 throw new SchedulerException("Registration of jobs and triggers failed: " + ex.getMessage()); 799 } 800 801 if (transactionStatus != null) { 802 this.transactionManager.commit(transactionStatus); 803 } 804 } 805 806 814 private boolean addJobToScheduler(JobDetail jobDetail) throws SchedulerException { 815 if (this.overwriteExistingJobs || 816 this.scheduler.getJobDetail(jobDetail.getName(), jobDetail.getGroup()) == null) { 817 this.scheduler.addJob(jobDetail, true); 818 return true; 819 } 820 else { 821 return false; 822 } 823 } 824 825 833 private boolean addTriggerToScheduler(Trigger trigger) throws SchedulerException { 834 boolean triggerExists = (this.scheduler.getTrigger(trigger.getName(), trigger.getGroup()) != null); 835 if (!triggerExists || this.overwriteExistingJobs) { 836 if (trigger instanceof JobDetailAwareTrigger) { 838 JobDetail jobDetail = ((JobDetailAwareTrigger) trigger).getJobDetail(); 839 if (!this.jobDetails.contains(jobDetail) && addJobToScheduler(jobDetail)) { 841 this.jobDetails.add(jobDetail); 842 } 843 } 844 if (!triggerExists) { 845 try { 846 this.scheduler.scheduleJob(trigger); 847 } 848 catch (ObjectAlreadyExistsException ex) { 849 if (logger.isDebugEnabled()) { 850 logger.debug("Unexpectedly found existing trigger, assumably due to cluster race condition: " + 851 ex.getMessage() + " - can safely be ignored"); 852 } 853 if (this.overwriteExistingJobs) { 854 this.scheduler.rescheduleJob(trigger.getName(), trigger.getGroup(), trigger); 855 } 856 } 857 } 858 else { 859 this.scheduler.rescheduleJob(trigger.getName(), trigger.getGroup(), trigger); 860 } 861 return true; 862 } 863 else { 864 return false; 865 } 866 } 867 868 869 875 protected void startScheduler(final Scheduler scheduler, final int startupDelay) throws SchedulerException { 876 if (startupDelay <= 0) { 877 logger.info("Starting Quartz Scheduler now"); 878 scheduler.start(); 879 } 880 else { 881 if (logger.isInfoEnabled()) { 882 logger.info("Will start Quartz Scheduler [" + scheduler.getSchedulerName() + 883 "] in " + startupDelay + " seconds"); 884 } 885 Thread schedulerThread = new Thread () { 886 public void run() { 887 try { 888 Thread.sleep(startupDelay * 1000); 889 } 890 catch (InterruptedException ex) { 891 } 893 if (logger.isInfoEnabled()) { 894 logger.info("Starting Quartz Scheduler now, after delay of " + startupDelay + " seconds"); 895 } 896 try { 897 scheduler.start(); 898 } 899 catch (SchedulerException ex) { 900 throw new SchedulingException("Could not start Quartz Scheduler after delay", ex); 901 } 902 } 903 }; 904 schedulerThread.setName("Quartz Scheduler [" + scheduler.getSchedulerName() + "]"); 905 schedulerThread.start(); 906 } 907 } 908 909 910 914 public Object getObject() { 915 return this.scheduler; 916 } 917 918 public Class getObjectType() { 919 return (this.scheduler != null) ? this.scheduler.getClass() : Scheduler.class; 920 } 921 922 public boolean isSingleton() { 923 return true; 924 } 925 926 927 931 public void start() throws SchedulingException { 932 if (this.scheduler != null) { 933 try { 934 this.scheduler.start(); 935 } 936 catch (SchedulerException ex) { 937 throw new SchedulingException("Could not start Quartz Scheduler", ex); 938 } 939 } 940 } 941 942 public void stop() throws SchedulingException { 943 if (this.scheduler != null) { 944 try { 945 this.scheduler.standby(); 946 } 947 catch (SchedulerException ex) { 948 throw new SchedulingException("Could not stop Quartz Scheduler", ex); 949 } 950 } 951 } 952 953 public boolean isRunning() throws SchedulingException { 954 if (this.scheduler != null) { 955 try { 956 return !this.scheduler.isInStandbyMode(); 957 } 958 catch (SchedulerException ex) { 959 return false; 960 } 961 } 962 return false; 963 } 964 965 966 970 974 public void destroy() throws SchedulerException { 975 logger.info("Shutting down Quartz Scheduler"); 976 this.scheduler.shutdown(this.waitForJobsToCompleteOnShutdown); 977 } 978 979 } 980 | Popular Tags |