From 05dc89676950e6c38935cd806aabebb718cca15c Mon Sep 17 00:00:00 2001 From: zhouping Date: Mon, 16 Mar 2020 16:13:11 +0800 Subject: [PATCH] =?UTF-8?q?MOBILE-25879=20=E5=85=BC=E5=AE=B9=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E7=AB=AF=EF=BC=8C=E5=8A=A0=E8=80=81quartz=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.third_step7.gradle | 4 +- fine-quartz-old/README.md | 3 + .../com/fr/third/org/quartz/Calendar.java | 111 + .../quartz/CriticalSchedulerException.java | 53 + .../fr/third/org/quartz/CronExpression.java | 1581 +++++++ .../com/fr/third/org/quartz/CronTrigger.java | 1075 +++++ .../fr/third/org/quartz/InterruptableJob.java | 93 + .../java/com/fr/third/org/quartz/Job.java | 79 + .../com/fr/third/org/quartz/JobDataMap.java | 504 +++ .../com/fr/third/org/quartz/JobDetail.java | 537 +++ .../third/org/quartz/JobExecutionContext.java | 368 ++ .../org/quartz/JobExecutionException.java | 182 + .../com/fr/third/org/quartz/JobListener.java | 96 + .../org/quartz/JobPersistenceException.java | 83 + .../org/quartz/NthIncludedDayTrigger.java | 1016 +++++ .../quartz/ObjectAlreadyExistsException.java | 89 + .../com/fr/third/org/quartz/Scheduler.java | 1102 +++++ .../org/quartz/SchedulerConfigException.java | 63 + .../fr/third/org/quartz/SchedulerContext.java | 62 + .../third/org/quartz/SchedulerException.java | 335 ++ .../fr/third/org/quartz/SchedulerFactory.java | 72 + .../third/org/quartz/SchedulerListener.java | 148 + .../third/org/quartz/SchedulerMetaData.java | 350 ++ .../fr/third/org/quartz/SimpleTrigger.java | 955 ++++ .../com/fr/third/org/quartz/StatefulJob.java | 57 + .../java/com/fr/third/org/quartz/Trigger.java | 1060 +++++ .../fr/third/org/quartz/TriggerListener.java | 134 + .../com/fr/third/org/quartz/TriggerUtils.java | 1401 ++++++ .../quartz/UnableToInterruptJobException.java | 63 + .../AbstractCollectionDecorator.java | 132 + .../AbstractIteratorDecorator.java | 58 + .../AbstractSerializableSetDecorator.java | 55 + .../collections/AbstractSetDecorator.java | 44 + .../quartz/collections/ListOrderedSet.java | 296 ++ .../fr/third/org/quartz/core/JobRunShell.java | 431 ++ .../org/quartz/core/JobRunShellFactory.java | 82 + .../org/quartz/core/QuartzScheduler.java | 2245 ++++++++++ .../quartz/core/QuartzSchedulerResources.java | 519 +++ .../quartz/core/QuartzSchedulerThread.java | 560 +++ .../quartz/core/RemotableQuartzScheduler.java | 235 + .../quartz/core/SchedulerSignalerImpl.java | 93 + .../org/quartz/core/SchedulingContext.java | 87 + .../org/quartz/core/mbeans-descriptors.xml | 234 + .../com/fr/third/org/quartz/core/package.html | 15 + .../org/quartz/ee/jta/JTAJobRunShell.java | 166 + .../quartz/ee/jta/JTAJobRunShellFactory.java | 114 + .../quartz/ee/jta/UserTransactionHelper.java | 225 + .../org/quartz/helpers/TriggerUtils.java | 1194 +++++ .../org/quartz/helpers/VersionPrinter.java | 54 + .../fr/third/org/quartz/helpers/package.html | 15 + .../quartz/impl/DirectSchedulerFactory.java | 482 ++ .../third/org/quartz/impl/QuartzServer.java | 197 + .../org/quartz/impl/RemoteMBeanScheduler.java | 1108 +++++ .../org/quartz/impl/RemoteScheduler.java | 1188 +++++ .../org/quartz/impl/SchedulerRepository.java | 104 + .../quartz/impl/StdJobRunShellFactory.java | 100 + .../third/org/quartz/impl/StdScheduler.java | 844 ++++ .../org/quartz/impl/StdSchedulerFactory.java | 1414 ++++++ .../quartz/impl/calendar/AnnualCalendar.java | 294 ++ .../quartz/impl/calendar/BaseCalendar.java | 283 ++ .../quartz/impl/calendar/CronCalendar.java | 262 ++ .../quartz/impl/calendar/DailyCalendar.java | 1034 +++++ .../quartz/impl/calendar/HolidayCalendar.java | 146 + .../quartz/impl/calendar/MonthlyCalendar.java | 218 + .../quartz/impl/calendar/WeeklyCalendar.java | 200 + ...eRestoringConnectionInvocationHandler.java | 159 + .../impl/jdbcjobstore/CloudscapeDelegate.java | 118 + .../quartz/impl/jdbcjobstore/Constants.java | 193 + .../impl/jdbcjobstore/DB2v6Delegate.java | 146 + .../impl/jdbcjobstore/DB2v7Delegate.java | 72 + .../impl/jdbcjobstore/DB2v8Delegate.java | 51 + .../quartz/impl/jdbcjobstore/DBSemaphore.java | 201 + .../impl/jdbcjobstore/DriverDelegate.java | 1435 ++++++ .../impl/jdbcjobstore/FiredTriggerRecord.java | 154 + .../impl/jdbcjobstore/HSQLDBDelegate.java | 122 + .../InvalidConfigurationException.java | 50 + .../JTANonClusteredSemaphore.java | 304 ++ .../quartz/impl/jdbcjobstore/JobStoreCMT.java | 255 ++ .../impl/jdbcjobstore/JobStoreSupport.java | 3936 +++++++++++++++++ .../quartz/impl/jdbcjobstore/JobStoreTX.java | 96 + .../impl/jdbcjobstore/LockException.java | 54 + .../impl/jdbcjobstore/MSSQLDelegate.java | 108 + .../jdbcjobstore/NoSuchDelegateException.java | 47 + .../impl/jdbcjobstore/PointbaseDelegate.java | 507 +++ .../impl/jdbcjobstore/PostgreSQLDelegate.java | 129 + .../jdbcjobstore/SchedulerStateRecord.java | 92 + .../quartz/impl/jdbcjobstore/Semaphore.java | 79 + .../impl/jdbcjobstore/SimpleSemaphore.java | 170 + .../impl/jdbcjobstore/StdJDBCConstants.java | 609 +++ .../impl/jdbcjobstore/StdJDBCDelegate.java | 3664 +++++++++++++++ .../jdbcjobstore/StdRowLockSemaphore.java | 137 + .../impl/jdbcjobstore/TablePrefixAware.java | 24 + .../jdbcjobstore/UpdateLockRowSemaphore.java | 126 + .../org/quartz/impl/jdbcjobstore/Util.java | 95 + .../com/fr/third/org/quartz/impl/package.html | 18 + .../fr/third/org/quartz/jobs/FileScanJob.java | 136 + .../org/quartz/jobs/FileScanJob.java.bak | 121 + .../org/quartz/jobs/FileScanListener.java | 33 + .../fr/third/org/quartz/jobs/NativeJob.java | 280 ++ .../com/fr/third/org/quartz/jobs/NoOpJob.java | 68 + .../org/quartz/jobs/ee/jmx/JMXInvokerJob.java | 190 + .../org/quartz/jobs/ee/mail/SendMailJob.java | 300 ++ .../listeners/BroadcastSchedulerListener.java | 126 + .../FilterAndBroadcastJobListener.java | 214 + .../FilterAndBroadcastTriggerListener.java | 228 + .../listeners/JobChainingJobListener.java | 108 + .../quartz/listeners/JobListenerSupport.java | 60 + .../listeners/SchedulerListenerSupport.java | 73 + .../listeners/TriggerListenerSupport.java | 67 + .../java/com/fr/third/org/quartz/package.html | 15 + ...dulerPluginWithUserTransactionSupport.java | 205 + .../history/LoggingJobHistoryPlugin.java | 542 +++ .../history/LoggingTriggerHistoryPlugin.java | 440 ++ .../management/ShutdownHookPlugin.java | 164 + .../com/fr/third/org/quartz/quartz.properties | 19 + .../simpl/CascadingClassLoadHelper.java | 214 + .../simpl/CascadingClassLoadHelper.java.bak | 202 + .../simpl/HostnameInstanceIdGenerator.java | 48 + .../InitThreadContextClassLoadHelper.java | 106 + .../InitThreadContextClassLoadHelper.java.bak | 96 + .../simpl/LoadingLoaderClassLoadHelper.java | 93 + .../LoadingLoaderClassLoadHelper.java.bak | 87 + .../simpl/PropertySettingJobFactory.java | 288 ++ .../third/org/quartz/simpl/RAMJobStore.java | 1615 +++++++ .../quartz/simpl/SimpleClassLoadHelper.java | 112 + .../simpl/SimpleClassLoadHelper.java.bak | 106 + .../simpl/SimpleInstanceIdGenerator.java | 39 + .../org/quartz/simpl/SimpleJobFactory.java | 63 + .../org/quartz/simpl/SimpleThreadPool.java | 576 +++ .../org/quartz/simpl/SimpleTimeBroker.java | 76 + .../simpl/ThreadContextClassLoadHelper.java | 94 + .../ThreadContextClassLoadHelper.java.bak | 89 + .../org/quartz/simpl/ZeroSizeThreadPool.java | 111 + .../fr/third/org/quartz/simpl/package.html | 17 + .../third/org/quartz/spi/ClassLoadHelper.java | 75 + .../org/quartz/spi/ClassLoadHelper.java.bak | 69 + .../org/quartz/spi/InstanceIdGenerator.java | 42 + .../fr/third/org/quartz/spi/JobFactory.java | 64 + .../com/fr/third/org/quartz/spi/JobStore.java | 664 +++ .../third/org/quartz/spi/SchedulerPlugin.java | 110 + .../org/quartz/spi/SchedulerSignaler.java | 47 + .../fr/third/org/quartz/spi/ThreadPool.java | 105 + .../fr/third/org/quartz/spi/TimeBroker.java | 89 + .../org/quartz/spi/TriggerFiredBundle.java | 138 + .../com/fr/third/org/quartz/spi/package.html | 17 + .../org/quartz/utils/ConnectionProvider.java | 54 + .../org/quartz/utils/DBConnectionManager.java | 146 + .../third/org/quartz/utils/DirtyFlagMap.java | 394 ++ .../org/quartz/utils/ExceptionHelper.java | 110 + .../quartz/utils/JNDIConnectionProvider.java | 191 + .../com/fr/third/org/quartz/utils/Key.java | 97 + .../com/fr/third/org/quartz/utils/Pair.java | 151 + .../utils/PoolingConnectionProvider.java | 203 + .../org/quartz/utils/PropertiesParser.java | 409 ++ .../quartz/utils/StringKeyDirtyFlagMap.java | 362 ++ .../third/org/quartz/utils/TriggerStatus.java | 127 + 156 files changed, 50639 insertions(+), 1 deletion(-) create mode 100644 fine-quartz-old/README.md create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/Calendar.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/CriticalSchedulerException.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/CronExpression.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/CronTrigger.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/InterruptableJob.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/Job.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobDataMap.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobDetail.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobExecutionContext.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobExecutionException.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobListener.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobPersistenceException.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/NthIncludedDayTrigger.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/ObjectAlreadyExistsException.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/Scheduler.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerConfigException.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerContext.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerException.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerFactory.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerListener.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerMetaData.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/SimpleTrigger.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/StatefulJob.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/Trigger.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/TriggerListener.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/TriggerUtils.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/UnableToInterruptJobException.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/collections/AbstractCollectionDecorator.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/collections/AbstractIteratorDecorator.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/collections/AbstractSerializableSetDecorator.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/collections/AbstractSetDecorator.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/collections/ListOrderedSet.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/JobRunShell.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/JobRunShellFactory.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/QuartzScheduler.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/QuartzSchedulerResources.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/QuartzSchedulerThread.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/RemotableQuartzScheduler.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/SchedulerSignalerImpl.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/SchedulingContext.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/mbeans-descriptors.xml create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/package.html create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/ee/jta/JTAJobRunShell.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/ee/jta/JTAJobRunShellFactory.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/ee/jta/UserTransactionHelper.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/helpers/TriggerUtils.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/helpers/VersionPrinter.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/helpers/package.html create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/DirectSchedulerFactory.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/QuartzServer.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/RemoteMBeanScheduler.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/RemoteScheduler.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/SchedulerRepository.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/StdJobRunShellFactory.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/StdScheduler.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/StdSchedulerFactory.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/AnnualCalendar.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/BaseCalendar.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/CronCalendar.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/DailyCalendar.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/HolidayCalendar.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/MonthlyCalendar.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/WeeklyCalendar.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/AttributeRestoringConnectionInvocationHandler.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/CloudscapeDelegate.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/Constants.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/DB2v6Delegate.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/DB2v7Delegate.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/DB2v8Delegate.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/DBSemaphore.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/DriverDelegate.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/FiredTriggerRecord.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/HSQLDBDelegate.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/InvalidConfigurationException.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/JTANonClusteredSemaphore.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/JobStoreCMT.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/JobStoreSupport.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/JobStoreTX.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/LockException.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/MSSQLDelegate.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/NoSuchDelegateException.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/PointbaseDelegate.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/PostgreSQLDelegate.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/SchedulerStateRecord.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/Semaphore.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/SimpleSemaphore.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/StdJDBCConstants.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/StdJDBCDelegate.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/StdRowLockSemaphore.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/TablePrefixAware.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/UpdateLockRowSemaphore.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/Util.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/package.html create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/FileScanJob.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/FileScanJob.java.bak create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/FileScanListener.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/NativeJob.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/NoOpJob.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/ee/jmx/JMXInvokerJob.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/ee/mail/SendMailJob.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/BroadcastSchedulerListener.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/FilterAndBroadcastJobListener.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/FilterAndBroadcastTriggerListener.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/JobChainingJobListener.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/JobListenerSupport.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/SchedulerListenerSupport.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/TriggerListenerSupport.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/package.html create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/plugins/SchedulerPluginWithUserTransactionSupport.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/plugins/history/LoggingJobHistoryPlugin.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/plugins/history/LoggingTriggerHistoryPlugin.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/plugins/management/ShutdownHookPlugin.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/quartz.properties create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/CascadingClassLoadHelper.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/CascadingClassLoadHelper.java.bak create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/HostnameInstanceIdGenerator.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/InitThreadContextClassLoadHelper.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/InitThreadContextClassLoadHelper.java.bak create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/LoadingLoaderClassLoadHelper.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/LoadingLoaderClassLoadHelper.java.bak create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/PropertySettingJobFactory.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/RAMJobStore.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleClassLoadHelper.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleClassLoadHelper.java.bak create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleInstanceIdGenerator.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleJobFactory.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleThreadPool.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleTimeBroker.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/ThreadContextClassLoadHelper.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/ThreadContextClassLoadHelper.java.bak create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/ZeroSizeThreadPool.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/package.html create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/ClassLoadHelper.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/ClassLoadHelper.java.bak create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/InstanceIdGenerator.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/JobFactory.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/JobStore.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/SchedulerPlugin.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/SchedulerSignaler.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/ThreadPool.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/TimeBroker.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/TriggerFiredBundle.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/package.html create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/ConnectionProvider.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/DBConnectionManager.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/DirtyFlagMap.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/ExceptionHelper.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/JNDIConnectionProvider.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/Key.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/Pair.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/PoolingConnectionProvider.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/PropertiesParser.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/StringKeyDirtyFlagMap.java create mode 100644 fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/TriggerStatus.java diff --git a/build.third_step7.gradle b/build.third_step7.gradle index 03cf6082e..e6b6a4682 100644 --- a/build.third_step7.gradle +++ b/build.third_step7.gradle @@ -25,7 +25,8 @@ sourceSets{ main{ java{ srcDirs=[ - "${srcDir}/fine-quartz/src" + "${srcDir}/fine-quartz/src", + "${srcDir}/fine-quartz-old/src/main/java" ] } } @@ -69,6 +70,7 @@ task copyFiles(type:Copy,dependsOn:'compileJava'){ copy{ println "------------------------------------------------copyfiles" with dataContent.call("${srcDir}/fine-quartz/src") + with dataContent.call("${srcDir}/fine-quartz-old/src/main/java") into "${classesDir}" } } diff --git a/fine-quartz-old/README.md b/fine-quartz-old/README.md new file mode 100644 index 000000000..8aa0c3589 --- /dev/null +++ b/fine-quartz-old/README.md @@ -0,0 +1,3 @@ +1.`fine-quartz-old`是为了与`fine-quartz`区别开,
+2.源码地址https://cloud.finedevelop.com/projects/PF/repos/thirdtools/browse,
+3.相比源码调整了默认的连接池为druid,以前是dbcp \ No newline at end of file diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/Calendar.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/Calendar.java new file mode 100644 index 000000000..f0a97e68f --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/Calendar.java @@ -0,0 +1,111 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ + + +package com.fr.third.org.quartz; + +/** + *

+ * An interface to be implemented by objects that define spaces of time during + * which an associated {@link Trigger} may fire. Calendars do not + * define actual fire times, but rather are used to limit a Trigger + * from firing on its normal schedule if necessary. Most Calendars include all + * times by default and allow the user to specify times to exclude. As such, it + * is often useful to think of Calendars as being used to exclude a block + * of time — as opposed to include a block of time. (i.e. the + * schedule "fire every five minutes except on Sundays" could be + * implemented with a SimpleTrigger and a + * WeeklyCalendar which excludes Sundays) + *

+ * + * @author James House + * @author Juergen Donnerstag + */ +public interface Calendar extends java.io.Serializable { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + int MONTH = 0; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Set a new base calendar or remove the existing one. + *

+ */ + void setBaseCalendar(Calendar baseCalendar); + + /** + *

+ * Get the base calendar. Will be null, if not set. + *

+ */ + Calendar getBaseCalendar(); + + /** + *

+ * Determine whether the given time (in milliseconds) is 'included' by the + * Calendar. + *

+ */ + boolean isTimeIncluded(long timeStamp); + + /** + *

+ * Determine the next time (in milliseconds) that is 'included' by the + * Calendar after the given time. + *

+ */ + long getNextIncludedTime(long timeStamp); + + /** + *

+ * Return the description given to the Calendar instance by + * its creator (if any). + *

+ * + * @return null if no description was set. + */ + String getDescription(); + + /** + *

+ * Set a description for the Calendar instance - may be + * useful for remembering/displaying the purpose of the calendar, though + * the description has no meaning to Quartz. + *

+ */ + void setDescription(String description); +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/CriticalSchedulerException.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/CriticalSchedulerException.java new file mode 100644 index 000000000..ff1e2e321 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/CriticalSchedulerException.java @@ -0,0 +1,53 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ + +package com.fr.third.org.quartz; + +/** + *

+ * An exception that is thrown to indicate that there has been a critical + * failure within the scheduler's core services (such as loss of database + * connectivity). + *

+ * + * @author James House + */ +public class CriticalSchedulerException extends SchedulerException { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Create a CriticalSchedulerException with the given message. + *

+ */ + public CriticalSchedulerException(String msg, int errCode) { + super(msg); + setErrorCode(errCode); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/CronExpression.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/CronExpression.java new file mode 100644 index 000000000..5cc92ab13 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/CronExpression.java @@ -0,0 +1,1581 @@ +package com.fr.third.org.quartz; + +import java.io.Serializable; +import java.text.ParseException; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.SortedSet; +import java.util.StringTokenizer; +import java.util.TimeZone; +import java.util.TreeSet; + +/** + * Provides a parser and evaluator for unix-like cron expressions. Cron + * expressions provide the ability to specify complex time combinations such as + * "At 8:00am every Monday through Friday" or "At 1:30am every + * last Friday of the month". + *

+ * Cron expressions are comprised of 6 required fields and one optional field + * separated by white space. The fields respectively are described as follows: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Field Name Allowed Values Allowed Special Characters
Seconds  + * 0-59  + * , - * /
Minutes  + * 0-59  + * , - * /
Hours  + * 0-23  + * , - * /
Day-of-month  + * 1-31  + * , - * ? / L W
Month  + * 1-12 or JAN-DEC  + * , - * /
Day-of-Week  + * 1-7 or SUN-SAT  + * , - * ? / L #
Year (Optional)  + * empty, 1970-2099  + * , - * /
+ *

+ * The '*' character is used to specify all values. For example, "*" + * in the minute field means "every minute". + *

+ * The '?' character is allowed for the day-of-month and day-of-week fields. It + * is used to specify 'no specific value'. This is useful when you need to + * specify something in one of the two fields, but not the other. + *

+ * The '-' character is used to specify ranges For example "10-12" in + * the hour field means "the hours 10, 11 and 12". + *

+ * The ',' character is used to specify additional values. For example + * "MON,WED,FRI" in the day-of-week field means "the days Monday, + * Wednesday, and Friday". + *

+ * The '/' character is used to specify increments. For example "0/15" + * in the seconds field means "the seconds 0, 15, 30, and 45". And + * "5/15" in the seconds field means "the seconds 5, 20, 35, and + * 50". Specifying '*' before the '/' is equivalent to specifying 0 is + * the value to start with. Essentially, for each field in the expression, there + * is a set of numbers that can be turned on or off. For seconds and minutes, + * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to + * 31, and for months 1 to 12. The "/" character simply helps you turn + * on every "nth" value in the given set. Thus "7/6" in the + * month field only turns on month "7", it does NOT mean every 6th + * month, please note that subtlety. + *

+ * The 'L' character is allowed for the day-of-month and day-of-week fields. + * This character is short-hand for "last", but it has different + * meaning in each of the two fields. For example, the value "L" in + * the day-of-month field means "the last day of the month" - day 31 + * for January, day 28 for February on non-leap years. If used in the + * day-of-week field by itself, it simply means "7" or + * "SAT". But if used in the day-of-week field after another value, it + * means "the last xxx day of the month" - for example "6L" + * means "the last friday of the month". When using the 'L' option, it + * is important not to specify lists, or ranges of values, as you'll get + * confusing results. + *

+ * The 'W' character is allowed for the day-of-month field. This character + * is used to specify the weekday (Monday-Friday) nearest the given day. As an + * example, if you were to specify "15W" as the value for the + * day-of-month field, the meaning is: "the nearest weekday to the 15th of + * the month". So if the 15th is a Saturday, the trigger will fire on + * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the + * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th. + * However if you specify "1W" as the value for day-of-month, and the + * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not + * 'jump' over the boundary of a month's days. The 'W' character can only be + * specified when the day-of-month is a single day, not a range or list of days. + *

+ * The 'L' and 'W' characters can also be combined for the day-of-month + * expression to yield 'LW', which translates to "last weekday of the + * month". + *

+ * The '#' character is allowed for the day-of-week field. This character is + * used to specify "the nth" XXX day of the month. For example, the + * value of "6#3" in the day-of-week field means the third Friday of + * the month (day 6 = Friday and "#3" = the 3rd one in the month). + * Other examples: "2#1" = the first Monday of the month and + * "4#5" = the fifth Wednesday of the month. Note that if you specify + * "#5" and there is not 5 of the given day-of-week in the month, then + * no firing will occur that month. If the '#' character is used, there can + * only be one expression in the day-of-week field ("3#1,6#3" is + * not valid, since there are two expressions). + *

+ * + *

+ * The legal characters and the names of months and days of the week are not + * case sensitive. + * + *

+ * NOTES: + *

+ *

+ * + * + * @author Sharada Jambula, James House + * @author Contributions from Mads Henderson + * @author Refactoring from CronTrigger to CronExpression by Aaron Craven + */ +public class CronExpression implements Serializable, Cloneable { + + private static final long serialVersionUID = 12423409423L; + + protected static final int SECOND = 0; + protected static final int MINUTE = 1; + protected static final int HOUR = 2; + protected static final int DAY_OF_MONTH = 3; + protected static final int MONTH = 4; + protected static final int DAY_OF_WEEK = 5; + protected static final int YEAR = 6; + protected static final int ALL_SPEC_INT = 99; // '*' + protected static final int NO_SPEC_INT = 98; // '?' + protected static final Integer ALL_SPEC = new Integer(ALL_SPEC_INT); + protected static final Integer NO_SPEC = new Integer(NO_SPEC_INT); + + protected static final Map monthMap = new HashMap(20); + protected static final Map dayMap = new HashMap(60); + static { + monthMap.put("JAN", new Integer(0)); + monthMap.put("FEB", new Integer(1)); + monthMap.put("MAR", new Integer(2)); + monthMap.put("APR", new Integer(3)); + monthMap.put("MAY", new Integer(4)); + monthMap.put("JUN", new Integer(5)); + monthMap.put("JUL", new Integer(6)); + monthMap.put("AUG", new Integer(7)); + monthMap.put("SEP", new Integer(8)); + monthMap.put("OCT", new Integer(9)); + monthMap.put("NOV", new Integer(10)); + monthMap.put("DEC", new Integer(11)); + + dayMap.put("SUN", new Integer(1)); + dayMap.put("MON", new Integer(2)); + dayMap.put("TUE", new Integer(3)); + dayMap.put("WED", new Integer(4)); + dayMap.put("THU", new Integer(5)); + dayMap.put("FRI", new Integer(6)); + dayMap.put("SAT", new Integer(7)); + } + + private String cronExpression = null; + private TimeZone timeZone = null; + protected transient TreeSet seconds; + protected transient TreeSet minutes; + protected transient TreeSet hours; + protected transient TreeSet daysOfMonth; + protected transient TreeSet months; + protected transient TreeSet daysOfWeek; + protected transient TreeSet years; + + protected transient boolean lastdayOfWeek = false; + protected transient int nthdayOfWeek = 0; + protected transient boolean lastdayOfMonth = false; + protected transient boolean nearestWeekday = false; + protected transient boolean expressionParsed = false; + + /** + * Constructs a new CronExpression based on the specified + * parameter. + * + * @param cronExpression String representation of the cron expression the + * new object should represent + * @throws java.text.ParseException + * if the string expression cannot be parsed into a valid + * CronExpression + */ + public CronExpression(String cronExpression) throws ParseException { + if (cronExpression == null) { + throw new IllegalArgumentException("cronExpression cannot be null"); + } + + this.cronExpression = cronExpression.toUpperCase(Locale.US); + + buildExpression(this.cronExpression); + } + + /** + * Indicates whether the given date satisfies the cron expression. Note that + * milliseconds are ignored, so two Dates falling on different milliseconds + * of the same second will always have the same result here. + * + * @param date the date to evaluate + * @return a boolean indicating whether the given date satisfies the cron + * expression + */ + public boolean isSatisfiedBy(Date date) { + Calendar testDateCal = Calendar.getInstance(getTimeZone()); + testDateCal.setTime(date); + testDateCal.set(Calendar.MILLISECOND, 0); + Date originalDate = testDateCal.getTime(); + + testDateCal.add(Calendar.SECOND, -1); + + Date timeAfter = getTimeAfter(testDateCal.getTime()); + + return ((timeAfter != null) && (timeAfter.equals(originalDate))); + } + + /** + * Returns the next date/time after the given date/time which + * satisfies the cron expression. + * + * @param date the date/time at which to begin the search for the next valid + * date/time + * @return the next valid date/time + */ + public Date getNextValidTimeAfter(Date date) { + return getTimeAfter(date); + } + + /** + * Returns the next date/time after the given date/time which does + * not satisfy the expression + * + * @param date the date/time at which to begin the search for the next + * invalid date/time + * @return the next valid date/time + */ + public Date getNextInvalidTimeAfter(Date date) { + long difference = 1000; + + //move back to the nearest second so differences will be accurate + Calendar adjustCal = Calendar.getInstance(getTimeZone()); + adjustCal.setTime(date); + adjustCal.set(Calendar.MILLISECOND, 0); + Date lastDate = adjustCal.getTime(); + + Date newDate = null; + + //TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution. + + //keep getting the next included time until it's farther than one second + // apart. At that point, lastDate is the last valid fire time. We return + // the second immediately following it. + while (difference == 1000) { + newDate = getTimeAfter(lastDate); + + difference = newDate.getTime() - lastDate.getTime(); + + if (difference == 1000) { + lastDate = newDate; + } + } + + return new Date(lastDate.getTime() + 1000); + } + + /** + * Returns the time zone for which this CronExpression + * will be resolved. + */ + public TimeZone getTimeZone() { + if (timeZone == null) { + timeZone = TimeZone.getDefault(); + } + + return timeZone; + } + + /** + * Sets the time zone for which this CronExpression + * will be resolved. + */ + public void setTimeZone(TimeZone timeZone) { + this.timeZone = timeZone; + } + + /** + * Returns the string representation of the CronExpression + * + * @return a string representation of the CronExpression + */ + public String toString() { + return cronExpression; + } + + /** + * Indicates whether the specified cron expression can be parsed into a + * valid cron expression + * + * @param cronExpression the expression to evaluate + * @return a boolean indicating whether the given expression is a valid cron + * expression + */ + public static boolean isValidExpression(String cronExpression) { + + try { + new CronExpression(cronExpression); + } catch (ParseException pe) { + return false; + } + + return true; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Expression Parsing Functions + // + //////////////////////////////////////////////////////////////////////////// + + protected void buildExpression(String expression) throws ParseException { + expressionParsed = true; + + try { + + if (seconds == null) { + seconds = new TreeSet(); + } + if (minutes == null) { + minutes = new TreeSet(); + } + if (hours == null) { + hours = new TreeSet(); + } + if (daysOfMonth == null) { + daysOfMonth = new TreeSet(); + } + if (months == null) { + months = new TreeSet(); + } + if (daysOfWeek == null) { + daysOfWeek = new TreeSet(); + } + if (years == null) { + years = new TreeSet(); + } + + int exprOn = SECOND; + + StringTokenizer exprsTok = new StringTokenizer(expression, " \t", + false); + + while (exprsTok.hasMoreTokens() && exprOn <= YEAR) { + String expr = exprsTok.nextToken().trim(); + + // throw an exception if L is used with other days of the month + if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.indexOf(",") >= 0) { + throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1); + } + // throw an exception if L is used with other days of the week + if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.indexOf(",") >= 0) { + throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1); + } + + StringTokenizer vTok = new StringTokenizer(expr, ","); + while (vTok.hasMoreTokens()) { + String v = vTok.nextToken(); + storeExpressionVals(0, v, exprOn); + } + + exprOn++; + } + + if (exprOn <= DAY_OF_WEEK) { + throw new ParseException("Unexpected end of expression.", + expression.length()); + } + + if (exprOn <= YEAR) { + storeExpressionVals(0, "*", YEAR); + } + + TreeSet dow = getSet(DAY_OF_WEEK); + TreeSet dom = getSet(DAY_OF_MONTH); + + // Copying the logic from the UnsupportedOperationException below + boolean dayOfMSpec = !dom.contains(NO_SPEC); + boolean dayOfWSpec = !dow.contains(NO_SPEC); + + if (dayOfMSpec && !dayOfWSpec) { + // skip + } else if (dayOfWSpec && !dayOfMSpec) { + // skip + } else { + throw new ParseException( + "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0); + } + } catch (ParseException pe) { + throw pe; + } catch (Exception e) { + throw new ParseException("Illegal cron expression format (" + + e.toString() + ")", 0); + } + } + + protected int storeExpressionVals(int pos, String s, int type) + throws ParseException { + + int incr = 0; + int i = skipWhiteSpace(pos, s); + if (i >= s.length()) { + return i; + } + char c = s.charAt(i); + if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW"))) { + String sub = s.substring(i, i + 3); + int sval = -1; + int eval = -1; + if (type == MONTH) { + sval = getMonthNumber(sub) + 1; + if (sval <= 0) { + throw new ParseException("Invalid Month value: '" + sub + "'", i); + } + if (s.length() > i + 3) { + c = s.charAt(i + 3); + if (c == '-') { + i += 4; + sub = s.substring(i, i + 3); + eval = getMonthNumber(sub) + 1; + if (eval <= 0) { + throw new ParseException("Invalid Month value: '" + sub + "'", i); + } + } + } + } else if (type == DAY_OF_WEEK) { + sval = getDayOfWeekNumber(sub); + if (sval < 0) { + throw new ParseException("Invalid Day-of-Week value: '" + + sub + "'", i); + } + if (s.length() > i + 3) { + c = s.charAt(i + 3); + if (c == '-') { + i += 4; + sub = s.substring(i, i + 3); + eval = getDayOfWeekNumber(sub); + if (eval < 0) { + throw new ParseException( + "Invalid Day-of-Week value: '" + sub + + "'", i); + } + } else if (c == '#') { + try { + i += 4; + nthdayOfWeek = Integer.parseInt(s.substring(i)); + if (nthdayOfWeek < 1 || nthdayOfWeek > 5) { + throw new Exception(); + } + } catch (Exception e) { + throw new ParseException( + "A numeric value between 1 and 5 must follow the '#' option", + i); + } + } else if (c == 'L') { + lastdayOfWeek = true; + i++; + } + } + + } else { + throw new ParseException( + "Illegal characters for this position: '" + sub + "'", + i); + } + if (eval != -1) { + incr = 1; + } + addToSet(sval, eval, incr, type); + return (i + 3); + } + + if (c == '?') { + i++; + if ((i + 1) < s.length() + && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) { + throw new ParseException("Illegal character after '?': " + + s.charAt(i), i); + } + if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) { + throw new ParseException( + "'?' can only be specfied for Day-of-Month or Day-of-Week.", + i); + } + if (type == DAY_OF_WEEK && !lastdayOfMonth) { + int val = ((Integer) daysOfMonth.last()).intValue(); + if (val == NO_SPEC_INT) { + throw new ParseException( + "'?' can only be specfied for Day-of-Month -OR- Day-of-Week.", + i); + } + } + + addToSet(NO_SPEC_INT, -1, 0, type); + return i; + } + + if (c == '*' || c == '/') { + if (c == '*' && (i + 1) >= s.length()) { + addToSet(ALL_SPEC_INT, -1, incr, type); + return i + 1; + } else if (c == '/' + && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s + .charAt(i + 1) == '\t')) { + throw new ParseException("'/' must be followed by an integer.", i); + } else if (c == '*') { + i++; + } + c = s.charAt(i); + if (c == '/') { // is an increment specified? + i++; + if (i >= s.length()) { + throw new ParseException("Unexpected end of string.", i); + } + + incr = getNumericValue(s, i); + + i++; + if (incr > 10) { + i++; + } + if (incr > 59 && (type == SECOND || type == MINUTE)) { + throw new ParseException("Increment > 60 : " + incr, i); + } else if (incr > 23 && (type == HOUR)) { + throw new ParseException("Increment > 24 : " + incr, i); + } else if (incr > 31 && (type == DAY_OF_MONTH)) { + throw new ParseException("Increment > 31 : " + incr, i); + } else if (incr > 7 && (type == DAY_OF_WEEK)) { + throw new ParseException("Increment > 7 : " + incr, i); + } else if (incr > 12 && (type == MONTH)) { + throw new ParseException("Increment > 12 : " + incr, i); + } + } else { + incr = 1; + } + + addToSet(ALL_SPEC_INT, -1, incr, type); + return i; + } else if (c == 'L') { + i++; + if (type == DAY_OF_MONTH) { + lastdayOfMonth = true; + } + if (type == DAY_OF_WEEK) { + addToSet(7, 7, 0, type); + } + if(type == DAY_OF_MONTH && s.length() > i) { + c = s.charAt(i); + if(c == 'W') { + nearestWeekday = true; + i++; + } + } + return i; + } else if (c >= '0' && c <= '9') { + int val = Integer.parseInt(String.valueOf(c)); + i++; + if (i >= s.length()) { + addToSet(val, -1, -1, type); + } else { + c = s.charAt(i); + if (c >= '0' && c <= '9') { + ValueSet vs = getValue(val, s, i); + val = vs.value; + i = vs.pos; + } + i = checkNext(i, s, val, type); + return i; + } + } else { + throw new ParseException("Unexpected character: " + c, i); + } + + return i; + } + + protected int checkNext(int pos, String s, int val, int type) + throws ParseException { + + int end = -1; + int i = pos; + + if (i >= s.length()) { + addToSet(val, end, -1, type); + return i; + } + + char c = s.charAt(pos); + + if (c == 'L') { + if (type == DAY_OF_WEEK) { + lastdayOfWeek = true; + } else { + throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i); + } + TreeSet set = getSet(type); + set.add(new Integer(val)); + i++; + return i; + } + + if (c == 'W') { + if (type == DAY_OF_MONTH) { + nearestWeekday = true; + } else { + throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i); + } + TreeSet set = getSet(type); + set.add(new Integer(val)); + i++; + return i; + } + + if (c == '#') { + if (type != DAY_OF_WEEK) { + throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i); + } + i++; + try { + nthdayOfWeek = Integer.parseInt(s.substring(i)); + if (nthdayOfWeek < 1 || nthdayOfWeek > 5) { + throw new Exception(); + } + } catch (Exception e) { + throw new ParseException( + "A numeric value between 1 and 5 must follow the '#' option", + i); + } + + TreeSet set = getSet(type); + set.add(new Integer(val)); + i++; + return i; + } + + if (c == '-') { + i++; + c = s.charAt(i); + int v = Integer.parseInt(String.valueOf(c)); + end = v; + i++; + if (i >= s.length()) { + addToSet(val, end, 1, type); + return i; + } + c = s.charAt(i); + if (c >= '0' && c <= '9') { + ValueSet vs = getValue(v, s, i); + int v1 = vs.value; + end = v1; + i = vs.pos; + } + if (i < s.length() && ((c = s.charAt(i)) == '/')) { + i++; + c = s.charAt(i); + int v2 = Integer.parseInt(String.valueOf(c)); + i++; + if (i >= s.length()) { + addToSet(val, end, v2, type); + return i; + } + c = s.charAt(i); + if (c >= '0' && c <= '9') { + ValueSet vs = getValue(v2, s, i); + int v3 = vs.value; + addToSet(val, end, v3, type); + i = vs.pos; + return i; + } else { + addToSet(val, end, v2, type); + return i; + } + } else { + addToSet(val, end, 1, type); + return i; + } + } + + if (c == '/') { + i++; + c = s.charAt(i); + int v2 = Integer.parseInt(String.valueOf(c)); + i++; + if (i >= s.length()) { + addToSet(val, end, v2, type); + return i; + } + c = s.charAt(i); + if (c >= '0' && c <= '9') { + ValueSet vs = getValue(v2, s, i); + int v3 = vs.value; + addToSet(val, end, v3, type); + i = vs.pos; + return i; + } else { + throw new ParseException("Unexpected character '" + c + "' after '/'", i); + } + } + + addToSet(val, end, 0, type); + i++; + return i; + } + + public String getCronExpression() { + return cronExpression; + } + + public String getExpressionSummary() { + StringBuffer buf = new StringBuffer(); + + buf.append("seconds: "); + buf.append(getExpressionSetSummary(seconds)); + buf.append("\n"); + buf.append("minutes: "); + buf.append(getExpressionSetSummary(minutes)); + buf.append("\n"); + buf.append("hours: "); + buf.append(getExpressionSetSummary(hours)); + buf.append("\n"); + buf.append("daysOfMonth: "); + buf.append(getExpressionSetSummary(daysOfMonth)); + buf.append("\n"); + buf.append("months: "); + buf.append(getExpressionSetSummary(months)); + buf.append("\n"); + buf.append("daysOfWeek: "); + buf.append(getExpressionSetSummary(daysOfWeek)); + buf.append("\n"); + buf.append("lastdayOfWeek: "); + buf.append(lastdayOfWeek); + buf.append("\n"); + buf.append("nearestWeekday: "); + buf.append(nearestWeekday); + buf.append("\n"); + buf.append("NthDayOfWeek: "); + buf.append(nthdayOfWeek); + buf.append("\n"); + buf.append("lastdayOfMonth: "); + buf.append(lastdayOfMonth); + buf.append("\n"); + buf.append("years: "); + buf.append(getExpressionSetSummary(years)); + buf.append("\n"); + + return buf.toString(); + } + + protected String getExpressionSetSummary(java.util.Set set) { + + if (set.contains(NO_SPEC)) { + return "?"; + } + if (set.contains(ALL_SPEC)) { + return "*"; + } + + StringBuffer buf = new StringBuffer(); + + Iterator itr = set.iterator(); + boolean first = true; + while (itr.hasNext()) { + Integer iVal = (Integer) itr.next(); + String val = iVal.toString(); + if (!first) { + buf.append(","); + } + buf.append(val); + first = false; + } + + return buf.toString(); + } + + protected String getExpressionSetSummary(java.util.ArrayList list) { + + if (list.contains(NO_SPEC)) { + return "?"; + } + if (list.contains(ALL_SPEC)) { + return "*"; + } + + StringBuffer buf = new StringBuffer(); + + Iterator itr = list.iterator(); + boolean first = true; + while (itr.hasNext()) { + Integer iVal = (Integer) itr.next(); + String val = iVal.toString(); + if (!first) { + buf.append(","); + } + buf.append(val); + first = false; + } + + return buf.toString(); + } + + protected int skipWhiteSpace(int i, String s) { + for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) { + ; + } + + return i; + } + + protected int findNextWhiteSpace(int i, String s) { + for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) { + ; + } + + return i; + } + + protected void addToSet(int val, int end, int incr, int type) + throws ParseException { + + TreeSet set = getSet(type); + + if (type == SECOND || type == MINUTE) { + if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) { + throw new ParseException( + "Minute and Second values must be between 0 and 59", + -1); + } + } else if (type == HOUR) { + if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) { + throw new ParseException( + "Hour values must be between 0 and 23", -1); + } + } else if (type == DAY_OF_MONTH) { + if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT) + && (val != NO_SPEC_INT)) { + throw new ParseException( + "Day of month values must be between 1 and 31", -1); + } + } else if (type == MONTH) { + if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) { + throw new ParseException( + "Month values must be between 1 and 12", -1); + } + } else if (type == DAY_OF_WEEK) { + if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT) + && (val != NO_SPEC_INT)) { + throw new ParseException( + "Day-of-Week values must be between 1 and 7", -1); + } + } + + if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) { + if (val != -1) { + set.add(new Integer(val)); + } else { + set.add(NO_SPEC); + } + + return; + } + + int startAt = val; + int stopAt = end; + + if (val == ALL_SPEC_INT && incr <= 0) { + incr = 1; + set.add(ALL_SPEC); // put in a marker, but also fill values + } + + if (type == SECOND || type == MINUTE) { + if (stopAt == -1) { + stopAt = 59; + } + if (startAt == -1 || startAt == ALL_SPEC_INT) { + startAt = 0; + } + } else if (type == HOUR) { + if (stopAt == -1) { + stopAt = 23; + } + if (startAt == -1 || startAt == ALL_SPEC_INT) { + startAt = 0; + } + } else if (type == DAY_OF_MONTH) { + if (stopAt == -1) { + stopAt = 31; + } + if (startAt == -1 || startAt == ALL_SPEC_INT) { + startAt = 1; + } + } else if (type == MONTH) { + if (stopAt == -1) { + stopAt = 12; + } + if (startAt == -1 || startAt == ALL_SPEC_INT) { + startAt = 1; + } + } else if (type == DAY_OF_WEEK) { + if (stopAt == -1) { + stopAt = 7; + } + if (startAt == -1 || startAt == ALL_SPEC_INT) { + startAt = 1; + } + } else if (type == YEAR) { + if (stopAt == -1) { + stopAt = 2099; + } + if (startAt == -1 || startAt == ALL_SPEC_INT) { + startAt = 1970; + } + } + + // if the end of the range is before the start, then we need to overflow into + // the next day, month etc. This is done by adding the maximum amount for that + // type, and using modulus max to determine the value being added. + int max = -1; + if (stopAt < startAt) { + switch (type) { + case SECOND : max = 60; break; + case MINUTE : max = 60; break; + case HOUR : max = 24; break; + case MONTH : max = 12; break; + case DAY_OF_WEEK : max = 7; break; + case DAY_OF_MONTH : max = 31; break; + case YEAR : throw new IllegalArgumentException("Start year must be less than stop year"); + default : throw new IllegalArgumentException("Unexpected type encountered"); + } + stopAt += max; + } + + for (int i = startAt; i <= stopAt; i += incr) { + if (max == -1) { + // ie: there's no max to overflow over + set.add(new Integer(i)); + } else { + // take the modulus to get the real value + int i2 = i % max; + + // 1-indexed ranges should not include 0, and should include their max + if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) { + i2 = max; + } + + set.add(new Integer(i2)); + } + } + } + + protected TreeSet getSet(int type) { + switch (type) { + case SECOND: + return seconds; + case MINUTE: + return minutes; + case HOUR: + return hours; + case DAY_OF_MONTH: + return daysOfMonth; + case MONTH: + return months; + case DAY_OF_WEEK: + return daysOfWeek; + case YEAR: + return years; + default: + return null; + } + } + + protected ValueSet getValue(int v, String s, int i) { + char c = s.charAt(i); + String s1 = String.valueOf(v); + while (c >= '0' && c <= '9') { + s1 += c; + i++; + if (i >= s.length()) { + break; + } + c = s.charAt(i); + } + ValueSet val = new ValueSet(); + + val.pos = (i < s.length()) ? i : i + 1; + val.value = Integer.parseInt(s1); + return val; + } + + protected int getNumericValue(String s, int i) { + int endOfVal = findNextWhiteSpace(i, s); + String val = s.substring(i, endOfVal); + return Integer.parseInt(val); + } + + protected int getMonthNumber(String s) { + Integer integer = (Integer) monthMap.get(s); + + if (integer == null) { + return -1; + } + + return integer.intValue(); + } + + protected int getDayOfWeekNumber(String s) { + Integer integer = (Integer) dayMap.get(s); + + if (integer == null) { + return -1; + } + + return integer.intValue(); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Computation Functions + // + //////////////////////////////////////////////////////////////////////////// + + protected Date getTimeAfter(Date afterTime) { + + Calendar cl = Calendar.getInstance(getTimeZone()); + + // move ahead one second, since we're computing the time *after* the + // given time + afterTime = new Date(afterTime.getTime() + 1000); + // CronTrigger does not deal with milliseconds + cl.setTime(afterTime); + cl.set(Calendar.MILLISECOND, 0); + + boolean gotOne = false; + // loop until we've computed the next time, or we've past the endTime + while (!gotOne) { + + //if (endTime != null && cl.getTime().after(endTime)) return null; + if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop... + return null; + } + + SortedSet st = null; + int t = 0; + + int sec = cl.get(Calendar.SECOND); + int min = cl.get(Calendar.MINUTE); + + // get second................................................. + st = seconds.tailSet(new Integer(sec)); + if (st != null && st.size() != 0) { + sec = ((Integer) st.first()).intValue(); + } else { + sec = ((Integer) seconds.first()).intValue(); + min++; + cl.set(Calendar.MINUTE, min); + } + cl.set(Calendar.SECOND, sec); + + min = cl.get(Calendar.MINUTE); + int hr = cl.get(Calendar.HOUR_OF_DAY); + t = -1; + + // get minute................................................. + st = minutes.tailSet(new Integer(min)); + if (st != null && st.size() != 0) { + t = min; + min = ((Integer) st.first()).intValue(); + } else { + min = ((Integer) minutes.first()).intValue(); + hr++; + } + if (min != t) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, min); + setCalendarHour(cl, hr); + continue; + } + cl.set(Calendar.MINUTE, min); + + hr = cl.get(Calendar.HOUR_OF_DAY); + int day = cl.get(Calendar.DAY_OF_MONTH); + t = -1; + + // get hour................................................... + st = hours.tailSet(new Integer(hr)); + if (st != null && st.size() != 0) { + t = hr; + hr = ((Integer) st.first()).intValue(); + } else { + hr = ((Integer) hours.first()).intValue(); + day++; + } + if (hr != t) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.DAY_OF_MONTH, day); + setCalendarHour(cl, hr); + continue; + } + cl.set(Calendar.HOUR_OF_DAY, hr); + + day = cl.get(Calendar.DAY_OF_MONTH); + int mon = cl.get(Calendar.MONTH) + 1; + // '+ 1' because calendar is 0-based for this field, and we are + // 1-based + t = -1; + int tmon = mon; + + // get day................................................... + boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC); + boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC); + if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule + st = daysOfMonth.tailSet(new Integer(day)); + if (lastdayOfMonth) { + if(!nearestWeekday) { + t = day; + day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + } else { + t = day; + day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + + java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone()); + tcal.set(Calendar.SECOND, 0); + tcal.set(Calendar.MINUTE, 0); + tcal.set(Calendar.HOUR_OF_DAY, 0); + tcal.set(Calendar.DAY_OF_MONTH, day); + tcal.set(Calendar.MONTH, mon - 1); + tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR)); + + int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + int dow = tcal.get(Calendar.DAY_OF_WEEK); + + if(dow == Calendar.SATURDAY && day == 1) { + day += 2; + } else if(dow == Calendar.SATURDAY) { + day -= 1; + } else if(dow == Calendar.SUNDAY && day == ldom) { + day -= 2; + } else if(dow == Calendar.SUNDAY) { + day += 1; + } + + tcal.set(Calendar.SECOND, sec); + tcal.set(Calendar.MINUTE, min); + tcal.set(Calendar.HOUR_OF_DAY, hr); + tcal.set(Calendar.DAY_OF_MONTH, day); + tcal.set(Calendar.MONTH, mon - 1); + Date nTime = tcal.getTime(); + if(nTime.before(afterTime)) { + day = 1; + mon++; + } + } + } else if(nearestWeekday) { + t = day; + day = ((Integer) daysOfMonth.first()).intValue(); + + java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone()); + tcal.set(Calendar.SECOND, 0); + tcal.set(Calendar.MINUTE, 0); + tcal.set(Calendar.HOUR_OF_DAY, 0); + tcal.set(Calendar.DAY_OF_MONTH, day); + tcal.set(Calendar.MONTH, mon - 1); + tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR)); + + int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + int dow = tcal.get(Calendar.DAY_OF_WEEK); + + if(dow == Calendar.SATURDAY && day == 1) { + day += 2; + } else if(dow == Calendar.SATURDAY) { + day -= 1; + } else if(dow == Calendar.SUNDAY && day == ldom) { + day -= 2; + } else if(dow == Calendar.SUNDAY) { + day += 1; + } + + + tcal.set(Calendar.SECOND, sec); + tcal.set(Calendar.MINUTE, min); + tcal.set(Calendar.HOUR_OF_DAY, hr); + tcal.set(Calendar.DAY_OF_MONTH, day); + tcal.set(Calendar.MONTH, mon - 1); + Date nTime = tcal.getTime(); + if(nTime.before(afterTime)) { + day = ((Integer) daysOfMonth.first()).intValue(); + mon++; + } + } else if (st != null && st.size() != 0) { + t = day; + day = ((Integer) st.first()).intValue(); + // make sure we don't over-run a short month, such as february + int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + if (day > lastDay) { + day = ((Integer) daysOfMonth.first()).intValue(); + mon++; + } + } else { + day = ((Integer) daysOfMonth.first()).intValue(); + mon++; + } + + if (day != t || mon != tmon) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, day); + cl.set(Calendar.MONTH, mon - 1); + // '- 1' because calendar is 0-based for this field, and we + // are 1-based + continue; + } + } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule + if (lastdayOfWeek) { // are we looking for the last XXX day of + // the month? + int dow = ((Integer) daysOfWeek.first()).intValue(); // desired + // d-o-w + int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w + int daysToAdd = 0; + if (cDow < dow) { + daysToAdd = dow - cDow; + } + if (cDow > dow) { + daysToAdd = dow + (7 - cDow); + } + + int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + + if (day + daysToAdd > lDay) { // did we already miss the + // last one? + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, 1); + cl.set(Calendar.MONTH, mon); + // no '- 1' here because we are promoting the month + continue; + } + + // find date of last occurance of this day in this month... + while ((day + daysToAdd + 7) <= lDay) { + daysToAdd += 7; + } + + day += daysToAdd; + + if (daysToAdd > 0) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, day); + cl.set(Calendar.MONTH, mon - 1); + // '- 1' here because we are not promoting the month + continue; + } + + } else if (nthdayOfWeek != 0) { + // are we looking for the Nth XXX day in the month? + int dow = ((Integer) daysOfWeek.first()).intValue(); // desired + // d-o-w + int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w + int daysToAdd = 0; + if (cDow < dow) { + daysToAdd = dow - cDow; + } else if (cDow > dow) { + daysToAdd = dow + (7 - cDow); + } + + boolean dayShifted = false; + if (daysToAdd > 0) { + dayShifted = true; + } + + day += daysToAdd; + int weekOfMonth = day / 7; + if (day % 7 > 0) { + weekOfMonth++; + } + + daysToAdd = (nthdayOfWeek - weekOfMonth) * 7; + day += daysToAdd; + if (daysToAdd < 0 + || day > getLastDayOfMonth(mon, cl + .get(Calendar.YEAR))) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, 1); + cl.set(Calendar.MONTH, mon); + // no '- 1' here because we are promoting the month + continue; + } else if (daysToAdd > 0 || dayShifted) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, day); + cl.set(Calendar.MONTH, mon - 1); + // '- 1' here because we are NOT promoting the month + continue; + } + } else { + int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w + int dow = ((Integer) daysOfWeek.first()).intValue(); // desired + // d-o-w + st = daysOfWeek.tailSet(new Integer(cDow)); + if (st != null && st.size() > 0) { + dow = ((Integer) st.first()).intValue(); + } + + int daysToAdd = 0; + if (cDow < dow) { + daysToAdd = dow - cDow; + } + if (cDow > dow) { + daysToAdd = dow + (7 - cDow); + } + + int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + + if (day + daysToAdd > lDay) { // will we pass the end of + // the month? + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, 1); + cl.set(Calendar.MONTH, mon); + // no '- 1' here because we are promoting the month + continue; + } else if (daysToAdd > 0) { // are we swithing days? + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd); + cl.set(Calendar.MONTH, mon - 1); + // '- 1' because calendar is 0-based for this field, + // and we are 1-based + continue; + } + } + } else { // dayOfWSpec && !dayOfMSpec + throw new UnsupportedOperationException( + "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented."); + // TODO: + } + cl.set(Calendar.DAY_OF_MONTH, day); + + mon = cl.get(Calendar.MONTH) + 1; + // '+ 1' because calendar is 0-based for this field, and we are + // 1-based + int year = cl.get(Calendar.YEAR); + t = -1; + + // test for expressions that never generate a valid fire date, + // but keep looping... + if (year > 2099) { + return null; + } + + // get month................................................... + st = months.tailSet(new Integer(mon)); + if (st != null && st.size() != 0) { + t = mon; + mon = ((Integer) st.first()).intValue(); + } else { + mon = ((Integer) months.first()).intValue(); + year++; + } + if (mon != t) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, 1); + cl.set(Calendar.MONTH, mon - 1); + // '- 1' because calendar is 0-based for this field, and we are + // 1-based + cl.set(Calendar.YEAR, year); + continue; + } + cl.set(Calendar.MONTH, mon - 1); + // '- 1' because calendar is 0-based for this field, and we are + // 1-based + + year = cl.get(Calendar.YEAR); + t = -1; + + // get year................................................... + st = years.tailSet(new Integer(year)); + if (st != null && st.size() != 0) { + t = year; + year = ((Integer) st.first()).intValue(); + } else { + return null; // ran out of years... + } + + if (year != t) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, 1); + cl.set(Calendar.MONTH, 0); + // '- 1' because calendar is 0-based for this field, and we are + // 1-based + cl.set(Calendar.YEAR, year); + continue; + } + cl.set(Calendar.YEAR, year); + + gotOne = true; + } // while( !done ) + + return cl.getTime(); + } + + /** + * Advance the calendar to the particular hour paying particular attention + * to daylight saving problems. + * + * @param cal + * @param hour + */ + protected void setCalendarHour(Calendar cal, int hour) { + cal.set(java.util.Calendar.HOUR_OF_DAY, hour); + if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) { + cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1); + } + } + + /** + * NOT YET IMPLEMENTED: Returns the time before the given time + * that the CronExpression matches. + */ + protected Date getTimeBefore(Date endTime) { + // TODO: implement QUARTZ-423 + return null; + } + + /** + * NOT YET IMPLEMENTED: Returns the final time that the + * CronExpression will match. + */ + public Date getFinalFireTime() { + // TODO: implement QUARTZ-423 + return null; + } + + protected boolean isLeapYear(int year) { + return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)); + } + + protected int getLastDayOfMonth(int monthNum, int year) { + + switch (monthNum) { + case 1: + return 31; + case 2: + return (isLeapYear(year)) ? 29 : 28; + case 3: + return 31; + case 4: + return 30; + case 5: + return 31; + case 6: + return 30; + case 7: + return 31; + case 8: + return 31; + case 9: + return 30; + case 10: + return 31; + case 11: + return 30; + case 12: + return 31; + default: + throw new IllegalArgumentException("Illegal month number: " + + monthNum); + } + } + + + private void readObject(java.io.ObjectInputStream stream) + throws java.io.IOException, ClassNotFoundException { + + stream.defaultReadObject(); + try { + buildExpression(cronExpression); + } catch (Exception ignore) { + } // never happens + } + + public Object clone() { + CronExpression copy = null; + try { + copy = new CronExpression(getCronExpression()); + copy.setTimeZone(getTimeZone()); + } catch (ParseException ex) { // never happens since the source is valid... + throw new IncompatibleClassChangeError("Not Cloneable."); + } + return copy; + } +} + +class ValueSet { + public int value; + + public int pos; +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/CronTrigger.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/CronTrigger.java new file mode 100644 index 000000000..9580cec92 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/CronTrigger.java @@ -0,0 +1,1075 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +import java.text.ParseException; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + + +/** + *

+ * A concrete {@link Trigger} that is used to fire a {@link com.fr.third.org.quartz.JobDetail} + * at given moments in time, defined with Unix 'cron-like' definitions. + *

+ * + *

+ * For those unfamiliar with "cron", this means being able to create a firing + * schedule such as: "At 8:00am every Monday through Friday" or "At 1:30am + * every last Friday of the month". + *

+ * + *

+ * The format of a "Cron-Expression" string is documented on the + * {@link com.fr.third.org.quartz.CronExpression} class. + *

+ * + *

+ * Here are some full examples:
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Expression Meaning
"0 0 12 * * ?"  + * Fire at 12pm (noon) every day
"0 15 10 ? * *"  + * Fire at 10:15am every day
"0 15 10 * * ?"  + * Fire at 10:15am every day
"0 15 10 * * ? *"  + * Fire at 10:15am every day
"0 15 10 * * ? 2005"  + * Fire at 10:15am every day during the year 2005 + *
"0 * 14 * * ?"  + * Fire every minute starting at 2pm and ending at 2:59pm, every day + *
"0 0/5 14 * * ?"  + * Fire every 5 minutes starting at 2pm and ending at 2:55pm, every day + *
"0 0/5 14,18 * * ?"  + * Fire every 5 minutes starting at 2pm and ending at 2:55pm, AND fire every 5 minutes starting at 6pm and ending at 6:55pm, every day + *
"0 0-5 14 * * ?"  + * Fire every minute starting at 2pm and ending at 2:05pm, every day + *
"0 10,44 14 ? 3 WED"  + * Fire at 2:10pm and at 2:44pm every Wednesday in the month of March. + *
"0 15 10 ? * MON-FRI"  + * Fire at 10:15am every Monday, Tuesday, Wednesday, Thursday and Friday + *
"0 15 10 15 * ?"  + * Fire at 10:15am on the 15th day of every month + *
"0 15 10 L * ?"  + * Fire at 10:15am on the last day of every month + *
"0 15 10 ? * 6L"  + * Fire at 10:15am on the last Friday of every month + *
"0 15 10 ? * 6L"  + * Fire at 10:15am on the last Friday of every month + *
"0 15 10 ? * 6L 2002-2005"  + * Fire at 10:15am on every last friday of every month during the years 2002, 2003, 2004 and 2005 + *
"0 15 10 ? * 6#3"  + * Fire at 10:15am on the third Friday of every month + *
+ *

+ * + *

+ * Pay attention to the effects of '?' and '*' in the day-of-week and + * day-of-month fields! + *

+ * + *

+ * NOTES: + *

+ *

+ * + * @see Trigger + * @see SimpleTrigger + * @see TriggerUtils + * + * @author Sharada Jambula, James House + * @author Contributions from Mads Henderson + */ +public class CronTrigger extends Trigger { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Required for serialization support. Introduced in Quartz 1.6.1 to + * maintain compatibility after the introduction of hasAdditionalProperties + * method. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = -8644953146451592766L; + + /** + *

+ * Instructs the {@link Scheduler} that upon a mis-fire + * situation, the {@link CronTrigger} wants to be fired now + * by Scheduler. + *

+ */ + public static final int MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1; + + /** + *

+ * Instructs the {@link Scheduler} that upon a mis-fire + * situation, the {@link CronTrigger} wants to have it's + * next-fire-time updated to the next time in the schedule after the + * current time (taking into account any associated {@link Calendar}, + * but it does not want to be fired now. + *

+ */ + public static final int MISFIRE_INSTRUCTION_DO_NOTHING = 2; + + private static final int YEAR_TO_GIVEUP_SCHEDULING_AT = 2299; + + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private CronExpression cronEx = null; + private Date startTime = null; + private Date endTime = null; + private Date nextFireTime = null; + private Date previousFireTime = null; + private transient TimeZone timeZone = null; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Create a CronTrigger with no settings. + *

+ * + *

+ * The start-time will also be set to the current time, and the time zone + * will be set the the system's default time zone. + *

+ */ + public CronTrigger() { + super(); + setStartTime(new Date()); + setTimeZone(TimeZone.getDefault()); + } + + /** + *

+ * Create a CronTrigger with the given name and group. + *

+ * + *

+ * The start-time will also be set to the current time, and the time zone + * will be set the the system's default time zone. + *

+ */ + public CronTrigger(String name, String group) { + super(name, group); + setStartTime(new Date()); + setTimeZone(TimeZone.getDefault()); + } + + /** + *

+ * Create a CronTrigger with the given name, group and + * expression. + *

+ * + *

+ * The start-time will also be set to the current time, and the time zone + * will be set the the system's default time zone. + *

+ */ + public CronTrigger(String name, String group, String cronExpression) + throws ParseException { + + super(name, group); + + setCronExpression(cronExpression); + + setStartTime(new Date()); + setTimeZone(TimeZone.getDefault()); + } + + /** + *

+ * Create a CronTrigger with the given name and group, and + * associated with the identified {@link com.fr.third.org.quartz.JobDetail}. + *

+ * + *

+ * The start-time will also be set to the current time, and the time zone + * will be set the the system's default time zone. + *

+ */ + public CronTrigger(String name, String group, String jobName, + String jobGroup) { + super(name, group, jobName, jobGroup); + setStartTime(new Date()); + setTimeZone(TimeZone.getDefault()); + } + + /** + *

+ * Create a CronTrigger with the given name and group, + * associated with the identified {@link com.fr.third.org.quartz.JobDetail}, + * and with the given "cron" expression. + *

+ * + *

+ * The start-time will also be set to the current time, and the time zone + * will be set the the system's default time zone. + *

+ */ + public CronTrigger(String name, String group, String jobName, + String jobGroup, String cronExpression) throws ParseException { + this(name, group, jobName, jobGroup, null, null, cronExpression, + TimeZone.getDefault()); + } + + /** + *

+ * Create a CronTrigger with the given name and group, + * associated with the identified {@link com.fr.third.org.quartz.JobDetail}, + * and with the given "cron" expression resolved with respect to the TimeZone. + *

+ */ + public CronTrigger(String name, String group, String jobName, + String jobGroup, String cronExpression, TimeZone timeZone) + throws ParseException { + this(name, group, jobName, jobGroup, null, null, cronExpression, + timeZone); + } + + /** + *

+ * Create a CronTrigger that will occur at the given time, + * until the given end time. + *

+ * + *

+ * If null, the start-time will also be set to the current time, the time + * zone will be set the the system's default. + *

+ * + * @param startTime + * A Date set to the time for the Trigger + * to fire. + * @param endTime + * A Date set to the time for the Trigger + * to quit repeat firing. + */ + public CronTrigger(String name, String group, String jobName, + String jobGroup, Date startTime, Date endTime, String cronExpression) + throws ParseException { + super(name, group, jobName, jobGroup); + + setCronExpression(cronExpression); + + if (startTime == null) { + startTime = new Date(); + } + setStartTime(startTime); + if (endTime != null) { + setEndTime(endTime); + } + setTimeZone(TimeZone.getDefault()); + + } + + /** + *

+ * Create a CronTrigger with fire time dictated by the + * cronExpression resolved with respect to the specified + * timeZone occuring from the startTime until + * the given endTime. + *

+ * + *

+ * If null, the start-time will also be set to the current time. If null, + * the time zone will be set to the system's default. + *

+ * + * @param name + * of the Trigger + * @param group + * of the Trigger + * @param jobName + * name of the {@link com.fr.third.org.quartz.JobDetail} + * executed on firetime + * @param jobGroup + * group of the {@link com.fr.third.org.quartz.JobDetail} + * executed on firetime + * @param startTime + * A Date set to the earliest time for the Trigger + * to start firing. + * @param endTime + * A Date set to the time for the Trigger + * to quit repeat firing. + * @param cronExpression + * A cron expression dictating the firing sequence of the Trigger + * @param timeZone + * Specifies for which time zone the cronExpression + * should be interprted, i.e. the expression 0 0 10 * * ?, is + * resolved to 10:00 am in this time zone. + * @throws ParseException + * if the cronExpression is invalid. + */ + public CronTrigger(String name, String group, String jobName, + String jobGroup, Date startTime, Date endTime, + String cronExpression, TimeZone timeZone) throws ParseException { + super(name, group, jobName, jobGroup); + + setCronExpression(cronExpression); + + if (startTime == null) { + startTime = new Date(); + } + setStartTime(startTime); + if (endTime != null) { + setEndTime(endTime); + } + if (timeZone == null) { + setTimeZone(TimeZone.getDefault()); + } else { + setTimeZone(timeZone); + } + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public Object clone() { + CronTrigger copy = (CronTrigger) super.clone(); + if (cronEx != null) { + copy.setCronExpression((CronExpression)cronEx.clone()); + } + return copy; + } + + public void setCronExpression(String cronExpression) throws ParseException { + TimeZone origTz = getTimeZone(); + this.cronEx = new CronExpression(cronExpression); + this.cronEx.setTimeZone(origTz); + } + + public String getCronExpression() { + return cronEx == null ? null : cronEx.getCronExpression(); + } + + /** + * Set the CronExpression to the given one. The TimeZone on the passed-in + * CronExpression over-rides any that was already set on the Trigger. + * + * @param cronExpression + */ + public void setCronExpression(CronExpression cronExpression) { + this.cronEx = cronExpression; + this.timeZone = cronExpression.getTimeZone(); + } + + /** + *

+ * Get the time at which the CronTrigger should occur. + *

+ */ + public Date getStartTime() { + return this.startTime; + } + + public void setStartTime(Date startTime) { + if (startTime == null) { + throw new IllegalArgumentException("Start time cannot be null"); + } + + Date eTime = getEndTime(); + if (eTime != null && startTime != null && eTime.before(startTime)) { + throw new IllegalArgumentException( + "End time cannot be before start time"); + } + + // round off millisecond... + // Note timeZone is not needed here as parameter for + // Calendar.getInstance(), + // since time zone is implicit when using a Date in the setTime method. + Calendar cl = Calendar.getInstance(); + cl.setTime(startTime); + cl.set(Calendar.MILLISECOND, 0); + + this.startTime = cl.getTime(); + } + + /** + *

+ * Get the time at which the CronTrigger should quit + * repeating - even if repeastCount isn't yet satisfied. + *

+ * + * @see #getFinalFireTime() + */ + public Date getEndTime() { + return this.endTime; + } + + public void setEndTime(Date endTime) { + Date sTime = getStartTime(); + if (sTime != null && endTime != null && sTime.after(endTime)) { + throw new IllegalArgumentException( + "End time cannot be before start time"); + } + + this.endTime = endTime; + } + + /** + *

+ * Returns the next time at which the Trigger is scheduled to fire. If + * the trigger will not fire again, null will be returned. Note that + * the time returned can possibly be in the past, if the time that was computed + * for the trigger to next fire has already arrived, but the scheduler has not yet + * been able to fire the trigger (which would likely be due to lack of resources + * e.g. threads). + *

+ * + *

The value returned is not guaranteed to be valid until after the Trigger + * has been added to the scheduler. + *

+ * + * @see TriggerUtils#computeFireTimesBetween(Trigger, com.fr.third.org.quartz.Calendar , Date, Date) + */ + public Date getNextFireTime() { + return this.nextFireTime; + } + + /** + *

+ * Returns the previous time at which the CronTrigger + * fired. If the trigger has not yet fired, null will be + * returned. + */ + public Date getPreviousFireTime() { + return this.previousFireTime; + } + + /** + *

+ * Sets the next time at which the CronTrigger will fire. + * This method should not be invoked by client code. + *

+ */ + public void setNextFireTime(Date nextFireTime) { + this.nextFireTime = nextFireTime; + } + + /** + *

+ * Set the previous time at which the CronTrigger fired. + *

+ * + *

+ * This method should not be invoked by client code. + *

+ */ + public void setPreviousFireTime(Date previousFireTime) { + this.previousFireTime = previousFireTime; + } + + /** + *

+ * Returns the time zone for which the cronExpression of + * this CronTrigger will be resolved. + *

+ */ + public TimeZone getTimeZone() { + + if(cronEx != null) { + return cronEx.getTimeZone(); + } + + if (timeZone == null) { + timeZone = TimeZone.getDefault(); + } + return timeZone; + } + + /** + *

+ * Sets the time zone for which the cronExpression of this + * CronTrigger will be resolved. + *

+ * + *

If {@link #setCronExpression(CronExpression)} is called after this + * method, the TimeZon setting on the CronExpression will "win". However + * if {@link #setCronExpression(String)} is called after this method, the + * time zone applied by this method will remain in effect, since the + * String cron expression does not carry a time zone! + */ + public void setTimeZone(TimeZone timeZone) { + if(cronEx != null) { + cronEx.setTimeZone(timeZone); + } + this.timeZone = timeZone; + } + + /** + *

+ * Returns the next time at which the CronTrigger will fire, + * after the given time. If the trigger will not fire after the given time, + * null will be returned. + *

+ * + *

+ * Note that the date returned is NOT validated against the related + * com.fr.third.org.quartz.Calendar (if any) + *

+ */ + public Date getFireTimeAfter(Date afterTime) { + if (afterTime == null) { + afterTime = new Date(); + } + + if (getStartTime().after(afterTime)) { + afterTime = new Date(getStartTime().getTime() - 1000l); + } + + if (getEndTime() != null && (afterTime.compareTo(getEndTime()) >= 0)) { + return null; + } + + Date pot = getTimeAfter(afterTime); + if (getEndTime() != null && pot != null && pot.after(getEndTime())) { + return null; + } + + return pot; + } + + /** + *

+ * NOT YET IMPLEMENTED: Returns the final time at which the + * CronTrigger will fire. + *

+ * + *

+ * Note that the return time *may* be in the past. and the date returned is + * not validated against com.fr.third.org.quartz.calendar + *

+ */ + public Date getFinalFireTime() { + Date resultTime; + if (getEndTime() != null) { + resultTime = getTimeBefore(new Date(getEndTime().getTime() + 1000l)); + } else { + resultTime = (cronEx == null) ? null : cronEx.getFinalFireTime(); + } + + if ((resultTime != null) && (getStartTime() != null) && (resultTime.before(getStartTime()))) { + return null; + } + + return resultTime; + } + + /** + *

+ * Determines whether or not the CronTrigger will occur + * again. + *

+ */ + public boolean mayFireAgain() { + return (getNextFireTime() != null); + } + + protected boolean validateMisfireInstruction(int misfireInstruction) { + if (misfireInstruction < MISFIRE_INSTRUCTION_SMART_POLICY) { + return false; + } + + if (misfireInstruction > MISFIRE_INSTRUCTION_DO_NOTHING) { + return false; + } + + return true; + } + + /** + *

+ * Updates the CronTrigger's state based on the + * MISFIRE_INSTRUCTION_XXX that was selected when the CronTrigger + * was created. + *

+ * + *

+ * If the misfire instruction is set to MISFIRE_INSTRUCTION_SMART_POLICY, + * then the following scheme will be used:
+ *

+ *

+ */ + public void updateAfterMisfire(com.fr.third.org.quartz.Calendar cal) { + int instr = getMisfireInstruction(); + + if (instr == MISFIRE_INSTRUCTION_SMART_POLICY) { + instr = MISFIRE_INSTRUCTION_FIRE_ONCE_NOW; + } + + if (instr == MISFIRE_INSTRUCTION_DO_NOTHING) { + Date newFireTime = getFireTimeAfter(new Date()); + while (newFireTime != null && cal != null + && !cal.isTimeIncluded(newFireTime.getTime())) { + newFireTime = getFireTimeAfter(newFireTime); + } + setNextFireTime(newFireTime); + } else if (instr == MISFIRE_INSTRUCTION_FIRE_ONCE_NOW) { + setNextFireTime(new Date()); + } + } + + /** + *

+ * Determines whether the date and (optionally) time of the given Calendar + * instance falls on a scheduled fire-time of this trigger. + *

+ * + *

+ * Equivalent to calling willFireOn(cal, false). + *

+ * + * @param test the date to compare + * + * @see #willFireOn(Calendar, boolean) + */ + public boolean willFireOn(Calendar test) { + return willFireOn(test, false); + } + + /** + *

+ * Determines whether the date and (optionally) time of the given Calendar + * instance falls on a scheduled fire-time of this trigger. + *

+ * + *

+ * Note that the value returned is NOT validated against the related + * com.fr.third.org.quartz.Calendar (if any) + *

+ * + * @param test the date to compare + * @param dayOnly if set to true, the method will only determine if the + * trigger will fire during the day represented by the given Calendar + * (hours, minutes and seconds will be ignored). + * @see #willFireOn(Calendar) + */ + public boolean willFireOn(Calendar test, boolean dayOnly) { + + test = (Calendar) test.clone(); + + test.set(Calendar.MILLISECOND, 0); // don't compare millis. + + if(dayOnly) { + test.set(Calendar.HOUR, 0); + test.set(Calendar.MINUTE, 0); + test.set(Calendar.SECOND, 0); + } + + Date testTime = test.getTime(); + + Date fta = getFireTimeAfter(new Date(test.getTime().getTime() - 1000)); + + Calendar p = Calendar.getInstance(test.getTimeZone()); + p.setTime(fta); + + int year = p.get(Calendar.YEAR); + int month = p.get(Calendar.MONTH); + int day = p.get(Calendar.DATE); + + if(dayOnly) { + return (year == test.get(Calendar.YEAR) + && month == test.get(Calendar.MONTH) + && day == test.get(Calendar.DATE)); + } + + while(fta.before(testTime)) { + fta = getFireTimeAfter(fta); + } + + if(fta.equals(testTime)) { + return true; + } + + return false; + } + + /** + *

+ * Called after the {@link Scheduler} has executed the + * {@link com.fr.third.org.quartz.JobDetail} associated with the Trigger + * in order to get the final instruction code from the trigger. + *

+ * + * @param context + * is the JobExecutionContext that was used by the + * Job'sexecute(xx) method. + * @param result + * is the JobExecutionException thrown by the + * Job, if any (may be null). + * @return one of the Trigger.INSTRUCTION_XXX constants. + * + * @see #INSTRUCTION_NOOP + * @see #INSTRUCTION_RE_EXECUTE_JOB + * @see #INSTRUCTION_DELETE_TRIGGER + * @see #INSTRUCTION_SET_TRIGGER_COMPLETE + * @see #triggered(Calendar) + */ + public int executionComplete(JobExecutionContext context, + JobExecutionException result) { + if (result != null && result.refireImmediately()) { + return INSTRUCTION_RE_EXECUTE_JOB; + } + + if (result != null && result.unscheduleFiringTrigger()) { + return INSTRUCTION_SET_TRIGGER_COMPLETE; + } + + if (result != null && result.unscheduleAllTriggers()) { + return INSTRUCTION_SET_ALL_JOB_TRIGGERS_COMPLETE; + } + + if (!mayFireAgain()) { + return INSTRUCTION_DELETE_TRIGGER; + } + + return INSTRUCTION_NOOP; + } + + /** + *

+ * Called when the {@link Scheduler} has decided to 'fire' + * the trigger (execute the associated Job), in order to + * give the Trigger a chance to update itself for its next + * triggering (if any). + *

+ * + * @see #executionComplete(JobExecutionContext, JobExecutionException) + */ + public void triggered(com.fr.third.org.quartz.Calendar calendar) { + previousFireTime = nextFireTime; + nextFireTime = getFireTimeAfter(nextFireTime); + + while (nextFireTime != null && calendar != null + && !calendar.isTimeIncluded(nextFireTime.getTime())) { + nextFireTime = getFireTimeAfter(nextFireTime); + } + } + + /** + * + * @see com.fr.third.org.quartz.Trigger#updateWithNewCalendar(com.fr.third.org.quartz.Calendar, long) + */ + public void updateWithNewCalendar(com.fr.third.org.quartz.Calendar calendar, long misfireThreshold) + { + nextFireTime = getFireTimeAfter(previousFireTime); + + if (nextFireTime == null || calendar == null) { + return; + } + + Date now = new Date(); + while (nextFireTime != null && !calendar.isTimeIncluded(nextFireTime.getTime())) { + + nextFireTime = getFireTimeAfter(nextFireTime); + + if(nextFireTime == null) + break; + + //avoid infinite loop + java.util.Calendar c = java.util.Calendar.getInstance(); + c.setTime(nextFireTime); + if (c.get(java.util.Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) { + nextFireTime = null; + } + + if(nextFireTime != null && nextFireTime.before(now)) { + long diff = now.getTime() - nextFireTime.getTime(); + if(diff >= misfireThreshold) { + nextFireTime = getFireTimeAfter(nextFireTime); + continue; + } + } + } + } + + /** + *

+ * Called by the scheduler at the time a Trigger is first + * added to the scheduler, in order to have the Trigger + * compute its first fire time, based on any associated calendar. + *

+ * + *

+ * After this method has been called, getNextFireTime() + * should return a valid answer. + *

+ * + * @return the first time at which the Trigger will be fired + * by the scheduler, which is also the same value getNextFireTime() + * will return (until after the first firing of the Trigger). + *

+ */ + public Date computeFirstFireTime(com.fr.third.org.quartz.Calendar calendar) { + nextFireTime = getFireTimeAfter(new Date(getStartTime().getTime() - 1000l)); + + while (nextFireTime != null && calendar != null + && !calendar.isTimeIncluded(nextFireTime.getTime())) { + nextFireTime = getFireTimeAfter(nextFireTime); + } + + return nextFireTime; + } + + public String getExpressionSummary() { + return cronEx == null ? null : cronEx.getExpressionSummary(); + } + + /** + * Used by extensions of CronTrigger to imply that there are additional + * properties, specifically so that extensions can choose whether to be + * stored as a serialized blob, or as a flattened CronTrigger table. + */ + public boolean hasAdditionalProperties() { + return false; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Computation Functions + // + //////////////////////////////////////////////////////////////////////////// + + protected Date getTimeAfter(Date afterTime) { + return (cronEx == null) ? null : cronEx.getTimeAfter(afterTime); + } + + /** + * NOT YET IMPLEMENTED: Returns the time before the given time + * that this CronTrigger will fire. + */ + protected Date getTimeBefore(Date endTime) { + return (cronEx == null) ? null : cronEx.getTimeBefore(endTime); + } + + public static void main(String[] args) // TODO: remove method after good + // unit testing + throws Exception { + + String expr = "15 10 0/4 * * ?"; + if(args != null && args.length > 0 && args[0] != null) { + expr = args[0]; + } + + CronTrigger ct = new CronTrigger("t", "g", "j", "g", new Date(), null, expr); + ct.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles")); + System.err.println(ct.getExpressionSummary()); + System.err.println("tz=" + ct.getTimeZone().getID()); + System.err.println(); + + java.util.List times = TriggerUtils.computeFireTimes(ct, null, 25); + + for (int i = 0; i < times.size(); i++) { + System.err.println("firetime = " + times.get(i)); + } + + Calendar tt = Calendar.getInstance(); + tt.set(Calendar.DATE, 17); + tt.set(Calendar.MONTH, 5 - 1); + tt.set(Calendar.HOUR, 11); + tt.set(Calendar.MINUTE, 0); + tt.set(Calendar.SECOND, 7); + + System.err.println("\nWill fire on: " + tt.getTime() + " -- " + ct.willFireOn(tt, false)); + + +// CRON Expression: 0 0 9 * * ? +// +// TimeZone.getDefault().getDisplayName() = Central African Time +// TimeZone.getDefault().getID() = Africa/Harare + // +//// System.err.println(); +//// System.err.println(); +//// System.err.println(); +//// System.err.println("Daylight test:"); +//// +//// CronTrigger trigger = new CronTrigger(); +//// +//// TimeZone timeZone = TimeZone.getTimeZone("America/Los_Angeles"); +//// // TimeZone timeZone = TimeZone.getDefault(); +//// +//// trigger.setTimeZone(timeZone); +//// trigger.setCronExpression("0 0 1 ? 4 *"); +//// +//// Date start = new Date(1056319200000L); +//// Date end = new Date(1087682399000L); +//// +//// trigger.setStartTime(start); +//// trigger.setEndTime(end); +//// +//// Date next = new Date(1056232800000L); +//// while (next != null) { +//// next = trigger.getFireTimeAfter(next); +//// if (next != null) { +//// Calendar cal = Calendar.getInstance(); +//// cal.setTimeZone(timeZone); +//// cal.setTime(next); +//// System.err.println(cal.get(Calendar.MONTH) + "/" +//// + cal.get(Calendar.DATE) + "/" + cal.get(Calendar.YEAR) +//// + " - " + cal.get(Calendar.HOUR_OF_DAY) + ":" +//// + cal.get(Calendar.MINUTE)); +//// } +//// } +//// +//// System.err.println(); +//// System.err.println(); +//// System.err.println(); +//// System.err.println("Midnight test:"); +//// +//// trigger = new CronTrigger(); +//// +//// timeZone = TimeZone.getTimeZone("Asia/Jerusalem"); +//// // timeZone = TimeZone.getTimeZone("America/Los_Angeles"); +//// // TimeZone timeZone = TimeZone.getDefault(); +//// +//// trigger.setTimeZone(timeZone); +//// trigger.setCronExpression("0 /15 * ? 4 *"); +//// +//// start = new Date(1056319200000L); +//// end = new Date(1087682399000L); +//// +//// trigger.setStartTime(start); +//// trigger.setEndTime(end); +//// +//// next = new Date(1056232800000L); +//// while (next != null) { +//// next = trigger.getFireTimeAfter(next); +//// if (next != null) { +//// Calendar cal = Calendar.getInstance(); +//// cal.setTimeZone(timeZone); +//// cal.setTime(next); +//// System.err.println(cal.get(Calendar.MONTH) + "/" +//// + cal.get(Calendar.DATE) + "/" + cal.get(Calendar.YEAR) +//// + " - " + cal.get(Calendar.HOUR_OF_DAY) + ":" +//// + cal.get(Calendar.MINUTE)); +//// } +//// } + + } +} + diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/InterruptableJob.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/InterruptableJob.java new file mode 100644 index 000000000..804aba503 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/InterruptableJob.java @@ -0,0 +1,93 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +/** + *

+ * The interface to be implemented by {@link Job}s that provide a + * mechanism for having their execution interrupted. It is NOT a requirment + * for jobs to implement this interface - in fact, for most people, none of + * their jobs will. + *

+ * + *

+ * The means of actually interrupting the Job must be implemented within the + * Job itself (the interrupt() method of this + * interface is simply a means for the scheduler to inform the Job + * that a request has been made for it to be interrupted). The mechanism that + * your jobs use to interrupt themselves might vary between implementations. + * However the principle idea in any implementation should be to have the + * body of the job's execute(..) periodically check some flag to + * see if an interruption has been requested, and if the flag is set, somehow + * abort the performance of the rest of the job's work. An example of + * interrupting a job can be found in the java source for the class + * com.fr.third.org.quartz.examples.DumbInterruptableJob. It is legal to use + * some combination of wait() and notify() + * synchronization within interrupt() and execute(..) + * in order to have the interrupt() method block until the + * execute(..) signals that it has noticed the set flag. + *

+ * + *

+ * If the Job performs some form of blocking I/O or similar functions, you may + * want to consider having the Job.execute(..) method store a + * reference to the calling Thread as a member variable. Then the + * impplementation of this interfaces interrupt() method can call + * interrupt() on that Thread. Before attempting this, make + * sure that you fully understand what java.lang.Thread.interrupt() + * does and doesn't do. Also make sure that you clear the Job's member + * reference to the Thread when the execute(..) method exits (preferrably in a + * finally block. + *

+ * + *

+ * See Example 7 (com.fr.third.org.quartz.examples.example7.DumbInterruptableJob) for a simple + * implementation demonstration. + *

+ * @see Job + * @see StatefulJob + * @see Scheduler#interrupt(String, String) + * + * @author James House + */ +public interface InterruptableJob extends Job { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Called by the {@link Scheduler} when a user + * interrupts the Job. + *

+ * + * @throws UnableToInterruptJobException + * if there is an exception while interrupting the job. + */ + void interrupt() + throws UnableToInterruptJobException; +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/Job.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/Job.java new file mode 100644 index 000000000..3ffcdc84a --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/Job.java @@ -0,0 +1,79 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +/** + *

+ * The interface to be implemented by classes which represent a 'job' to be + * performed. + *

+ * + *

+ * Instances of Job must have a public + * no-argument constructor. + *

+ * + *

+ * JobDataMap provides a mechanism for 'instance member data' + * that may be required by some implementations of this interface. + *

+ * + * @see JobDetail + * @see StatefulJob + * @see Trigger + * @see Scheduler + * + * @author James House + */ +public interface Job { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Called by the {@link Scheduler} when a {@link Trigger} + * fires that is associated with the Job. + *

+ * + *

+ * The implementation may wish to set a + * {@link JobExecutionContext#setResult(Object) result} object on the + * {@link JobExecutionContext} before this method exits. The result itself + * is meaningless to Quartz, but may be informative to + * {@link JobListener}s or + * {@link TriggerListener}s that are watching the job's + * execution. + *

+ * + * @throws JobExecutionException + * if there is an exception while executing the job. + */ + void execute(JobExecutionContext context) + throws JobExecutionException; + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobDataMap.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobDataMap.java new file mode 100644 index 000000000..f280a7fd1 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobDataMap.java @@ -0,0 +1,504 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +import java.io.Serializable; +import java.util.Map; + +import com.fr.third.org.quartz.utils.StringKeyDirtyFlagMap; + +/** + *

+ * Holds state information for Job instances. + *

+ * + *

+ * JobDataMap instances are stored once when the Job + * is added to a scheduler. They are also re-persisted after every execution of + * StatefulJob instances. + *

+ * + *

+ * JobDataMap instances can also be stored with a + * Trigger. This can be useful in the case where you have a Job + * that is stored in the scheduler for regular/repeated use by multiple + * Triggers, yet with each independent triggering, you want to supply the + * Job with different data inputs. + *

+ * + *

+ * The JobExecutionContext passed to a Job at execution time + * also contains a convenience JobDataMap that is the result + * of merging the contents of the trigger's JobDataMap (if any) over the + * Job's JobDataMap (if any). + *

+ * + * @see Job + * @see StatefulJob + * @see Trigger + * @see JobExecutionContext + * + * @author James House + */ +public class JobDataMap extends StringKeyDirtyFlagMap implements Serializable { + + private static final long serialVersionUID = -6939901990106713909L; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Create an empty JobDataMap. + *

+ */ + public JobDataMap() { + super(15); + } + + /** + *

+ * Create a JobDataMap with the given data. + *

+ */ + public JobDataMap(Map map) { + this(); + + putAll(map); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Adds the given boolean value as a string version to the + * Job's data map. + *

+ */ + public void putAsString(String key, boolean value) { + String strValue = new Boolean(value).toString(); + + super.put(key, strValue); + } + + /** + *

+ * Adds the given Boolean value as a string version to the + * Job's data map. + *

+ */ + public void putAsString(String key, Boolean value) { + String strValue = value.toString(); + + super.put(key, strValue); + } + + /** + *

+ * Adds the given char value as a string version to the + * Job's data map. + *

+ */ + public void putAsString(String key, char value) { + String strValue = new Character(value).toString(); + + super.put(key, strValue); + } + + /** + *

+ * Adds the given Character value as a string version to the + * Job's data map. + *

+ */ + public void putAsString(String key, Character value) { + String strValue = value.toString(); + + super.put(key, strValue); + } + + /** + *

+ * Adds the given double value as a string version to the + * Job's data map. + *

+ */ + public void putAsString(String key, double value) { + String strValue = new Double(value).toString(); + + super.put(key, strValue); + } + + /** + *

+ * Adds the given Double value as a string version to the + * Job's data map. + *

+ */ + public void putAsString(String key, Double value) { + String strValue = value.toString(); + + super.put(key, strValue); + } + + /** + *

+ * Adds the given float value as a string version to the + * Job's data map. + *

+ */ + public void putAsString(String key, float value) { + String strValue = new Float(value).toString(); + + super.put(key, strValue); + } + + /** + *

+ * Adds the given Float value as a string version to the + * Job's data map. + *

+ */ + public void putAsString(String key, Float value) { + String strValue = value.toString(); + + super.put(key, strValue); + } + + /** + *

+ * Adds the given int value as a string version to the + * Job's data map. + *

+ */ + public void putAsString(String key, int value) { + String strValue = new Integer(value).toString(); + + super.put(key, strValue); + } + + /** + *

+ * Adds the given Integer value as a string version to the + * Job's data map. + *

+ */ + public void putAsString(String key, Integer value) { + String strValue = value.toString(); + + super.put(key, strValue); + } + + /** + *

+ * Adds the given long value as a string version to the + * Job's data map. + *

+ */ + public void putAsString(String key, long value) { + String strValue = new Long(value).toString(); + + super.put(key, strValue); + } + + /** + *

+ * Adds the given Long value as a string version to the + * Job's data map. + *

+ */ + public void putAsString(String key, Long value) { + String strValue = value.toString(); + + super.put(key, strValue); + } + + /** + *

+ * Retrieve the identified int value from the JobDataMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a String. + */ + public int getIntFromString(String key) { + Object obj = get(key); + + return new Integer((String) obj).intValue(); + } + + /** + *

+ * Retrieve the identified int value from the JobDataMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a String or Integer. + */ + public int getIntValue(String key) { + Object obj = get(key); + + if(obj instanceof String) { + return getIntFromString(key); + } else { + return getInt(key); + } + } + + /** + *

+ * Retrieve the identified int value from the JobDataMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a String. + */ + public Integer getIntegerFromString(String key) { + Object obj = get(key); + + return new Integer((String) obj); + } + + /** + *

+ * Retrieve the identified boolean value from the JobDataMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a String. + */ + public boolean getBooleanValueFromString(String key) { + Object obj = get(key); + + return new Boolean((String) obj).booleanValue(); + } + + /** + *

+ * Retrieve the identified boolean value from the + * JobDataMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a String or Boolean. + */ + public boolean getBooleanValue(String key) { + Object obj = get(key); + + if(obj instanceof String) { + return getBooleanValueFromString(key); + } else { + return getBoolean(key); + } + } + + /** + *

+ * Retrieve the identified Boolean value from the JobDataMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a String. + */ + public Boolean getBooleanFromString(String key) { + Object obj = get(key); + + return new Boolean((String) obj); + } + + /** + *

+ * Retrieve the identified char value from the JobDataMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a String. + */ + public char getCharFromString(String key) { + Object obj = get(key); + + return ((String) obj).charAt(0); + } + + /** + *

+ * Retrieve the identified Character value from the JobDataMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a String. + */ + public Character getCharacterFromString(String key) { + Object obj = get(key); + + return new Character(((String) obj).charAt(0)); + } + + /** + *

+ * Retrieve the identified double value from the JobDataMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a String. + */ + public double getDoubleValueFromString(String key) { + Object obj = get(key); + + return new Double((String) obj).doubleValue(); + } + + /** + *

+ * Retrieve the identified double value from the JobDataMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a String or Double. + */ + public double getDoubleValue(String key) { + Object obj = get(key); + + if(obj instanceof String) { + return getDoubleValueFromString(key); + } else { + return getDouble(key); + } + } + + /** + *

+ * Retrieve the identified Double value from the JobDataMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a String. + */ + public Double getDoubleFromString(String key) { + Object obj = get(key); + + return new Double((String) obj); + } + + /** + *

+ * Retrieve the identified float value from the JobDataMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a String. + */ + public float getFloatValueFromString(String key) { + Object obj = get(key); + + return new Float((String) obj).floatValue(); + } + + /** + *

+ * Retrieve the identified float value from the JobDataMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a String or Float. + */ + public float getFloatValue(String key) { + Object obj = get(key); + + if(obj instanceof String) { + return getFloatValueFromString(key); + } else { + return getFloat(key); + } + } + + /** + *

+ * Retrieve the identified Float value from the JobDataMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a String. + */ + public Float getFloatFromString(String key) { + Object obj = get(key); + + return new Float((String) obj); + } + + /** + *

+ * Retrieve the identified long value from the JobDataMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a String. + */ + public long getLongValueFromString(String key) { + Object obj = get(key); + + return new Long((String) obj).longValue(); + } + + /** + *

+ * Retrieve the identified long value from the JobDataMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a String or Long. + */ + public long getLongValue(String key) { + Object obj = get(key); + + if(obj instanceof String) { + return getLongValueFromString(key); + } else { + return getLong(key); + } + } + + /** + *

+ * Retrieve the identified Long value from the JobDataMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a String. + */ + public Long getLongFromString(String key) { + Object obj = get(key); + + return new Long((String) obj); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobDetail.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobDetail.java new file mode 100644 index 000000000..cac18f04a --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobDetail.java @@ -0,0 +1,537 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +import java.util.HashSet; +import java.util.Set; + +import com.fr.third.org.quartz.collections.ListOrderedSet; +import com.fr.third.org.quartz.utils.Key; + +/** + *

+ * Conveys the detail properties of a given Job instance. + *

+ * + *

+ * Quartz does not store an actual instance of a Job class, but + * instead allows you to define an instance of one, through the use of a JobDetail. + *

+ * + *

+ * Jobs have a name and group associated with them, which + * should uniquely identify them within a single {@link Scheduler}. + *

+ * + *

+ * Triggers are the 'mechanism' by which Jobs + * are scheduled. Many Triggers can point to the same Job, + * but a single Trigger can only point to one Job. + *

+ * + * @see Job + * @see StatefulJob + * @see JobDataMap + * @see Trigger + * + * @author James House + * @author Sharada Jambula + */ +public class JobDetail implements Cloneable, java.io.Serializable { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private String name; + + private String group = Scheduler.DEFAULT_GROUP; + + private String description; + + private Class jobClass; + + private JobDataMap jobDataMap; + + private boolean volatility = false; + + private boolean durability = false; + + private boolean shouldRecover = false; + + private Set jobListeners = ListOrderedSet.decorate(new HashSet()); + + private transient Key key = null; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Create a JobDetail with no specified name or group, and + * the default settings of all the other properties. + *

+ * + *

+ * Note that the {@link #setName(String)},{@link #setGroup(String)}and + * {@link #setJobClass(Class)}methods must be called before the job can be + * placed into a {@link Scheduler} + *

+ */ + public JobDetail() { + // do nothing... + } + + /** + *

+ * Create a JobDetail with the given name, and group, and + * the default settings of all the other properties. + *

+ * + * @param group if null, Scheduler.DEFAULT_GROUP will be used. + * + * @exception IllegalArgumentException + * if nameis null or empty, or the group is an empty string. + */ + public JobDetail(String name, String group, Class jobClass) { + setName(name); + setGroup(group); + setJobClass(jobClass); + } + + /** + *

+ * Create a JobDetail with the given name, and group, and + * the given settings of all the other properties. + *

+ * + * @param group if null, Scheduler.DEFAULT_GROUP will be used. + * + * @exception IllegalArgumentException + * if nameis null or empty, or the group is an empty string. + */ + public JobDetail(String name, String group, Class jobClass, + boolean volatility, boolean durability, boolean recover) { + setName(name); + setGroup(group); + setJobClass(jobClass); + setVolatility(volatility); + setDurability(durability); + setRequestsRecovery(recover); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Get the name of this Job. + *

+ */ + public String getName() { + return name; + } + + /** + *

+ * Set the name of this Job. + *

+ * + * @exception IllegalArgumentException + * if name is null or empty. + */ + public void setName(String name) { + if (name == null || name.trim().length() == 0) { + throw new IllegalArgumentException("Job name cannot be empty."); + } + + this.name = name; + } + + /** + *

+ * Get the group of this Job. + *

+ */ + public String getGroup() { + return group; + } + + /** + *

+ * Set the group of this Job. + *

+ * + * @param group if null, Scheduler.DEFAULT_GROUP will be used. + * + * @exception IllegalArgumentException + * if the group is an empty string. + */ + public void setGroup(String group) { + if (group != null && group.trim().length() == 0) { + throw new IllegalArgumentException( + "Group name cannot be empty."); + } + + if (group == null) { + group = Scheduler.DEFAULT_GROUP; + } + + this.group = group; + } + + /** + *

+ * Returns the 'full name' of the JobDetail in the format + * "group.name". + *

+ */ + public String getFullName() { + return group + "." + name; + } + + public Key getKey() { + if(key == null) { + key = new Key(getName(), getGroup()); + } + + return key; + } + + /** + *

+ * Return the description given to the Job instance by its + * creator (if any). + *

+ * + * @return null if no description was set. + */ + public String getDescription() { + return description; + } + + /** + *

+ * Set a description for the Job instance - may be useful + * for remembering/displaying the purpose of the job, though the + * description has no meaning to Quartz. + *

+ */ + public void setDescription(String description) { + this.description = description; + } + + /** + *

+ * Get the instance of Job that will be executed. + *

+ */ + public Class getJobClass() { + return jobClass; + } + + /** + *

+ * Set the instance of Job that will be executed. + *

+ * + * @exception IllegalArgumentException + * if jobClass is null or the class is not a Job. + */ + public void setJobClass(Class jobClass) { + if (jobClass == null) { + throw new IllegalArgumentException("Job class cannot be null."); + } + + if (!Job.class.isAssignableFrom(jobClass)) { + throw new IllegalArgumentException( + "Job class must implement the Job interface."); + } + + this.jobClass = jobClass; + } + + /** + *

+ * Get the JobDataMap that is associated with the Job. + *

+ */ + public JobDataMap getJobDataMap() { + if (jobDataMap == null) { + jobDataMap = new JobDataMap(); + } + return jobDataMap; + } + + /** + *

+ * Set the JobDataMap to be associated with the Job. + *

+ */ + public void setJobDataMap(JobDataMap jobDataMap) { + this.jobDataMap = jobDataMap; + } + + /** + *

+ * Validates whether the properties of the JobDetail are + * valid for submission into a Scheduler. + * + * @throws IllegalStateException + * if a required property (such as Name, Group, Class) is not + * set. + */ + public void validate() throws SchedulerException { + if (name == null) { + throw new SchedulerException("Job's name cannot be null", + SchedulerException.ERR_CLIENT_ERROR); + } + + if (group == null) { + throw new SchedulerException("Job's group cannot be null", + SchedulerException.ERR_CLIENT_ERROR); + } + + if (jobClass == null) { + throw new SchedulerException("Job's class cannot be null", + SchedulerException.ERR_CLIENT_ERROR); + } + } + + /** + *

+ * Set whether or not the Job should be persisted in the + * {@link com.fr.third.org.quartz.spi.JobStore} for re-use after program + * restarts. + *

+ * + *

+ * If not explicitly set, the default value is false. + *

+ */ + public void setVolatility(boolean volatility) { + this.volatility = volatility; + } + + /** + *

+ * Set whether or not the Job should remain stored after it + * is orphaned (no {@link Trigger}s point to it). + *

+ * + *

+ * If not explicitly set, the default value is false. + *

+ */ + public void setDurability(boolean durability) { + this.durability = durability; + } + + /** + *

+ * Set whether or not the the Scheduler should re-execute + * the Job if a 'recovery' or 'fail-over' situation is + * encountered. + *

+ * + *

+ * If not explicitly set, the default value is false. + *

+ * + * @see JobExecutionContext#isRecovering() + */ + public void setRequestsRecovery(boolean shouldRecover) { + this.shouldRecover = shouldRecover; + } + + /** + *

+ * Whether or not the Job should not be persisted in the + * {@link com.fr.third.org.quartz.spi.JobStore} for re-use after program + * restarts. + *

+ * + *

+ * If not explicitly set, the default value is false. + *

+ * + * @return true if the Job should be garbage + * collected along with the {@link Scheduler}. + */ + public boolean isVolatile() { + return volatility; + } + + /** + *

+ * Whether or not the Job should remain stored after it is + * orphaned (no {@link Trigger}s point to it). + *

+ * + *

+ * If not explicitly set, the default value is false. + *

+ * + * @return true if the Job should remain persisted after + * being orphaned. + */ + public boolean isDurable() { + return durability; + } + + /** + *

+ * Whether or not the Job implements the interface {@link StatefulJob}. + *

+ */ + public boolean isStateful() { + if (jobClass == null) { + return false; + } + + return (StatefulJob.class.isAssignableFrom(jobClass)); + } + + /** + *

+ * Instructs the Scheduler whether or not the Job + * should be re-executed if a 'recovery' or 'fail-over' situation is + * encountered. + *

+ * + *

+ * If not explicitly set, the default value is false. + *

+ * + * @see JobExecutionContext#isRecovering() + */ + public boolean requestsRecovery() { + return shouldRecover; + } + + /** + *

+ * Add the specified name of a {@link JobListener} to the + * end of the Job's list of listeners. + *

+ */ + public void addJobListener(String name) { + if (jobListeners.add(name) == false) { + throw new IllegalArgumentException( + "Job listener '" + name + "' is already registered for job detail: " + getFullName()); + } + } + + /** + *

+ * Remove the specified name of a {@link JobListener} from + * the Job's list of listeners. + *

+ * + * @return true if the given name was found in the list, and removed + */ + public boolean removeJobListener(String name) { + return jobListeners.remove(name); + } + + /** + *

+ * Returns an array of String s containing the names of all + * {@link JobListener}s assigned to the Job, + * in the order in which they should be notified. + *

+ */ + public String[] getJobListenerNames() { + return (String[])jobListeners.toArray(new String[jobListeners.size()]); + } + + /** + *

+ * Return a simple string representation of this object. + *

+ */ + public String toString() { + return "JobDetail '" + getFullName() + "': jobClass: '" + + ((getJobClass() == null) ? null : getJobClass().getName()) + + " isStateful: " + isStateful() + " isVolatile: " + + isVolatile() + " isDurable: " + isDurable() + + " requestsRecovers: " + requestsRecovery(); + } + + public boolean equals(Object obj) { + if (!(obj instanceof JobDetail)) { + return false; + } + + JobDetail other = (JobDetail) obj; + + if (other.getName() == null && getName() != null) { + return false; + } + if (other.getName() != null && !other.getName().equals(getName())) { + return false; + } + + if (other.getGroup() == null && getGroup() != null) { + return false; + } + if (other.getGroup() != null && !other.getGroup().equals(getGroup())) { + return false; + } + + return true; + } + + public int hashCode() { + return getFullName().hashCode(); + } + + public Object clone() { + JobDetail copy; + try { + copy = (JobDetail) super.clone(); + copy.jobListeners = ListOrderedSet.decorate(new HashSet()); + copy.jobListeners.addAll(jobListeners); + if (jobDataMap != null) { + copy.jobDataMap = (JobDataMap) jobDataMap.clone(); + } + } catch (CloneNotSupportedException ex) { + throw new IncompatibleClassChangeError("Not Cloneable."); + } + + return copy; + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobExecutionContext.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobExecutionContext.java new file mode 100644 index 000000000..b5f47efc0 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobExecutionContext.java @@ -0,0 +1,368 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +import java.util.Date; +import java.util.HashMap; + +import com.fr.third.org.quartz.spi.TriggerFiredBundle; + +/** + *

+ * A context bundle containing handles to various environment information, that + * is given to a {@link com.fr.third.org.quartz.JobDetail} instance as it is + * executed, and to a {@link Trigger} instance after the + * execution completes. + *

+ * + *

+ * The JobDataMap found on this object (via the + * getMergedJobDataMap() method) serves as a convenience - + * it is a merge of the JobDataMap found on the + * JobDetail and the one found on the Trigger, with + * the value in the latter overriding any same-named values in the former. + * It is thus considered a 'best practice' that the execute code of a Job + * retrieve data from the JobDataMap found on this object NOTE: Do not + * expect value 'set' into this JobDataMap to somehow be set back onto a + * StatefulJob's own JobDataMap. + *

+ * + *

+ * JobExecutionContext s are also returned from the + * Scheduler.getCurrentlyExecutingJobs() + * method. These are the same instances as those passed into the jobs that are + * currently executing within the scheduler. The exception to this is when your + * application is using Quartz remotely (i.e. via RMI) - in which case you get + * a clone of the JobExecutionContexts, and their references to + * the Scheduler and Job instances have been lost (a + * clone of the JobDetail is still available - just not a handle + * to the job instance that is running). + *

+ * + * @see #getJobDetail() + * @see #getScheduler() + * @see #getMergedJobDataMap() + * + * @see Job + * @see Trigger + * @see JobDataMap + * + * @author James House + */ +public class JobExecutionContext implements java.io.Serializable { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private transient Scheduler scheduler; + + private Trigger trigger; + + private JobDetail jobDetail; + + private JobDataMap jobDataMap; + + private transient Job job; + + private Calendar calendar; + + private boolean recovering = false; + + private int numRefires = 0; + + private Date fireTime; + + private Date scheduledFireTime; + + private Date prevFireTime; + + private Date nextFireTime; + + private long jobRunTime = -1; + + private Object result; + + private HashMap data = new HashMap(); + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Create a JobExcecutionContext with the given context data. + *

+ */ + public JobExecutionContext(Scheduler scheduler, + TriggerFiredBundle firedBundle, Job job) { + this.scheduler = scheduler; + this.trigger = firedBundle.getTrigger(); + this.calendar = firedBundle.getCalendar(); + this.jobDetail = firedBundle.getJobDetail(); + this.job = job; + this.recovering = firedBundle.isRecovering(); + this.fireTime = firedBundle.getFireTime(); + this.scheduledFireTime = firedBundle.getScheduledFireTime(); + this.prevFireTime = firedBundle.getPrevFireTime(); + this.nextFireTime = firedBundle.getNextFireTime(); + + this.jobDataMap = new JobDataMap(); + this.jobDataMap.putAll(jobDetail.getJobDataMap()); + this.jobDataMap.putAll(trigger.getJobDataMap()); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Get a handle to the Scheduler instance that fired the + * Job. + *

+ */ + public Scheduler getScheduler() { + return scheduler; + } + + /** + *

+ * Get a handle to the Trigger instance that fired the + * Job. + *

+ */ + public Trigger getTrigger() { + return trigger; + } + + /** + *

+ * Get a handle to the Calendar referenced by the Trigger + * instance that fired the Job. + *

+ */ + public Calendar getCalendar() { + return calendar; + } + + /** + *

+ * If the Job is being re-executed because of a 'recovery' + * situation, this method will return true. + *

+ */ + public boolean isRecovering() { + return recovering; + } + + public void incrementRefireCount() { + numRefires++; + } + + public int getRefireCount() { + return numRefires; + } + + /** + *

+ * Get the convenience JobDataMap of this execution context. + *

+ * + *

+ * The JobDataMap found on this object serves as a convenience - + * it is a merge of the JobDataMap found on the + * JobDetail and the one found on the Trigger, with + * the value in the latter overriding any same-named values in the former. + * It is thus considered a 'best practice' that the execute code of a Job + * retrieve data from the JobDataMap found on this object. + *

+ * + *

NOTE: Do not + * expect value 'set' into this JobDataMap to somehow be set back onto a + * StatefulJob's own JobDataMap. + *

+ * + *

+ * Attempts to change the contents of this map typically result in an + * IllegalStateException. + *

+ * + */ + public JobDataMap getMergedJobDataMap() { + return jobDataMap; + } + + /** + *

+ * Get the JobDetail associated with the Job. + *

+ */ + public JobDetail getJobDetail() { + return jobDetail; + } + + /** + *

+ * Get the instance of the Job that was created for this + * execution. + *

+ * + *

+ * Note: The Job instance is not available through remote scheduler + * interfaces. + *

+ */ + public Job getJobInstance() { + return job; + } + + /** + * The actual time the trigger fired. For instance the scheduled time may + * have been 10:00:00 but the actual fire time may have been 10:00:03 if + * the scheduler was too busy. + * + * @return Returns the fireTime. + * @see #getScheduledFireTime() + */ + public Date getFireTime() { + return fireTime; + } + + /** + * The scheduled time the trigger fired for. For instance the scheduled + * time may have been 10:00:00 but the actual fire time may have been + * 10:00:03 if the scheduler was too busy. + * + * @return Returns the scheduledFireTime. + * @see #getFireTime() + */ + public Date getScheduledFireTime() { + return scheduledFireTime; + } + + public Date getPreviousFireTime() { + return prevFireTime; + } + + public Date getNextFireTime() { + return nextFireTime; + } + + public String toString() { + return "JobExecutionContext:" + " trigger: '" + + getTrigger().getFullName() + " job: " + + getJobDetail().getFullName() + " fireTime: '" + getFireTime() + + " scheduledFireTime: " + getScheduledFireTime() + + " previousFireTime: '" + getPreviousFireTime() + + " nextFireTime: " + getNextFireTime() + " isRecovering: " + + isRecovering() + " refireCount: " + getRefireCount(); + } + + /** + * Returns the result (if any) that the Job set before its + * execution completed (the type of object set as the result is entirely up + * to the particular job). + * + *

+ * The result itself is meaningless to Quartz, but may be informative + * to {@link JobListener}s or + * {@link TriggerListener}s that are watching the job's + * execution. + *

+ * + * @return Returns the result. + */ + public Object getResult() { + return result; + } + + /** + * Set the result (if any) of the Job's execution (the type of + * object set as the result is entirely up to the particular job). + * + *

+ * The result itself is meaningless to Quartz, but may be informative + * to {@link JobListener}s or + * {@link TriggerListener}s that are watching the job's + * execution. + *

+ */ + public void setResult(Object result) { + this.result = result; + } + + /** + * The amount of time the job ran for (in milliseconds). The returned + * value will be -1 until the job has actually completed (or thrown an + * exception), and is therefore generally only useful to + * JobListeners and TriggerListeners. + * + * @return Returns the jobRunTime. + */ + public long getJobRunTime() { + return jobRunTime; + } + + /** + * @param jobRunTime The jobRunTime to set. + */ + public void setJobRunTime(long jobRunTime) { + this.jobRunTime = jobRunTime; + } + + /** + * Put the specified value into the context's data map with the given key. + * Possibly useful for sharing data between listeners and jobs. + * + *

NOTE: this data is volatile - it is lost after the job execution + * completes, and all TriggerListeners and JobListeners have been + * notified.

+ * + * @param key + * @param value + */ + public void put(Object key, Object value) { + data.put(key, value); + } + + /** + * Get the value with the given key from the context's data map. + * + * @param key + */ + public Object get(Object key) { + return data.get(key); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobExecutionException.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobExecutionException.java new file mode 100644 index 000000000..9ea782370 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobExecutionException.java @@ -0,0 +1,182 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +/** + *

+ * An exception that can be thrown by a {@link com.fr.third.org.quartz.Job} + * to indicate to the Quartz {@link Scheduler} that an error + * occured while executing, and whether or not the Job requests + * to be re-fired immediately (using the same {@link JobExecutionContext}, + * or whether it wants to be unscheduled. + *

+ * + *

+ * Note that if the flag for 'refire immediately' is set, the flags for + * unscheduling the Job are ignored. + *

+ * + * @see Job + * @see JobExecutionContext + * @see SchedulerException + * + * @author James House + */ +public class JobExecutionException extends SchedulerException { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private boolean refire = false; + + private boolean unscheduleTrigg = false; + + private boolean unscheduleAllTriggs = false; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Create a JobExcecutionException, with the 're-fire immediately' flag set + * to false. + *

+ */ + public JobExecutionException() { + } + + /** + *

+ * Create a JobExcecutionException, with the given cause. + *

+ */ + public JobExecutionException(Throwable cause) { + super(cause); + } + + /** + *

+ * Create a JobExcecutionException, with the given message. + *

+ */ + public JobExecutionException(String msg) { + super(msg); + } + + /** + *

+ * Create a JobExcecutionException with the 're-fire immediately' flag set + * to the given value. + *

+ */ + public JobExecutionException(boolean refireImmediately) { + refire = refireImmediately; + } + + /** + *

+ * Create a JobExcecutionException with the given underlying exception, and + * the 're-fire immediately' flag set to the given value. + *

+ */ + public JobExecutionException(Throwable cause, boolean refireImmediately) { + super(cause); + + refire = refireImmediately; + } + + /** + *

+ * Create a JobExcecutionException with the given message, and underlying + * exception. + *

+ */ + public JobExecutionException(String msg, Throwable cause) { + super(msg, cause); + } + + /** + *

+ * Create a JobExcecutionException with the given message, and underlying + * exception, and the 're-fire immediately' flag set to the given value. + *

+ */ + public JobExecutionException(String msg, Throwable cause, + boolean refireImmediately) { + super(msg, cause); + + refire = refireImmediately; + } + + /** + * Create a JobExcecutionException with the given message and the 're-fire + * immediately' flag set to the given value. + */ + public JobExecutionException(String msg, boolean refireImmediately) { + super(msg); + + refire = refireImmediately; + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public void setRefireImmediately(boolean refire) { + this.refire = refire; + } + + public boolean refireImmediately() { + return refire; + } + + public void setUnscheduleFiringTrigger(boolean unscheduleTrigg) { + this.unscheduleTrigg = unscheduleTrigg; + } + + public boolean unscheduleFiringTrigger() { + return unscheduleTrigg; + } + + public void setUnscheduleAllTriggers(boolean unscheduleAllTriggs) { + this.unscheduleAllTriggs = unscheduleAllTriggs; + } + + public boolean unscheduleAllTriggers() { + return unscheduleAllTriggs; + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobListener.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobListener.java new file mode 100644 index 000000000..3f66c8ff2 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobListener.java @@ -0,0 +1,96 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +/** + *

+ * The interface to be implemented by classes that want to be informed when a + * {@link com.fr.third.org.quartz.JobDetail} executes. In general, + * applications that use a Scheduler will not have use for this + * mechanism. + *

+ * + * @see Scheduler + * @see Job + * @see JobExecutionContext + * @see JobExecutionException + * @see TriggerListener + * + * @author James House + */ +public interface JobListener { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Get the name of the JobListener. + *

+ */ + String getName(); + + /** + *

+ * Called by the {@link Scheduler} when a {@link com.fr.third.org.quartz.JobDetail} + * is about to be executed (an associated {@link Trigger} + * has occured). + *

+ * + *

+ * This method will not be invoked if the execution of the Job was vetoed + * by a {@link TriggerListener}. + *

+ * + * @see #jobExecutionVetoed(JobExecutionContext) + */ + void jobToBeExecuted(JobExecutionContext context); + + /** + *

+ * Called by the {@link Scheduler} when a {@link com.fr.third.org.quartz.JobDetail} + * was about to be executed (an associated {@link Trigger} + * has occured), but a {@link TriggerListener} vetoed it's + * execution. + *

+ * + * @see #jobToBeExecuted(JobExecutionContext) + */ + void jobExecutionVetoed(JobExecutionContext context); + + + /** + *

+ * Called by the {@link Scheduler} after a {@link com.fr.third.org.quartz.JobDetail} + * has been executed, and be for the associated Trigger's + * triggered(xx) method has been called. + *

+ */ + void jobWasExecuted(JobExecutionContext context, + JobExecutionException jobException); + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobPersistenceException.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobPersistenceException.java new file mode 100644 index 000000000..d74fd0668 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/JobPersistenceException.java @@ -0,0 +1,83 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +/** + *

+ * An exception that is thrown to indicate that there has been a failure in the + * scheduler's underlying persistence mechanism. + *

+ * + * @author James House + */ +public class JobPersistenceException extends SchedulerException { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Create a JobPersistenceException with the given message. + *

+ */ + public JobPersistenceException(String msg) { + super(msg); + setErrorCode(ERR_PERSISTENCE); + } + + /** + *

+ * Create a JobPersistenceException with the given message + * and error code. + *

+ */ + public JobPersistenceException(String msg, int errCode) { + super(msg, errCode); + } + + /** + *

+ * Create a JobPersistenceException with the given message + * and cause. + *

+ */ + public JobPersistenceException(String msg, Throwable cause) { + super(msg, cause); + setErrorCode(ERR_PERSISTENCE); + } + + /** + *

+ * Create a JobPersistenceException with the given message, + * cause and error code. + *

+ */ + public JobPersistenceException(String msg, Throwable cause, int errorCode) { + super(msg, cause, errorCode); + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/NthIncludedDayTrigger.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/NthIncludedDayTrigger.java new file mode 100644 index 000000000..fe1bbd84d --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/NthIncludedDayTrigger.java @@ -0,0 +1,1016 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package com.fr.third.org.quartz; + +import java.text.NumberFormat; +import java.util.Date; +import java.util.TimeZone; + +/** + * A trigger which fires on the Nth day of every interval type + * ({@link #INTERVAL_TYPE_WEEKLY}, {@link #INTERVAL_TYPE_MONTHLY} or + * {@link #INTERVAL_TYPE_YEARLY}) that is not excluded by the associated + * calendar. When determining what the Nth day of the month or year + * is, NthIncludedDayTrigger will skip excluded days on the + * associated calendar. This would commonly be used in an Nth + * business day situation, in which the user wishes to fire a particular job on + * the Nth business day (i.e. the 5th business day of + * every month). Each NthIncludedDayTrigger also has an associated + * fireAtTime which indicates at what time of day the trigger is + * to fire. + *

+ * All NthIncludedDayTriggers default to a monthly interval type + * (fires on the Nth day of every month) with N = 1 (first + * non-excluded day) and fireAtTime set to 12:00 PM (noon). These + * values can be changed using the {@link #setN}, {@link #setIntervalType}, and + * {@link #setFireAtTime} methods. Users may also want to note the + * {@link #setNextFireCutoffInterval} and {@link #getNextFireCutoffInterval} + * methods. + *

+ * Take, for example, the following calendar: + *

+ *

+ *        July                  August                September
+ * Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa
+ *                 1  W       1  2  3  4  5  W                1  2  W
+ *  W  H  5  6  7  8  W    W  8  9 10 11 12  W    W  H  6  7  8  9  W
+ *  W 11 12 13 14 15  W    W 15 16 17 18 19  W    W 12 13 14 15 16  W
+ *  W 18 19 20 21 22  W    W 22 23 24 25 26  W    W 19 20 21 22 23  W
+ *  W 25 26 27 28 29  W    W 29 30 31             W 26 27 28 29 30
+ *  W
+ * 
+ *

+ * Where W's represent weekend days, and H's represent holidays, all of which + * are excluded on a calendar associated with an + * NthIncludedDayTrigger with n=5 and + * intervalType=INTERVAL_TYPE_MONTHLY. In this case, the trigger + * would fire on the 8th of July (because of the July 4 holiday), + * the 5th of August, and the 8th of September (because + * of Labor Day). + * + * @author Aaron Craven + */ +public class NthIncludedDayTrigger extends Trigger { + + static final long serialVersionUID = 6267700049629328293L; + + /** + * Instructs the Scheduler that upon a mis-fire situation, the + * NthIncludedDayTrigger wants to be fired now by the + * Scheduler + */ + public static final int MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1; + + /** + * Instructs the Scheduler that upon a mis-fire situation, the + * NthIncludedDayTrigger wants to have + * nextFireTime updated to the next time in the schedule after + * the current time, but it does not want to be fired now. + */ + public static final int MISFIRE_INSTRUCTION_DO_NOTHING = 2; + + /** + * indicates a monthly trigger type (fires on the Nth included + * day of every month). + */ + public static final int INTERVAL_TYPE_MONTHLY = 1; + + /** + * indicates a yearly trigger type (fires on the Nth included + * day of every year). + */ + public static final int INTERVAL_TYPE_YEARLY = 2; + + /** + * indicates a weekly trigger type (fires on the Nth included + * day of every week). When using this interval type, care must be taken + * not to think of the value of n as an analog to + * java.util.Calendar.DAY_OF_WEEK. Such a comparison can only + * be drawn when there are no calendars associated with the trigger. To + * illustrate, consider an NthIncludedDayTrigger with + * n = 3 which is associated with a Calendar excluding + * non-weekdays. The trigger would fire on the 3rd + * included day of the week, which would be 4th + * actual day of the week. + */ + public static final int INTERVAL_TYPE_WEEKLY = 3; + + private Date startTime = new Date(); + private Date endTime; + private Date previousFireTime; + private Date nextFireTime; + private Calendar calendar; + + private int n = 1; + private int intervalType = INTERVAL_TYPE_MONTHLY; + private int fireAtHour = 12; + private int fireAtMinute = 0; + private int fireAtSecond = 0; + private int nextFireCutoffInterval = 12; + + private TimeZone timeZone; + + /** + * Create an NthIncludedDayTrigger with no specified name, + * group, or JobDetail. This will result initially in a + * default monthly trigger that fires on the first day of every month at + * 12:00 PM (n=1, + * intervalType={@link #INTERVAL_TYPE_MONTHLY}, + * fireAtTime="12:00"). + *

+ * Note that setName(), setGroup(), + * setJobName(), and setJobGroup(), must be + * called before the NthIncludedDayTrigger can be placed into + * a Scheduler. + */ + public NthIncludedDayTrigger() { + super(); + } + + /** + * Create an NthIncludedDayTrigger with the given name and + * group but no specified JobDetail. This will result + * initially in a default monthly trigger that fires on the first day of + * every month at 12:00 PM (n=1, + * intervalType={@link #INTERVAL_TYPE_MONTHLY}, + * fireAtTime="12:00"). + *

+ * Note that setJobName() and setJobGroup() must + * be called before the NthIncludedDayTrigger can be placed + * into a Scheduler. + * + * @param name the name for the NthIncludedDayTrigger + * @param group the group for the NthIncludedDayTrigger + */ + public NthIncludedDayTrigger(String name, String group) { + super(name, group); + } + + /** + * Create an NthIncludedDayTrigger with the given name and + * group and the specified JobDetail. This will result + * initially in a default monthly trigger that fires on the first day of + * every month at 12:00 PM (n=1, + * intervalType={@link #INTERVAL_TYPE_MONTHLY}, + * fireAtTime="12:00"). + * + * @param name the name for the NthIncludedDayTrigger + * @param group the group for the NthIncludedDayTrigger + * @param jobName the name of the job to associate with the + * NthIncludedDayTrigger + * @param jobGroup the group containing the job to associate with the + * NthIncludedDayTrigger + */ + public NthIncludedDayTrigger(String name, String group, String jobName, + String jobGroup) { + super(name, group, jobName, jobGroup); + } + + /** + * Sets the day of the interval on which the + * NthIncludedDayTrigger should fire. If the Nth + * day of the interval does not exist (i.e. the 32nd of a + * month), the trigger simply will never fire. N may not be less than 1. + * + * @param n the day of the interval on which the trigger should fire. + * @throws java.lang.IllegalArgumentException + * the value entered for N was not valid (probably less than or + * equal to zero). + * @see #getN() + */ + public void setN(int n) { + if (n > 0) { + this.n = n; + } else { + throw new IllegalArgumentException("N must be greater than 0."); + } + } + + /** + * Returns the day of the interval on which the + * NthIncludedDayTrigger should fire. + * + * @return the value of n + * @see #setN(int) + */ + public int getN() { + return this.n; + } + + /** + * Sets the interval type for the NthIncludedDayTrigger. If + * {@link #INTERVAL_TYPE_MONTHLY}, the trigger will fire on the + * Nth included day of every month. If + * {@link #INTERVAL_TYPE_YEARLY}, the trigger will fire on the + * Nth included day of every year. If + * {@link #INTERVAL_TYPE_WEEKLY}, the trigger will fire on the + * Nth included day of every week. + * + * @param intervalType the interval type for the trigger + * @throws java.lang.IllegalArgumentException + * the value of intervalType is not valid. Valid + * values are represented by the INTERVAL_TYPE_WEEKLY, + * INTERVAL_TYPE_MONTHLY and INTERVAL_TYPE_YEARLY constants. + * @see #getIntervalType() + * @see #INTERVAL_TYPE_WEEKLY + * @see #INTERVAL_TYPE_MONTHLY + * @see #INTERVAL_TYPE_YEARLY + */ + public void setIntervalType(int intervalType) { + switch (intervalType) { + case INTERVAL_TYPE_WEEKLY: + this.intervalType = intervalType; + break; + case INTERVAL_TYPE_MONTHLY: + this.intervalType = intervalType; + break; + case INTERVAL_TYPE_YEARLY: + this.intervalType = intervalType; + break; + default: + throw new IllegalArgumentException("Invalid Interval Type:" + + intervalType); + } + } + + /** + * Returns the interval type for the NthIncludedDayTrigger. + * + * @return the trigger's interval type + * @see #setIntervalType(int) + * @see #INTERVAL_TYPE_WEEKLY + * @see #INTERVAL_TYPE_MONTHLY + * @see #INTERVAL_TYPE_YEARLY + */ + public int getIntervalType() { + return this.intervalType; + } + + /** + * Sets the fire time for the NthIncludedDayTrigger, which + * should be represented as a string with the format + * "HH:MM[:SS]", with HH representing the 24-hour clock hour + * of the fire time. Hours can be represented as either a one-digit or + * two-digit number. Seconds are optional. + * + * @param fireAtTime the time at which the trigger should fire + * @throws java.lang.IllegalArgumentException + * the specified value for fireAtTime could not be + * successfully parsed into a valid time of day. + * @see #getFireAtTime() + */ + public void setFireAtTime(String fireAtTime) { + int newFireHour; + int newFireMinute; + int newFireSecond = 0; + + try { + int i = fireAtTime.indexOf(":"); + newFireHour = Integer.parseInt(fireAtTime.substring(0, i)); + newFireMinute = Integer.parseInt(fireAtTime.substring(i+1, i+3)); + i = fireAtTime.indexOf(":", i+1); + if (i > -1) { + newFireSecond = Integer.parseInt(fireAtTime.substring(i+1)); + } + } catch (Exception e) { + throw new IllegalArgumentException( + "Could not parse time expression '" + fireAtTime + "':" + e.getMessage()); + } + + // Check ranges + if ((newFireHour < 0) || (newFireHour > 23)) { + throw new IllegalArgumentException( + "Could not parse time expression '" + fireAtTime + "':" + + "fireAtHour must be between 0 and 23"); + } else if ((newFireMinute < 0) || (newFireMinute > 59)) { + throw new IllegalArgumentException( + "Could not parse time expression '" + fireAtTime + "':" + + "fireAtMinute must be between 0 and 59"); + } else if ((newFireSecond < 0) || (newFireSecond > 59)) { + throw new IllegalArgumentException( + "Could not parse time expression '" + fireAtTime + "':" + + "fireAtMinute must be between 0 and 59"); + } + + fireAtHour = newFireHour; + fireAtMinute = newFireMinute; + fireAtSecond = newFireSecond; + } + + /** + * Returns the fire time for the NthIncludedDayTrigger as a + * string with the format "HH:MM[:SS]", with HH representing the + * 24-hour clock hour of the fire time. Seconds are optional and their + * inclusion depends on whether or not they were provided to + * {@link #setFireAtTime(String)}. + * + * @return the fire time for the trigger + * @see #setFireAtTime(String) + */ + public String getFireAtTime() { + NumberFormat format = NumberFormat.getNumberInstance(); + format.setMaximumIntegerDigits(2); + format.setMinimumIntegerDigits(2); + format.setMaximumFractionDigits(0); + + return format.format(this.fireAtHour) + ":" + + format.format(this.fireAtMinute) + ":" + + format.format(this.fireAtSecond); + } + + /** + * Sets the nextFireCutoffInterval for the + * NthIncludedDayTrigger. + *

+ * Because of the conceptual design of NthIncludedDayTrigger, + * it is not always possible to decide with certainty that the trigger + * will never fire again. Therefore, it will search for the next + * fire time up to a given cutoff. These cutoffs can be changed by using the + * {@link #setNextFireCutoffInterval(int)} and + * {@link #getNextFireCutoffInterval()} methods. The default cutoff is 12 + * of the intervals specified by {@link #getIntervalType() + * intervalType}. + *

+ * In most cases, the default value of this setting (12) is sufficient (it + * is highly unlikely, for example, that you will need to look at more than + * 12 months of dates to ensure that your trigger will never fire again). + * However, this setting is included to allow for the rare exceptions where + * this might not be true. + *

+ * For example, if your trigger is associated with a calendar that excludes + * a great many dates in the next 12 months, and hardly any following that, + * it is possible (if n is large enough) that you could run + * into this situation. + * + * @param nextFireCutoffInterval the desired cutoff interval + * @see #getNextFireCutoffInterval() + * @see #getNextFireTime() + */ + public void setNextFireCutoffInterval(int nextFireCutoffInterval) { + this.nextFireCutoffInterval = nextFireCutoffInterval; + } + + /** + * Returns the nextFireCutoffInterval for the + * NthIncludedDayTrigger. + *

+ * Because of the conceptual design of NthIncludedDayTrigger, + * it is not always possible to decide with certainty that the trigger + * will never fire again. Therefore, it will search for the next + * fire time up to a given cutoff. These cutoffs can be changed by using the + * {@link #setNextFireCutoffInterval(int)} and + * {@link #getNextFireCutoffInterval()} methods. The default cutoff is 12 + * of the intervals specified by {@link #getIntervalType() + * intervalType}. + * + * @return the chosen cutoff interval + * @see #setNextFireCutoffInterval(int) + * @see #getNextFireTime() + */ + public int getNextFireCutoffInterval() { + return this.nextFireCutoffInterval; + } + + /** + * Sets the date/time on which the trigger may begin firing. This defines + * the initial boundary for trigger firings — the trigger will not + * fire prior to this date and time. Defaults to the current date and time + * when the NthIncludedDayTrigger is created. + * + * @param startTime the initial boundary for trigger firings + * @throws java.lang.IllegalArgumentException + * the specified start time is after the current end time or is + * null + * @see #getStartTime() + */ + public void setStartTime(Date startTime) { + if (startTime == null) { + throw new IllegalArgumentException("Start time may not be null"); + } + if ((this.endTime != null) && endTime.before(startTime)) { + throw new IllegalArgumentException("Start time must be before end time."); + } + this.startTime = startTime; + } + + /** + * Returns the date/time on which the trigger may begin firing. This + * defines the initial boundary for trigger firings — the trigger + * will not fire prior to this date and time. + * + * @return the initial date/time on which the trigger may begin firing + * @see #setStartTime(Date) + */ + public Date getStartTime() { + return this.startTime; + } + + /** + * Sets the date/time on which the trigger must stop firing. This defines + * the final boundary for trigger firings — the trigger will not + * fire after to this date and time. If this value is null, no end time + * boundary is assumed, and the trigger can continue indefinitely. + * + * @param endTime the final boundary for trigger firings + * @throws java.lang.IllegalArgumentException + * the specified end time is before the current start time + * @see #getEndTime() + */ + public void setEndTime(Date endTime) { + if ((endTime != null) && endTime.before(startTime)) { + throw new IllegalArgumentException("End time must be after start time."); + } + this.endTime = endTime; + } + + /** + * Returns the date/time on which the trigger must stop firing. This + * defines the final boundary for trigger firings — the trigger will + * not fire after to this date and time. If this value is null, no end time + * boundary is assumed, and the trigger can continue indefinitely. + * + * @return the date/time on which the trigger must stop firing + * @see #setEndTime(Date) + */ + public Date getEndTime() { + return this.endTime; + } + + /** + * Sets the time zone in which the fireAtTime will be resolved. + * If no time zone is provided, then the default time zone will be used. + * + * @see TimeZone#getDefault() + * @see #getTimeZone() + * @see #getFireAtTime() + * @see #setFireAtTime(String) + */ + public void setTimeZone(TimeZone timeZone) { + this.timeZone = timeZone; + } + + /** + * Gets the time zone in which the fireAtTime will be resolved. + * If no time zone was explicitly set, then the default time zone is used. + * + * @see TimeZone#getDefault() + * @see #getTimeZone() + * @see #getFireAtTime() + * @see #setFireAtTime(String) + */ + public TimeZone getTimeZone() { + if (timeZone == null) { + timeZone = TimeZone.getDefault(); + } + return timeZone; + } + + + /** + * Returns the next time at which the NthIncludedDayTrigger + * will fire. If the trigger will not fire again, null will be + * returned. + *

+ * Because of the conceptual design of NthIncludedDayTrigger, + * it is not always possible to decide with certainty that the trigger + * will never fire again. Therefore, it will search for the next + * fire time up to a given cutoff. These cutoffs can be changed by using the + * {@link #setNextFireCutoffInterval(int)} and + * {@link #getNextFireCutoffInterval()} methods. The default cutoff is 12 + * of the intervals specified by {@link #getIntervalType() + * intervalType}. + *

+ * The returned value is not guaranteed to be valid until after + * the trigger has been added to the scheduler. + * + * @return the next fire time for the trigger + * @see #getNextFireCutoffInterval() + * @see #setNextFireCutoffInterval(int) + * @see #getFireTimeAfter(Date) + */ + public Date getNextFireTime() { + return this.nextFireTime; + } + + /** + * Returns the previous time at which the + * NthIncludedDayTrigger fired. If the trigger has not yet + * fired, null will be returned. + * + * @return the previous fire time for the trigger + */ + public Date getPreviousFireTime() { + return this.previousFireTime; + } + + /** + * Returns the first time the NthIncludedDayTrigger will fire + * after the specified date. + *

+ * Because of the conceptual design of NthIncludedDayTrigger, + * it is not always possible to decide with certainty that the trigger + * will never fire again. Therefore, it will search for the next + * fire time up to a given cutoff. These cutoffs can be changed by using the + * {@link #setNextFireCutoffInterval(int)} and + * {@link #getNextFireCutoffInterval()} methods. The default cutoff is 12 + * of the intervals specified by {@link #getIntervalType() + * intervalType}. + *

+ * Therefore, for triggers with intervalType = + * {@link NthIncludedDayTrigger#INTERVAL_TYPE_WEEKLY + * INTERVAL_TYPE_WEEKLY}, if the trigger will not fire within 12 + * weeks after the given date/time, null will be returned. For + * triggers with intervalType = + * {@link NthIncludedDayTrigger#INTERVAL_TYPE_MONTHLY + * INTERVAL_TYPE_MONTHLY}, if the trigger will not fire within 12 + * months after the given date/time, null will be returned. + * For triggers with intervalType = + * {@link NthIncludedDayTrigger#INTERVAL_TYPE_YEARLY + * INTERVAL_TYPE_YEARLY}, if the trigger will not fire within 12 + * years after the given date/time, null will be returned. In + * all cases, if the trigger will not fire before endTime, + * null will be returned. + * + * @param afterTime The time after which to find the nearest fire time. + * This argument is treated as exclusive — that is, + * if afterTime is a valid fire time for the trigger, it + * will not be returned as the next fire time. + * @return the first time the trigger will fire following the specified + * date + */ + public Date getFireTimeAfter(Date afterTime) { + if (afterTime == null) { + afterTime = new Date(); + } + + if (afterTime.before(this.startTime)) { + afterTime = new Date(startTime.getTime() - 1000l); + } + + if (this.intervalType == INTERVAL_TYPE_WEEKLY) { + return getWeeklyFireTimeAfter(afterTime); + } else if (this.intervalType == INTERVAL_TYPE_MONTHLY) { + return getMonthlyFireTimeAfter(afterTime); + } else if (this.intervalType == INTERVAL_TYPE_YEARLY) { + return getYearlyFireTimeAfter(afterTime); + } else { + return null; + } + } + + /** + * Returns the last time the NthIncludedDayTrigger will fire. + * If the trigger will not fire at any point between startTime + * and endTime, or there is not endTime, + * null will be returned. + * + * @return the last time the trigger will fire, or null if there is no + * last time. + */ + public Date getFinalFireTime() { + if(endTime == null) + return null; + + Date finalTime = null; + java.util.Calendar currCal = java.util.Calendar.getInstance(); + currCal.setTime(this.endTime); + + while ((finalTime == null) + && (this.startTime.before(currCal.getTime()))) { + currCal.add(java.util.Calendar.DATE, -1); + + finalTime = getFireTimeAfter(currCal.getTime()); + } + + return finalTime; + } + + /** + * Called when the Scheduler has decided to 'fire' the trigger + * (execute the associated Job), in order to give the + * Trigger a chance to update itself for its next triggering + * (if any). + */ + public void triggered(Calendar calendar) { + this.calendar = calendar; + this.previousFireTime = this.nextFireTime; + this.nextFireTime = getFireTimeAfter(this.nextFireTime); + } + + /** + * Called by the scheduler at the time a Trigger is first + * added to the scheduler, in order to have the Trigger + * compute its first fire time, based on any associated calendar. + *

+ * After this method has been called, getNextFireTime() + * should return a valid answer. + *

+ * + * @return the first time at which the Trigger will be fired + * by the scheduler, which is also the same value + * {@link #getNextFireTime()} will return (until after the first + * firing of the Trigger). + */ + public Date computeFirstFireTime(Calendar calendar) { + this.calendar = calendar; + this.nextFireTime = + getFireTimeAfter(new Date(this.startTime.getTime() - 1000l)); + + return this.nextFireTime; + } + + /** + * Called after the Scheduler has executed the + * JobDetail associated with the Trigger in order + * to get the final instruction code from the trigger. + * + * @param jobCtx the JobExecutionContext that was used by the + * Job's execute() method. + * @param result the JobExecutionException thrown by the + * Job, if any (may be null) + * @return one of the Trigger.INSTRUCTION_XXX constants. + */ + public int executionComplete(JobExecutionContext jobCtx, + JobExecutionException result) { + if (result != null && result.refireImmediately()) { + return INSTRUCTION_RE_EXECUTE_JOB; + } + + if (result != null && result.unscheduleFiringTrigger()) { + return INSTRUCTION_SET_TRIGGER_COMPLETE; + } + + if (result != null && result.unscheduleAllTriggers()) { + return INSTRUCTION_SET_ALL_JOB_TRIGGERS_COMPLETE; + } + + if (!mayFireAgain()) { + return INSTRUCTION_DELETE_TRIGGER; + } + + return INSTRUCTION_NOOP; + } + + /** + * Used by the Scheduler to determine whether or not it is + * possible for this Trigger to fire again. + *

+ * If the returned value is false then the + * Scheduler may remove the Trigger from the + * JobStore + * + * @return a boolean indicator of whether the trigger could potentially fire + * again + */ + public boolean mayFireAgain() { + return (getNextFireTime() != null); + } + + /** + * Indicates whether misfireInstruction is a valid misfire + * instruction for this Trigger. + * + * @return whether misfireInstruction is valid. + */ + protected boolean validateMisfireInstruction(int misfireInstruction) { + if ((misfireInstruction == MISFIRE_INSTRUCTION_SMART_POLICY) || + (misfireInstruction == MISFIRE_INSTRUCTION_DO_NOTHING) || + (misfireInstruction == MISFIRE_INSTRUCTION_FIRE_ONCE_NOW)) { + return true; + } else { + return false; + } + } + + /** + * Updates the NthIncludedDayTrigger's state based on the + * MISFIRE_INSTRUCTION_XXX that was selected when the + * NthIncludedDayTrigger was created + *

+ * If the misfire instruction is set to MISFIRE_INSTRUCTION_SMART_POLICY, + * then the instruction will be interpreted as + * {@link #MISFIRE_INSTRUCTION_FIRE_ONCE_NOW}. + * + * @param calendar a new or updated calendar to use for the trigger + */ + public void updateAfterMisfire(Calendar calendar) { + int instruction = getMisfireInstruction(); + + this.calendar = calendar; + + if (instruction == MISFIRE_INSTRUCTION_SMART_POLICY) { + instruction = MISFIRE_INSTRUCTION_FIRE_ONCE_NOW; + } + + if (instruction == MISFIRE_INSTRUCTION_DO_NOTHING) { + this.nextFireTime = getFireTimeAfter(new Date()); + } else if (instruction == MISFIRE_INSTRUCTION_FIRE_ONCE_NOW) { + this.nextFireTime = new Date(); + } + } + + /** + * Updates the NthIncludedDayTrigger's state based on the + * given new version of the associated Calendar. + * + * @param calendar a new or updated calendar to use for the trigger + * @param misfireThreshold the amount of time (in milliseconds) that must + * be between "now" and the time the next + * firing of the trigger is supposed to occur. + */ + public void updateWithNewCalendar(Calendar calendar, + long misfireThreshold) { + Date now = new Date(); + long diff; + + this.calendar = calendar; + this.nextFireTime = getFireTimeAfter(this.previousFireTime); + + if ((this.nextFireTime != null) && (this.nextFireTime.before(now))) { + diff = now.getTime() - this.nextFireTime.getTime(); + if (diff >= misfireThreshold) { + this.nextFireTime = getFireTimeAfter(this.nextFireTime); + } + } + } + + /** + * Calculates the first time an NthIncludedDayTrigger with + * intervalType = {@link #INTERVAL_TYPE_WEEKLY} will fire + * after the specified date. See {@link #getNextFireTime} for more + * information. + * + * @param afterDate The time after which to find the nearest fire time. + * This argument is treated as exclusive — that is, + * if afterTime is a valid fire time for the trigger, it + * will not be returned as the next fire time. + * @return the first time the trigger will fire following the specified + * date + */ + private Date getWeeklyFireTimeAfter(Date afterDate) { + int currN = 0; + java.util.Calendar afterCal; + java.util.Calendar currCal; + int currWeek; + int weekCount = 0; + boolean gotOne = false; + + afterCal = java.util.Calendar.getInstance(getTimeZone()); + afterCal.setTime(afterDate); + + currCal = java.util.Calendar.getInstance(getTimeZone()); + currCal.set(afterCal.get(java.util.Calendar.YEAR), + afterCal.get(java.util.Calendar.MONTH), + afterCal.get(java.util.Calendar.DAY_OF_MONTH)); + + //move to the first day of the week + while (currCal.get(java.util.Calendar.DAY_OF_WEEK) != + currCal.getFirstDayOfWeek()) { + currCal.add(java.util.Calendar.DAY_OF_MONTH, -1); + } + + currCal.set(java.util.Calendar.HOUR_OF_DAY, this.fireAtHour); + currCal.set(java.util.Calendar.MINUTE, this.fireAtMinute); + currCal.set(java.util.Calendar.SECOND, this.fireAtSecond); + currCal.set(java.util.Calendar.MILLISECOND, 0); + + currWeek = currCal.get(java.util.Calendar.WEEK_OF_YEAR); + + while ((!gotOne) && (weekCount < this.nextFireCutoffInterval)) { + while ((currN != this.n) && (weekCount < 12)) { + //if we move into a new week, reset the current "n" counter + if (currCal.get(java.util.Calendar.WEEK_OF_YEAR) != currWeek) { + currN = 0; + weekCount++; + currWeek = currCal.get(java.util.Calendar.WEEK_OF_YEAR); + } + + //treating a null calendar as an all-inclusive calendar, + // increment currN if the current date being tested is included + // on the calendar + if ((calendar == null) + || (calendar.isTimeIncluded(currCal.getTime().getTime()))) { + currN++; + } + + if (currN != this.n) { + currCal.add(java.util.Calendar.DATE, 1); + } + + //if we pass endTime, drop out and return null. + if ((this.endTime != null) + && (currCal.getTime().after(this.endTime))) { + return null; + } + } + + //We found an "n" or we've checked the requisite number of weeks. + // If we've found an "n", is it the right one? -- that is, we could + // be looking at an nth day PRIOR to afterDate + if (currN == this.n) { + if (afterDate.before(currCal.getTime())) { + gotOne = true; + } else { //resume checking on the first day of the next week + + //move back to the beginning of the week and add 7 days + while (currCal.get(java.util.Calendar.DAY_OF_WEEK) != + currCal.getFirstDayOfWeek()) { + currCal.add(java.util.Calendar.DAY_OF_MONTH, -1); + } + currCal.add(java.util.Calendar.DAY_OF_MONTH, 7); + + currN = 0; + } + } + } + + if (weekCount < this.nextFireCutoffInterval) { + return currCal.getTime(); + } else { + return null; + } + } + + /** + * Calculates the first time an NthIncludedDayTrigger with + * intervalType = {@link #INTERVAL_TYPE_MONTHLY} will fire + * after the specified date. See {@link #getNextFireTime} for more + * information. + * + * @param afterDate The time after which to find the nearest fire time. + * This argument is treated as exclusive — that is, + * if afterTime is a valid fire time for the trigger, it + * will not be returned as the next fire time. + * @return the first time the trigger will fire following the specified + * date + */ + private Date getMonthlyFireTimeAfter(Date afterDate) { + int currN = 0; + java.util.Calendar afterCal; + java.util.Calendar currCal; + int currMonth; + int monthCount = 0; + boolean gotOne = false; + + afterCal = java.util.Calendar.getInstance(getTimeZone()); + afterCal.setTime(afterDate); + + currCal = java.util.Calendar.getInstance(getTimeZone()); + currCal.set(afterCal.get(java.util.Calendar.YEAR), + afterCal.get(java.util.Calendar.MONTH), 1); + currCal.set(java.util.Calendar.HOUR_OF_DAY, this.fireAtHour); + currCal.set(java.util.Calendar.MINUTE, this.fireAtMinute); + currCal.set(java.util.Calendar.SECOND, this.fireAtSecond); + currCal.set(java.util.Calendar.MILLISECOND, 0); + + currMonth = currCal.get(java.util.Calendar.MONTH); + + while ((!gotOne) && (monthCount < this.nextFireCutoffInterval)) { + while ((currN != this.n) && (monthCount < 12)) { + //if we move into a new month, reset the current "n" counter + if (currCal.get(java.util.Calendar.MONTH) != currMonth) { + currN = 0; + monthCount++; + currMonth = currCal.get(java.util.Calendar.MONTH); + } + + //treating a null calendar as an all-inclusive calendar, + // increment currN if the current date being tested is included + // on the calendar + if ((calendar == null) + || (calendar.isTimeIncluded(currCal.getTime().getTime()))) { + currN++; + } + + if (currN != this.n) { + currCal.add(java.util.Calendar.DATE, 1); + } + + //if we pass endTime, drop out and return null. + if ((this.endTime != null) + && (currCal.getTime().after(this.endTime))) { + return null; + } + } + + //We found an "n" or we've checked the requisite number of months. + // If we've found an "n", is it the right one? -- that is, we could + // be looking at an nth day PRIOR to afterDate + if (currN == this.n) { + if (afterDate.before(currCal.getTime())) { + gotOne = true; + } else { //resume checking on the first day of the next month + currCal.set(java.util.Calendar.DAY_OF_MONTH, 1); + currCal.add(java.util.Calendar.MONTH, 1); + currN = 0; + } + } + } + + if (monthCount < this.nextFireCutoffInterval) { + return currCal.getTime(); + } else { + return null; + } + } + + /** + * Calculates the first time an NthIncludedDayTrigger with + * intervalType = {@link #INTERVAL_TYPE_YEARLY} will fire + * after the specified date. See {@link #getNextFireTime} for more + * information. + * + * @param afterDate The time after which to find the nearest fire time. + * This argument is treated as exclusive — that is, + * if afterTime is a valid fire time for the trigger, it + * will not be returned as the next fire time. + * @return the first time the trigger will fire following the specified + * date + */ + private Date getYearlyFireTimeAfter(Date afterDate) { + int currN = 0; + java.util.Calendar afterCal; + java.util.Calendar currCal; + int currYear; + int yearCount = 0; + boolean gotOne = false; + + afterCal = java.util.Calendar.getInstance(getTimeZone()); + afterCal.setTime(afterDate); + + currCal = java.util.Calendar.getInstance(getTimeZone()); + currCal.set(afterCal.get(java.util.Calendar.YEAR), + java.util.Calendar.JANUARY, 1); + currCal.set(java.util.Calendar.HOUR_OF_DAY, this.fireAtHour); + currCal.set(java.util.Calendar.MINUTE, this.fireAtMinute); + currCal.set(java.util.Calendar.SECOND, this.fireAtSecond); + currCal.set(java.util.Calendar.MILLISECOND, 0); + + currYear = currCal.get(java.util.Calendar.YEAR); + + while ((!gotOne) && (yearCount < this.nextFireCutoffInterval)) { + while ((currN != this.n) && (yearCount < 5)) { + //if we move into a new year, reset the current "n" counter + if (currCal.get(java.util.Calendar.YEAR) != currYear) { + currN = 0; + yearCount++; + currYear = currCal.get(java.util.Calendar.YEAR); + } + + //treating a null calendar as an all-inclusive calendar, + // increment currN if the current date being tested is included + // on the calendar + if ((calendar == null) + || (calendar.isTimeIncluded(currCal.getTime().getTime()))) { + currN++; + } + + if (currN != this.n) { + currCal.add(java.util.Calendar.DATE, 1); + } + + //if we pass endTime, drop out and return null. + if ((this.endTime != null) + && (currCal.getTime().after(this.endTime))) { + return null; + } + } + + //We found an "n" or we've checked the requisite number of years. + // If we've found an "n", is it the right one? -- that is, we + // could be looking at an nth day PRIOR to afterDate + if (currN == this.n) { + if (afterDate.before(currCal.getTime())) { + gotOne = true; + } else { //resume checking on the first day of the next year + currCal.set(java.util.Calendar.DAY_OF_MONTH, 1); + currCal.set(java.util.Calendar.MONTH, + java.util.Calendar.JANUARY); + currCal.add(java.util.Calendar.YEAR, 1); + currN = 0; + } + } + } + + if (yearCount < this.nextFireCutoffInterval) { + return currCal.getTime(); + } else { + return null; + } + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/ObjectAlreadyExistsException.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/ObjectAlreadyExistsException.java new file mode 100644 index 000000000..05b07b7bd --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/ObjectAlreadyExistsException.java @@ -0,0 +1,89 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +/** + *

+ * An exception that is thrown to indicate that an attempt to store a new + * object (i.e. {@link com.fr.third.org.quartz.JobDetail},{@link Trigger} + * or {@link Calendar}) in a {@link Scheduler} + * failed, because one with the same name & group already exists. + *

+ * + * @author James House + */ +public class ObjectAlreadyExistsException extends JobPersistenceException { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Create a ObjectAlreadyExistsException with the given + * message. + *

+ */ + public ObjectAlreadyExistsException(String msg) { + super(msg); + } + + /** + *

+ * Create a ObjectAlreadyExistsException and auto-generate a + * message using the name/group from the given JobDetail. + *

+ * + *

+ * The message will read:
"Unable to store Job with name: '__' and + * group: '__', because one already exists with this identification." + *

+ */ + public ObjectAlreadyExistsException(JobDetail offendingJob) { + super("Unable to store Job with name: '" + offendingJob.getName() + + "' and group: '" + offendingJob.getGroup() + + "', because one already exists with this identification."); + } + + /** + *

+ * Create a ObjectAlreadyExistsException and auto-generate a + * message using the name/group from the given Trigger. + *

+ * + *

+ * The message will read:
"Unable to store Trigger with name: '__' and + * group: '__', because one already exists with this identification." + *

+ */ + public ObjectAlreadyExistsException(Trigger offendingTrigger) { + super("Unable to store Trigger with name: '" + + offendingTrigger.getName() + "' and group: '" + + offendingTrigger.getGroup() + + "', because one already exists with this identification."); + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/Scheduler.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/Scheduler.java new file mode 100644 index 000000000..5db9a350e --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/Scheduler.java @@ -0,0 +1,1102 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +import java.util.Date; +import java.util.List; +import java.util.Set; + +import com.fr.third.org.quartz.spi.JobFactory; + +/** + *

+ * This is the main interface of a Quartz Scheduler. + *

+ * + *

+ * A Scheduler maintains a registery of {@link com.fr.third.org.quartz.JobDetail} + * s and {@link Trigger}s. Once registered, the Scheduler + * is responible for executing Job s when their associated + * Trigger s fire (when their scheduled time arrives). + *

+ * + *

+ * Scheduler instances are produced by a {@link SchedulerFactory}. + * A scheduler that has already been created/initialized can be found and used + * through the same factory that produced it. After a Scheduler + * has been created, it is in "stand-by" mode, and must have its + * start() method called before it will fire any Jobs. + *

+ * + *

+ * Job s are to be created by the 'client program', by defining + * a class that implements the {@link com.fr.third.org.quartz.Job} + * interface. {@link JobDetail} objects are then created (also + * by the client) to define a individual instances of the Job. + * JobDetail instances can then be registered with the Scheduler + * via the scheduleJob(JobDetail, Trigger) or addJob(JobDetail, boolean) + * method. + *

+ * + *

+ * Trigger s can then be defined to fire individual Job + * instances based on given schedules. SimpleTrigger s are most + * useful for one-time firings, or firing at an exact moment in time, with N + * repeats with a given delay between them. CronTrigger s allow + * scheduling based on time of day, day of week, day of month, and month of + * year. + *

+ * + *

+ * Job s and Trigger s have a name and group + * associated with them, which should uniquely identify them within a single + * {@link Scheduler}. The 'group' feature may be useful for + * creating logical groupings or categorizations of Jobs s and + * Triggerss. If you don't have need for assigning a group to a + * given Jobs of Triggers, then you can use the + * DEFAULT_GROUP constant defined on this interface. + *

+ * + *

+ * Stored Job s can also be 'manually' triggered through the use + * of the triggerJob(String jobName, String jobGroup) function. + *

+ * + *

+ * Client programs may also be interested in the 'listener' interfaces that are + * available from Quartz. The {@link JobListener} interface + * provides notifications of Job executions. The {@link TriggerListener} + * interface provides notifications of Trigger firings. The + * {@link SchedulerListener} interface provides notifications of + * Scheduler events and errors. + *

+ * + *

+ * The setup/configuration of a Scheduler instance is very + * customizable. Please consult the documentation distributed with Quartz. + *

+ * + * @see Job + * @see JobDetail + * @see Trigger + * @see JobListener + * @see TriggerListener + * @see SchedulerListener + * + * @author James House + * @author Sharada Jambula + */ +public interface Scheduler { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * A (possibly) usefull constant that can be used for specifying the group + * that Job and Trigger instances belong to. + *

+ */ + String DEFAULT_GROUP = "DEFAULT"; + + /** + *

+ * A constant Trigger group name used internally by the + * scheduler - clients should not use the value of this constant + * ("MANUAL_TRIGGER") for the name of a Trigger's group. + *

+ */ + String DEFAULT_MANUAL_TRIGGERS = "MANUAL_TRIGGER"; + + /** + *

+ * A constant Trigger group name used internally by the + * scheduler - clients should not use the value of this constant + * ("RECOVERING_JOBS") for the name of a Trigger's group. + *

+ * + * @see com.fr.third.org.quartz.JobDetail#requestsRecovery() + */ + String DEFAULT_RECOVERY_GROUP = "RECOVERING_JOBS"; + + /** + *

+ * A constant Trigger group name used internally by the + * scheduler - clients should not use the value of this constant + * ("FAILED_OVER_JOBS") for the name of a Trigger's group. + *

+ * + * @see com.fr.third.org.quartz.JobDetail#requestsRecovery() + */ + String DEFAULT_FAIL_OVER_GROUP = "FAILED_OVER_JOBS"; + + + /** + * A constant JobDataMap key that can be used to retrieve the + * name of the original Trigger from a recovery trigger's + * data map in the case of a job recovering after a failed scheduler + * instance. + * + * @see com.fr.third.org.quartz.JobDetail#requestsRecovery() + */ + String FAILED_JOB_ORIGINAL_TRIGGER_NAME = "QRTZ_FAILED_JOB_ORIG_TRIGGER_NAME"; + + /** + * A constant JobDataMap key that can be used to retrieve the + * group of the original Trigger from a recovery trigger's + * data map in the case of a job recovering after a failed scheduler + * instance. + * + * @see com.fr.third.org.quartz.JobDetail#requestsRecovery() + */ + String FAILED_JOB_ORIGINAL_TRIGGER_GROUP = "QRTZ_FAILED_JOB_ORIG_TRIGGER_GROUP"; + + /** + * A constant JobDataMap key that can be used to retrieve the + * scheduled fire time of the original Trigger from a recovery + * trigger's data map in the case of a job recovering after a failed scheduler + * instance. + * + * @see com.fr.third.org.quartz.JobDetail#requestsRecovery() + */ + String FAILED_JOB_ORIGINAL_TRIGGER_FIRETIME_IN_MILLISECONDS = "QRTZ_FAILED_JOB_ORIG_TRIGGER_FIRETIME_IN_MILLISECONDS_AS_STRING"; + + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Returns the name of the Scheduler. + *

+ */ + String getSchedulerName() throws SchedulerException; + + /** + *

+ * Returns the instance Id of the Scheduler. + *

+ */ + String getSchedulerInstanceId() throws SchedulerException; + + /** + *

+ * Returns the SchedulerContext of the Scheduler. + *

+ */ + SchedulerContext getContext() throws SchedulerException; + + /////////////////////////////////////////////////////////////////////////// + /// + /// Schedululer State Management Methods + /// + /////////////////////////////////////////////////////////////////////////// + + /** + *

+ * Starts the Scheduler's threads that fire {@link Trigger}s. + * When a scheduler is first created it is in "stand-by" mode, and will not + * fire triggers. The scheduler can also be put into stand-by mode by + * calling the standby() method. + *

+ * + *

+ * The misfire/recovery process will be started, if it is the initial call + * to this method on this scheduler instance. + *

+ * + * @throws SchedulerException + * if shutdown() has been called, or there is an + * error within the Scheduler. + * + * @see #startDelayed(int) + * @see #standby() + * @see #shutdown() + */ + void start() throws SchedulerException; + + /** + *

+ * Calls {#start()} after the indicated number of seconds. + * (This call does not block). This can be useful within applications that + * have initializers that create the scheduler immediately, before the + * resources needed by the executing jobs have been fully initialized. + *

+ * + * @throws SchedulerException + * if shutdown() has been called, or there is an + * error within the Scheduler. + * + * @see #start() + * @see #standby() + * @see #shutdown() + */ + void startDelayed(int seconds) throws SchedulerException; + + /** + * Whether the scheduler has been started. + * + *

+ * Note: This only reflects whether {@link #start()} has ever + * been called on this Scheduler, so it will return true even + * if the Scheduler is currently in standby mode or has been + * since shutdown. + *

+ * + * @see #start() + * @see #isShutdown() + * @see #isInStandbyMode() + */ + boolean isStarted() throws SchedulerException; + + /** + *

+ * Temporarily halts the Scheduler's firing of {@link Trigger}s. + *

+ * + *

+ * When start() is called (to bring the scheduler out of + * stand-by mode), trigger misfire instructions will NOT be applied + * during the execution of the start() method - any misfires + * will be detected immediately afterward (by the JobStore's + * normal process). + *

+ * + *

+ * The scheduler is not destroyed, and can be re-started at any time. + *

+ * + * @see #start() + * @see #pauseAll() + */ + void standby() throws SchedulerException; + + /** + * @deprecated replaced by better-named standby() method. + * @see #standby() + */ + void pause() throws SchedulerException; + + /** + *

+ * Reports whether the Scheduler is in stand-by mode. + *

+ * + * @see #standby() + * @see #start() + */ + boolean isInStandbyMode() throws SchedulerException; + + /** + * @deprecated + * @see #isInStandbyMode() + */ + boolean isPaused() throws SchedulerException; + + /** + *

+ * Halts the Scheduler's firing of {@link Trigger}s, + * and cleans up all resources associated with the Scheduler. Equivalent to + * shutdown(false). + *

+ * + *

+ * The scheduler cannot be re-started. + *

+ * + * @see #shutdown(boolean) + */ + void shutdown() throws SchedulerException; + + /** + *

+ * Halts the Scheduler's firing of {@link Trigger}s, + * and cleans up all resources associated with the Scheduler. + *

+ * + *

+ * The scheduler cannot be re-started. + *

+ * + * @param waitForJobsToComplete + * if true the scheduler will not allow this method + * to return until all currently executing jobs have completed. + * + * @see #shutdown + */ + void shutdown(boolean waitForJobsToComplete) + throws SchedulerException; + + /** + *

+ * Reports whether the Scheduler has been shutdown. + *

+ */ + boolean isShutdown() throws SchedulerException; + + /** + *

+ * Get a SchedulerMetaData object describiing the settings + * and capabilities of the scheduler instance. + *

+ * + *

+ * Note that the data returned is an 'instantaneous' snap-shot, and that as + * soon as it's returned, the meta data values may be different. + *

+ */ + SchedulerMetaData getMetaData() throws SchedulerException; + + /** + *

+ * Return a list of JobExecutionContext objects that + * represent all currently executing Jobs in this Scheduler instance. + *

+ * + *

+ * This method is not cluster aware. That is, it will only return Jobs + * currently executing in this Scheduler instance, not across the entire + * cluster. + *

+ * + *

+ * Note that the list returned is an 'instantaneous' snap-shot, and that as + * soon as it's returned, the true list of executing jobs may be different. + * Also please read the doc associated with JobExecutionContext- + * especially if you're using RMI. + *

+ * + * @see JobExecutionContext + */ + List getCurrentlyExecutingJobs() throws SchedulerException; + + /** + *

+ * Set the JobFactory that will be responsible for producing + * instances of Job classes. + *

+ * + *

+ * JobFactories may be of use to those wishing to have their application + * produce Job instances via some special mechanism, such as to + * give the opertunity for dependency injection. + *

+ * + * @see com.fr.third.org.quartz.spi.JobFactory + */ + void setJobFactory(JobFactory factory) throws SchedulerException; + + /////////////////////////////////////////////////////////////////////////// + /// + /// Scheduling-related Methods + /// + /////////////////////////////////////////////////////////////////////////// + + /** + *

+ * Add the given {@link com.fr.third.org.quartz.JobDetail} to the + * Scheduler, and associate the given {@link Trigger} with + * it. + *

+ * + *

+ * If the given Trigger does not reference any Job, then it + * will be set to reference the Job passed with it into this method. + *

+ * + * @throws SchedulerException + * if the Job or Trigger cannot be added to the Scheduler, or + * there is an internal Scheduler error. + */ + Date scheduleJob(JobDetail jobDetail, Trigger trigger) + throws SchedulerException; + + /** + *

+ * Schedule the given {@link com.fr.third.org.quartz.Trigger} with the + * Job identified by the Trigger's settings. + *

+ * + * @throws SchedulerException + * if the indicated Job does not exist, or the Trigger cannot be + * added to the Scheduler, or there is an internal Scheduler + * error. + */ + Date scheduleJob(Trigger trigger) throws SchedulerException; + + /** + *

+ * Remove the indicated {@link Trigger} from the scheduler. + *

+ */ + boolean unscheduleJob(String triggerName, String groupName) + throws SchedulerException; + + /** + *

+ * Remove (delete) the {@link com.fr.third.org.quartz.Trigger} with the + * given name, and store the new given one - which must be associated + * with the same job (the new trigger must have the job name & group specified) + * - however, the new trigger need not have the same name as the old trigger. + *

+ * + * @param triggerName + * The name of the Trigger to be replaced. + * @param groupName + * The group name of the Trigger to be replaced. + * @param newTrigger + * The new Trigger to be stored. + * @return null if a Trigger with the given + * name & group was not found and removed from the store, otherwise + * the first fire time of the newly scheduled trigger. + */ + Date rescheduleJob(String triggerName, + String groupName, Trigger newTrigger) throws SchedulerException; + + + /** + *

+ * Add the given Job to the Scheduler - with no associated + * Trigger. The Job will be 'dormant' until + * it is scheduled with a Trigger, or Scheduler.triggerJob() + * is called for it. + *

+ * + *

+ * The Job must by definition be 'durable', if it is not, + * SchedulerException will be thrown. + *

+ * + * @throws SchedulerException + * if there is an internal Scheduler error, or if the Job is not + * durable, or a Job with the same name already exists, and + * replace is false. + */ + void addJob(JobDetail jobDetail, boolean replace) + throws SchedulerException; + + /** + *

+ * Delete the identified Job from the Scheduler - and any + * associated Triggers. + *

+ * + * @return true if the Job was found and deleted. + * @throws SchedulerException + * if there is an internal Scheduler error. + */ + boolean deleteJob(String jobName, String groupName) + throws SchedulerException; + + /** + *

+ * Trigger the identified {@link com.fr.third.org.quartz.JobDetail} + * (execute it now) - the generated trigger will be non-volatile. + *

+ */ + void triggerJob(String jobName, String groupName) + throws SchedulerException; + + /** + *

+ * Trigger the identified {@link com.fr.third.org.quartz.JobDetail} + * (execute it now) - the generated trigger will be volatile. + *

+ */ + void triggerJobWithVolatileTrigger(String jobName, String groupName) + throws SchedulerException; + + /** + *

+ * Trigger the identified {@link com.fr.third.org.quartz.JobDetail} + * (execute it now) - the generated trigger will be non-volatile. + *

+ * + * @param jobName the name of the Job to trigger + * @param groupName the group name of the Job to trigger + * @param data the (possibly null) JobDataMap to be + * associated with the trigger that fires the job immediately. + */ + void triggerJob(String jobName, String groupName, JobDataMap data) + throws SchedulerException; + + /** + *

+ * Trigger the identified {@link com.fr.third.org.quartz.JobDetail} + * (execute it now) - the generated trigger will be volatile. + *

+ * + * @param jobName the name of the Job to trigger + * @param groupName the group name of the Job to trigger + * @param data the (possibly null) JobDataMap to be + * associated with the trigger that fires the job immediately. + */ + void triggerJobWithVolatileTrigger(String jobName, String groupName, JobDataMap data) + throws SchedulerException; + + /** + *

+ * Pause the {@link com.fr.third.org.quartz.JobDetail} with the given + * name - by pausing all of its current Triggers. + *

+ * + * @see #resumeJob(String, String) + */ + void pauseJob(String jobName, String groupName) + throws SchedulerException; + + /** + *

+ * Pause all of the {@link com.fr.third.org.quartz.JobDetail}s in the + * given group - by pausing all of their Triggers. + *

+ * + *

+ * The Scheduler will "remember" that the group is paused, and impose the + * pause on any new jobs that are added to the group while the group is + * paused. + *

+ * + * @see #resumeJobGroup(String) + */ + void pauseJobGroup(String groupName) throws SchedulerException; + + /** + *

+ * Pause the {@link Trigger} with the given name. + *

+ * + * @see #resumeTrigger(String, String) + */ + void pauseTrigger(String triggerName, String groupName) + throws SchedulerException; + + /** + *

+ * Pause all of the {@link Trigger}s in the given group. + *

+ * + *

+ * The Scheduler will "remember" that the group is paused, and impose the + * pause on any new triggers that are added to the group while the group is + * paused. + *

+ * + * @see #resumeTriggerGroup(String) + */ + void pauseTriggerGroup(String groupName) throws SchedulerException; + + /** + *

+ * Resume (un-pause) the {@link com.fr.third.org.quartz.JobDetail} with + * the given name. + *

+ * + *

+ * If any of the Job'sTrigger s missed one + * or more fire-times, then the Trigger's misfire + * instruction will be applied. + *

+ * + * @see #pauseJob(String, String) + */ + void resumeJob(String jobName, String groupName) + throws SchedulerException; + + /** + *

+ * Resume (un-pause) all of the {@link com.fr.third.org.quartz.JobDetail}s + * in the given group. + *

+ * + *

+ * If any of the Job s had Trigger s that + * missed one or more fire-times, then the Trigger's + * misfire instruction will be applied. + *

+ * + * @see #pauseJobGroup(String) + */ + void resumeJobGroup(String groupName) throws SchedulerException; + + /** + *

+ * Resume (un-pause) the {@link Trigger} with the given + * name. + *

+ * + *

+ * If the Trigger missed one or more fire-times, then the + * Trigger's misfire instruction will be applied. + *

+ * + * @see #pauseTrigger(String, String) + */ + void resumeTrigger(String triggerName, String groupName) + throws SchedulerException; + + /** + *

+ * Resume (un-pause) all of the {@link Trigger}s in the + * given group. + *

+ * + *

+ * If any Trigger missed one or more fire-times, then the + * Trigger's misfire instruction will be applied. + *

+ * + * @see #pauseTriggerGroup(String) + */ + void resumeTriggerGroup(String groupName) throws SchedulerException; + + /** + *

+ * Pause all triggers - similar to calling pauseTriggerGroup(group) + * on every group, however, after using this method resumeAll() + * must be called to clear the scheduler's state of 'remembering' that all + * new triggers will be paused as they are added. + *

+ * + *

+ * When resumeAll() is called (to un-pause), trigger misfire + * instructions WILL be applied. + *

+ * + * @see #resumeAll() + * @see #pauseTriggerGroup(String) + * @see #standby() + */ + void pauseAll() throws SchedulerException; + + /** + *

+ * Resume (un-pause) all triggers - similar to calling + * resumeTriggerGroup(group) on every group. + *

+ * + *

+ * If any Trigger missed one or more fire-times, then the + * Trigger's misfire instruction will be applied. + *

+ * + * @see #pauseAll() + */ + void resumeAll() throws SchedulerException; + + /** + *

+ * Get the names of all known {@link com.fr.third.org.quartz.JobDetail} + * groups. + *

+ */ + String[] getJobGroupNames() throws SchedulerException; + + /** + *

+ * Get the names of all the {@link com.fr.third.org.quartz.JobDetail}s + * in the given group. + *

+ */ + String[] getJobNames(String groupName) throws SchedulerException; + + /** + *

+ * Get all {@link Trigger} s that are associated with the + * identified {@link com.fr.third.org.quartz.JobDetail}. + *

+ */ + Trigger[] getTriggersOfJob(String jobName, String groupName) + throws SchedulerException; + + /** + *

+ * Get the names of all known {@link Trigger} groups. + *

+ */ + String[] getTriggerGroupNames() throws SchedulerException; + + /** + *

+ * Get the names of all the {@link Trigger}s in the given + * group. + *

+ */ + String[] getTriggerNames(String groupName) throws SchedulerException; + + /** + *

+ * Get the names of all {@link Trigger} groups that are paused. + *

+ */ + Set getPausedTriggerGroups() throws SchedulerException; + + /** + *

+ * Get the {@link JobDetail} for the Job + * instance with the given name and group. + *

+ */ + JobDetail getJobDetail(String jobName, String jobGroup) + throws SchedulerException; + + /** + *

+ * Get the {@link Trigger} instance with the given name and + * group. + *

+ */ + Trigger getTrigger(String triggerName, String triggerGroup) + throws SchedulerException; + + /** + *

+ * Get the current state of the identified {@link Trigger}. + *

+ * + * @see Trigger#STATE_NORMAL + * @see Trigger#STATE_PAUSED + * @see Trigger#STATE_COMPLETE + * @see Trigger#STATE_ERROR + * @see Trigger#STATE_BLOCKED + * @see Trigger#STATE_NONE + */ + int getTriggerState(String triggerName, String triggerGroup) + throws SchedulerException; + + /** + *

+ * Add (register) the given Calendar to the Scheduler. + *

+ * + * @param updateTriggers whether or not to update existing triggers that + * referenced the already existing calendar so that they are 'correct' + * based on the new trigger. + * + * + * @throws SchedulerException + * if there is an internal Scheduler error, or a Calendar with + * the same name already exists, and replace is + * false. + */ + void addCalendar(String calName, Calendar calendar, boolean replace, boolean updateTriggers) + throws SchedulerException; + + /** + *

+ * Delete the identified Calendar from the Scheduler. + *

+ * + * @return true if the Calendar was found and deleted. + * @throws SchedulerException + * if there is an internal Scheduler error. + */ + boolean deleteCalendar(String calName) throws SchedulerException; + + /** + *

+ * Get the {@link Calendar} instance with the given name. + *

+ */ + Calendar getCalendar(String calName) throws SchedulerException; + + /** + *

+ * Get the names of all registered {@link Calendar}s. + *

+ */ + String[] getCalendarNames() throws SchedulerException; + + /** + *

+ * Request the interruption, within this Scheduler instance, of all + * currently executing instances of the identified Job, which + * must be an implementor of the InterruptableJob interface. + *

+ * + *

+ * If more than one instance of the identified job is currently executing, + * the InterruptableJob#interrupt() method will be called on + * each instance. However, there is a limitation that in the case that + * interrupt() on one instances throws an exception, all + * remaining instances (that have not yet been interrupted) will not have + * their interrupt() method called. + *

+ * + *

+ * If you wish to interrupt a specific instance of a job (when more than + * one is executing) you can do so by calling + * {@link #getCurrentlyExecutingJobs()} to obtain a handle + * to the job instance, and then invoke interrupt() on it + * yourself. + *

+ * + *

+ * This method is not cluster aware. That is, it will only interrupt + * instances of the identified InterruptableJob currently executing in this + * Scheduler instance, not across the entire cluster. + *

+ * + * @param jobName + * @param groupName + * @return true is at least one instance of the identified job was found + * and interrupted. + * @throws UnableToInterruptJobException if the job does not implement + * InterruptableJob, or there is an exception while + * interrupting the job. + * @see InterruptableJob#interrupt() + * @see #getCurrentlyExecutingJobs() + */ + boolean interrupt(String jobName, String groupName) throws UnableToInterruptJobException; + + /////////////////////////////////////////////////////////////////////////// + /// + /// Listener-related Methods + /// + /////////////////////////////////////////////////////////////////////////// + + /** + *

+ * Add the given {@link JobListener} to the Scheduler's + * global list. + *

+ * + *

+ * Listeners in the 'global' list receive notification of execution events + * for ALL {@link com.fr.third.org.quartz.JobDetail}s. + *

+ */ + void addGlobalJobListener(JobListener jobListener) + throws SchedulerException; + + /** + *

+ * Add the given {@link JobListener} to the Scheduler's + * list, of registered JobListeners. + */ + void addJobListener(JobListener jobListener) + throws SchedulerException; + + /** + *

+ * Remove the given {@link JobListener} from the Scheduler's + * list of global listeners. + *

+ * + * @return true if the identifed listener was found in the list, and + * removed. + * + * @deprecated Use {@link #removeGlobalJobListener(String)} + */ + boolean removeGlobalJobListener(JobListener jobListener) + throws SchedulerException; + + /** + *

+ * Remove the identifed {@link JobListener} from the Scheduler's + * list of global listeners. + *

+ * + * @return true if the identifed listener was found in the list, and + * removed. + */ + boolean removeGlobalJobListener(String name) + throws SchedulerException; + + /** + *

+ * Remove the identifed {@link JobListener} from the Scheduler's + * list of registered listeners. + *

+ * + * @return true if the identifed listener was found in the list, and + * removed. + */ + boolean removeJobListener(String name) throws SchedulerException; + + /** + *

+ * Get a List containing all of the {@link JobListener} s in + * the Scheduler'sglobal list. + *

+ */ + List getGlobalJobListeners() throws SchedulerException; + + /** + *

+ * Get a Set containing the names of all the non-global{@link JobListener} + * s registered with the Scheduler. + *

+ */ + Set getJobListenerNames() throws SchedulerException; + + /** + *

+ * Get the global{@link JobListener} that has + * the given name. + *

+ */ + JobListener getGlobalJobListener(String name) throws SchedulerException; + + /** + *

+ * Get the non-global{@link JobListener} that has + * the given name. + *

+ */ + JobListener getJobListener(String name) throws SchedulerException; + + /** + *

+ * Add the given {@link TriggerListener} to the Scheduler's + * global list. + *

+ * + *

+ * Listeners in the 'global' list receive notification of execution events + * for ALL {@link Trigger}s. + *

+ */ + void addGlobalTriggerListener(TriggerListener triggerListener) + throws SchedulerException; + + /** + *

+ * Add the given {@link TriggerListener} to the Scheduler's + * list, of registered TriggerListeners. + */ + void addTriggerListener(TriggerListener triggerListener) + throws SchedulerException; + + /** + *

+ * Remove the given {@link TriggerListener} from the Scheduler's + * list of global listeners. + *

+ * + * @return true if the identifed listener was found in the list, and + * removed. + * + * @deprecated Use {@link #removeGlobalTriggerListener(String)} + */ + boolean removeGlobalTriggerListener(TriggerListener triggerListener) + throws SchedulerException; + + /** + *

+ * Remove the identifed {@link TriggerListener} from the Scheduler's + * list of global listeners. + *

+ * + * @return true if the identifed listener was found in the list, and + * removed. + */ + boolean removeGlobalTriggerListener(String name) + throws SchedulerException; + + + /** + *

+ * Remove the identifed {@link TriggerListener} from the + * Scheduler's list of registered listeners. + *

+ * + * @return true if the identifed listener was found in the list, and + * removed. + */ + boolean removeTriggerListener(String name) throws SchedulerException; + + /** + *

+ * Get a List containing all of the {@link TriggerListener} + * s in the Scheduler'sglobal list. + *

+ */ + List getGlobalTriggerListeners() throws SchedulerException; + + /** + *

+ * Get a Set containing the names of all the non-global{@link TriggerListener} + * s registered with the Scheduler. + *

+ */ + Set getTriggerListenerNames() throws SchedulerException; + + /** + *

+ * Get the global{@link TriggerListener} that + * has the given name. + *

+ */ + TriggerListener getGlobalTriggerListener(String name) + throws SchedulerException; + + /** + *

+ * Get the non-global{@link TriggerListener} that + * has the given name. + *

+ */ + TriggerListener getTriggerListener(String name) + throws SchedulerException; + + /** + *

+ * Register the given {@link SchedulerListener} with the + * Scheduler. + *

+ */ + void addSchedulerListener(SchedulerListener schedulerListener) + throws SchedulerException; + + /** + *

+ * Remove the given {@link SchedulerListener} from the + * Scheduler. + *

+ * + * @return true if the identifed listener was found in the list, and + * removed. + */ + boolean removeSchedulerListener(SchedulerListener schedulerListener) + throws SchedulerException; + + /** + *

+ * Get a List containing all of the {@link SchedulerListener} + * s registered with the Scheduler. + *

+ */ + List getSchedulerListeners() throws SchedulerException; + + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerConfigException.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerConfigException.java new file mode 100644 index 000000000..a78eb0a30 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerConfigException.java @@ -0,0 +1,63 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +/** + *

+ * An exception that is thrown to indicate that there is a misconfiguration of + * the SchedulerFactory- or one of the components it + * configures. + *

+ * + * @author James House + */ +public class SchedulerConfigException extends SchedulerException { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Create a JobPersistenceException with the given message. + *

+ */ + public SchedulerConfigException(String msg) { + super(msg, ERR_BAD_CONFIGURATION); + } + + /** + *

+ * Create a JobPersistenceException with the given message + * and cause. + *

+ */ + public SchedulerConfigException(String msg, Throwable cause) { + super(msg, cause); + setErrorCode(ERR_BAD_CONFIGURATION); + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerContext.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerContext.java new file mode 100644 index 000000000..bfd4c1173 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerContext.java @@ -0,0 +1,62 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +import java.io.Serializable; +import java.util.Map; + +import com.fr.third.org.quartz.utils.StringKeyDirtyFlagMap; + +/** + *

+ * Holds context/environment data that can be made available to Jobs as they + * are executed. This feature is much like the ServletContext feature when + * working with J2EE servlets. + *

+ * + *

+ * Future versions of Quartz may make distinctions on how it propogates + * data in SchedulerContext between instances of proxies to a + * single scheduler instance - i.e. if Quartz is being used via RMI. + *

+ * + * @see Scheduler#getContext + * + * @author James House + */ +public class SchedulerContext extends StringKeyDirtyFlagMap implements Serializable { + /** + * Create an empty SchedulerContext. + */ + public SchedulerContext() { + super(15); + } + + /** + * Create a SchedulerContext with the given data. + */ + public SchedulerContext(Map map) { + this(); + + putAll(map); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerException.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerException.java new file mode 100644 index 000000000..604d1b717 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerException.java @@ -0,0 +1,335 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +import java.io.PrintStream; +import java.io.PrintWriter; + +import com.fr.third.org.quartz.utils.ExceptionHelper; + +/** + *

+ * Base class for exceptions thrown by the Quartz {@link Scheduler}. + *

+ * + *

+ * SchedulerException s may contain a reference to another + * Exception, which was the underlying cause of the SchedulerException. + *

+ * + * @author James House + */ +public class SchedulerException extends Exception { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public static final int ERR_UNSPECIFIED = 0; + + public static final int ERR_BAD_CONFIGURATION = 50; + + public static final int ERR_TIME_BROKER_FAILURE = 70; + + public static final int ERR_CLIENT_ERROR = 100; + + public static final int ERR_COMMUNICATION_FAILURE = 200; + + public static final int ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION = 210; + + public static final int ERR_PERSISTENCE = 400; + + public static final int ERR_PERSISTENCE_JOB_DOES_NOT_EXIST = 410; + + public static final int ERR_PERSISTENCE_CALENDAR_DOES_NOT_EXIST = 420; + + public static final int ERR_PERSISTENCE_TRIGGER_DOES_NOT_EXIST = 430; + + public static final int ERR_PERSISTENCE_CRITICAL_FAILURE = 499; + + public static final int ERR_THREAD_POOL = 500; + + public static final int ERR_THREAD_POOL_EXHAUSTED = 510; + + public static final int ERR_THREAD_POOL_CRITICAL_FAILURE = 599; + + public static final int ERR_JOB_LISTENER = 600; + + public static final int ERR_JOB_LISTENER_NOT_FOUND = 610; + + public static final int ERR_TRIGGER_LISTENER = 700; + + public static final int ERR_TRIGGER_LISTENER_NOT_FOUND = 710; + + public static final int ERR_JOB_EXECUTION_THREW_EXCEPTION = 800; + + public static final int ERR_TRIGGER_THREW_EXCEPTION = 850; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private Throwable cause; + + private int errorCode = ERR_UNSPECIFIED; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public SchedulerException() { + super(); + } + + public SchedulerException(String msg) { + super(msg); + } + + public SchedulerException(String msg, int errorCode) { + super(msg); + setErrorCode(errorCode); + } + + public SchedulerException(Throwable cause) { + super(cause.toString()); + setCause(cause); + } + + public SchedulerException(String msg, Throwable cause) { + super(msg); + setCause(cause); + } + + public SchedulerException(String msg, Throwable cause, int errorCode) { + super(msg); + setCause(cause); + setErrorCode(errorCode); + } + + private void setCause(Throwable cause) { + if (ExceptionHelper.supportsNestedThrowable()) { + ExceptionHelper.setCause(this, cause); + } else { + this.cause = cause; + } + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Return the exception that is the underlying cause of this exception. + *

+ * + *

+ * This may be used to find more detail about the cause of the error. + *

+ * + * @return the underlying exception, or null if there is not + * one. + */ + public Throwable getUnderlyingException() { + return (ExceptionHelper.supportsNestedThrowable()) ? + ExceptionHelper.getCause(this) : cause; + } + + /** + *

+ * Get the error code associated with this exception. + *

+ * + *

+ * This may be used to find more detail about the cause of the error. + *

+ * + * @return one of the ERR_XXX constants defined in this class. + */ + public int getErrorCode() { + return errorCode; + } + + /** + *

+ * Get the error code associated with this exception. + *

+ * + *

+ * This may be used to provide more detail about the cause of the error. + *

+ * + * @param errorCode + * one of the ERR_XXX constants defined in this class. + */ + public void setErrorCode(int errorCode) { + this.errorCode = errorCode; + } + + /** + *

+ * Determine if the specified error code is in the 'ERR_PERSISTENCE' + * category of errors. + *

+ */ + public boolean isPersistenceError() { + return (errorCode >= ERR_PERSISTENCE && errorCode <= ERR_PERSISTENCE + 99); + } + + /** + *

+ * Determine if the specified error code is in the 'ERR_THREAD_POOL' + * category of errors. + *

+ */ + public boolean isThreadPoolError() { + return (errorCode >= ERR_THREAD_POOL && errorCode <= ERR_THREAD_POOL + 99); + } + + /** + *

+ * Determine if the specified error code is in the 'ERR_JOB_LISTENER' + * category of errors. + *

+ */ + public boolean isJobListenerError() { + return (errorCode >= ERR_JOB_LISTENER && errorCode <= ERR_JOB_LISTENER + 99); + } + + /** + *

+ * Determine if the specified error code is in the 'ERR_TRIGGER_LISTENER' + * category of errors. + *

+ */ + public boolean isTriggerListenerError() { + return (errorCode >= ERR_TRIGGER_LISTENER && errorCode <= ERR_TRIGGER_LISTENER + 99); + } + + /** + *

+ * Determine if the specified error code is in the 'ERR_CLIENT_ERROR' + * category of errors. + *

+ */ + public boolean isClientError() { + return (errorCode >= ERR_CLIENT_ERROR && errorCode <= ERR_CLIENT_ERROR + 99); + } + + /** + *

+ * Determine if the specified error code is in the 'ERR_CLIENT_ERROR' + * category of errors. + *

+ */ + public boolean isConfigurationError() { + return (errorCode >= ERR_BAD_CONFIGURATION && errorCode <= ERR_BAD_CONFIGURATION + 49); + } + + public String toString() { + Throwable cause = getUnderlyingException(); + if (cause == null || cause == this) { + return super.toString(); + } else { + return super.toString() + " [See nested exception: " + cause + "]"; + } + } + + /** + *

+ * Print a stack trace to the standard error stream. + *

+ * + *

+ * This overridden version will print the nested stack trace if available, + * otherwise it prints only this exception's stack. + *

+ */ + public void printStackTrace() { + printStackTrace(System.err); + } + + /** + *

+ * Print a stack trace to the specified stream. + *

+ * + *

+ * This overridden version will print the nested stack trace if available, + * otherwise it prints only this exception's stack. + *

+ * + * @param out + * the stream to which the stack traces will be printed. + */ + public void printStackTrace(PrintStream out) { + super.printStackTrace(out); + + if (cause != null) { + synchronized (out) { + out.println("* Nested Exception (Underlying Cause) ---------------"); + cause.printStackTrace(out); + } + } + } + + /** + *

+ * Print a stack trace to the specified writer. + *

+ * + *

+ * This overridden version will print the nested stack trace if available, + * otherwise it prints this exception's stack. + *

+ * + * @param out + * the writer to which the stack traces will be printed. + */ + public void printStackTrace(PrintWriter out) { + super.printStackTrace(out); + + if (cause != null) { + synchronized (out) { + out.println("* Nested Exception (Underlying Cause) ---------------"); + cause.printStackTrace(out); + } + } + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerFactory.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerFactory.java new file mode 100644 index 000000000..08af7a323 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerFactory.java @@ -0,0 +1,72 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +import java.util.Collection; + +/** + *

+ * Provides a mechanism for obtaining client-usable handles to Scheduler + * instances. + *

+ * + * @see Scheduler + * @see com.fr.third.org.quartz.impl.StdSchedulerFactory + * + * @author James House + */ +public interface SchedulerFactory { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Returns a client-usable handle to a Scheduler. + *

+ * + * @throws SchedulerException + * if there is a problem with the underlying Scheduler. + */ + Scheduler getScheduler() throws SchedulerException; + + /** + *

+ * Returns a handle to the Scheduler with the given name, if it exists. + *

+ */ + Scheduler getScheduler(String schedName) throws SchedulerException; + + /** + *

+ * Returns handles to all known Schedulers (made by any SchedulerFactory + * within this jvm.). + *

+ */ + Collection getAllSchedulers() throws SchedulerException; + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerListener.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerListener.java new file mode 100644 index 000000000..6eced3ac7 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerListener.java @@ -0,0 +1,148 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +/** + *

+ * The interface to be implemented by classes that want to be informed of major + * {@link Scheduler} events. + *

+ * + * @see Scheduler + * @see JobListener + * @see TriggerListener + * + * @author James House + */ +public interface SchedulerListener { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Called by the {@link Scheduler} when a {@link com.fr.third.org.quartz.JobDetail} + * is scheduled. + *

+ */ + void jobScheduled(Trigger trigger); + + /** + *

+ * Called by the {@link Scheduler} when a {@link com.fr.third.org.quartz.JobDetail} + * is unscheduled. + *

+ */ + void jobUnscheduled(String triggerName, String triggerGroup); + + /** + *

+ * Called by the {@link Scheduler} when a {@link Trigger} + * has reached the condition in which it will never fire again. + *

+ */ + void triggerFinalized(Trigger trigger); + + /** + *

+ * Called by the {@link Scheduler} when a {@link Trigger} + * or group of {@link Trigger}s has been paused. + *

+ * + *

+ * If a group was paused, then the triggerName parameter + * will be null. + *

+ */ + void triggersPaused(String triggerName, String triggerGroup); + + /** + *

+ * Called by the {@link Scheduler} when a {@link Trigger} + * or group of {@link Trigger}s has been un-paused. + *

+ * + *

+ * If a group was resumed, then the triggerName parameter + * will be null. + *

+ */ + void triggersResumed(String triggerName, String triggerGroup); + + /** + *

+ * Called by the {@link Scheduler} when a {@link com.fr.third.org.quartz.JobDetail} + * or group of {@link com.fr.third.org.quartz.JobDetail}s has been + * paused. + *

+ * + *

+ * If a group was paused, then the jobName parameter will be + * null. If all jobs were paused, then both parameters will be null. + *

+ */ + void jobsPaused(String jobName, String jobGroup); + + /** + *

+ * Called by the {@link Scheduler} when a {@link com.fr.third.org.quartz.JobDetail} + * or group of {@link com.fr.third.org.quartz.JobDetail}s has been + * un-paused. + *

+ * + *

+ * If a group was resumed, then the jobName parameter will + * be null. If all jobs were paused, then both parameters will be null. + *

+ */ + void jobsResumed(String jobName, String jobGroup); + + /** + *

+ * Called by the {@link Scheduler} when a serious error has + * occured within the scheduler - such as repeated failures in the JobStore, + * or the inability to instantiate a Job instance when its + * Trigger has fired. + *

+ * + *

+ * The getErrorCode() method of the given SchedulerException + * can be used to determine more specific information about the type of + * error that was encountered. + *

+ */ + void schedulerError(String msg, SchedulerException cause); + + /** + *

+ * Called by the {@link Scheduler} to inform the listener + * that it has shutdown. + *

+ */ + void schedulerShutdown(); + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerMetaData.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerMetaData.java new file mode 100644 index 000000000..bdc8ecf6d --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SchedulerMetaData.java @@ -0,0 +1,350 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +import java.util.Date; + +/** + *

+ * Describes the settings and capabilities of a given {@link Scheduler} + * instance. + *

+ * + * @author James House + */ +public class SchedulerMetaData implements java.io.Serializable { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private String schedName; + + private String schedInst; + + private Class schedClass; + + private boolean isRemote; + + private boolean started; + + private boolean isInStandbyMode; + + private boolean shutdown; + + private Date startTime; + + private int numJobsExec; + + private Class jsClass; + + private boolean jsPersistent; + + private Class tpClass; + + private int tpSize; + + private String version; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public SchedulerMetaData(String schedName, String schedInst, + Class schedClass, boolean isRemote, boolean started, + boolean isInStandbyMode, boolean shutdown, Date startTime, int numJobsExec, + Class jsClass, boolean jsPersistent, Class tpClass, int tpSize, + String version) { + this.schedName = schedName; + this.schedInst = schedInst; + this.schedClass = schedClass; + this.isRemote = isRemote; + this.started = started; + this.isInStandbyMode = isInStandbyMode; + this.shutdown = shutdown; + this.startTime = startTime; + this.numJobsExec = numJobsExec; + this.jsClass = jsClass; + this.jsPersistent = jsPersistent; + this.tpClass = tpClass; + this.tpSize = tpSize; + this.version = version; + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Returns the name of the Scheduler. + *

+ */ + public String getSchedulerName() { + return schedName; + } + + /** + *

+ * Returns the instance Id of the Scheduler. + *

+ */ + public String getSchedulerInstanceId() { + return schedInst; + } + + /** + *

+ * Returns the class-name of the Scheduler instance. + *

+ */ + public Class getSchedulerClass() { + return schedClass; + } + + /** + *

+ * Returns the Date at which the Scheduler started running. + *

+ * + * @return null if the scheduler has not been started. + */ + public Date runningSince() { + return startTime; + } + + /** + *

+ * Returns the number of jobs executed since the Scheduler + * started.. + *

+ */ + public int numJobsExecuted() { + return numJobsExec; + } + + /** + *

+ * Returns whether the Scheduler is being used remotely (via + * RMI). + *

+ */ + public boolean isSchedulerRemote() { + return isRemote; + } + + /** + *

+ * Returns whether the scheduler has been started. + *

+ * + *

+ * Note: isStarted() may return true even if + * isInStandbyMode() returns true. + *

+ */ + public boolean isStarted() { + return started; + } + + /** + * Reports whether the Scheduler is in standby mode. + */ + public boolean isInStandbyMode() { + return isInStandbyMode; + } + + /** + * Reports whether the Scheduler is paused. + * + * @deprecated Please use {@link #isInStandbyMode()}. + * + * @see #isInStandbyMode() + */ + public boolean isPaused() { + return isInStandbyMode(); + } + + /** + *

+ * Reports whether the Scheduler has been shutdown. + *

+ */ + public boolean isShutdown() { + return shutdown; + } + + /** + *

+ * Returns the class-name of the JobStore instance that is + * being used by the Scheduler. + *

+ */ + public Class getJobStoreClass() { + return jsClass; + } + + /** + *

+ * Returns whether or not the Scheduler'sJobStore + * instance supports persistence. + *

+ */ + public boolean jobStoreSupportsPersistence() { + return jsPersistent; + } + + /** + *

+ * Returns the class-name of the ThreadPool instance that is + * being used by the Scheduler. + *

+ */ + public Class getThreadPoolClass() { + return tpClass; + } + + /** + *

+ * Returns the number of threads currently in the Scheduler's + * ThreadPool. + *

+ */ + public int getThreadPoolSize() { + return tpSize; + } + + /** + *

+ * Returns the version of Quartz that is running. + *

+ */ + public String getVersion() { + return version; + } + + /** + *

+ * Return a simple string representation of this object. + *

+ */ + public String toString() { + try { + return getSummary(); + } catch (SchedulerException se) { + return "SchedulerMetaData: undeterminable."; + } + } + + /** + *

+ * Returns a formatted (human readable) String describing all the Scheduler's + * meta-data values. + *

+ * + *

+ * The format of the String looks something like this: + * + *

+     * 
+     * 
+     *  Quartz Scheduler 'SchedulerName' with instanceId 'SchedulerInstanceId' Scheduler class: 'com.fr.third.org.quartz.impl.StdScheduler' - running locally. Running since: '11:33am on Jul 19, 2002' Not currently paused. Number of Triggers fired: '123' Using thread pool 'com.fr.third.org.quartz.simpl.SimpleThreadPool' - with '8' threads Using job-store 'com.fr.third.org.quartz.impl.JDBCJobStore' - which supports persistence.
+     * 
+ * + *

+ */ + public String getSummary() throws SchedulerException { + StringBuffer str = new StringBuffer("Quartz Scheduler (v"); + str.append(getVersion()); + str.append(") '"); + + str.append(getSchedulerName()); + str.append("' with instanceId '"); + str.append(getSchedulerInstanceId()); + str.append("'\n"); + + str.append(" Scheduler class: '"); + str.append(getSchedulerClass().getName()); + str.append("'"); + if (isSchedulerRemote()) { + str.append(" - access via RMI."); + } else { + str.append(" - running locally."); + } + str.append("\n"); + + if (!isShutdown()) { + if (runningSince() != null) { + str.append(" Running since: "); + str.append(runningSince()); + } else { + str.append("NOT STARTED."); + } + str.append("\n"); + + if (isInStandbyMode()) { + str.append(" Currently in standby mode."); + } else { + str.append(" Not currently in standby mode."); + } + } else { + str.append(" Scheduler has been SHUTDOWN."); + } + str.append("\n"); + + str.append(" Number of jobs executed: "); + str.append(numJobsExecuted()); + str.append("\n"); + + str.append(" Using thread pool '"); + str.append(getThreadPoolClass().getName()); + str.append("' - with "); + str.append(getThreadPoolSize()); + str.append(" threads."); + str.append("\n"); + + str.append(" Using job-store '"); + str.append(getJobStoreClass().getName()); + str.append("' - which "); + if (jobStoreSupportsPersistence()) { + str.append("supports persistence."); + } else { + str.append("does not support persistence."); + } + str.append("\n"); + + return str.toString(); + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SimpleTrigger.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SimpleTrigger.java new file mode 100644 index 000000000..6daa37523 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/SimpleTrigger.java @@ -0,0 +1,955 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +import java.util.Date; + + +/** + *

+ * A concrete {@link Trigger} that is used to fire a {@link com.fr.third.org.quartz.JobDetail} + * at a given moment in time, and optionally repeated at a specified interval. + *

+ * + * @see Trigger + * @see CronTrigger + * @see TriggerUtils + * + * @author James House + * @author contributions by Lieven Govaerts of Ebitec Nv, Belgium. + */ +public class SimpleTrigger extends Trigger { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Required for serialization support. Introduced in Quartz 1.6.1 to + * maintain compatibility after the introduction of hasAdditionalProperties + * method. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = -3735980074222850397L; + + /** + *

+ * Instructs the {@link Scheduler} that upon a mis-fire + * situation, the {@link SimpleTrigger} wants to be fired + * now by Scheduler. + *

+ * + *

+ * NOTE: This instruction should typically only be used for + * 'one-shot' (non-repeating) Triggers. If it is used on a trigger with a + * repeat count > 0 then it is equivalent to the instruction {@link #MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT} + * . + *

+ */ + public static final int MISFIRE_INSTRUCTION_FIRE_NOW = 1; + + /** + *

+ * Instructs the {@link Scheduler} that upon a mis-fire + * situation, the {@link SimpleTrigger} wants to be + * re-scheduled to 'now' (even if the associated {@link Calendar} + * excludes 'now') with the repeat count left as-is. This does obey the + * Trigger end-time however, so if 'now' is after the + * end-time the Trigger will not fire again. + *

+ * + *

+ * NOTE: Use of this instruction causes the trigger to 'forget' + * the start-time and repeat-count that it was originally setup with (this + * is only an issue if you for some reason wanted to be able to tell what + * the original values were at some later time). + *

+ */ + public static final int MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT = 2; + + /** + *

+ * Instructs the {@link Scheduler} that upon a mis-fire + * situation, the {@link SimpleTrigger} wants to be + * re-scheduled to 'now' (even if the associated {@link Calendar} + * excludes 'now') with the repeat count set to what it would be, if it had + * not missed any firings. This does obey the Trigger end-time + * however, so if 'now' is after the end-time the Trigger will + * not fire again. + *

+ * + *

+ * NOTE: Use of this instruction causes the trigger to 'forget' + * the start-time and repeat-count that it was originally setup with. + * Instead, the repeat count on the trigger will be changed to whatever + * the remaining repeat count is (this is only an issue if you for some + * reason wanted to be able to tell what the original values were at some + * later time). + *

+ * + *

+ * NOTE: This instruction could cause the Trigger + * to go to the 'COMPLETE' state after firing 'now', if all the + * repeat-fire-times where missed. + *

+ */ + public static final int MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT = 3; + + /** + *

+ * Instructs the {@link Scheduler} that upon a mis-fire + * situation, the {@link SimpleTrigger} wants to be + * re-scheduled to the next scheduled time after 'now' - taking into + * account any associated {@link Calendar}, and with the + * repeat count set to what it would be, if it had not missed any firings. + *

+ * + *

+ * NOTE/WARNING: This instruction could cause the Trigger + * to go directly to the 'COMPLETE' state if all fire-times where missed. + *

+ */ + public static final int MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT = 4; + + /** + *

+ * Instructs the {@link Scheduler} that upon a mis-fire + * situation, the {@link SimpleTrigger} wants to be + * re-scheduled to the next scheduled time after 'now' - taking into + * account any associated {@link Calendar}, and with the + * repeat count left unchanged. + *

+ * + *

+ * NOTE/WARNING: This instruction could cause the Trigger + * to go directly to the 'COMPLETE' state if the end-time of the trigger + * has arrived. + *

+ */ + public static final int MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT = 5; + + /** + *

+ * Used to indicate the 'repeat count' of the trigger is indefinite. Or in + * other words, the trigger should repeat continually until the trigger's + * ending timestamp. + *

+ */ + public static final int REPEAT_INDEFINITELY = -1; + + private static final int YEAR_TO_GIVEUP_SCHEDULING_AT = 2299; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private Date startTime = null; + + private Date endTime = null; + + private Date nextFireTime = null; + + private Date previousFireTime = null; + + private int repeatCount = 0; + + private long repeatInterval = 0; + + private int timesTriggered = 0; + + private boolean complete = false; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Create a SimpleTrigger with no settings. + *

+ */ + public SimpleTrigger() { + super(); + } + + /** + *

+ * Create a SimpleTrigger that will occur immediately, and + * not repeat. + *

+ */ + public SimpleTrigger(String name, String group) { + this(name, group, new Date(), null, 0, 0); + } + + /** + *

+ * Create a SimpleTrigger that will occur immediately, and + * repeat at the the given interval the given number of times. + *

+ */ + public SimpleTrigger(String name, String group, int repeatCount, + long repeatInterval) { + this(name, group, new Date(), null, repeatCount, repeatInterval); + } + + /** + *

+ * Create a SimpleTrigger that will occur at the given time, + * and not repeat. + *

+ */ + public SimpleTrigger(String name, String group, Date startTime) { + this(name, group, startTime, null, 0, 0); + } + + /** + *

+ * Create a SimpleTrigger that will occur at the given time, + * and repeat at the the given interval the given number of times, or until + * the given end time. + *

+ * + * @param startTime + * A Date set to the time for the Trigger + * to fire. + * @param endTime + * A Date set to the time for the Trigger + * to quit repeat firing. + * @param repeatCount + * The number of times for the Trigger to repeat + * firing, use {@link #REPEAT_INDEFINITELY} for unlimited times. + * @param repeatInterval + * The number of milliseconds to pause between the repeat firing. + */ + public SimpleTrigger(String name, String group, Date startTime, + Date endTime, int repeatCount, long repeatInterval) { + super(name, group); + + setStartTime(startTime); + setEndTime(endTime); + setRepeatCount(repeatCount); + setRepeatInterval(repeatInterval); + } + + /** + *

+ * Create a SimpleTrigger that will occur at the given time, + * fire the identified Job and repeat at the the given + * interval the given number of times, or until the given end time. + *

+ * + * @param startTime + * A Date set to the time for the Trigger + * to fire. + * @param endTime + * A Date set to the time for the Trigger + * to quit repeat firing. + * @param repeatCount + * The number of times for the Trigger to repeat + * firing, use {@link #REPEAT_INDEFINITELY}for unlimitted times. + * @param repeatInterval + * The number of milliseconds to pause between the repeat firing. + */ + public SimpleTrigger(String name, String group, String jobName, + String jobGroup, Date startTime, Date endTime, int repeatCount, + long repeatInterval) { + super(name, group, jobName, jobGroup); + + setStartTime(startTime); + setEndTime(endTime); + setRepeatCount(repeatCount); + setRepeatInterval(repeatInterval); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Get the time at which the SimpleTrigger should occur. + *

+ */ + public Date getStartTime() { + return startTime; + } + + /** + *

+ * Set the time at which the SimpleTrigger should occur. + *

+ * + * @exception IllegalArgumentException + * if startTime is null. + */ + public void setStartTime(Date startTime) { + if (startTime == null) { + throw new IllegalArgumentException("Start time cannot be null"); + } + + Date eTime = getEndTime(); + if (eTime != null && startTime != null && eTime.before(startTime)) { + throw new IllegalArgumentException( + "End time cannot be before start time"); + } + + this.startTime = startTime; + } + + /** + *

+ * Get the time at which the SimpleTrigger should quit + * repeating - even if repeastCount isn't yet satisfied. + *

+ * + * @see #getFinalFireTime() + */ + public Date getEndTime() { + return endTime; + } + + /** + *

+ * Set the time at which the SimpleTrigger should quit + * repeating (and be automatically deleted). + *

+ * + * @exception IllegalArgumentException + * if endTime is before start time. + */ + public void setEndTime(Date endTime) { + Date sTime = getStartTime(); + if (sTime != null && endTime != null && sTime.after(endTime)) { + throw new IllegalArgumentException( + "End time cannot be before start time"); + } + + this.endTime = endTime; + } + + /** + *

+ * Get the the number of times the SimpleTrigger should + * repeat, after which it will be automatically deleted. + *

+ * + * @see #REPEAT_INDEFINITELY + */ + public int getRepeatCount() { + return repeatCount; + } + + /** + *

+ * Set the the number of time the SimpleTrigger should + * repeat, after which it will be automatically deleted. + *

+ * + * @see #REPEAT_INDEFINITELY + * @exception IllegalArgumentException + * if repeatCount is < 0 + */ + public void setRepeatCount(int repeatCount) { + if (repeatCount < 0 && repeatCount != REPEAT_INDEFINITELY) { + throw new IllegalArgumentException( + "Repeat count must be >= 0, use the " + + "constant REPEAT_INDEFINITELY for infinite."); + } + + this.repeatCount = repeatCount; + } + + /** + *

+ * Get the the time interval (in milliseconds) at which the SimpleTrigger + * should repeat. + *

+ */ + public long getRepeatInterval() { + return repeatInterval; + } + + /** + *

+ * Set the the time interval (in milliseconds) at which the SimpleTrigger + * should repeat. + *

+ * + * @exception IllegalArgumentException + * if repeatInterval is <= 0 + */ + public void setRepeatInterval(long repeatInterval) { + if (repeatInterval < 0) { + throw new IllegalArgumentException( + "Repeat interval must be >= 0"); + } + + this.repeatInterval = repeatInterval; + } + + /** + *

+ * Get the number of times the SimpleTrigger has already + * fired. + *

+ */ + public int getTimesTriggered() { + return timesTriggered; + } + + /** + *

+ * Set the number of times the SimpleTrigger has already + * fired. + *

+ */ + public void setTimesTriggered(int timesTriggered) { + this.timesTriggered = timesTriggered; + } + + protected boolean validateMisfireInstruction(int misfireInstruction) { + if (misfireInstruction < MISFIRE_INSTRUCTION_SMART_POLICY) { + return false; + } + + if (misfireInstruction > MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT) { + return false; + } + + return true; + } + + /** + *

+ * Updates the SimpleTrigger's state based on the + * MISFIRE_INSTRUCTION_XXX that was selected when the SimpleTrigger + * was created. + *

+ * + *

+ * If the misfire instruction is set to MISFIRE_INSTRUCTION_SMART_POLICY, + * then the following scheme will be used:
+ *

+ *

+ */ + public void updateAfterMisfire(Calendar cal) { + int instr = getMisfireInstruction(); + if (instr == Trigger.MISFIRE_INSTRUCTION_SMART_POLICY) { + if (getRepeatCount() == 0) { + instr = MISFIRE_INSTRUCTION_FIRE_NOW; + } else if (getRepeatCount() == REPEAT_INDEFINITELY) { + instr = MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT; + } else { + // if (getRepeatCount() > 0) + instr = MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT; + } + } else if (instr == MISFIRE_INSTRUCTION_FIRE_NOW && getRepeatCount() != 0) { + instr = MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT; + } + + if (instr == MISFIRE_INSTRUCTION_FIRE_NOW) { + setNextFireTime(new Date()); + } else if (instr == MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT) { + Date newFireTime = getFireTimeAfter(new Date()); + while (newFireTime != null && cal != null + && !cal.isTimeIncluded(newFireTime.getTime())) { + newFireTime = getFireTimeAfter(newFireTime); + + if(newFireTime == null) + break; + + //avoid infinite loop + java.util.Calendar c = java.util.Calendar.getInstance(); + c.setTime(newFireTime); + if (c.get(java.util.Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) { + newFireTime = null; + } + } + setNextFireTime(newFireTime); + } else if (instr == MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT) { + Date newFireTime = getFireTimeAfter(new Date()); + while (newFireTime != null && cal != null + && !cal.isTimeIncluded(newFireTime.getTime())) { + newFireTime = getFireTimeAfter(newFireTime); + + if(newFireTime == null) + break; + + //avoid infinite loop + java.util.Calendar c = java.util.Calendar.getInstance(); + c.setTime(newFireTime); + if (c.get(java.util.Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) { + newFireTime = null; + } + } + if (newFireTime != null) { + int timesMissed = computeNumTimesFiredBetween(nextFireTime, + newFireTime); + setTimesTriggered(getTimesTriggered() + timesMissed); + } + + setNextFireTime(newFireTime); + } else if (instr == MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT) { + Date newFireTime = new Date(); + if (repeatCount != 0 && repeatCount != REPEAT_INDEFINITELY) { + setRepeatCount(getRepeatCount() - getTimesTriggered()); + setTimesTriggered(0); + } + + if (getEndTime() != null && getEndTime().before(newFireTime)) { + setNextFireTime(null); // We are past the end time + } else { + setStartTime(newFireTime); + setNextFireTime(newFireTime); + } + } else if (instr == MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT) { + Date newFireTime = new Date(); + + int timesMissed = computeNumTimesFiredBetween(nextFireTime, + newFireTime); + + if (repeatCount != 0 && repeatCount != REPEAT_INDEFINITELY) { + int remainingCount = getRepeatCount() + - (getTimesTriggered() + timesMissed); + if (remainingCount <= 0) { + remainingCount = 0; + } + setRepeatCount(remainingCount); + setTimesTriggered(0); + } + + if (getEndTime() != null && getEndTime().before(newFireTime)) { + setNextFireTime(null); // We are past the end time + } else { + setStartTime(newFireTime); + setNextFireTime(newFireTime); + } + } + + } + + /** + *

+ * Called when the {@link Scheduler} has decided to 'fire' + * the trigger (execute the associated Job), in order to + * give the Trigger a chance to update itself for its next + * triggering (if any). + *

+ * + * @see #executionComplete(JobExecutionContext, JobExecutionException) + */ + public void triggered(Calendar calendar) { + timesTriggered++; + previousFireTime = nextFireTime; + nextFireTime = getFireTimeAfter(nextFireTime); + + while (nextFireTime != null && calendar != null + && !calendar.isTimeIncluded(nextFireTime.getTime())) { + + nextFireTime = getFireTimeAfter(nextFireTime); + + if(nextFireTime == null) + break; + + //avoid infinite loop + java.util.Calendar c = java.util.Calendar.getInstance(); + c.setTime(nextFireTime); + if (c.get(java.util.Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) { + nextFireTime = null; + } + } + } + + /** + * + * @see com.fr.third.org.quartz.Trigger#updateWithNewCalendar(com.fr.third.org.quartz.Calendar, long) + */ + public void updateWithNewCalendar(Calendar calendar, long misfireThreshold) + { + nextFireTime = getFireTimeAfter(previousFireTime); + + if (nextFireTime == null || calendar == null) { + return; + } + + Date now = new Date(); + while (nextFireTime != null && !calendar.isTimeIncluded(nextFireTime.getTime())) { + + nextFireTime = getFireTimeAfter(nextFireTime); + + if(nextFireTime == null) + break; + + //avoid infinite loop + java.util.Calendar c = java.util.Calendar.getInstance(); + c.setTime(nextFireTime); + if (c.get(java.util.Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) { + nextFireTime = null; + } + + if(nextFireTime != null && nextFireTime.before(now)) { + long diff = now.getTime() - nextFireTime.getTime(); + if(diff >= misfireThreshold) { + nextFireTime = getFireTimeAfter(nextFireTime); + } + } + } + } + + /** + *

+ * Called by the scheduler at the time a Trigger is first + * added to the scheduler, in order to have the Trigger + * compute its first fire time, based on any associated calendar. + *

+ * + *

+ * After this method has been called, getNextFireTime() + * should return a valid answer. + *

+ * + * @return the first time at which the Trigger will be fired + * by the scheduler, which is also the same value getNextFireTime() + * will return (until after the first firing of the Trigger). + *

+ */ + public Date computeFirstFireTime(Calendar calendar) { + nextFireTime = getStartTime(); + + while (nextFireTime != null && calendar != null + && !calendar.isTimeIncluded(nextFireTime.getTime())) { + nextFireTime = getFireTimeAfter(nextFireTime); + + if(nextFireTime == null) + break; + + //avoid infinite loop + java.util.Calendar c = java.util.Calendar.getInstance(); + c.setTime(nextFireTime); + if (c.get(java.util.Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) { + return null; + } + } + + return nextFireTime; + } + + /** + *

+ * Called after the {@link Scheduler} has executed the + * {@link com.fr.third.org.quartz.JobDetail} associated with the Trigger + * in order to get the final instruction code from the trigger. + *

+ * + * @param context + * is the JobExecutionContext that was used by the + * Job'sexecute(xx) method. + * @param result + * is the JobExecutionException thrown by the + * Job, if any (may be null). + * @return one of the Trigger.INSTRUCTION_XXX constants. + * + * @see #INSTRUCTION_NOOP + * @see #INSTRUCTION_RE_EXECUTE_JOB + * @see #INSTRUCTION_DELETE_TRIGGER + * @see #INSTRUCTION_SET_TRIGGER_COMPLETE + * @see #triggered(Calendar) + */ + public int executionComplete(JobExecutionContext context, + JobExecutionException result) { + if (result != null && result.refireImmediately()) { + return INSTRUCTION_RE_EXECUTE_JOB; + } + + if (result != null && result.unscheduleFiringTrigger()) { + return INSTRUCTION_SET_TRIGGER_COMPLETE; + } + + if (result != null && result.unscheduleAllTriggers()) { + return INSTRUCTION_SET_ALL_JOB_TRIGGERS_COMPLETE; + } + + if (!mayFireAgain()) { + return INSTRUCTION_DELETE_TRIGGER; + } + + return INSTRUCTION_NOOP; + } + + /** + *

+ * Returns the next time at which the Trigger is scheduled to fire. If + * the trigger will not fire again, null will be returned. Note that + * the time returned can possibly be in the past, if the time that was computed + * for the trigger to next fire has already arrived, but the scheduler has not yet + * been able to fire the trigger (which would likely be due to lack of resources + * e.g. threads). + *

+ * + *

The value returned is not guaranteed to be valid until after the Trigger + * has been added to the scheduler. + *

+ * + * @see TriggerUtils#computeFireTimesBetween(Trigger, Calendar, Date, Date) + */ + public Date getNextFireTime() { + return nextFireTime; + } + + /** + *

+ * Returns the previous time at which the SimpleTrigger + * fired. If the trigger has not yet fired, null will be + * returned. + */ + public Date getPreviousFireTime() { + return previousFireTime; + } + + /** + *

+ * Set the next time at which the SimpleTrigger should fire. + *

+ * + *

+ * This method should not be invoked by client code. + *

+ */ + public void setNextFireTime(Date nextFireTime) { + this.nextFireTime = nextFireTime; + } + + /** + *

+ * Set the previous time at which the SimpleTrigger fired. + *

+ * + *

+ * This method should not be invoked by client code. + *

+ */ + public void setPreviousFireTime(Date previousFireTime) { + this.previousFireTime = previousFireTime; + } + + /** + *

+ * Returns the next time at which the SimpleTrigger will + * fire, after the given time. If the trigger will not fire after the given + * time, null will be returned. + *

+ */ + public Date getFireTimeAfter(Date afterTime) { + if (complete) { + return null; + } + + if ((timesTriggered > repeatCount) + && (repeatCount != REPEAT_INDEFINITELY)) { + return null; + } + + if (afterTime == null) { + afterTime = new Date(); + } + + if (repeatCount == 0 && afterTime.compareTo(getStartTime()) >= 0) { + return null; + } + + long startMillis = getStartTime().getTime(); + long afterMillis = afterTime.getTime(); + long endMillis = (getEndTime() == null) ? Long.MAX_VALUE : getEndTime() + .getTime(); + + if (endMillis <= afterMillis) { + return null; + } + + if (afterMillis < startMillis) { + return new Date(startMillis); + } + + long numberOfTimesExecuted = ((afterMillis - startMillis) / repeatInterval) + 1; + + if ((numberOfTimesExecuted > repeatCount) && + (repeatCount != REPEAT_INDEFINITELY)) { + return null; + } + + Date time = new Date(startMillis + (numberOfTimesExecuted * repeatInterval)); + + if (endMillis <= time.getTime()) { + return null; + } + + return time; + } + + /** + *

+ * Returns the last time at which the SimpleTrigger will + * fire, before the given time. If the trigger will not fire before the + * given time, null will be returned. + *

+ */ + public Date getFireTimeBefore(Date end) { + if (end.getTime() < getStartTime().getTime()) { + return null; + } + + int numFires = computeNumTimesFiredBetween(getStartTime(), end); + + return new Date(getStartTime().getTime() + (numFires * repeatInterval)); + } + + public int computeNumTimesFiredBetween(Date start, Date end) { + + if(repeatInterval < 1) { + return 0; + } + + long time = end.getTime() - start.getTime(); + + return (int) (time / repeatInterval); + } + + /** + *

+ * Returns the final time at which the SimpleTrigger will + * fire, if repeatCount is REPEAT_INDEFINITELY, null will be returned. + *

+ * + *

+ * Note that the return time may be in the past. + *

+ */ + public Date getFinalFireTime() { + if (repeatCount == 0) { + return startTime; + } + + if (repeatCount == REPEAT_INDEFINITELY) { + return (getEndTime() == null) ? null : getFireTimeBefore(getEndTime()); + } + + long lastTrigger = startTime.getTime() + (repeatCount * repeatInterval); + + if ((getEndTime() == null) || (lastTrigger < getEndTime().getTime())) { + return new Date(lastTrigger); + } else { + return getFireTimeBefore(getEndTime()); + } + } + + /** + *

+ * Determines whether or not the SimpleTrigger will occur + * again. + *

+ */ + public boolean mayFireAgain() { + return (getNextFireTime() != null); + } + + /** + *

+ * Validates whether the properties of the JobDetail are + * valid for submission into a Scheduler. + * + * @throws IllegalStateException + * if a required property (such as Name, Group, Class) is not + * set. + */ + public void validate() throws SchedulerException { + super.validate(); + + if (repeatCount != 0 && repeatInterval < 1) { + throw new SchedulerException("Repeat Interval cannot be zero.", + SchedulerException.ERR_CLIENT_ERROR); + } + } + + /** + * Used by extensions of SimpleTrigger to imply that there are additional + * properties, specifically so that extensions can choose whether to be + * stored as a serialized blob, or as a flattened SimpleTrigger table. + */ + public boolean hasAdditionalProperties() { + return false; + } + + public static void main(String[] args) // TODO: remove method after good + // unit testing + throws Exception { + + Date sdt = new Date(); + + Date edt = new Date(sdt.getTime() + 55000L); + + SimpleTrigger st = new SimpleTrigger("t", "g", "j", "g", sdt, edt, 10, + 10000L); + + System.err.println(); + + st.computeFirstFireTime(null); + + System.err.println("lastTime=" + st.getFinalFireTime()); + + java.util.List times = TriggerUtils.computeFireTimes(st, null, 50); + + for (int i = 0; i < times.size(); i++) { + System.err.println("firetime = " + times.get(i)); + } + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/StatefulJob.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/StatefulJob.java new file mode 100644 index 000000000..02c483afb --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/StatefulJob.java @@ -0,0 +1,57 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +/** + *

+ * A marker interface for {@link com.fr.third.org.quartz.JobDetail} s that + * wish to have their state maintained between executions. + *

+ * + *

+ * StatefulJob instances follow slightly different rules from + * regular Job instances. The key difference is that their + * associated {@link JobDataMap} is re-persisted after every + * execution of the job, thus preserving state for the next execution. The + * other difference is that stateful jobs are not allowed to execute + * concurrently, which means new triggers that occur before the completion of + * the execute(xx) method will be delayed. + *

+ * + * @see Job + * @see JobDetail + * @see JobDataMap + * @see Scheduler + * + * @author James House + */ +public interface StatefulJob extends Job { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/Trigger.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/Trigger.java new file mode 100644 index 000000000..90adebd76 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/Trigger.java @@ -0,0 +1,1060 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +import java.util.Date; +import java.util.LinkedList; + +import com.fr.third.org.quartz.utils.Key; + + +/** + *

+ * The base abstract class to be extended by all Triggers. + *

+ * + *

+ * Triggers s have a name and group associated with them, which + * should uniquely identify them within a single {@link Scheduler}. + *

+ * + *

+ * Triggers are the 'mechanism' by which Job s + * are scheduled. Many Trigger s can point to the same Job, + * but a single Trigger can only point to one Job. + *

+ * + *

+ * Triggers can 'send' parameters/data to Jobs by placing contents + * into the JobDataMap on the Trigger. + *

+ * + * @see SimpleTrigger + * @see CronTrigger + * @see NthIncludedDayTrigger + * @see TriggerUtils + * @see JobDataMap + * @see JobExecutionContext + * + * @author James House + * @author Sharada Jambula + */ +public abstract class Trigger implements java.io.Serializable, Cloneable, + Comparable { + + private static final long serialVersionUID = -3904243490805975570L; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Instructs the {@link Scheduler} that the {@link Trigger} + * has no further instructions. + *

+ */ + public static final int INSTRUCTION_NOOP = 0; + + /** + *

+ * Instructs the {@link Scheduler} that the {@link Trigger} + * wants the {@link com.fr.third.org.quartz.JobDetail} to re-execute + * immediately. If not in a 'RECOVERING' or 'FAILED_OVER' situation, the + * execution context will be re-used (giving the Job the + * abilitiy to 'see' anything placed in the context by its last execution). + *

+ */ + public static final int INSTRUCTION_RE_EXECUTE_JOB = 1; + + /** + *

+ * Instructs the {@link Scheduler} that the {@link Trigger} + * should be put in the COMPLETE state. + *

+ */ + public static final int INSTRUCTION_SET_TRIGGER_COMPLETE = 2; + + /** + *

+ * Instructs the {@link Scheduler} that the {@link Trigger} + * wants itself deleted. + *

+ */ + public static final int INSTRUCTION_DELETE_TRIGGER = 3; + + /** + *

+ * Instructs the {@link Scheduler} that all Trigger + * s referencing the same {@link com.fr.third.org.quartz.JobDetail} as + * this one should be put in the COMPLETE state. + *

+ */ + public static final int INSTRUCTION_SET_ALL_JOB_TRIGGERS_COMPLETE = 4; + + /** + *

+ * Instructs the {@link Scheduler} that all Trigger + * s referencing the same {@link com.fr.third.org.quartz.JobDetail} as + * this one should be put in the ERROR state. + *

+ */ + public static final int INSTRUCTION_SET_TRIGGER_ERROR = 5; + + /** + *

+ * Instructs the {@link Scheduler} that the Trigger + * should be put in the ERROR state. + *

+ */ + public static final int INSTRUCTION_SET_ALL_JOB_TRIGGERS_ERROR = 6; + + /** + *

+ * Instructs the {@link Scheduler} that upon a mis-fire + * situation, the updateAfterMisfire() method will be called + * on the Trigger to determine the mis-fire instruction. + *

+ * + *

+ * In order to see if this instruction fits your needs, you should look at + * the documentation for the getSmartMisfirePolicy() method + * on the particular Trigger implementation you are using. + *

+ */ + public static final int MISFIRE_INSTRUCTION_SMART_POLICY = 0; + + /** + *

+ * Indicates that the Trigger is in the "normal" state. + *

+ */ + public static final int STATE_NORMAL = 0; + + /** + *

+ * Indicates that the Trigger is in the "paused" state. + *

+ */ + public static final int STATE_PAUSED = 1; + + /** + *

+ * Indicates that the Trigger is in the "complete" state. + *

+ * + *

+ * "Complete" indicates that the trigger has not remaining fire-times in + * its schedule. + *

+ */ + public static final int STATE_COMPLETE = 2; + + /** + *

+ * Indicates that the Trigger is in the "error" state. + *

+ * + *

+ * A Trigger arrives at the error state when the scheduler + * attempts to fire it, but cannot due to an error creating and executing + * its related job. Often this is due to the Job's + * class not existing in the classpath. + *

+ * + *

+ * When the trigger is in the error state, the scheduler will make no + * attempts to fire it. + *

+ */ + public static final int STATE_ERROR = 3; + + + /** + *

+ * Indicates that the Trigger is in the "blocked" state. + *

+ * + *

+ * A Trigger arrives at the blocked state when the job that + * it is associated with is a StatefulJob and it is + * currently executing. + *

+ * + * @see StatefulJob + */ + public static final int STATE_BLOCKED = 4; + + /** + *

+ * Indicates that the Trigger does not exist. + *

+ */ + public static final int STATE_NONE = -1; + + /** + * The default value for priority. + */ + public static final int DEFAULT_PRIORITY = 5; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private String name; + + private String group = Scheduler.DEFAULT_GROUP; + + private String jobName; + + private String jobGroup = Scheduler.DEFAULT_GROUP; + + private String description; + + private JobDataMap jobDataMap; + + private boolean volatility = false; + + private String calendarName = null; + + private String fireInstanceId = null; + + private int misfireInstruction = MISFIRE_INSTRUCTION_SMART_POLICY; + + private LinkedList triggerListeners = new LinkedList(); + + private int priority = DEFAULT_PRIORITY; + + private transient Key key = null; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + + + /** + *

+ * Create a Trigger with no specified name, group, or {@link com.fr.third.org.quartz.JobDetail}. + *

+ * + *

+ * Note that the {@link #setName(String)},{@link #setGroup(String)}and + * the {@link #setJobName(String)}and {@link #setJobGroup(String)}methods + * must be called before the Trigger can be placed into a + * {@link Scheduler}. + *

+ */ + public Trigger() { + // do nothing... + } + + /** + *

+ * Create a Trigger with the given name, and group. + *

+ * + *

+ * Note that the {@link #setJobName(String)}and + * {@link #setJobGroup(String)}methods must be called before the Trigger + * can be placed into a {@link Scheduler}. + *

+ * + * @param group if null, Scheduler.DEFAULT_GROUP will be used. + * + * @exception IllegalArgumentException + * if name is null or empty, or the group is an empty string. + */ + public Trigger(String name, String group) { + setName(name); + setGroup(group); + } + + /** + *

+ * Create a Trigger with the given name, and group. + *

+ * + * @param group if null, Scheduler.DEFAULT_GROUP will be used. + * + * @exception IllegalArgumentException + * if name is null or empty, or the group is an empty string. + */ + public Trigger(String name, String group, String jobName, String jobGroup) { + setName(name); + setGroup(group); + setJobName(jobName); + setJobGroup(jobGroup); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Get the name of this Trigger. + *

+ */ + public String getName() { + return name; + } + + /** + *

+ * Set the name of this Trigger. + *

+ * + * @exception IllegalArgumentException + * if name is null or empty. + */ + public void setName(String name) { + if (name == null || name.trim().length() == 0) { + throw new IllegalArgumentException( + "Trigger name cannot be null or empty."); + } + + this.name = name; + } + + /** + *

+ * Get the group of this Trigger. + *

+ */ + public String getGroup() { + return group; + } + + /** + *

+ * Set the name of this Trigger. + *

+ * + * @param group if null, Scheduler.DEFAULT_GROUP will be used. + * + * @exception IllegalArgumentException + * if group is an empty string. + */ + public void setGroup(String group) { + if (group != null && group.trim().length() == 0) { + throw new IllegalArgumentException( + "Group name cannot be an empty string."); + } + + if(group == null) { + group = Scheduler.DEFAULT_GROUP; + } + + this.group = group; + } + + /** + *

+ * Get the name of the associated {@link com.fr.third.org.quartz.JobDetail}. + *

+ */ + public String getJobName() { + return jobName; + } + + /** + *

+ * Set the name of the associated {@link com.fr.third.org.quartz.JobDetail}. + *

+ * + * @exception IllegalArgumentException + * if jobName is null or empty. + */ + public void setJobName(String jobName) { + if (jobName == null || jobName.trim().length() == 0) { + throw new IllegalArgumentException( + "Job name cannot be null or empty."); + } + + this.jobName = jobName; + } + + /** + *

+ * Get the name of the associated {@link com.fr.third.org.quartz.JobDetail}'s + * group. + *

+ */ + public String getJobGroup() { + return jobGroup; + } + + /** + *

+ * Set the name of the associated {@link com.fr.third.org.quartz.JobDetail}'s + * group. + *

+ * + * @param jobGroup if null, Scheduler.DEFAULT_GROUP will be used. + * + * @exception IllegalArgumentException + * if group is an empty string. + */ + public void setJobGroup(String jobGroup) { + if (jobGroup != null && jobGroup.trim().length() == 0) { + throw new IllegalArgumentException( + "Group name cannot be null or empty."); + } + + if(jobGroup == null) { + jobGroup = Scheduler.DEFAULT_GROUP; + } + + this.jobGroup = jobGroup; + } + + /** + *

+ * Returns the 'full name' of the Trigger in the format + * "group.name". + *

+ */ + public String getFullName() { + return group + "." + name; + } + + public Key getKey() { + if(key == null) { + key = new Key(getName(), getGroup()); + } + + return key; + } + + /** + *

+ * Returns the 'full name' of the Job that the Trigger + * points to, in the format "group.name". + *

+ */ + public String getFullJobName() { + return jobGroup + "." + jobName; + } + + /** + *

+ * Return the description given to the Trigger instance by + * its creator (if any). + *

+ * + * @return null if no description was set. + */ + public String getDescription() { + return description; + } + + /** + *

+ * Set a description for the Trigger instance - may be + * useful for remembering/displaying the purpose of the trigger, though the + * description has no meaning to Quartz. + *

+ */ + public void setDescription(String description) { + this.description = description; + } + + /** + *

+ * Set whether or not the Trigger should be persisted in the + * {@link com.fr.third.org.quartz.spi.JobStore} for re-use after program + * restarts. + *

+ */ + public void setVolatility(boolean volatility) { + this.volatility = volatility; + } + + /** + *

+ * Associate the {@link Calendar} with the given name with + * this Trigger. + *

+ * + * @param calendarName + * use null to dis-associate a Calendar. + */ + public void setCalendarName(String calendarName) { + this.calendarName = calendarName; + } + + /** + *

+ * Get the name of the {@link Calendar} associated with this + * Trigger. + *

+ * + * @return null if there is no associated Calendar. + */ + public String getCalendarName() { + return calendarName; + } + + /** + *

+ * Get the JobDataMap that is associated with the + * Trigger. + *

+ * + *

+ * Changes made to this map during job execution are not re-persisted, and + * in fact typically result in an IllegalStateException. + *

+ */ + public JobDataMap getJobDataMap() { + if (jobDataMap == null) { + jobDataMap = new JobDataMap(); + } + return jobDataMap; + } + + + /** + *

+ * Set the JobDataMap to be associated with the + * Trigger. + *

+ */ + public void setJobDataMap(JobDataMap jobDataMap) { + this.jobDataMap = jobDataMap; + } + + /** + *

+ * Whether or not the Trigger should be persisted in the + * {@link com.fr.third.org.quartz.spi.JobStore} for re-use after program + * restarts. + *

+ * + *

+ * If not explicitly set, the default value is false. + *

+ * + * @return true if the Trigger should be + * garbage collected along with the {@link Scheduler}. + */ + public boolean isVolatile() { + return volatility; + } + + /** + * The priority of a Trigger acts as a tiebreaker such that if + * two Triggers have the same scheduled fire time, then the + * one with the higher priority will get first access to a worker + * thread. + * + *

+ * If not explicitly set, the default value is 5. + *

+ * + * @see #DEFAULT_PRIORITY + */ + public int getPriority() { + return priority; + } + + + /** + * The priority of a Trigger acts as a tie breaker such that if + * two Triggers have the same scheduled fire time, then Quartz + * will do its best to give the one with the higher priority first access + * to a worker thread. + * + *

+ * If not explicitly set, the default value is 5. + *

+ * + * @see #DEFAULT_PRIORITY + */ + public void setPriority(int priority) { + this.priority = priority; + } + + /** + *

+ * Add the specified name of a {@link TriggerListener} to + * the end of the Trigger's list of listeners. + *

+ */ + public void addTriggerListener(String name) { + if (triggerListeners.contains(name)) { + throw new IllegalArgumentException( + "Trigger listener '" + name + "' is already registered for trigger: " + getFullName()); + } + + triggerListeners.add(name); + } + + /** + *

+ * Remove the specified name of a {@link TriggerListener} + * from the Trigger's list of listeners. + *

+ * + * @return true if the given name was found in the list, and removed + */ + public boolean removeTriggerListener(String name) { + return triggerListeners.remove(name); + } + + /** + *

+ * Returns an array of String containing the names of all + * {@link TriggerListener}s assigned to the Trigger, + * in the order in which they should be notified. + *

+ */ + public String[] getTriggerListenerNames() { + return (String[])triggerListeners.toArray(new String[triggerListeners.size()]); + } + + /** + * Remove all {@link TriggerListener}s from the Trigger. + */ + public void clearAllTriggerListeners() { + triggerListeners.clear(); + } + + /** + *

+ * This method should not be used by the Quartz client. + *

+ * + *

+ * Called when the {@link Scheduler} has decided to 'fire' + * the trigger (execute the associated Job), in order to + * give the Trigger a chance to update itself for its next + * triggering (if any). + *

+ * + * @see #executionComplete(JobExecutionContext, JobExecutionException) + */ + public abstract void triggered(Calendar calendar); + + /** + *

+ * This method should not be used by the Quartz client. + *

+ * + *

+ * Called by the scheduler at the time a Trigger is first + * added to the scheduler, in order to have the Trigger + * compute its first fire time, based on any associated calendar. + *

+ * + *

+ * After this method has been called, getNextFireTime() + * should return a valid answer. + *

+ * + * @return the first time at which the Trigger will be fired + * by the scheduler, which is also the same value getNextFireTime() + * will return (until after the first firing of the Trigger). + *

+ */ + public abstract Date computeFirstFireTime(Calendar calendar); + + /** + *

+ * This method should not be used by the Quartz client. + *

+ * + *

+ * Called after the {@link Scheduler} has executed the + * {@link com.fr.third.org.quartz.JobDetail} associated with the Trigger + * in order to get the final instruction code from the trigger. + *

+ * + * @param context + * is the JobExecutionContext that was used by the + * Job'sexecute(xx) method. + * @param result + * is the JobExecutionException thrown by the + * Job, if any (may be null). + * @return one of the Trigger.INSTRUCTION_XXX constants. + * + * @see #INSTRUCTION_NOOP + * @see #INSTRUCTION_RE_EXECUTE_JOB + * @see #INSTRUCTION_DELETE_TRIGGER + * @see #INSTRUCTION_SET_TRIGGER_COMPLETE + * @see #triggered(Calendar) + */ + public abstract int executionComplete(JobExecutionContext context, + JobExecutionException result); + + /** + *

+ * Used by the {@link Scheduler} to determine whether or not + * it is possible for this Trigger to fire again. + *

+ * + *

+ * If the returned value is false then the Scheduler + * may remove the Trigger from the {@link com.fr.third.org.quartz.spi.JobStore}. + *

+ */ + public abstract boolean mayFireAgain(); + + /** + *

+ * Get the time at which the Trigger should occur. + *

+ */ + public abstract Date getStartTime(); + + /** + *

+ * The time at which the trigger's scheduling should start. May or may not + * be the first actual fire time of the trigger, depending upon the type of + * trigger and the settings of the other properties of the trigger. However + * the first actual first time will not be before this date. + *

+ *

+ * Setting a value in the past may cause a new trigger to compute a first + * fire time that is in the past, which may cause an immediate misfire + * of the trigger. + *

+ */ + public abstract void setStartTime(Date startTime); + + public abstract void setEndTime(Date endTime); + + /** + *

+ * Get the time at which the Trigger should quit repeating - + * even if an assigned 'repeatCount' isn't yet satisfied. + *

+ * + * @see #getFinalFireTime() + */ + public abstract Date getEndTime(); + + /** + *

+ * Returns the next time at which the Trigger is scheduled to fire. If + * the trigger will not fire again, null will be returned. Note that + * the time returned can possibly be in the past, if the time that was computed + * for the trigger to next fire has already arrived, but the scheduler has not yet + * been able to fire the trigger (which would likely be due to lack of resources + * e.g. threads). + *

+ * + *

The value returned is not guaranteed to be valid until after the Trigger + * has been added to the scheduler. + *

+ * + * @see TriggerUtils#computeFireTimesBetween(Trigger, Calendar, Date, Date) + */ + public abstract Date getNextFireTime(); + + /** + *

+ * Returns the previous time at which the Trigger fired. + * If the trigger has not yet fired, null will be returned. + */ + public abstract Date getPreviousFireTime(); + + /** + *

+ * Returns the next time at which the Trigger will fire, + * after the given time. If the trigger will not fire after the given time, + * null will be returned. + *

+ */ + public abstract Date getFireTimeAfter(Date afterTime); + + /** + *

+ * Returns the last time at which the Trigger will fire, if + * the Trigger will repeat indefinitely, null will be returned. + *

+ * + *

+ * Note that the return time *may* be in the past. + *

+ */ + public abstract Date getFinalFireTime(); + + /** + *

+ * Set the instruction the Scheduler should be given for + * handling misfire situations for this Trigger- the + * concrete Trigger type that you are using will have + * defined a set of additional MISFIRE_INSTRUCTION_XXX + * constants that may be passed to this method. + *

+ * + *

+ * If not explicitly set, the default value is MISFIRE_INSTRUCTION_SMART_POLICY. + *

+ * + * @see #MISFIRE_INSTRUCTION_SMART_POLICY + * @see #updateAfterMisfire(Calendar) + * @see SimpleTrigger + * @see CronTrigger + */ + public void setMisfireInstruction(int misfireInstruction) { + if (!validateMisfireInstruction(misfireInstruction)) { + throw new IllegalArgumentException( + "The misfire instruction code is invalid for this type of trigger."); + } + this.misfireInstruction = misfireInstruction; + } + + protected abstract boolean validateMisfireInstruction(int misfireInstruction); + + /** + *

+ * Get the instruction the Scheduler should be given for + * handling misfire situations for this Trigger- the + * concrete Trigger type that you are using will have + * defined a set of additional MISFIRE_INSTRUCTION_XXX + * constants that may be passed to this method. + *

+ * + *

+ * If not explicitly set, the default value is MISFIRE_INSTRUCTION_SMART_POLICY. + *

+ * + * @see #MISFIRE_INSTRUCTION_SMART_POLICY + * @see #updateAfterMisfire(Calendar) + * @see SimpleTrigger + * @see CronTrigger + */ + public int getMisfireInstruction() { + return misfireInstruction; + } + + /** + *

+ * This method should not be used by the Quartz client. + *

+ * + *

+ * To be implemented by the concrete classes that extend this class. + *

+ * + *

+ * The implementation should update the Trigger's state + * based on the MISFIRE_INSTRUCTION_XXX that was selected when the Trigger + * was created. + *

+ */ + public abstract void updateAfterMisfire(Calendar cal); + + /** + *

+ * This method should not be used by the Quartz client. + *

+ * + *

+ * To be implemented by the concrete class. + *

+ * + *

+ * The implementation should update the Trigger's state + * based on the given new version of the associated Calendar + * (the state should be updated so that it's next fire time is appropriate + * given the Calendar's new settings). + *

+ * + * @param cal + */ + public abstract void updateWithNewCalendar(Calendar cal, long misfireThreshold); + + /** + *

+ * Validates whether the properties of the JobDetail are + * valid for submission into a Scheduler. + * + * @throws IllegalStateException + * if a required property (such as Name, Group, Class) is not + * set. + */ + public void validate() throws SchedulerException { + if (name == null) { + throw new SchedulerException("Trigger's name cannot be null", + SchedulerException.ERR_CLIENT_ERROR); + } + + if (group == null) { + throw new SchedulerException("Trigger's group cannot be null", + SchedulerException.ERR_CLIENT_ERROR); + } + + if (jobName == null) { + throw new SchedulerException( + "Trigger's related Job's name cannot be null", + SchedulerException.ERR_CLIENT_ERROR); + } + + if (jobGroup == null) { + throw new SchedulerException( + "Trigger's related Job's group cannot be null", + SchedulerException.ERR_CLIENT_ERROR); + } + } + + /** + *

+ * This method should not be used by the Quartz client. + *

+ * + *

+ * Usable by {@link com.fr.third.org.quartz.spi.JobStore} + * implementations, in order to facilitate 'recognizing' instances of fired + * Trigger s as their jobs complete execution. + *

+ * + * + */ + public void setFireInstanceId(String id) { + this.fireInstanceId = id; + } + + /** + *

+ * This method should not be used by the Quartz client. + *

+ */ + public String getFireInstanceId() { + return fireInstanceId; + } + + /** + *

+ * Return a simple string representation of this object. + *

+ */ + public String toString() { + return "Trigger '" + getFullName() + "': triggerClass: '" + + getClass().getName() + " isVolatile: " + isVolatile() + + " calendar: '" + getCalendarName() + "' misfireInstruction: " + + getMisfireInstruction() + " nextFireTime: " + getNextFireTime(); + } + + /** + *

+ * Compare the next fire time of this Trigger to that of + * another. + *

+ */ + public int compareTo(Object obj) { + Trigger other = (Trigger) obj; + + Date myTime = getNextFireTime(); + Date otherTime = other.getNextFireTime(); + + if (myTime == null && otherTime == null) { + return 0; + } + + if (myTime == null) { + return 1; + } + + if (otherTime == null) { + return -1; + } + + if(myTime.before(otherTime)) { + return -1; + } + + if(myTime.after(otherTime)) { + return 1; + } + + return 0; + } + + public boolean equals(Object obj) { + if (!(obj instanceof Trigger)) { + return false; + } + + Trigger other = (Trigger) obj; + + if (other.getName() == null && getName() != null) { + return false; + } + if (other.getName() != null && !other.getName().equals(getName())) { + return false; + } + + if (other.getGroup() == null && getGroup() != null) { + return false; + } + if (other.getGroup() != null && !other.getGroup().equals(getGroup())) { + return false; + } + + return true; + } + + + public int hashCode() { + return getFullName().hashCode(); + } + + public Object clone() { + Trigger copy; + try { + copy = (Trigger) super.clone(); + + copy.triggerListeners = (LinkedList)triggerListeners.clone(); + + // Shallow copy the jobDataMap. Note that this means that if a user + // modifies a value object in this map from the cloned Trigger + // they will also be modifying this Trigger. + if (jobDataMap != null) { + copy.jobDataMap = (JobDataMap)jobDataMap.clone(); + } + + } catch (CloneNotSupportedException ex) { + throw new IncompatibleClassChangeError("Not Cloneable."); + } + return copy; + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/TriggerListener.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/TriggerListener.java new file mode 100644 index 000000000..6dd642b34 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/TriggerListener.java @@ -0,0 +1,134 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +/** + *

+ * The interface to be implemented by classes that want to be informed when a + * {@link Trigger} fires. In general, applications that use a + * Scheduler will not have use for this mechanism. + *

+ * + * @see Scheduler + * @see Trigger + * @see JobListener + * @see JobExecutionContext + * + * @author James House + */ +public interface TriggerListener { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Get the name of the TriggerListener. + *

+ */ + String getName(); + + /** + *

+ * Called by the {@link Scheduler} when a {@link Trigger} + * has fired, and it's associated {@link com.fr.third.org.quartz.JobDetail} + * is about to be executed. + *

+ * + *

+ * It is called before the vetoJobExecution(..) method of this + * interface. + *

+ * + * @param trigger + * The Trigger that has fired. + * @param context + * The JobExecutionContext that will be passed to + * the Job'sexecute(xx) method. + */ + void triggerFired(Trigger trigger, JobExecutionContext context); + + /** + *

+ * Called by the {@link Scheduler} when a {@link Trigger} + * has fired, and it's associated {@link com.fr.third.org.quartz.JobDetail} + * is about to be executed. + *

+ * + *

+ * It is called after the triggerFired(..) method of this + * interface. + *

+ * + * @param trigger + * The Trigger that has fired. + * @param context + * The JobExecutionContext that will be passed to + * the Job'sexecute(xx) method. + */ + boolean vetoJobExecution(Trigger trigger, JobExecutionContext context); + + + /** + *

+ * Called by the {@link Scheduler} when a {@link Trigger} + * has misfired. + *

+ * + *

+ * Consideration should be given to how much time is spent in this method, + * as it will affect all triggers that are misfiring. If you have lots + * of triggers misfiring at once, it could be an issue it this method + * does a lot. + *

+ * + * @param trigger + * The Trigger that has misfired. + */ + void triggerMisfired(Trigger trigger); + + /** + *

+ * Called by the {@link Scheduler} when a {@link Trigger} + * has fired, it's associated {@link com.fr.third.org.quartz.JobDetail} + * has been executed, and it's triggered(xx) method has been + * called. + *

+ * + * @param trigger + * The Trigger that was fired. + * @param context + * The JobExecutionContext that was passed to the + * Job'sexecute(xx) method. + * @param triggerInstructionCode + * the result of the call on the Trigger'striggered(xx) + * method. + */ + void triggerComplete(Trigger trigger, JobExecutionContext context, + int triggerInstructionCode); + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/TriggerUtils.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/TriggerUtils.java new file mode 100644 index 000000000..1430ca81a --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/TriggerUtils.java @@ -0,0 +1,1401 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz; + +import java.util.Calendar; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.TimeZone; + +/** + *

+ * Convenience and utility methods for simplifying the construction and + * configuration of {@link Trigger}s. + *

+ * + *

+ * Please submit suggestions for additional convenience methods to either the + * Quartz user forum or the developer's mail list at + * source forge. + *

+ * + * @see CronTrigger + * @see SimpleTrigger + * + * @author James House + */ +public class TriggerUtils { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public static final int SUNDAY = 1; + + public static final int MONDAY = 2; + + public static final int TUESDAY = 3; + + public static final int WEDNESDAY = 4; + + public static final int THURSDAY = 5; + + public static final int FRIDAY = 6; + + public static final int SATURDAY = 7; + + public static final int LAST_DAY_OF_MONTH = -1; + + public static final long MILLISECONDS_IN_MINUTE = 60l * 1000l; + + public static final long MILLISECONDS_IN_HOUR = 60l * 60l * 1000l; + + public static final long SECONDS_IN_DAY = 24l * 60l * 60L; + + public static final long MILLISECONDS_IN_DAY = SECONDS_IN_DAY * 1000l; + + /** + * Private constructor because this is a pure utility class. + */ + private TriggerUtils() { + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private static void validateDayOfWeek(int dayOfWeek) { + if (dayOfWeek < SUNDAY || dayOfWeek > SATURDAY) { + throw new IllegalArgumentException("Invalid day of week."); + } + } + + private static void validateHour(int hour) { + if (hour < 0 || hour > 23) { + throw new IllegalArgumentException( + "Invalid hour (must be >= 0 and <= 23)."); + } + } + + private static void validateMinute(int minute) { + if (minute < 0 || minute > 59) { + throw new IllegalArgumentException( + "Invalid minute (must be >= 0 and <= 59)."); + } + } + + private static void validateSecond(int second) { + if (second < 0 || second > 59) { + throw new IllegalArgumentException( + "Invalid second (must be >= 0 and <= 59)."); + } + } + + private static void validateDayOfMonth(int day) { + if ((day < 1 || day > 31) && day != LAST_DAY_OF_MONTH) { + throw new IllegalArgumentException("Invalid day of month."); + } + } + + private static void validateMonth(int month) { + if (month < 1 || month > 12) { + throw new IllegalArgumentException( + "Invalid month (must be >= 1 and <= 12."); + } + } + + private static void validateYear(int year) { + if (year < 1970 || year > 2099) { + throw new IllegalArgumentException( + "Invalid year (must be >= 1970 and <= 2099."); + } + } + + /** + *

+ * Set the given Trigger's name to the given value, and its + * group to the default group (Scheduler.DEFAULT_GROUP). + *

+ * + * @param trig the tigger to change name to + * @param name the new trigger name + */ + public static void setTriggerIdentity(Trigger trig, String name) { + setTriggerIdentity(trig, name, Scheduler.DEFAULT_GROUP); + } + + /** + *

+ * Set the given Trigger's name to the given value, and its + * group to the given group. + *

+ * + * @param trig the tigger to change name to + * @param name the new trigger name + * @param group the new trigger group + */ + public static void setTriggerIdentity( + Trigger trig, String name, String group) { + trig.setName(name); + trig.setGroup(group); + } + + /** + *

+ * Make a trigger that will fire every day at the given time. + *

+ * + *

+ * The generated trigger will not have its name, group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + * @param hour the hour (0-23) upon which to fire + * @param minute the minute (0-59) upon which to fire + * @return the new trigger + */ + public static Trigger makeDailyTrigger(int hour, int minute) { + validateHour(hour); + validateMinute(minute); + + CronTrigger trig = new CronTrigger(); + + try { + trig.setCronExpression("0 " + minute + " " + hour + " ? * *"); + } catch (Exception ignore) { + return null; /* never happens... */ + } + + trig.setStartTime(new Date()); + + return trig; + } + + /** + *

+ * Make a trigger that will fire every day at the given time. + *

+ * + *

+ * The generated trigger will not have its group or end-time set. + * The Start time defaults to 'now'. + *

+ * + * @param trigName the trigger's name + * @param hour the hour (0-23) upon which to fire + * @param minute the minute (0-59) upon which to fire + * @return the newly created trigger + */ + public static Trigger makeDailyTrigger( + String trigName, int hour, int minute) { + Trigger trig = makeDailyTrigger(hour, minute); + trig.setName(trigName); + return trig; + } + + /** + *

+ * Make a trigger that will fire every week at the given day and time. + *

+ * + *

+ * The generated trigger will not have its name, group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + * @param dayOfWeek (1-7) the day of week upon which to fire + * @param hour the hour (0-23) upon which to fire + * @param minute the minute (0-59) upon which to fire + * @return the new trigger + * + * @see #SUNDAY + * @see #MONDAY + * @see #TUESDAY + * @see #WEDNESDAY + * @see #THURSDAY + * @see #FRIDAY + * @see #SATURDAY + */ + public static Trigger makeWeeklyTrigger( + int dayOfWeek, int hour, int minute) { + validateDayOfWeek(dayOfWeek); + validateHour(hour); + validateMinute(minute); + + CronTrigger trig = new CronTrigger(); + + try { + trig.setCronExpression("0 " + minute + " " + hour + " ? * " + + dayOfWeek); + } catch (Exception ignore) { + return null; /* never happens... */ + } + + trig.setStartTime(new Date()); + + return trig; + } + + /** + *

+ * Make a trigger that will fire every week at the given day and time. + *

+ * + *

+ * The generated trigger will not have its group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + * @param trigName the trigger's name + * @param dayOfWeek (1-7) the day of week upon which to fire + * @param hour the hour (0-23) upon which to fire + * @param minute the minute (0-59) upon which to fire + * @return the newly created trigger + * + * @see #SUNDAY + * @see #MONDAY + * @see #TUESDAY + * @see #WEDNESDAY + * @see #THURSDAY + * @see #FRIDAY + * @see #SATURDAY + */ + public static Trigger makeWeeklyTrigger( + String trigName, int dayOfWeek, int hour, int minute) { + Trigger trig = makeWeeklyTrigger(dayOfWeek, hour, minute); + trig.setName(trigName); + return trig; + } + + + /** + *

+ * Make a trigger that will fire every month at the given day and time. + *

+ * + *

+ * The generated trigger will not have its name, group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + *

+ * If the day of the month specified does not occur in a given month, a + * firing will not occur that month. (i.e. if dayOfMonth is specified as + * 31, no firing will occur in the months of the year with fewer than 31 + * days). + *

+ * + * @param dayOfMonth (1-31, or -1) the day of week upon which to fire + * @param hour the hour (0-23) upon which to fire + * @param minute the minute (0-59) upon which to fire + * @return the newly created trigger + */ + public static Trigger makeMonthlyTrigger( + int dayOfMonth, int hour, int minute) { + validateDayOfMonth(dayOfMonth); + validateHour(hour); + validateMinute(minute); + + CronTrigger trig = new CronTrigger(); + + try { + if (dayOfMonth != LAST_DAY_OF_MONTH) { + trig.setCronExpression("0 " + minute + " " + hour + " " + dayOfMonth + " * ?"); + } else { + trig.setCronExpression("0 " + minute + " " + hour + " L * ?"); + } + } catch (Exception ignore) { + return null; /* never happens... */ + } + + trig.setStartTime(new Date()); + + return trig; + } + + /** + *

+ * Make a trigger that will fire every month at the given day and time. + *

+ * + *

+ * The generated trigger will not have its group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + *

+ * If the day of the month specified does not occur in a given month, a + * firing will not occur that month. (i.e. if dayOfMonth is specified as + * 31, no firing will occur in the months of the year with fewer than 31 + * days). + *

+ * + * @param trigName the trigger's name + * @param dayOfMonth (1-31, or -1) the day of week upon which to fire + * @param hour the hour (0-23) upon which to fire + * @param minute the minute (0-59) upon which to fire + * @return the newly created trigger + */ + public static Trigger makeMonthlyTrigger( + String trigName, int dayOfMonth, int hour, int minute) { + Trigger trig = makeMonthlyTrigger(dayOfMonth, hour, minute); + trig.setName(trigName); + return trig; + } + + /* + *

Make a trigger that will fire every N days at the given time.

+ * + *

TThe generated trigger will not have its name, group, + * start-time and end-time set.

+ * + * @param hour the hour (0-23) upon which to fire @param minute the minute + * (0-59) upon which to fire @param interval the number of days between + * firings public static Trigger makeDailyTrigger(int interval, int hour, + * int minute) { + * + * SimpleTrigger trig = new SimpleTrigger(); + * + * MILLISECONDS_IN_DAY); + * trig.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); + * + * return trig; + * } + */ + + /** + *

+ * Make a trigger that will fire repeatCount times, waiting + * repeatInterval milliseconds between each fire. + *

+ * + *

+ * The generated trigger will not have its name, group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + * @param repeatCount the number of times to fire the trigger + * @param repeatInterval the number of milliseconds to wait between fires + * @return the newly created trigger + */ + public static Trigger makeImmediateTrigger( + int repeatCount, long repeatInterval) { + SimpleTrigger trig = new SimpleTrigger(); + trig.setStartTime( new Date() ); + trig.setRepeatCount(repeatCount); + trig.setRepeatInterval(repeatInterval); + return trig; + } + + /** + *

+ * Make a trigger that will fire repeatCount times, waiting + * repeatInterval milliseconds between each fire. + *

+ * + *

+ * The generated trigger will not have its name, group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + * @param trigName the trigger's name + * @param repeatCount the number of times to fire the trigger + * @param repeatInterval the number of milliseconds to wait between fires + * @return the new trigger + */ + public static Trigger makeImmediateTrigger( + String trigName, int repeatCount, long repeatInterval) { + Trigger trig = makeImmediateTrigger(repeatCount, repeatInterval); + trig.setName(trigName); + return trig; + } + + /** + *

+ * Make a trigger that will fire every second, indefinitely. + *

+ * + *

+ * The generated trigger will not have its name, group, + * or end-time set. The Start time defaults to 'now'. + *

+ * @return the new trigger + */ + public static Trigger makeSecondlyTrigger() { + return makeSecondlyTrigger(1, SimpleTrigger.REPEAT_INDEFINITELY); + } + + /** + *

+ * Make a trigger that will fire every second, indefinitely. + *

+ * + *

+ * The generated trigger will not have its group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + * @param trigName the trigger's name + * @return the new trigger + */ + public static Trigger makeSecondlyTrigger(String trigName) { + return makeSecondlyTrigger( + trigName, 1, SimpleTrigger.REPEAT_INDEFINITELY); + } + + + /** + *

+ * Make a trigger that will fire every N seconds, indefinitely. + *

+ * + *

+ * The generated trigger will not have its name, group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + * @param intervalInSeconds the number of seconds between firings + * @return the new trigger + */ + public static Trigger makeSecondlyTrigger(int intervalInSeconds) { + return makeSecondlyTrigger( + intervalInSeconds, SimpleTrigger.REPEAT_INDEFINITELY); + } + + /** + *

+ * Make a trigger that will fire every N seconds, with the given number of + * repeats. + *

+ * + *

+ * The generated trigger will not have its name, group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + * @param intervalInSeconds the number of seconds between firings + * @param repeatCount the number of times to repeat the firing + * @return the new trigger + */ + public static Trigger makeSecondlyTrigger( + int intervalInSeconds, int repeatCount) { + SimpleTrigger trig = new SimpleTrigger(); + + trig.setRepeatInterval(intervalInSeconds * 1000l); + trig.setRepeatCount(repeatCount); + trig.setStartTime(new Date()); + + return trig; + } + + /** + *

+ * Make a trigger that will fire every N seconds, with the given number of + * repeats. + *

+ * + *

+ * The generated trigger will not have its group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + * @param trigName the trigger's name + * @param intervalInSeconds the number of seconds between firings + * @param repeatCount the number of times to repeat the firing + * @return the new trigger + */ + public static Trigger makeSecondlyTrigger( + String trigName, int intervalInSeconds, int repeatCount) { + Trigger trig = makeSecondlyTrigger(intervalInSeconds, repeatCount); + trig.setName(trigName); + return trig; + } + + /** + *

+ * Make a trigger that will fire every minute, indefinitely. + *

+ * + *

+ * The generated trigger will not have its name, group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + * @return the new trigger + */ + public static Trigger makeMinutelyTrigger() { + return makeMinutelyTrigger(1, SimpleTrigger.REPEAT_INDEFINITELY); + } + + /** + *

+ * Make a trigger that will fire every minute, indefinitely. + *

+ * + *

+ * The generated trigger will not have its group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + * @param trigName the trigger's name + * @return the new trigger + */ + public static Trigger makeMinutelyTrigger(String trigName) { + return makeMinutelyTrigger( + trigName, 1, SimpleTrigger.REPEAT_INDEFINITELY); + } + + /** + *

+ * Make a trigger that will fire every N minutes, indefinitely. + *

+ * + *

+ * The generated trigger will not have its name, group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + * @param intervalInMinutes the number of minutes between firings + * @return the new trigger + */ + public static Trigger makeMinutelyTrigger(int intervalInMinutes) { + return makeMinutelyTrigger( + intervalInMinutes, SimpleTrigger.REPEAT_INDEFINITELY); + } + + /** + *

+ * Make a trigger that will fire every N minutes, with the given number of + * repeats. + *

+ * + *

+ * The generated trigger will not have its name, group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + * @param intervalInMinutes the number of minutes between firings + * @param repeatCount the number of times to repeat the firing + * @return the new trigger + */ + public static Trigger makeMinutelyTrigger( + int intervalInMinutes, int repeatCount) { + SimpleTrigger trig = new SimpleTrigger(); + + trig.setRepeatInterval(intervalInMinutes * MILLISECONDS_IN_MINUTE); + trig.setRepeatCount(repeatCount); + + trig.setStartTime(new Date()); + + return trig; + } + + /** + *

+ * Make a trigger that will fire every N minutes, with the given number of + * repeats. + *

+ * + *

+ * The generated trigger will not have its group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + * @param trigName the trigger's name + * @param intervalInMinutes the number of minutes between firings + * @param repeatCount the number of times to repeat the firing + * @return the new trigger + */ + public static Trigger makeMinutelyTrigger( + String trigName, int intervalInMinutes, int repeatCount) { + Trigger trig = makeMinutelyTrigger(intervalInMinutes, repeatCount); + trig.setName(trigName); + return trig; + } + + /** + *

+ * Make a trigger that will fire every hour, indefinitely. + *

+ * + *

+ * The generated trigger will not have its name, group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + * @return the new trigger + */ + public static Trigger makeHourlyTrigger() { + return makeHourlyTrigger(1, SimpleTrigger.REPEAT_INDEFINITELY); + } + + /** + *

+ * Make a trigger that will fire every hour, indefinitely. + *

+ * + *

+ * The generated trigger will not have its group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + * @param trigName the trigger's name + * @return the new trigger + */ + public static Trigger makeHourlyTrigger(String trigName) { + return makeHourlyTrigger( + trigName, 1, SimpleTrigger.REPEAT_INDEFINITELY); + } + + /** + *

+ * Make a trigger that will fire every N hours, indefinitely. + *

+ * + *

+ * The generated trigger will not have its name, group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + * @param intervalInHours the number of hours between firings + * @return the new trigger + */ + public static Trigger makeHourlyTrigger(int intervalInHours) { + return makeHourlyTrigger( + intervalInHours, SimpleTrigger.REPEAT_INDEFINITELY); + } + + /** + *

+ * Make a trigger that will fire every N hours, with the given number of + * repeats. + *

+ * + *

+ * The generated trigger will not have its name, group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + * @param intervalInHours the number of hours between firings + * @param repeatCount the number of times to repeat the firing + * @return the new trigger + */ + public static Trigger makeHourlyTrigger( + int intervalInHours, int repeatCount) { + SimpleTrigger trig = new SimpleTrigger(); + + trig.setRepeatInterval(intervalInHours * MILLISECONDS_IN_HOUR); + trig.setRepeatCount(repeatCount); + + trig.setStartTime(new Date()); + + return trig; + } + + /** + *

+ * Make a trigger that will fire every N hours, with the given number of + * repeats. + *

+ * + *

+ * The generated trigger will not have its group, + * or end-time set. The Start time defaults to 'now'. + *

+ * + * @param trigName the trigger's name + * @param intervalInHours the number of hours between firings + * @param repeatCount the number of times to repeat the firing + * @return the new trigger + */ + public static Trigger makeHourlyTrigger( + String trigName, int intervalInHours, int repeatCount) { + Trigger trig =makeHourlyTrigger(intervalInHours, repeatCount); + trig.setName(trigName); + return trig; + } + + /** + *

+ * Returns a date that is rounded to the next even hour above the given + * date. + *

+ * + *

+ * For example an input date with a time of 08:13:54 would result in a date + * with the time of 09:00:00. If the date's time is in the 23rd hour, the + * date's 'day' will be promoted, and the time will be set to 00:00:00. + *

+ * + * @param date + * the Date to round, if null the current time will + * be used + * @return the new rounded date + */ + public static Date getEvenHourDate(Date date) { + if (date == null) { + date = new Date(); + } + + Calendar c = Calendar.getInstance(); + c.setTime(date); + c.setLenient(true); + + c.set(Calendar.HOUR_OF_DAY, c.get(Calendar.HOUR_OF_DAY) + 1); + c.set(Calendar.MINUTE, 0); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + /** + *

+ * Returns a date that is rounded to the previous even hour below the given + * date. + *

+ * + *

+ * For example an input date with a time of 08:13:54 would result in a date + * with the time of 08:00:00. + *

+ * + * @param date + * the Date to round, if null the current time will + * be used + * @return the new rounded date + */ + public static Date getEvenHourDateBefore(Date date) { + if (date == null) { + date = new Date(); + } + + Calendar c = Calendar.getInstance(); + c.setTime(date); + + c.set(Calendar.MINUTE, 0); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + /** + *

+ * Returns a date that is rounded to the next even minute above the given + * date. + *

+ * + *

+ * For example an input date with a time of 08:13:54 would result in a date + * with the time of 08:14:00. If the date's time is in the 59th minute, + * then the hour (and possibly the day) will be promoted. + *

+ * + * @param date + * the Date to round, if null the current time will + * be used + * @return the new rounded date + */ + public static Date getEvenMinuteDate(Date date) { + if (date == null) { + date = new Date(); + } + + Calendar c = Calendar.getInstance(); + c.setTime(date); + c.setLenient(true); + + c.set(Calendar.MINUTE, c.get(Calendar.MINUTE) + 1); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + /** + *

+ * Returns a date that is rounded to the previous even minute below the + * given date. + *

+ * + *

+ * For example an input date with a time of 08:13:54 would result in a date + * with the time of 08:13:00. + *

+ * + * @param date + * the Date to round, if null the current time will + * be used + * @return the new rounded date + */ + public static Date getEvenMinuteDateBefore(Date date) { + if (date == null) { + date = new Date(); + } + + Calendar c = Calendar.getInstance(); + c.setTime(date); + + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + /** + *

+ * Returns a date that is rounded to the next even second above the given + * date. + *

+ * + * @param date + * the Date to round, if null the current time will + * be used + * @return the new rounded date + */ + public static Date getEvenSecondDate(Date date) { + if (date == null) { + date = new Date(); + } + + Calendar c = Calendar.getInstance(); + c.setTime(date); + c.setLenient(true); + + c.set(Calendar.SECOND, c.get(Calendar.SECOND) + 1); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + /** + *

+ * Returns a date that is rounded to the previous even second below the + * given date. + *

+ * + *

+ * For example an input date with a time of 08:13:54.341 would result in a + * date with the time of 08:13:00.000. + *

+ * + * @param date + * the Date to round, if null the current time will + * be used + * @return the new rounded date + */ + public static Date getEvenSecondDateBefore(Date date) { + if (date == null) { + date = new Date(); + } + + Calendar c = Calendar.getInstance(); + c.setTime(date); + + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + /** + *

+ * Returns a date that is rounded to the next even multiple of the given + * minute. + *

+ * + *

+ * For example an input date with a time of 08:13:54, and an input + * minute-base of 5 would result in a date with the time of 08:15:00. The + * same input date with an input minute-base of 10 would result in a date + * with the time of 08:20:00. But a date with the time 08:53:31 and an + * input minute-base of 45 would result in 09:00:00, because the even-hour + * is the next 'base' for 45-minute intervals. + *

+ * + *

+ * More examples: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Input TimeMinute-BaseResult Time
11:16:412011:20:00
11:36:412011:40:00
11:46:412012:00:00
11:26:413011:30:00
11:36:413012:00:00
11:16:411711:17:00
11:17:411711:34:00
11:52:411712:00:00
11:52:41511:55:00
11:57:41512:00:00
11:17:41012:00:00
11:17:41111:08:00
+ *

+ * + * @param date + * the Date to round, if null the current time will + * be used + * @param minuteBase + * the base-minute to set the time on + * @return the new rounded date + * + * @see #getNextGivenSecondDate(Date, int) + */ + public static Date getNextGivenMinuteDate(Date date, int minuteBase) { + if (minuteBase < 0 || minuteBase > 59) { + throw new IllegalArgumentException( + "minuteBase must be >=0 and <= 59"); + } + + if (date == null) { + date = new Date(); + } + + Calendar c = Calendar.getInstance(); + c.setTime(date); + c.setLenient(true); + + if (minuteBase == 0) { + c.set(Calendar.HOUR_OF_DAY, c.get(Calendar.HOUR_OF_DAY) + 1); + c.set(Calendar.MINUTE, 0); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + int minute = c.get(Calendar.MINUTE); + + int arItr = minute / minuteBase; + + int nextMinuteOccurance = minuteBase * (arItr + 1); + + if (nextMinuteOccurance < 60) { + c.set(Calendar.MINUTE, nextMinuteOccurance); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } else { + c.set(Calendar.HOUR_OF_DAY, c.get(Calendar.HOUR_OF_DAY) + 1); + c.set(Calendar.MINUTE, 0); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + } + + /** + *

+ * Returns a date that is rounded to the next even multiple of the given + * minute. + *

+ * + *

+ * The rules for calculating the second are the same as those for + * calculating the minute in the method + * getNextGivenMinuteDate(..). + *

+ * + * @param date the Date to round, if null the current time will + * be used + * @param secondBase the base-second to set the time on + * @return the new rounded date + * + * @see #getNextGivenMinuteDate(Date, int) + */ + public static Date getNextGivenSecondDate(Date date, int secondBase) { + if (secondBase < 0 || secondBase > 59) { + throw new IllegalArgumentException( + "secondBase must be >=0 and <= 59"); + } + + if (date == null) { + date = new Date(); + } + + Calendar c = Calendar.getInstance(); + c.setTime(date); + c.setLenient(true); + + if (secondBase == 0) { + c.set(Calendar.MINUTE, c.get(Calendar.MINUTE) + 1); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + int second = c.get(Calendar.SECOND); + + int arItr = second / secondBase; + + int nextSecondOccurance = secondBase * (arItr + 1); + + if (nextSecondOccurance < 60) { + c.set(Calendar.SECOND, nextSecondOccurance); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } else { + c.set(Calendar.MINUTE, c.get(Calendar.MINUTE) + 1); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + } + + /** + *

+ * Get a Date object that represents the given time, on + * today's date. + *

+ * + * @param second + * The value (0-59) to give the seconds field of the date + * @param minute + * The value (0-59) to give the minutes field of the date + * @param hour + * The value (0-23) to give the hours field of the date + * @return the new date + */ + public static Date getDateOf(int second, int minute, int hour) { + validateSecond(second); + validateMinute(minute); + validateHour(hour); + + Date date = new Date(); + + Calendar c = Calendar.getInstance(); + c.setTime(date); + c.setLenient(true); + + c.set(Calendar.HOUR_OF_DAY, hour); + c.set(Calendar.MINUTE, minute); + c.set(Calendar.SECOND, second); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + /** + *

+ * Get a Date object that represents the given time, on the + * given date. + *

+ * + * @param second + * The value (0-59) to give the seconds field of the date + * @param minute + * The value (0-59) to give the minutes field of the date + * @param hour + * The value (0-23) to give the hours field of the date + * @param dayOfMonth + * The value (1-31) to give the day of month field of the date + * @param month + * The value (1-12) to give the month field of the date + * @return the new date + */ + public static Date getDateOf(int second, int minute, int hour, + int dayOfMonth, int month) { + validateSecond(second); + validateMinute(minute); + validateHour(hour); + validateDayOfMonth(dayOfMonth); + validateMonth(month); + + Date date = new Date(); + + Calendar c = Calendar.getInstance(); + c.setTime(date); + + c.set(Calendar.MONTH, month - 1); + c.set(Calendar.DAY_OF_MONTH, dayOfMonth); + c.set(Calendar.HOUR_OF_DAY, hour); + c.set(Calendar.MINUTE, minute); + c.set(Calendar.SECOND, second); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + /** + *

+ * Get a Date object that represents the given time, on the + * given date. + *

+ * + * @param second + * The value (0-59) to give the seconds field of the date + * @param minute + * The value (0-59) to give the minutes field of the date + * @param hour + * The value (0-23) to give the hours field of the date + * @param dayOfMonth + * The value (1-31) to give the day of month field of the date + * @param month + * The value (1-12) to give the month field of the date + * @param year + * The value (1970-2099) to give the year field of the date + * @return the new date + */ + public static Date getDateOf(int second, int minute, int hour, + int dayOfMonth, int month, int year) { + validateSecond(second); + validateMinute(minute); + validateHour(hour); + validateDayOfMonth(dayOfMonth); + validateMonth(month); + validateYear(year); + + Date date = new Date(); + + Calendar c = Calendar.getInstance(); + c.setTime(date); + + c.set(Calendar.YEAR, year); + c.set(Calendar.MONTH, month - 1); + c.set(Calendar.DAY_OF_MONTH, dayOfMonth); + c.set(Calendar.HOUR_OF_DAY, hour); + c.set(Calendar.MINUTE, minute); + c.set(Calendar.SECOND, second); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + /** + * Returns a list of Dates that are the next fire times of a + * Trigger. + * The input trigger will be cloned before any work is done, so you need + * not worry about its state being altered by this method. + * + * @param trigg + * The trigger upon which to do the work + * @param cal + * The calendar to apply to the trigger's schedule + * @param numTimes + * The number of next fire times to produce + * @return List of java.util.Date objects + */ + public static List computeFireTimes(Trigger trigg, com.fr.third.org.quartz.Calendar cal, + int numTimes) { + LinkedList lst = new LinkedList(); + + Trigger t = (Trigger) trigg.clone(); + + if (t.getNextFireTime() == null) { + t.computeFirstFireTime(cal); + } + + for (int i = 0; i < numTimes; i++) { + Date d = t.getNextFireTime(); + if (d != null) { + lst.add(d); + t.triggered(cal); + } else { + break; + } + } + + return java.util.Collections.unmodifiableList(lst); + } + + /** + * Returns a list of Dates that are the next fire times of a + * Trigger + * that fall within the given date range. The input trigger will be cloned + * before any work is done, so you need not worry about its state being + * altered by this method. + * + *

+ * NOTE: if this is a trigger that has previously fired within the given + * date range, then firings which have already occured will not be listed + * in the output List. + *

+ * + * @param trigg + * The trigger upon which to do the work + * @param cal + * The calendar to apply to the trigger's schedule + * @param from + * The starting date at which to find fire times + * @param to + * The ending date at which to stop finding fire times + * @return List of java.util.Date objects + */ + public static List computeFireTimesBetween(Trigger trigg, + com.fr.third.org.quartz.Calendar cal, Date from, Date to) { + LinkedList lst = new LinkedList(); + + Trigger t = (Trigger) trigg.clone(); + + if (t.getNextFireTime() == null) { + t.setStartTime(from); + t.setEndTime(to); + t.computeFirstFireTime(cal); + } + + // TODO: this method could be more efficient by using logic specific + // to the type of trigger ... + while (true) { + Date d = t.getNextFireTime(); + if (d != null) { + if (d.before(from)) { + t.triggered(cal); + continue; + } + if (d.after(to)) { + break; + } + lst.add(d); + t.triggered(cal); + } else { + break; + } + } + + return java.util.Collections.unmodifiableList(lst); + } + + /** + * Translate a date & time from a users timezone to the another + * (probably server) timezone to assist in creating a simple trigger with + * the right date & time. + * + * @param date the date to translate + * @param src the original time-zone + * @param dest the destination time-zone + * @return the translated date + */ + public static Date translateTime(Date date, TimeZone src, TimeZone dest) { + + Date newDate = new Date(); + + int offset = ( + getOffset(date.getTime(), dest) - getOffset(date.getTime(), src)); + + newDate.setTime(date.getTime() - offset); + + return newDate; + } + + /** + * Gets the offset from UT for the given date in the given timezone, + * taking into account daylight savings. + * + *

+ * Equivalent of TimeZone.getOffset(date) in JDK 1.4, but Quartz is trying + * to support JDK 1.3. + *

+ * + * @param date the date (in milliseconds) that is the base for the offset + * @param tz the time-zone to calculate to offset to + * @return the offset + */ + public static int getOffset(long date, TimeZone tz) { + + if (tz.inDaylightTime(new Date(date))) { + return tz.getRawOffset() + getDSTSavings(tz); + } + + return tz.getRawOffset(); + } + + /** + *

+ * Equivalent of TimeZone.getDSTSavings() in JDK 1.4, but Quartz is trying + * to support JDK 1.3. + *

+ * + * @param tz the target time-zone + * @return the amount of saving time in milliseconds + */ + public static int getDSTSavings(TimeZone tz) { + + if (tz.useDaylightTime()) { + return 3600000; + } + return 0; + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/UnableToInterruptJobException.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/UnableToInterruptJobException.java new file mode 100644 index 000000000..d9e0440c3 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/UnableToInterruptJobException.java @@ -0,0 +1,63 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ + +package com.fr.third.org.quartz; + +/** + *

+ * An exception that is thrown to indicate that a call to + * InterruptableJob.interrupt() failed without interrupting the Job. + *

+ * + * @see com.fr.third.org.quartz.InterruptableJob#interrupt() + * + * @author James House + */ +public class UnableToInterruptJobException extends SchedulerException { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Create a UnableToInterruptJobException with the given message. + *

+ */ + public UnableToInterruptJobException(String msg) { + super(msg); + } + + /** + *

+ * Create a UnableToInterruptJobException with the given cause. + *

+ */ + public UnableToInterruptJobException(Throwable cause) { + super(cause); + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/collections/AbstractCollectionDecorator.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/collections/AbstractCollectionDecorator.java new file mode 100644 index 000000000..3bce5d918 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/collections/AbstractCollectionDecorator.java @@ -0,0 +1,132 @@ +package com.fr.third.org.quartz.collections; + + +import java.util.Collection; +import java.util.Iterator; + +/** + * Decorates another Collection to provide additional behaviour. + *

+ * Each method call made on this Collection is forwarded to the + * decorated Collection. This class is used as a framework on which + * to build to extensions such as synchronized and unmodifiable behaviour. The + * main advantage of decoration is that one decorator can wrap any implementation + * of Collection, whereas sub-classing requires a new class to be + * written for each implementation. + *

+ * This implementation does not perform any special processing with + * {@link #iterator()}. Instead it simply returns the value from the + * wrapped collection. This may be undesirable, for example if you are trying + * to write an unmodifiable implementation it might provide a loophole. + * + * @since Commons Collections 3.0 + * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $ + * + * @author Stephen Colebourne + * @author Paul Jack + */ +public abstract class AbstractCollectionDecorator implements Collection { + + /** The collection being decorated */ + protected Collection collection; + + /** + * Constructor only used in deserialization, do not use otherwise. + * @since Commons Collections 3.1 + */ + protected AbstractCollectionDecorator() { + super(); + } + + /** + * Constructor that wraps (not copies). + * + * @param coll the collection to decorate, must not be null + * @throws IllegalArgumentException if the collection is null + */ + protected AbstractCollectionDecorator(Collection coll) { + if (coll == null) { + throw new IllegalArgumentException("Collection must not be null"); + } + this.collection = coll; + } + + /** + * Gets the collection being decorated. + * + * @return the decorated collection + */ + protected Collection getCollection() { + return collection; + } + + //----------------------------------------------------------------------- + public boolean add(Object object) { + return collection.add(object); + } + + public boolean addAll(Collection coll) { + return collection.addAll(coll); + } + + public void clear() { + collection.clear(); + } + + public boolean contains(Object object) { + return collection.contains(object); + } + + public boolean isEmpty() { + return collection.isEmpty(); + } + + public Iterator iterator() { + return collection.iterator(); + } + + public boolean remove(Object object) { + return collection.remove(object); + } + + public int size() { + return collection.size(); + } + + public Object[] toArray() { + return collection.toArray(); + } + + public Object[] toArray(Object[] object) { + return collection.toArray(object); + } + + public boolean containsAll(Collection coll) { + return collection.containsAll(coll); + } + + public boolean removeAll(Collection coll) { + return collection.removeAll(coll); + } + + public boolean retainAll(Collection coll) { + return collection.retainAll(coll); + } + + public boolean equals(Object object) { + if (object == this) { + return true; + } + return collection.equals(object); + } + + public int hashCode() { + return collection.hashCode(); + } + + public String toString() { + return collection.toString(); + } + +} + diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/collections/AbstractIteratorDecorator.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/collections/AbstractIteratorDecorator.java new file mode 100644 index 000000000..f6c9660a5 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/collections/AbstractIteratorDecorator.java @@ -0,0 +1,58 @@ +package com.fr.third.org.quartz.collections; + +import java.util.Iterator; + +/** + * Provides basic behaviour for decorating an iterator with extra functionality. + *

+ * All methods are forwarded to the decorated iterator. + * + * @since Commons Collections 3.0 + * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $ + * + * @author James Strachan + * @author Stephen Colebourne + */ +public class AbstractIteratorDecorator implements Iterator { + + /** The iterator being decorated */ + protected final Iterator iterator; + + //----------------------------------------------------------------------- + /** + * Constructor that decorates the specified iterator. + * + * @param iterator the iterator to decorate, must not be null + * @throws IllegalArgumentException if the collection is null + */ + public AbstractIteratorDecorator(Iterator iterator) { + super(); + if (iterator == null) { + throw new IllegalArgumentException("Iterator must not be null"); + } + this.iterator = iterator; + } + + /** + * Gets the iterator being decorated. + * + * @return the decorated iterator + */ + protected Iterator getIterator() { + return iterator; + } + + //----------------------------------------------------------------------- + public boolean hasNext() { + return iterator.hasNext(); + } + + public Object next() { + return iterator.next(); + } + + public void remove() { + iterator.remove(); + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/collections/AbstractSerializableSetDecorator.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/collections/AbstractSerializableSetDecorator.java new file mode 100644 index 000000000..5aa737162 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/collections/AbstractSerializableSetDecorator.java @@ -0,0 +1,55 @@ +package com.fr.third.org.quartz.collections; + + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Set; + +/** + * Serializable subclass of AbstractSetDecorator. + * + * @author Stephen Colebourne + * @since Commons Collections 3.1 + */ +public abstract class AbstractSerializableSetDecorator + extends AbstractSetDecorator + implements Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 1229469966212206107L; + + /** + * Constructor. + */ + protected AbstractSerializableSetDecorator(Set set) { + super(set); + } + + //----------------------------------------------------------------------- + /** + * Write the set out using a custom routine. + * + * @param out the output stream + * @throws IOException + */ + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(collection); + } + + /** + * Read the set in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + */ + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + collection = (Collection) in.readObject(); + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/collections/AbstractSetDecorator.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/collections/AbstractSetDecorator.java new file mode 100644 index 000000000..18a610c33 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/collections/AbstractSetDecorator.java @@ -0,0 +1,44 @@ +package com.fr.third.org.quartz.collections; + +import java.util.Set; + +/** + * Decorates another Set to provide additional behaviour. + *

+ * Methods are forwarded directly to the decorated set. + * + * @since Commons Collections 3.0 + * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $ + * + * @author Stephen Colebourne + */ +public abstract class AbstractSetDecorator extends AbstractCollectionDecorator implements Set { + + /** + * Constructor only used in deserialization, do not use otherwise. + * @since Commons Collections 3.1 + */ + protected AbstractSetDecorator() { + super(); + } + + /** + * Constructor that wraps (not copies). + * + * @param set the set to decorate, must not be null + * @throws IllegalArgumentException if set is null + */ + protected AbstractSetDecorator(Set set) { + super(set); + } + + /** + * Gets the set being decorated. + * + * @return the decorated set + */ + protected Set getSet() { + return (Set) getCollection(); + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/collections/ListOrderedSet.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/collections/ListOrderedSet.java new file mode 100644 index 000000000..62af6910a --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/collections/ListOrderedSet.java @@ -0,0 +1,296 @@ +package com.fr.third.org.quartz.collections; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import com.fr.third.org.quartz.collections.AbstractIteratorDecorator; + +/** + * Decorates another Set to ensure that the order of addition + * is retained and used by the iterator. + *

+ * If an object is added to the set for a second time, it will remain in the + * original position in the iteration. + * The order can be observed from the set via the iterator or toArray methods. + *

+ * The ListOrderedSet also has various useful direct methods. These include many + * from List, such as get(int), remove(int) + * and indexOf(int). An unmodifiable List view of + * the set can be obtained via asList(). + *

+ * This class cannot implement the List interface directly as + * various interface methods (notably equals/hashCode) are incompatable with a set. + *

+ * This class is Serializable from Commons Collections 3.1. + * + * @since Commons Collections 3.0 + * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $ + * + * @author Stephen Colebourne + * @author Henning P. Schmiedehausen + */ +public class ListOrderedSet extends AbstractSerializableSetDecorator implements Set { + + /** Serialization version */ + private static final long serialVersionUID = -228664372470420141L; + + /** Internal list to hold the sequence of objects */ + protected final List setOrder; + + /** + * Factory method to create an ordered set specifying the list and set to use. + *

+ * The list and set must both be empty. + * + * @param set the set to decorate, must be empty and not null + * @param list the list to decorate, must be empty and not null + * @throws IllegalArgumentException if set or list is null + * @throws IllegalArgumentException if either the set or list is not empty + * @since Commons Collections 3.1 + */ + public static ListOrderedSet decorate(Set set, List list) { + if (set == null) { + throw new IllegalArgumentException("Set must not be null"); + } + if (list == null) { + throw new IllegalArgumentException("List must not be null"); + } + if (set.size() > 0 || list.size() > 0) { + throw new IllegalArgumentException("Set and List must be empty"); + } + return new ListOrderedSet(set, list); + } + + /** + * Factory method to create an ordered set. + *

+ * An ArrayList is used to retain order. + * + * @param set the set to decorate, must not be null + * @throws IllegalArgumentException if set is null + */ + public static ListOrderedSet decorate(Set set) { + return new ListOrderedSet(set); + } + + /** + * Factory method to create an ordered set using the supplied list to retain order. + *

+ * A HashSet is used for the set behaviour. + *

+ * NOTE: If the list contains duplicates, the duplicates are removed, + * altering the specified list. + * + * @param list the list to decorate, must not be null + * @throws IllegalArgumentException if list is null + */ + public static ListOrderedSet decorate(List list) { + if (list == null) { + throw new IllegalArgumentException("List must not be null"); + } + Set set = new HashSet(list); + list.retainAll(set); + + return new ListOrderedSet(set, list); + } + + //----------------------------------------------------------------------- + /** + * Constructs a new empty ListOrderedSet using + * a HashSet and an ArrayList internally. + * + * @since Commons Collections 3.1 + */ + public ListOrderedSet() { + super(new HashSet()); + setOrder = new ArrayList(); + } + + /** + * Constructor that wraps (not copies). + * + * @param set the set to decorate, must not be null + * @throws IllegalArgumentException if set is null + */ + protected ListOrderedSet(Set set) { + super(set); + setOrder = new ArrayList(set); + } + + /** + * Constructor that wraps (not copies) the Set and specifies the list to use. + *

+ * The set and list must both be correctly initialised to the same elements. + * + * @param set the set to decorate, must not be null + * @param list the list to decorate, must not be null + * @throws IllegalArgumentException if set or list is null + */ + protected ListOrderedSet(Set set, List list) { + super(set); + if (list == null) { + throw new IllegalArgumentException("List must not be null"); + } + setOrder = list; + } + + //----------------------------------------------------------------------- + /** + * Gets an unmodifiable view of the order of the Set. + * + * @return an unmodifiable list view + */ +//p:ûбҪתList +// public List asList() { +// return UnmodifiableList.decorate(setOrder); +// } + + //----------------------------------------------------------------------- + public void clear() { + collection.clear(); + setOrder.clear(); + } + + public Iterator iterator() { + return new OrderedSetIterator(setOrder.iterator(), collection); + } + + public boolean add(Object object) { + if (collection.contains(object)) { + // re-adding doesn't change order + return collection.add(object); + } else { + // first add, so add to both set and list + boolean result = collection.add(object); + setOrder.add(object); + return result; + } + } + + public boolean addAll(Collection coll) { + boolean result = false; + for (Iterator it = coll.iterator(); it.hasNext();) { + Object object = it.next(); + result = result | add(object); + } + return result; + } + + public boolean remove(Object object) { + boolean result = collection.remove(object); + setOrder.remove(object); + return result; + } + + public boolean removeAll(Collection coll) { + boolean result = false; + for (Iterator it = coll.iterator(); it.hasNext();) { + Object object = it.next(); + result = result | remove(object); + } + return result; + } + + public boolean retainAll(Collection coll) { + boolean result = collection.retainAll(coll); + if (result == false) { + return false; + } else if (collection.size() == 0) { + setOrder.clear(); + } else { + for (Iterator it = setOrder.iterator(); it.hasNext();) { + Object object = it.next(); + if (collection.contains(object) == false) { + it.remove(); + } + } + } + return result; + } + + public Object[] toArray() { + return setOrder.toArray(); + } + + public Object[] toArray(Object a[]) { + return setOrder.toArray(a); + } + + //----------------------------------------------------------------------- + public Object get(int index) { + return setOrder.get(index); + } + + public int indexOf(Object object) { + return setOrder.indexOf(object); + } + + public void add(int index, Object object) { + if (contains(object) == false) { + collection.add(object); + setOrder.add(index, object); + } + } + + public boolean addAll(int index, Collection coll) { + boolean changed = false; + for (Iterator it = coll.iterator(); it.hasNext();) { + Object object = it.next(); + if (contains(object) == false) { + collection.add(object); + setOrder.add(index, object); + index++; + changed = true; + } + } + return changed; + } + + public Object remove(int index) { + Object obj = setOrder.remove(index); + remove(obj); + return obj; + } + + /** + * Uses the underlying List's toString so that order is achieved. + * This means that the decorated Set's toString is not used, so + * any custom toStrings will be ignored. + */ + // Fortunately List.toString and Set.toString look the same + public String toString() { + return setOrder.toString(); + } + + //----------------------------------------------------------------------- + /** + * Internal iterator handle remove. + */ + static class OrderedSetIterator extends AbstractIteratorDecorator { + + /** Object we iterate on */ + protected final Collection set; + /** Last object retrieved */ + protected Object last; + + private OrderedSetIterator(Iterator iterator, Collection set) { + super(iterator); + this.set = set; + } + + public Object next() { + last = iterator.next(); + return last; + } + + public void remove() { + set.remove(last); + iterator.remove(); + last = null; + } + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/JobRunShell.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/JobRunShell.java new file mode 100644 index 000000000..c5af60faa --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/JobRunShell.java @@ -0,0 +1,431 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.core; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.Job; +import com.fr.third.org.quartz.JobDetail; +import com.fr.third.org.quartz.JobExecutionContext; +import com.fr.third.org.quartz.JobExecutionException; +import com.fr.third.org.quartz.JobPersistenceException; +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.Trigger; +import com.fr.third.org.quartz.spi.TriggerFiredBundle; + +/** + *

+ * JobRunShell instances are responsible for providing the 'safe' environment + * for Job s to run in, and for performing all of the work of + * executing the Job, catching ANY thrown exceptions, updating + * the Trigger with the Job's completion code, + * etc. + *

+ * + *

+ * A JobRunShell instance is created by a JobRunShellFactory + * on behalf of the QuartzSchedulerThread which then runs the + * shell in a thread from the configured ThreadPool when the + * scheduler determines that a Job has been triggered. + *

+ * + * @see JobRunShellFactory + * @see com.fr.third.org.quartz.core.QuartzSchedulerThread + * @see com.fr.third.org.quartz.Job + * @see com.fr.third.org.quartz.Trigger + * + * @author James House + */ +public class JobRunShell implements Runnable { + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + protected JobExecutionContext jec = null; + + protected QuartzScheduler qs = null; + + protected Scheduler scheduler = null; + + protected SchedulingContext schdCtxt = null; + + protected JobRunShellFactory jobRunShellFactory = null; + + protected boolean shutdownRequested = false; + + private final Log log = LogFactory.getLog(getClass()); + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Create a JobRunShell instance with the given settings. + *

+ * + * @param jobRunShellFactory + * A handle to the JobRunShellFactory that produced + * this JobRunShell. + * @param scheduler + * The Scheduler instance that should be made + * available within the JobExecutionContext. + * @param schdCtxt + * the SchedulingContext that should be used by the + * JobRunShell when making updates to the JobStore. + */ + public JobRunShell(JobRunShellFactory jobRunShellFactory, + Scheduler scheduler, SchedulingContext schdCtxt) { + this.jobRunShellFactory = jobRunShellFactory; + this.scheduler = scheduler; + this.schdCtxt = schdCtxt; + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + protected Log getLog() { + return log; + } + + public void initialize(QuartzScheduler qs, TriggerFiredBundle firedBundle) + throws SchedulerException { + this.qs = qs; + + Job job = null; + JobDetail jobDetail = firedBundle.getJobDetail(); + + try { + job = qs.getJobFactory().newJob(firedBundle); + } catch (SchedulerException se) { + qs.notifySchedulerListenersError( + "An error occured instantiating job to be executed. job= '" + + jobDetail.getFullName() + "'", se); + throw se; + } catch (Throwable ncdfe) { // such as NoClassDefFoundError + SchedulerException se = new SchedulerException( + "Problem instantiating class '" + + jobDetail.getJobClass().getName() + "' - ", ncdfe); + qs.notifySchedulerListenersError( + "An error occured instantiating job to be executed. job= '" + + jobDetail.getFullName() + "'", se); + throw se; + } + + this.jec = new JobExecutionContext(scheduler, firedBundle, job); + } + + public void requestShutdown() { + shutdownRequested = true; + } + + public void run() { + try { + Trigger trigger = jec.getTrigger(); + JobDetail jobDetail = jec.getJobDetail(); + + do { + + JobExecutionException jobExEx = null; + Job job = jec.getJobInstance(); + + try { + begin(); + } catch (SchedulerException se) { + qs.notifySchedulerListenersError("Error executing Job (" + + jec.getJobDetail().getFullName() + + ": couldn't begin execution.", se); + break; + } + + // notify job & trigger listeners... + try { + if (!notifyListenersBeginning(jec)) { + break; + } + } catch(VetoedException ve) { + try { + int instCode = trigger.executionComplete(jec, null); + try { + qs.notifyJobStoreJobVetoed(schdCtxt, trigger, jobDetail, instCode); + } catch(JobPersistenceException jpe) { + vetoedJobRetryLoop(trigger, jobDetail, instCode); + } + complete(true); + } catch (SchedulerException se) { + qs.notifySchedulerListenersError("Error during veto of Job (" + + jec.getJobDetail().getFullName() + + ": couldn't finalize execution.", se); + } + break; + } + + long startTime = System.currentTimeMillis(); + long endTime = startTime; + + // execute the job + try { + log.debug("Calling execute on job " + jobDetail.getFullName()); + job.execute(jec); + endTime = System.currentTimeMillis(); + } catch (JobExecutionException jee) { + endTime = System.currentTimeMillis(); + jobExEx = jee; + getLog().info("Job " + jobDetail.getFullName() + + " threw a JobExecutionException: ", jobExEx); + } catch (Throwable e) { + endTime = System.currentTimeMillis(); + getLog().error("Job " + jobDetail.getFullName() + + " threw an unhandled Exception: ", e); + SchedulerException se = new SchedulerException( + "Job threw an unhandled exception.", e); + se.setErrorCode(SchedulerException.ERR_JOB_EXECUTION_THREW_EXCEPTION); + qs.notifySchedulerListenersError("Job (" + + jec.getJobDetail().getFullName() + + " threw an exception.", se); + jobExEx = new JobExecutionException(se, false); + jobExEx.setErrorCode(JobExecutionException.ERR_JOB_EXECUTION_THREW_EXCEPTION); + } + + jec.setJobRunTime(endTime - startTime); + + // notify all job listeners + if (!notifyJobListenersComplete(jec, jobExEx)) { + break; + } + + int instCode = Trigger.INSTRUCTION_NOOP; + + // update the trigger + try { + instCode = trigger.executionComplete(jec, jobExEx); + } catch (Exception e) { + // If this happens, there's a bug in the trigger... + SchedulerException se = new SchedulerException( + "Trigger threw an unhandled exception.", e); + se.setErrorCode(SchedulerException.ERR_TRIGGER_THREW_EXCEPTION); + qs.notifySchedulerListenersError( + "Please report this error to the Quartz developers.", + se); + } + + // notify all trigger listeners + if (!notifyTriggerListenersComplete(jec, instCode)) { + break; + } + + // update job/trigger or re-execute job + if (instCode == Trigger.INSTRUCTION_RE_EXECUTE_JOB) { + jec.incrementRefireCount(); + try { + complete(false); + } catch (SchedulerException se) { + qs.notifySchedulerListenersError("Error executing Job (" + + jec.getJobDetail().getFullName() + + ": couldn't finalize execution.", se); + } + continue; + } + + try { + complete(true); + } catch (SchedulerException se) { + qs.notifySchedulerListenersError("Error executing Job (" + + jec.getJobDetail().getFullName() + + ": couldn't finalize execution.", se); + continue; + } + + try { + qs.notifyJobStoreJobComplete(schdCtxt, trigger, jobDetail, + instCode); + } catch (JobPersistenceException jpe) { + qs.notifySchedulerListenersError( + "An error occured while marking executed job complete. job= '" + + jobDetail.getFullName() + "'", jpe); + if (!completeTriggerRetryLoop(trigger, jobDetail, instCode)) { + return; + } + } + + break; + } while (true); + + } finally { + jobRunShellFactory.returnJobRunShell(this); + } + } + + protected void begin() throws SchedulerException { + } + + protected void complete(boolean successfulExecution) + throws SchedulerException { + } + + public void passivate() { + jec = null; + qs = null; + } + + private boolean notifyListenersBeginning(JobExecutionContext jec) throws VetoedException { + + boolean vetoed = false; + + // notify all trigger listeners + try { + vetoed = qs.notifyTriggerListenersFired(jec); + } catch (SchedulerException se) { + qs.notifySchedulerListenersError( + "Unable to notify TriggerListener(s) while firing trigger " + + "(Trigger and Job will NOT be fired!). trigger= " + + jec.getTrigger().getFullName() + " job= " + + jec.getJobDetail().getFullName(), se); + + return false; + } + + if(vetoed) { + try { + qs.notifyJobListenersWasVetoed(jec); + } catch (SchedulerException se) { + qs.notifySchedulerListenersError( + "Unable to notify JobListener(s) of vetoed execution " + + "while firing trigger (Trigger and Job will NOT be " + + "fired!). trigger= " + + jec.getTrigger().getFullName() + " job= " + + jec.getJobDetail().getFullName(), se); + + } + throw new VetoedException(); + } + + // notify all job listeners + try { + qs.notifyJobListenersToBeExecuted(jec); + } catch (SchedulerException se) { + qs.notifySchedulerListenersError( + "Unable to notify JobListener(s) of Job to be executed: " + + "(Job will NOT be executed!). trigger= " + + jec.getTrigger().getFullName() + " job= " + + jec.getJobDetail().getFullName(), se); + + return false; + } + + return true; + } + + private boolean notifyJobListenersComplete(JobExecutionContext jec, + JobExecutionException jobExEx) { + try { + qs.notifyJobListenersWasExecuted(jec, jobExEx); + } catch (SchedulerException se) { + qs.notifySchedulerListenersError( + "Unable to notify JobListener(s) of Job that was executed: " + + "(error will be ignored). trigger= " + + jec.getTrigger().getFullName() + " job= " + + jec.getJobDetail().getFullName(), se); + + return false; + } + + return true; + } + + private boolean notifyTriggerListenersComplete(JobExecutionContext jec, + int instCode) { + try { + qs.notifyTriggerListenersComplete(jec, instCode); + + } catch (SchedulerException se) { + qs.notifySchedulerListenersError( + "Unable to notify TriggerListener(s) of Job that was executed: " + + "(error will be ignored). trigger= " + + jec.getTrigger().getFullName() + " job= " + + jec.getJobDetail().getFullName(), se); + + return false; + } + if (jec.getTrigger().getNextFireTime() == null) { + qs.notifySchedulerListenersFinalized(jec.getTrigger()); + } + + return true; + } + + public boolean completeTriggerRetryLoop(Trigger trigger, + JobDetail jobDetail, int instCode) { + while (!shutdownRequested) { + try { + Thread.sleep(5 * 1000L); // retry every 5 seconds (the db + // connection must be failed) + qs.notifyJobStoreJobComplete(schdCtxt, trigger, jobDetail, + instCode); + return true; + } catch (JobPersistenceException jpe) { + qs.notifySchedulerListenersError( + "An error occured while marking executed job complete. job= '" + + jobDetail.getFullName() + "'", jpe); + } catch (InterruptedException ignore) { + } + } + return false; + } + + public boolean vetoedJobRetryLoop(Trigger trigger, JobDetail jobDetail, int instCode) { + while (!shutdownRequested) { + try { + Thread.sleep(5 * 1000L); // retry every 5 seconds (the db + // connection must be failed) + qs.notifyJobStoreJobVetoed(schdCtxt, trigger, jobDetail, instCode); + return true; + } catch (JobPersistenceException jpe) { + qs.notifySchedulerListenersError( + "An error occured while marking executed job vetoed. job= '" + + jobDetail.getFullName() + "'", jpe); + } catch (InterruptedException ignore) { + } + } + return false; + } + + class VetoedException extends Exception { + public VetoedException() { + } + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/JobRunShellFactory.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/JobRunShellFactory.java new file mode 100644 index 000000000..c6d781a87 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/JobRunShellFactory.java @@ -0,0 +1,82 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.core; + +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SchedulerConfigException; +import com.fr.third.org.quartz.SchedulerException; + +/** + *

+ * Responsible for creating the instances of {@link JobRunShell} + * to be used within the {@link QuartzScheduler} instance. + *

+ * + *

+ * Although this interface looks a lot like an 'object pool', implementations + * do not have to support the re-use of instances. If an implementation does + * not wish to pool instances, then the borrowJobRunShell() + * method would simply create a new instance, and the returnJobRunShell + * method would do nothing. + *

+ * + * @author James House + */ +public interface JobRunShellFactory { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Initialize the factory, providing a handle to the Scheduler + * that should be made available within the JobRunShell and + * the JobExecutionCOntext s within it, and a handle to the + * SchedulingContext that the shell will use in its own + * operations with the JobStore. + *

+ */ + void initialize(Scheduler scheduler, SchedulingContext schedCtxt) + throws SchedulerConfigException; + + /** + *

+ * Called by the {@link com.fr.third.org.quartz.core.QuartzSchedulerThread} + * to obtain instances of {@link JobRunShell}. + *

+ */ + JobRunShell borrowJobRunShell() throws SchedulerException; + + /** + *

+ * Called by the {@link com.fr.third.org.quartz.core.QuartzSchedulerThread} + * to return instances of {@link JobRunShell}. + *

+ */ + void returnJobRunShell(JobRunShell jobRunShell); + +} \ No newline at end of file diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/QuartzScheduler.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/QuartzScheduler.java new file mode 100644 index 000000000..3d1c81a3f --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/QuartzScheduler.java @@ -0,0 +1,2245 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.core; + +import java.io.IOException; +import java.io.InputStream; +import java.rmi.RemoteException; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.rmi.server.UnicastRemoteObject; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; +import java.util.Random; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.Calendar; +import com.fr.third.org.quartz.InterruptableJob; +import com.fr.third.org.quartz.Job; +import com.fr.third.org.quartz.JobDataMap; +import com.fr.third.org.quartz.JobDetail; +import com.fr.third.org.quartz.JobExecutionContext; +import com.fr.third.org.quartz.JobExecutionException; +import com.fr.third.org.quartz.JobListener; +import com.fr.third.org.quartz.JobPersistenceException; +import com.fr.third.org.quartz.ObjectAlreadyExistsException; +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SchedulerContext; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.SchedulerListener; +import com.fr.third.org.quartz.listeners.SchedulerListenerSupport; +import com.fr.third.org.quartz.Trigger; +import com.fr.third.org.quartz.TriggerListener; +import com.fr.third.org.quartz.UnableToInterruptJobException; +import com.fr.third.org.quartz.impl.SchedulerRepository; +import com.fr.third.org.quartz.simpl.SimpleJobFactory; +import com.fr.third.org.quartz.spi.JobFactory; +import com.fr.third.org.quartz.spi.SchedulerPlugin; +import com.fr.third.org.quartz.spi.SchedulerSignaler; + +/** + *

+ * This is the heart of Quartz, an indirect implementation of the {@link com.fr.third.org.quartz.Scheduler} + * interface, containing methods to schedule {@link com.fr.third.org.quartz.Job}s, + * register {@link com.fr.third.org.quartz.JobListener} instances, etc. + *

// TODO: more docs... + * + * @see com.fr.third.org.quartz.Scheduler + * @see com.fr.third.org.quartz.core.QuartzSchedulerThread + * @see com.fr.third.org.quartz.spi.JobStore + * @see com.fr.third.org.quartz.spi.ThreadPool + * + * @author James House + */ +public class QuartzScheduler implements RemotableQuartzScheduler { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private static String VERSION_MAJOR = "UNKNOWN"; + private static String VERSION_MINOR = "UNKNOWN"; + private static String VERSION_ITERATION = "UNKNOWN"; + + static { + Properties props = new Properties(); + InputStream is = null; + try { + is = QuartzScheduler.class.getResourceAsStream("/build.properties"); + if(is != null) { + props.load(is); + VERSION_MAJOR = props.getProperty("version.major"); + VERSION_MINOR = props.getProperty("version.minor"); + VERSION_ITERATION = props.getProperty("version.iter"); + } + } catch (IOException e) { + (LogFactory.getLog(QuartzScheduler.class)).error( + "Error loading version info from build.properties.", e); + } finally { + if(is != null) { + try { is.close(); } catch(Exception ignore) {} + } + } + + } + + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private QuartzSchedulerResources resources; + + private QuartzSchedulerThread schedThread; + + private ThreadGroup threadGroup; + + private SchedulerContext context = new SchedulerContext(); + + private HashMap jobListeners = new HashMap(10); + + private HashMap globalJobListeners = new HashMap(10); + + private HashMap triggerListeners = new HashMap(10); + + private HashMap globalTriggerListeners = new HashMap(10); + + private ArrayList schedulerListeners = new ArrayList(10); + + private JobFactory jobFactory = new SimpleJobFactory(); + + ExecutingJobsManager jobMgr = null; + + ErrorLogger errLogger = null; + + private SchedulerSignaler signaler; + + private Random random = new Random(); + + private ArrayList holdToPreventGC = new ArrayList(5); + + private boolean signalOnSchedulingChange = true; + + private boolean closed = false; + private boolean shuttingDown = false; + + private Date initialStart = null; + + private final Log log = LogFactory.getLog(getClass()); + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Create a QuartzScheduler with the given configuration + * properties. + *

+ * + * @see QuartzSchedulerResources + */ + public QuartzScheduler(QuartzSchedulerResources resources, + SchedulingContext ctxt, long idleWaitTime, long dbRetryInterval) + throws SchedulerException { + this.resources = resources; + try { + bind(); + } catch (Exception re) { + throw new SchedulerException( + "Unable to bind scheduler to RMI Registry.", re); + } + + if (resources.getJMXExport()) { + try { + registerJMX(); + } catch (Exception e) { + throw new SchedulerException( + "Unable to register scheduler with MBeanServer.", e); + } + } + + this.schedThread = new QuartzSchedulerThread(this, resources, ctxt); + if (idleWaitTime > 0) { + this.schedThread.setIdleWaitTime(idleWaitTime); + } + if (dbRetryInterval > 0) { + this.schedThread.setDbFailureRetryInterval(dbRetryInterval); + } + + jobMgr = new ExecutingJobsManager(); + addGlobalJobListener(jobMgr); + errLogger = new ErrorLogger(); + addSchedulerListener(errLogger); + + signaler = new SchedulerSignalerImpl(this, this.schedThread); + + getLog().info("Quartz Scheduler v." + getVersion() + " created."); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public String getVersion() { + return getVersionMajor() + "." + getVersionMinor() + "." + + getVersionIteration(); + } + + public static String getVersionMajor() { + return VERSION_MAJOR; + } + + public static String getVersionMinor() { + return VERSION_MINOR; + } + + public static String getVersionIteration() { + return VERSION_ITERATION; + } + + public SchedulerSignaler getSchedulerSignaler() { + return signaler; + } + + public Log getLog() { + return log; + } + + /** + * Register the scheduler in the local MBeanServer. + */ + private void registerJMX() throws Exception { +//p:֧jmx +// org.apache.commons.modeler.Registry registry = +// org.apache.commons.modeler.Registry.getRegistry(null, null); +// +// String jmxObjectName = resources.getJMXObjectName(); +// +// registry.registerComponent(this, jmxObjectName, null); +// +// getLog().info("Scheduler registered with local MBeanServer under name '" + jmxObjectName + "'"); + } + + /** + * Unregister the scheduler from the local MBeanServer. + */ + private void unregisterJMX() throws Exception { +//p:֧jmx +// org.apache.commons.modeler.Registry registry = +// org.apache.commons.modeler.Registry.getRegistry(null, null); +// +// String jmxObjectName = resources.getJMXObjectName(); +// +// registry.unregisterComponent(jmxObjectName); +// +// getLog().info("Scheduler unregistered from name '" + jmxObjectName + "' in the local MBeanServer."); + } + + /** + *

+ * Bind the scheduler to an RMI registry. + *

+ */ + private void bind() throws RemoteException { + String host = resources.getRMIRegistryHost(); + // don't export if we're not configured to do so... + if (host == null || host.length() == 0) { + return; + } + + RemotableQuartzScheduler exportable = null; + + if(resources.getRMIServerPort() > 0) { + exportable = (RemotableQuartzScheduler) UnicastRemoteObject + .exportObject(this, resources.getRMIServerPort()); + } else { + exportable = (RemotableQuartzScheduler) UnicastRemoteObject + .exportObject(this); + } + + Registry registry = null; + + if (resources.getRMICreateRegistryStrategy().equals( + QuartzSchedulerResources.CREATE_REGISTRY_AS_NEEDED)) { + try { + // First try to get an existing one, instead of creating it, + // since if + // we're in a web-app being 'hot' re-depoloyed, then the JVM + // still + // has the registry that we created above the first time... + registry = LocateRegistry.getRegistry(resources + .getRMIRegistryPort()); + registry.list(); + } catch (Exception e) { + registry = LocateRegistry.createRegistry(resources + .getRMIRegistryPort()); + } + } else if (resources.getRMICreateRegistryStrategy().equals( + QuartzSchedulerResources.CREATE_REGISTRY_ALWAYS)) { + try { + registry = LocateRegistry.createRegistry(resources + .getRMIRegistryPort()); + } catch (Exception e) { + // Fall back to an existing one, instead of creating it, since + // if + // we're in a web-app being 'hot' re-depoloyed, then the JVM + // still + // has the registry that we created above the first time... + registry = LocateRegistry.getRegistry(resources + .getRMIRegistryPort()); + } + } else { + registry = LocateRegistry.getRegistry(resources + .getRMIRegistryHost(), resources.getRMIRegistryPort()); + } + + String bindName = resources.getRMIBindName(); + + registry.rebind(bindName, exportable); + + getLog().info("Scheduler bound to RMI registry under name '" + bindName + "'"); + } + + /** + *

+ * Un-bind the scheduler from an RMI registry. + *

+ */ + private void unBind() throws RemoteException { + String host = resources.getRMIRegistryHost(); + // don't un-export if we're not configured to do so... + if (host == null || host.length() == 0) { + return; + } + + Registry registry = LocateRegistry.getRegistry(resources + .getRMIRegistryHost(), resources.getRMIRegistryPort()); + + String bindName = resources.getRMIBindName(); + + try { + registry.unbind(bindName); + UnicastRemoteObject.unexportObject(this, true); + } catch (java.rmi.NotBoundException nbe) { + } + + getLog().info("Scheduler un-bound from name '" + bindName + "' in RMI registry"); + } + + /** + *

+ * Returns the name of the QuartzScheduler. + *

+ */ + public String getSchedulerName() { + return resources.getName(); + } + + /** + *

+ * Returns the instance Id of the QuartzScheduler. + *

+ */ + public String getSchedulerInstanceId() { + return resources.getInstanceId(); + } + + /** + *

+ * Returns the name of the QuartzScheduler. + *

+ */ + public ThreadGroup getSchedulerThreadGroup() { + if (threadGroup == null) { + threadGroup = new ThreadGroup("QuartzScheduler:" + + getSchedulerName()); + if (resources.getMakeSchedulerThreadDaemon()) { + threadGroup.setDaemon(true); + } + } + + return threadGroup; + } + + public void addNoGCObject(Object obj) { + holdToPreventGC.add(obj); + } + + public boolean removeNoGCObject(Object obj) { + return holdToPreventGC.remove(obj); + } + + /** + *

+ * Returns the SchedulerContext of the Scheduler. + *

+ */ + public SchedulerContext getSchedulerContext() throws SchedulerException { + return context; + } + + public boolean isSignalOnSchedulingChange() { + return signalOnSchedulingChange; + } + + public void setSignalOnSchedulingChange(boolean signalOnSchedulingChange) { + this.signalOnSchedulingChange = signalOnSchedulingChange; + } + + /////////////////////////////////////////////////////////////////////////// + /// + /// Schedululer State Management Methods + /// + /////////////////////////////////////////////////////////////////////////// + + /** + *

+ * Starts the QuartzScheduler's threads that fire {@link com.fr.third.org.quartz.Trigger}s. + *

+ * + *

+ * All {@link com.fr.third.org.quartz.Trigger}s that have misfired will + * be passed to the appropriate TriggerListener(s). + *

+ */ + public void start() throws SchedulerException { + + if (shuttingDown|| closed) { + throw new SchedulerException( + "The Scheduler cannot be restarted after shutdown() has been called."); + } + + if (initialStart == null) { + initialStart = new Date(); + this.resources.getJobStore().schedulerStarted(); + startPlugins(); + } + + schedThread.togglePause(false); + + getLog().info( + "Scheduler " + resources.getUniqueIdentifier() + " started."); + } + + public void startDelayed(final int seconds) throws SchedulerException + { + if (shuttingDown || closed) { + throw new SchedulerException( + "The Scheduler cannot be restarted after shutdown() has been called."); + } + + Thread t = new Thread(new Runnable() { + public void run() { + try { Thread.sleep(seconds * 1000L); } + catch(InterruptedException ignore) {} + try { start(); } + catch(SchedulerException se) { + getLog().error("Unable to start secheduler after startup delay.", se); + } + } + }); + t.start(); + } + + /** + *

+ * Temporarily halts the QuartzScheduler's firing of {@link com.fr.third.org.quartz.Trigger}s. + *

+ * + *

+ * The scheduler is not destroyed, and can be re-started at any time. + *

+ */ + public void standby() { + schedThread.togglePause(true); + getLog().info( + "Scheduler " + resources.getUniqueIdentifier() + " paused."); + } + + /** + *

+ * Reports whether the Scheduler is paused. + *

+ */ + public boolean isInStandbyMode() { + return schedThread.isPaused(); + } + + public Date runningSince() { + return initialStart; + } + + public int numJobsExecuted() { + return jobMgr.getNumJobsFired(); + } + + public Class getJobStoreClass() { + return resources.getJobStore().getClass(); + } + + public boolean supportsPersistence() { + return resources.getJobStore().supportsPersistence(); + } + + public Class getThreadPoolClass() { + return resources.getThreadPool().getClass(); + } + + public int getThreadPoolSize() { + return resources.getThreadPool().getPoolSize(); + } + + /** + *

+ * Halts the QuartzScheduler's firing of {@link com.fr.third.org.quartz.Trigger}s, + * and cleans up all resources associated with the QuartzScheduler. + * Equivalent to shutdown(false). + *

+ * + *

+ * The scheduler cannot be re-started. + *

+ */ + public void shutdown() { + shutdown(false); + } + + /** + *

+ * Halts the QuartzScheduler's firing of {@link com.fr.third.org.quartz.Trigger}s, + * and cleans up all resources associated with the QuartzScheduler. + *

+ * + *

+ * The scheduler cannot be re-started. + *

+ * + * @param waitForJobsToComplete + * if true the scheduler will not allow this method + * to return until all currently executing jobs have completed. + */ + public void shutdown(boolean waitForJobsToComplete) { + + if(shuttingDown || closed) { + return; + } + + shuttingDown = true; + + getLog().info( + "Scheduler " + resources.getUniqueIdentifier() + + " shutting down."); + standby(); + + + schedThread.halt(); + + resources.getThreadPool().shutdown(waitForJobsToComplete); + + if (waitForJobsToComplete) { + while (jobMgr.getNumJobsCurrentlyExecuting() > 0) { + try { + Thread.sleep(100); + } catch (Exception ignore) { + } + } + } + + // Scheduler thread may have be waiting for the fire time of an acquired + // trigger and need time to release the trigger once halted, so make sure + // the thread is dead before continuing to shutdown the job store. + try { + schedThread.join(); + } catch (InterruptedException ignore) { + } + + closed = true; + + resources.getJobStore().shutdown(); + + notifySchedulerListenersShutdown(); + + shutdownPlugins(); + + SchedulerRepository.getInstance().remove(resources.getName()); + + holdToPreventGC.clear(); + + try { + unBind(); + } catch (RemoteException re) { + } + + if (resources.getJMXExport()) { + try { + unregisterJMX(); + } catch (Exception e) { + } + } + + getLog().info( + "Scheduler " + resources.getUniqueIdentifier() + + " shutdown complete."); + } + + /** + *

+ * Reports whether the Scheduler has been shutdown. + *

+ */ + public boolean isShutdown() { + return closed; + } + + public void validateState() throws SchedulerException { + if (isShutdown()) { + throw new SchedulerException("The Scheduler has been shutdown."); + } + + // other conditions to check (?) + } + + /** + *

+ * Return a list of JobExecutionContext objects that + * represent all currently executing Jobs in this Scheduler instance. + *

+ * + *

+ * This method is not cluster aware. That is, it will only return Jobs + * currently executing in this Scheduler instance, not across the entire + * cluster. + *

+ * + *

+ * Note that the list returned is an 'instantaneous' snap-shot, and that as + * soon as it's returned, the true list of executing jobs may be different. + *

+ */ + public List getCurrentlyExecutingJobs() { + return jobMgr.getExecutingJobs(); + } + + /////////////////////////////////////////////////////////////////////////// + /// + /// Scheduling-related Methods + /// + /////////////////////////////////////////////////////////////////////////// + + /** + *

+ * Add the {@link com.fr.third.org.quartz.Job} identified by the given + * {@link com.fr.third.org.quartz.JobDetail} to the Scheduler, and + * associate the given {@link com.fr.third.org.quartz.Trigger} with it. + *

+ * + *

+ * If the given Trigger does not reference any Job, then it + * will be set to reference the Job passed with it into this method. + *

+ * + * @throws SchedulerException + * if the Job or Trigger cannot be added to the Scheduler, or + * there is an internal Scheduler error. + */ + public Date scheduleJob(SchedulingContext ctxt, JobDetail jobDetail, + Trigger trigger) throws SchedulerException { + validateState(); + + if (jobDetail == null) { + throw new SchedulerException("JobDetail cannot be null", + SchedulerException.ERR_CLIENT_ERROR); + } + + if (trigger == null) { + throw new SchedulerException("Trigger cannot be null", + SchedulerException.ERR_CLIENT_ERROR); + } + + jobDetail.validate(); + + if (trigger.getJobName() == null) { + trigger.setJobName(jobDetail.getName()); + trigger.setJobGroup(jobDetail.getGroup()); + } else if (trigger.getJobName() != null + && !trigger.getJobName().equals(jobDetail.getName())) { + throw new SchedulerException( + "Trigger does not reference given job!", + SchedulerException.ERR_CLIENT_ERROR); + } else if (trigger.getJobGroup() != null + && !trigger.getJobGroup().equals(jobDetail.getGroup())) { + throw new SchedulerException( + "Trigger does not reference given job!", + SchedulerException.ERR_CLIENT_ERROR); + } + + trigger.validate(); + + Calendar cal = null; + if (trigger.getCalendarName() != null) { + cal = resources.getJobStore().retrieveCalendar(ctxt, + trigger.getCalendarName()); + } + Date ft = trigger.computeFirstFireTime(cal); + + if (ft == null) { + throw new SchedulerException( + "Based on configured schedule, the given trigger will never fire.", + SchedulerException.ERR_CLIENT_ERROR); + } + + resources.getJobStore().storeJobAndTrigger(ctxt, jobDetail, trigger); + notifySchedulerThread(trigger.getNextFireTime().getTime()); + notifySchedulerListenersSchduled(trigger); + + return ft; + } + + /** + *

+ * Schedule the given {@link com.fr.third.org.quartz.Trigger} with the + * Job identified by the Trigger's settings. + *

+ * + * @throws SchedulerException + * if the indicated Job does not exist, or the Trigger cannot be + * added to the Scheduler, or there is an internal Scheduler + * error. + */ + public Date scheduleJob(SchedulingContext ctxt, Trigger trigger) + throws SchedulerException { + validateState(); + + if (trigger == null) { + throw new SchedulerException("Trigger cannot be null", + SchedulerException.ERR_CLIENT_ERROR); + } + + trigger.validate(); + + Calendar cal = null; + if (trigger.getCalendarName() != null) { + cal = resources.getJobStore().retrieveCalendar(ctxt, + trigger.getCalendarName()); + if(cal == null) { + throw new SchedulerException( + "Calendar not found: " + trigger.getCalendarName(), + SchedulerException.ERR_PERSISTENCE_CALENDAR_DOES_NOT_EXIST); + } + } + Date ft = trigger.computeFirstFireTime(cal); + + if (ft == null) { + throw new SchedulerException( + "Based on configured schedule, the given trigger will never fire.", + SchedulerException.ERR_CLIENT_ERROR); + } + + resources.getJobStore().storeTrigger(ctxt, trigger, false); + notifySchedulerThread(trigger.getNextFireTime().getTime()); + notifySchedulerListenersSchduled(trigger); + + return ft; + } + + /** + *

+ * Add the given Job to the Scheduler - with no associated + * Trigger. The Job will be 'dormant' until + * it is scheduled with a Trigger, or Scheduler.triggerJob() + * is called for it. + *

+ * + *

+ * The Job must by definition be 'durable', if it is not, + * SchedulerException will be thrown. + *

+ * + * @throws SchedulerException + * if there is an internal Scheduler error, or if the Job is not + * durable, or a Job with the same name already exists, and + * replace is false. + */ + public void addJob(SchedulingContext ctxt, JobDetail jobDetail, + boolean replace) throws SchedulerException { + validateState(); + + if (!jobDetail.isDurable() && !replace) { + throw new SchedulerException( + "Jobs added with no trigger must be durable.", + SchedulerException.ERR_CLIENT_ERROR); + } + + resources.getJobStore().storeJob(ctxt, jobDetail, replace); + } + + /** + *

+ * Delete the identified Job from the Scheduler - and any + * associated Triggers. + *

+ * + * @return true if the Job was found and deleted. + * @throws SchedulerException + * if there is an internal Scheduler error. + */ + public boolean deleteJob(SchedulingContext ctxt, String jobName, + String groupName) throws SchedulerException { + validateState(); + + if(groupName == null) { + groupName = Scheduler.DEFAULT_GROUP; + } + + return resources.getJobStore().removeJob(ctxt, jobName, groupName); + } + + /** + *

+ * Remove the indicated {@link com.fr.third.org.quartz.Trigger} from the + * scheduler. + *

+ */ + public boolean unscheduleJob(SchedulingContext ctxt, String triggerName, + String groupName) throws SchedulerException { + validateState(); + + if(groupName == null) { + groupName = Scheduler.DEFAULT_GROUP; + } + + if (resources.getJobStore().removeTrigger(ctxt, triggerName, groupName)) { + notifySchedulerThread(0L); + notifySchedulerListenersUnschduled(triggerName, groupName); + } else { + return false; + } + + return true; + } + + + /** + *

+ * Remove (delete) the {@link com.fr.third.org.quartz.Trigger} with the + * given name, and store the new given one - which must be associated + * with the same job. + *

+ * + * @param triggerName + * The name of the Trigger to be removed. + * @param groupName + * The group name of the Trigger to be removed. + * @param newTrigger + * The new Trigger to be stored. + * @return null if a Trigger with the given + * name & group was not found and removed from the store, otherwise + * the first fire time of the newly scheduled trigger. + */ + public Date rescheduleJob(SchedulingContext ctxt, String triggerName, + String groupName, Trigger newTrigger) throws SchedulerException { + validateState(); + + if(groupName == null) { + groupName = Scheduler.DEFAULT_GROUP; + } + + newTrigger.validate(); + + Calendar cal = null; + if (newTrigger.getCalendarName() != null) { + cal = resources.getJobStore().retrieveCalendar(ctxt, + newTrigger.getCalendarName()); + } + Date ft = newTrigger.computeFirstFireTime(cal); + + if (ft == null) { + throw new SchedulerException( + "Based on configured schedule, the given trigger will never fire.", + SchedulerException.ERR_CLIENT_ERROR); + } + + if (resources.getJobStore().replaceTrigger(ctxt, triggerName, groupName, newTrigger)) { + notifySchedulerThread(newTrigger.getNextFireTime().getTime()); + notifySchedulerListenersUnschduled(triggerName, groupName); + notifySchedulerListenersSchduled(newTrigger); + } else { + return null; + } + + return ft; + + } + + + private String newTriggerId() { + long r = random.nextLong(); + if (r < 0) { + r = -r; + } + return "MT_" + + Long.toString(r, 30 + (int) (System.currentTimeMillis() % 7)); + } + + /** + *

+ * Trigger the identified {@link com.fr.third.org.quartz.Job} (execute it + * now) - with a non-volatile trigger. + *

+ */ + public void triggerJob(SchedulingContext ctxt, String jobName, + String groupName, JobDataMap data) throws SchedulerException { + validateState(); + + if(groupName == null) { + groupName = Scheduler.DEFAULT_GROUP; + } + + Trigger trig = new com.fr.third.org.quartz.SimpleTrigger(newTriggerId(), + Scheduler.DEFAULT_MANUAL_TRIGGERS, jobName, groupName, + new Date(), null, 0, 0); + trig.setVolatility(false); + trig.computeFirstFireTime(null); + if(data != null) { + trig.setJobDataMap(data); + } + + boolean collision = true; + while (collision) { + try { + resources.getJobStore().storeTrigger(ctxt, trig, false); + collision = false; + } catch (ObjectAlreadyExistsException oaee) { + trig.setName(newTriggerId()); + } + } + + notifySchedulerThread(trig.getNextFireTime().getTime()); + notifySchedulerListenersSchduled(trig); + } + + /** + *

+ * Trigger the identified {@link com.fr.third.org.quartz.Job} (execute it + * now) - with a volatile trigger. + *

+ */ + public void triggerJobWithVolatileTrigger(SchedulingContext ctxt, + String jobName, String groupName, JobDataMap data) throws SchedulerException { + validateState(); + + if(groupName == null) { + groupName = Scheduler.DEFAULT_GROUP; + } + + Trigger trig = new com.fr.third.org.quartz.SimpleTrigger(newTriggerId(), + Scheduler.DEFAULT_MANUAL_TRIGGERS, jobName, groupName, + new Date(), null, 0, 0); + trig.setVolatility(true); + trig.computeFirstFireTime(null); + if(data != null) { + trig.setJobDataMap(data); + } + + boolean collision = true; + while (collision) { + try { + resources.getJobStore().storeTrigger(ctxt, trig, false); + collision = false; + } catch (ObjectAlreadyExistsException oaee) { + trig.setName(newTriggerId()); + } + } + + notifySchedulerThread(trig.getNextFireTime().getTime()); + notifySchedulerListenersSchduled(trig); + } + + /** + *

+ * Pause the {@link Trigger} with the given name. + *

+ * + */ + public void pauseTrigger(SchedulingContext ctxt, String triggerName, + String groupName) throws SchedulerException { + validateState(); + + if(groupName == null) { + groupName = Scheduler.DEFAULT_GROUP; + } + + resources.getJobStore().pauseTrigger(ctxt, triggerName, groupName); + notifySchedulerThread(0L); + notifySchedulerListenersPausedTrigger(triggerName, groupName); + } + + /** + *

+ * Pause all of the {@link Trigger}s in the given group. + *

+ * + */ + public void pauseTriggerGroup(SchedulingContext ctxt, String groupName) + throws SchedulerException { + validateState(); + + if(groupName == null) { + groupName = Scheduler.DEFAULT_GROUP; + } + + resources.getJobStore().pauseTriggerGroup(ctxt, groupName); + notifySchedulerThread(0L); + notifySchedulerListenersPausedTrigger(null, groupName); + } + + /** + *

+ * Pause the {@link com.fr.third.org.quartz.JobDetail} with the given + * name - by pausing all of its current Triggers. + *

+ * + */ + public void pauseJob(SchedulingContext ctxt, String jobName, + String groupName) throws SchedulerException { + validateState(); + + if(groupName == null) { + groupName = Scheduler.DEFAULT_GROUP; + } + + resources.getJobStore().pauseJob(ctxt, jobName, groupName); + notifySchedulerThread(0L); + notifySchedulerListenersPausedJob(jobName, groupName); + } + + /** + *

+ * Pause all of the {@link com.fr.third.org.quartz.JobDetail}s in the + * given group - by pausing all of their Triggers. + *

+ * + */ + public void pauseJobGroup(SchedulingContext ctxt, String groupName) + throws SchedulerException { + validateState(); + + if(groupName == null) { + groupName = Scheduler.DEFAULT_GROUP; + } + + resources.getJobStore().pauseJobGroup(ctxt, groupName); + notifySchedulerThread(0L); + notifySchedulerListenersPausedJob(null, groupName); + } + + /** + *

+ * Resume (un-pause) the {@link Trigger} with the given + * name. + *

+ * + *

+ * If the Trigger missed one or more fire-times, then the + * Trigger's misfire instruction will be applied. + *

+ * + */ + public void resumeTrigger(SchedulingContext ctxt, String triggerName, + String groupName) throws SchedulerException { + validateState(); + + if(groupName == null) { + groupName = Scheduler.DEFAULT_GROUP; + } + + resources.getJobStore().resumeTrigger(ctxt, triggerName, groupName); + notifySchedulerThread(0L); + notifySchedulerListenersResumedTrigger(triggerName, groupName); + } + + /** + *

+ * Resume (un-pause) all of the {@link Trigger}s in the + * given group. + *

+ * + *

+ * If any Trigger missed one or more fire-times, then the + * Trigger's misfire instruction will be applied. + *

+ * + */ + public void resumeTriggerGroup(SchedulingContext ctxt, String groupName) + throws SchedulerException { + validateState(); + + if(groupName == null) { + groupName = Scheduler.DEFAULT_GROUP; + } + + resources.getJobStore().resumeTriggerGroup(ctxt, groupName); + notifySchedulerThread(0L); + notifySchedulerListenersResumedTrigger(null, groupName); + } + + public Set getPausedTriggerGroups(SchedulingContext ctxt) throws SchedulerException { + return resources.getJobStore().getPausedTriggerGroups(ctxt); + } + + /** + *

+ * Resume (un-pause) the {@link com.fr.third.org.quartz.JobDetail} with + * the given name. + *

+ * + *

+ * If any of the Job'sTrigger s missed one + * or more fire-times, then the Trigger's misfire + * instruction will be applied. + *

+ * + */ + public void resumeJob(SchedulingContext ctxt, String jobName, + String groupName) throws SchedulerException { + validateState(); + + if(groupName == null) { + groupName = Scheduler.DEFAULT_GROUP; + } + + resources.getJobStore().resumeJob(ctxt, jobName, groupName); + notifySchedulerThread(0L); + notifySchedulerListenersResumedJob(jobName, groupName); + } + + /** + *

+ * Resume (un-pause) all of the {@link com.fr.third.org.quartz.JobDetail}s + * in the given group. + *

+ * + *

+ * If any of the Job s had Trigger s that + * missed one or more fire-times, then the Trigger's + * misfire instruction will be applied. + *

+ * + */ + public void resumeJobGroup(SchedulingContext ctxt, String groupName) + throws SchedulerException { + validateState(); + + if(groupName == null) { + groupName = Scheduler.DEFAULT_GROUP; + } + + resources.getJobStore().resumeJobGroup(ctxt, groupName); + notifySchedulerThread(0L); + notifySchedulerListenersResumedJob(null, groupName); + } + + /** + *

+ * Pause all triggers - equivalent of calling pauseTriggerGroup(group) + * on every group. + *

+ * + *

+ * When resumeAll() is called (to un-pause), trigger misfire + * instructions WILL be applied. + *

+ * + * @see #resumeAll(SchedulingContext) + * @see #pauseTriggerGroup(SchedulingContext, String) + * @see #standby() + */ + public void pauseAll(SchedulingContext ctxt) throws SchedulerException { + validateState(); + + resources.getJobStore().pauseAll(ctxt); + notifySchedulerThread(0L); + notifySchedulerListenersPausedTrigger(null, null); + } + + /** + *

+ * Resume (un-pause) all triggers - equivalent of calling resumeTriggerGroup(group) + * on every group. + *

+ * + *

+ * If any Trigger missed one or more fire-times, then the + * Trigger's misfire instruction will be applied. + *

+ * + * @see #pauseAll(SchedulingContext) + */ + public void resumeAll(SchedulingContext ctxt) throws SchedulerException { + validateState(); + + resources.getJobStore().resumeAll(ctxt); + notifySchedulerThread(0L); + notifySchedulerListenersResumedTrigger(null, null); + } + + /** + *

+ * Get the names of all known {@link com.fr.third.org.quartz.Job} groups. + *

+ */ + public String[] getJobGroupNames(SchedulingContext ctxt) + throws SchedulerException { + validateState(); + + return resources.getJobStore().getJobGroupNames(ctxt); + } + + /** + *

+ * Get the names of all the {@link com.fr.third.org.quartz.Job}s in the + * given group. + *

+ */ + public String[] getJobNames(SchedulingContext ctxt, String groupName) + throws SchedulerException { + validateState(); + + if(groupName == null) { + groupName = Scheduler.DEFAULT_GROUP; + } + + return resources.getJobStore().getJobNames(ctxt, groupName); + } + + /** + *

+ * Get all {@link Trigger} s that are associated with the + * identified {@link com.fr.third.org.quartz.JobDetail}. + *

+ */ + public Trigger[] getTriggersOfJob(SchedulingContext ctxt, String jobName, + String groupName) throws SchedulerException { + validateState(); + + if(groupName == null) { + groupName = Scheduler.DEFAULT_GROUP; + } + + return resources.getJobStore().getTriggersForJob(ctxt, jobName, + groupName); + } + + /** + *

+ * Get the names of all known {@link com.fr.third.org.quartz.Trigger} + * groups. + *

+ */ + public String[] getTriggerGroupNames(SchedulingContext ctxt) + throws SchedulerException { + validateState(); + + return resources.getJobStore().getTriggerGroupNames(ctxt); + } + + /** + *

+ * Get the names of all the {@link com.fr.third.org.quartz.Trigger}s in + * the given group. + *

+ */ + public String[] getTriggerNames(SchedulingContext ctxt, String groupName) + throws SchedulerException { + validateState(); + + if(groupName == null) { + groupName = Scheduler.DEFAULT_GROUP; + } + + return resources.getJobStore().getTriggerNames(ctxt, groupName); + } + + /** + *

+ * Get the {@link JobDetail} for the Job + * instance with the given name and group. + *

+ */ + public JobDetail getJobDetail(SchedulingContext ctxt, String jobName, + String jobGroup) throws SchedulerException { + validateState(); + + if(jobGroup == null) { + jobGroup = Scheduler.DEFAULT_GROUP; + } + + return resources.getJobStore().retrieveJob(ctxt, jobName, jobGroup); + } + + /** + *

+ * Get the {@link Trigger} instance with the given name and + * group. + *

+ */ + public Trigger getTrigger(SchedulingContext ctxt, String triggerName, + String triggerGroup) throws SchedulerException { + validateState(); + + if(triggerGroup == null) { + triggerGroup = Scheduler.DEFAULT_GROUP; + } + + return resources.getJobStore().retrieveTrigger(ctxt, triggerName, + triggerGroup); + } + + /** + *

+ * Get the current state of the identified {@link Trigger}. + *

+ * + * @see Trigger#STATE_NORMAL + * @see Trigger#STATE_PAUSED + * @see Trigger#STATE_COMPLETE + * @see Trigger#STATE_ERROR + */ + public int getTriggerState(SchedulingContext ctxt, String triggerName, + String triggerGroup) throws SchedulerException { + validateState(); + + if(triggerGroup == null) { + triggerGroup = Scheduler.DEFAULT_GROUP; + } + + return resources.getJobStore().getTriggerState(ctxt, triggerName, + triggerGroup); + } + + /** + *

+ * Add (register) the given Calendar to the Scheduler. + *

+ * + * @throws SchedulerException + * if there is an internal Scheduler error, or a Calendar with + * the same name already exists, and replace is + * false. + */ + public void addCalendar(SchedulingContext ctxt, String calName, + Calendar calendar, boolean replace, boolean updateTriggers) throws SchedulerException { + validateState(); + + resources.getJobStore().storeCalendar(ctxt, calName, calendar, replace, updateTriggers); + } + + /** + *

+ * Delete the identified Calendar from the Scheduler. + *

+ * + * @return true if the Calendar was found and deleted. + * @throws SchedulerException + * if there is an internal Scheduler error. + */ + public boolean deleteCalendar(SchedulingContext ctxt, String calName) + throws SchedulerException { + validateState(); + + return resources.getJobStore().removeCalendar(ctxt, calName); + } + + /** + *

+ * Get the {@link Calendar} instance with the given name. + *

+ */ + public Calendar getCalendar(SchedulingContext ctxt, String calName) + throws SchedulerException { + validateState(); + + return resources.getJobStore().retrieveCalendar(ctxt, calName); + } + + /** + *

+ * Get the names of all registered {@link Calendar}s. + *

+ */ + public String[] getCalendarNames(SchedulingContext ctxt) + throws SchedulerException { + validateState(); + + return resources.getJobStore().getCalendarNames(ctxt); + } + + /** + *

+ * Add the given {@link com.fr.third.org.quartz.JobListener} to the + * Scheduler'sglobal list. + *

+ * + *

+ * Listeners in the 'global' list receive notification of execution events + * for ALL {@link com.fr.third.org.quartz.Job}s. + *

+ */ + public void addGlobalJobListener(JobListener jobListener) { + if (jobListener.getName() == null + || jobListener.getName().length() == 0) { + throw new IllegalArgumentException( + "JobListener name cannot be empty."); + } + + synchronized (globalJobListeners) { + globalJobListeners.put(jobListener.getName(), jobListener); + } + } + + /** + *

+ * Add the given {@link com.fr.third.org.quartz.JobListener} to the + * Scheduler's list, of registered JobListeners. + */ + public void addJobListener(JobListener jobListener) { + if (jobListener.getName() == null + || jobListener.getName().length() == 0) { + throw new IllegalArgumentException( + "JobListener name cannot be empty."); + } + + synchronized (jobListeners) { + jobListeners.put(jobListener.getName(), jobListener); + } + } + + /** + *

+ * Remove the given {@link com.fr.third.org.quartz.JobListener} from the + * Scheduler's list of global listeners. + *

+ * + * @return true if the identifed listener was found in the list, and + * removed. + * + * @deprecated Use {@link #removeGlobalJobListener(String)} + */ + public boolean removeGlobalJobListener(JobListener jobListener) { + return removeGlobalJobListener((jobListener == null) ? null : jobListener.getName()); + } + + /** + *

+ * Remove the identifed {@link JobListener} from the Scheduler's + * list of global listeners. + *

+ * + * @return true if the identifed listener was found in the list, and + * removed. + */ + public boolean removeGlobalJobListener(String name) { + synchronized (globalJobListeners) { + return (globalJobListeners.remove(name) != null); + } + } + + /** + *

+ * Remove the identifed {@link com.fr.third.org.quartz.JobListener} from + * the Scheduler's list of registered listeners. + *

+ * + * @return true if the identifed listener was found in the list, and + * removed. + */ + public boolean removeJobListener(String name) { + synchronized (jobListeners) { + return (jobListeners.remove(name) != null); + } + } + + /** + *

+ * Get a List containing all of the {@link com.fr.third.org.quartz.JobListener} + * s in the Scheduler'sglobal list. + *

+ */ + public List getGlobalJobListeners() { + synchronized (globalJobListeners) { + return new LinkedList(globalJobListeners.values()); + } + } + + /** + *

+ * Get a Set containing the names of all the non-global{@link com.fr.third.org.quartz.JobListener} + * s registered with the Scheduler. + *

+ */ + public Set getJobListenerNames() { + synchronized (jobListeners) { + return new HashSet(jobListeners.keySet()); + } + } + + /** + *

+ * Get the global{@link com.fr.third.org.quartz.JobListener} + * that has the given name. + *

+ */ + public JobListener getGlobalJobListener(String name) { + synchronized (globalJobListeners) { + return (JobListener)globalJobListeners.get(name); + } + } + + /** + *

+ * Get the non-global{@link com.fr.third.org.quartz.JobListener} + * that has the given name. + *

+ */ + public JobListener getJobListener(String name) { + synchronized (jobListeners) { + return (JobListener) jobListeners.get(name); + } + } + + /** + *

+ * Add the given {@link com.fr.third.org.quartz.TriggerListener} to the + * Scheduler'sglobal list. + *

+ * + *

+ * Listeners in the 'global' list receive notification of execution events + * for ALL {@link com.fr.third.org.quartz.Trigger}s. + *

+ */ + public void addGlobalTriggerListener(TriggerListener triggerListener) { + if (triggerListener.getName() == null + || triggerListener.getName().length() == 0) { + throw new IllegalArgumentException( + "TriggerListener name cannot be empty."); + } + + synchronized (globalTriggerListeners) { + globalTriggerListeners.put(triggerListener.getName(), triggerListener); + } + } + + /** + *

+ * Add the given {@link com.fr.third.org.quartz.TriggerListener} to the + * Scheduler's list, of registered TriggerListeners. + */ + public void addTriggerListener(TriggerListener triggerListener) { + if (triggerListener.getName() == null + || triggerListener.getName().length() == 0) { + throw new IllegalArgumentException( + "TriggerListener name cannot be empty."); + } + + synchronized (triggerListeners) { + triggerListeners.put(triggerListener.getName(), triggerListener); + } + } + + /** + *

+ * Remove the given {@link com.fr.third.org.quartz.TriggerListener} from + * the Scheduler's list of global listeners. + *

+ * + * @return true if the identifed listener was found in the list, and + * removed. + * + * @deprecated Use {@link #removeGlobalTriggerListener(String)} + */ + public boolean removeGlobalTriggerListener(TriggerListener triggerListener) { + return removeGlobalTriggerListener((triggerListener == null) ? null : triggerListener.getName()); + } + + /** + *

+ * Remove the identifed {@link TriggerListener} from the Scheduler's + * list of global listeners. + *

+ * + * @return true if the identifed listener was found in the list, and + * removed. + */ + public boolean removeGlobalTriggerListener(String name) { + synchronized (globalTriggerListeners) { + return (globalTriggerListeners.remove(name) != null); + } + } + + /** + *

+ * Remove the identifed {@link com.fr.third.org.quartz.TriggerListener} + * from the Scheduler's list of registered listeners. + *

+ * + * @return true if the identifed listener was found in the list, and + * removed. + */ + public boolean removeTriggerListener(String name) { + synchronized (triggerListeners) { + return (triggerListeners.remove(name) != null); + } + } + + /** + *

+ * Get a list containing all of the {@link com.fr.third.org.quartz.TriggerListener} + * s in the Scheduler'sglobal list. + *

+ */ + public List getGlobalTriggerListeners() { + synchronized (globalTriggerListeners) { + return new LinkedList(globalTriggerListeners.values()); + } + } + + /** + *

+ * Get a Set containing the names of all the non-global{@link com.fr.third.org.quartz.TriggerListener} + * s registered with the Scheduler. + *

+ */ + public Set getTriggerListenerNames() { + synchronized (triggerListeners) { + return new HashSet(triggerListeners.keySet()); + } + } + + /** + *

+ * Get the global{@link TriggerListener} that + * has the given name. + *

+ */ + public TriggerListener getGlobalTriggerListener(String name) { + synchronized (globalTriggerListeners) { + return (TriggerListener)globalTriggerListeners.get(name); + } + } + + /** + *

+ * Get the non-global{@link com.fr.third.org.quartz.TriggerListener} + * that has the given name. + *

+ */ + public TriggerListener getTriggerListener(String name) { + synchronized (triggerListeners) { + return (TriggerListener) triggerListeners.get(name); + } + } + + /** + *

+ * Register the given {@link SchedulerListener} with the + * Scheduler. + *

+ */ + public void addSchedulerListener(SchedulerListener schedulerListener) { + synchronized (schedulerListeners) { + schedulerListeners.add(schedulerListener); + } + } + + /** + *

+ * Remove the given {@link SchedulerListener} from the + * Scheduler. + *

+ * + * @return true if the identifed listener was found in the list, and + * removed. + */ + public boolean removeSchedulerListener(SchedulerListener schedulerListener) { + synchronized (schedulerListeners) { + return schedulerListeners.remove(schedulerListener); + } + } + + /** + *

+ * Get a List containing all of the {@link SchedulerListener} + * s registered with the Scheduler. + *

+ */ + public List getSchedulerListeners() { + synchronized (schedulerListeners) { + return (List)schedulerListeners.clone(); + } + } + + protected void notifyJobStoreJobComplete(SchedulingContext ctxt, + Trigger trigger, JobDetail detail, int instCode) + throws JobPersistenceException { + + resources.getJobStore().triggeredJobComplete(ctxt, trigger, detail, + instCode); + } + + protected void notifyJobStoreJobVetoed(SchedulingContext ctxt, + Trigger trigger, JobDetail detail, int instCode) + throws JobPersistenceException { + + resources.getJobStore().triggeredJobComplete(ctxt, trigger, detail, instCode); + } + + protected void notifySchedulerThread(long candidateNewNextFireTime) { + if (isSignalOnSchedulingChange()) { + signaler.signalSchedulingChange(candidateNewNextFireTime); + } + } + + private List buildTriggerListenerList(String[] additionalLstnrs) + throws SchedulerException { + List triggerListeners = getGlobalTriggerListeners(); + for (int i = 0; i < additionalLstnrs.length; i++) { + TriggerListener tl = getTriggerListener(additionalLstnrs[i]); + + if (tl != null) { + triggerListeners.add(tl); + } else { + throw new SchedulerException("TriggerListener '" + + additionalLstnrs[i] + "' not found.", + SchedulerException.ERR_TRIGGER_LISTENER_NOT_FOUND); + } + } + + return triggerListeners; + } + + private List buildJobListenerList(String[] additionalLstnrs) + throws SchedulerException { + List jobListeners = getGlobalJobListeners(); + for (int i = 0; i < additionalLstnrs.length; i++) { + JobListener jl = getJobListener(additionalLstnrs[i]); + + if (jl != null) { + jobListeners.add(jl); + } else { + throw new SchedulerException("JobListener '" + + additionalLstnrs[i] + "' not found.", + SchedulerException.ERR_JOB_LISTENER_NOT_FOUND); + } + } + + return jobListeners; + } + + public boolean notifyTriggerListenersFired(JobExecutionContext jec) + throws SchedulerException { + // build a list of all trigger listeners that are to be notified... + List triggerListeners = buildTriggerListenerList(jec.getTrigger() + .getTriggerListenerNames()); + + boolean vetoedExecution = false; + + // notify all trigger listeners in the list + java.util.Iterator itr = triggerListeners.iterator(); + while (itr.hasNext()) { + TriggerListener tl = (TriggerListener) itr.next(); + try { + tl.triggerFired(jec.getTrigger(), jec); + + if(tl.vetoJobExecution(jec.getTrigger(), jec)) { + vetoedExecution = true; + } + } catch (Exception e) { + SchedulerException se = new SchedulerException( + "TriggerListener '" + tl.getName() + + "' threw exception: " + e.getMessage(), e); + se.setErrorCode(SchedulerException.ERR_TRIGGER_LISTENER); + throw se; + } + } + + return vetoedExecution; + } + + + public void notifyTriggerListenersMisfired(Trigger trigger) + throws SchedulerException { + // build a list of all trigger listeners that are to be notified... + List triggerListeners = buildTriggerListenerList(trigger + .getTriggerListenerNames()); + + // notify all trigger listeners in the list + java.util.Iterator itr = triggerListeners.iterator(); + while (itr.hasNext()) { + TriggerListener tl = (TriggerListener) itr.next(); + try { + tl.triggerMisfired(trigger); + } catch (Exception e) { + SchedulerException se = new SchedulerException( + "TriggerListener '" + tl.getName() + + "' threw exception: " + e.getMessage(), e); + se.setErrorCode(SchedulerException.ERR_TRIGGER_LISTENER); + throw se; + } + } + } + + public void notifyTriggerListenersComplete(JobExecutionContext jec, + int instCode) throws SchedulerException { + // build a list of all trigger listeners that are to be notified... + List triggerListeners = buildTriggerListenerList(jec.getTrigger() + .getTriggerListenerNames()); + + // notify all trigger listeners in the list + java.util.Iterator itr = triggerListeners.iterator(); + while (itr.hasNext()) { + TriggerListener tl = (TriggerListener) itr.next(); + try { + tl.triggerComplete(jec.getTrigger(), jec, instCode); + } catch (Exception e) { + SchedulerException se = new SchedulerException( + "TriggerListener '" + tl.getName() + + "' threw exception: " + e.getMessage(), e); + se.setErrorCode(SchedulerException.ERR_TRIGGER_LISTENER); + throw se; + } + } + } + + public void notifyJobListenersToBeExecuted(JobExecutionContext jec) + throws SchedulerException { + // build a list of all job listeners that are to be notified... + List jobListeners = buildJobListenerList(jec.getJobDetail() + .getJobListenerNames()); + + // notify all job listeners + java.util.Iterator itr = jobListeners.iterator(); + while (itr.hasNext()) { + JobListener jl = (JobListener) itr.next(); + try { + jl.jobToBeExecuted(jec); + } catch (Exception e) { + SchedulerException se = new SchedulerException( + "JobListener '" + jl.getName() + "' threw exception: " + + e.getMessage(), e); + se.setErrorCode(SchedulerException.ERR_JOB_LISTENER); + throw se; + } + } + } + + public void notifyJobListenersWasVetoed(JobExecutionContext jec) + throws SchedulerException { + // build a list of all job listeners that are to be notified... + List jobListeners = buildJobListenerList(jec.getJobDetail() + .getJobListenerNames()); + + // notify all job listeners + java.util.Iterator itr = jobListeners.iterator(); + while (itr.hasNext()) { + JobListener jl = (JobListener) itr.next(); + try { + jl.jobExecutionVetoed(jec); + } catch (Exception e) { + SchedulerException se = new SchedulerException( + "JobListener '" + jl.getName() + "' threw exception: " + + e.getMessage(), e); + se.setErrorCode(SchedulerException.ERR_JOB_LISTENER); + throw se; + } + } + } + + public void notifyJobListenersWasExecuted(JobExecutionContext jec, + JobExecutionException je) throws SchedulerException { + // build a list of all job listeners that are to be notified... + List jobListeners = buildJobListenerList(jec.getJobDetail() + .getJobListenerNames()); + + // notify all job listeners + java.util.Iterator itr = jobListeners.iterator(); + while (itr.hasNext()) { + JobListener jl = (JobListener) itr.next(); + try { + jl.jobWasExecuted(jec, je); + } catch (Exception e) { + SchedulerException se = new SchedulerException( + "JobListener '" + jl.getName() + "' threw exception: " + + e.getMessage(), e); + se.setErrorCode(SchedulerException.ERR_JOB_LISTENER); + throw se; + } + } + } + + public void notifySchedulerListenersError(String msg, SchedulerException se) { + // build a list of all scheduler listeners that are to be notified... + List schedListeners = getSchedulerListeners(); + + // notify all scheduler listeners + java.util.Iterator itr = schedListeners.iterator(); + while (itr.hasNext()) { + SchedulerListener sl = (SchedulerListener) itr.next(); + try { + sl.schedulerError(msg, se); + } catch (Exception e) { + getLog() + .error( + "Error while notifying SchedulerListener of error: ", + e); + getLog().error( + " Original error (for notification) was: " + msg, se); + } + } + } + + public void notifySchedulerListenersSchduled(Trigger trigger) { + // build a list of all scheduler listeners that are to be notified... + List schedListeners = getSchedulerListeners(); + + // notify all scheduler listeners + java.util.Iterator itr = schedListeners.iterator(); + while (itr.hasNext()) { + SchedulerListener sl = (SchedulerListener) itr.next(); + try { + sl.jobScheduled(trigger); + } catch (Exception e) { + getLog().error( + "Error while notifying SchedulerListener of scheduled job." + + " Triger=" + trigger.getFullName(), e); + } + } + } + + public void notifySchedulerListenersUnschduled(String triggerName, + String triggerGroup) { + // build a list of all scheduler listeners that are to be notified... + List schedListeners = getSchedulerListeners(); + + // notify all scheduler listeners + java.util.Iterator itr = schedListeners.iterator(); + while (itr.hasNext()) { + SchedulerListener sl = (SchedulerListener) itr.next(); + try { + sl.jobUnscheduled(triggerName, triggerGroup); + } catch (Exception e) { + getLog().error( + "Error while notifying SchedulerListener of unscheduled job." + + " Triger=" + triggerGroup + "." + + triggerName, e); + } + } + } + + public void notifySchedulerListenersFinalized(Trigger trigger) { + // build a list of all scheduler listeners that are to be notified... + List schedListeners = getSchedulerListeners(); + + // notify all scheduler listeners + java.util.Iterator itr = schedListeners.iterator(); + while (itr.hasNext()) { + SchedulerListener sl = (SchedulerListener) itr.next(); + try { + sl.triggerFinalized(trigger); + } catch (Exception e) { + getLog().error( + "Error while notifying SchedulerListener of finalized trigger." + + " Triger=" + trigger.getFullName(), e); + } + } + } + + public void notifySchedulerListenersPausedTrigger(String name, String group) { + // build a list of all job listeners that are to be notified... + List schedListeners = getSchedulerListeners(); + + // notify all scheduler listeners + java.util.Iterator itr = schedListeners.iterator(); + while (itr.hasNext()) { + SchedulerListener sl = (SchedulerListener) itr.next(); + try { + sl.triggersPaused(name, group); + } catch (Exception e) { + getLog().error( + "Error while notifying SchedulerListener of paused trigger/group." + + " Triger=" + group + "." + name, e); + } + } + } + + public void notifySchedulerListenersResumedTrigger(String name, String group) { + // build a list of all job listeners that are to be notified... + List schedListeners = getSchedulerListeners(); + + // notify all scheduler listeners + java.util.Iterator itr = schedListeners.iterator(); + while (itr.hasNext()) { + SchedulerListener sl = (SchedulerListener) itr.next(); + try { + sl.triggersResumed(name, group); + } catch (Exception e) { + getLog().error( + "Error while notifying SchedulerListener of resumed trigger/group." + + " Triger=" + group + "." + name, e); + } + } + } + + public void notifySchedulerListenersPausedJob(String name, String group) { + // build a list of all job listeners that are to be notified... + List schedListeners = getSchedulerListeners(); + + // notify all scheduler listeners + java.util.Iterator itr = schedListeners.iterator(); + while (itr.hasNext()) { + SchedulerListener sl = (SchedulerListener) itr.next(); + try { + sl.jobsPaused(name, group); + } catch (Exception e) { + getLog().error( + "Error while notifying SchedulerListener of paused job/group." + + " Job=" + group + "." + name, e); + } + } + } + + public void notifySchedulerListenersResumedJob(String name, String group) { + // build a list of all job listeners that are to be notified... + List schedListeners = getSchedulerListeners(); + + // notify all scheduler listeners + java.util.Iterator itr = schedListeners.iterator(); + while (itr.hasNext()) { + SchedulerListener sl = (SchedulerListener) itr.next(); + try { + sl.jobsResumed(name, group); + } catch (Exception e) { + getLog().error( + "Error while notifying SchedulerListener of resumed job/group." + + " Job=" + group + "." + name, e); + } + } + } + + public void notifySchedulerListenersShutdown() { + // build a list of all job listeners that are to be notified... + List schedListeners = getSchedulerListeners(); + + // notify all scheduler listeners + java.util.Iterator itr = schedListeners.iterator(); + while (itr.hasNext()) { + SchedulerListener sl = (SchedulerListener) itr.next(); + try { + sl.schedulerShutdown(); + } catch (Exception e) { + getLog().error( + "Error while notifying SchedulerListener of shutdown.", + e); + } + } + } + + public void setJobFactory(JobFactory factory) throws SchedulerException { + + if(factory == null) { + throw new IllegalArgumentException("JobFactory cannot be set to null!"); + } + + getLog().info("JobFactory set to: " + factory); + + this.jobFactory = factory; + } + + public JobFactory getJobFactory() { + return jobFactory; + } + + + /** + * Interrupt all instances of the identified InterruptableJob executing in + * this Scheduler instance. + * + *

+ * This method is not cluster aware. That is, it will only interrupt + * instances of the identified InterruptableJob currently executing in this + * Scheduler instance, not across the entire cluster. + *

+ * + * @see com.fr.third.org.quartz.core.RemotableQuartzScheduler#interrupt(com.fr.third.org.quartz.core.SchedulingContext, java.lang.String, java.lang.String) + */ + public boolean interrupt(SchedulingContext ctxt, String jobName, String groupName) throws UnableToInterruptJobException { + + if(groupName == null) { + groupName = Scheduler.DEFAULT_GROUP; + } + + List jobs = getCurrentlyExecutingJobs(); + java.util.Iterator it = jobs.iterator(); + + JobExecutionContext jec = null; + JobDetail jobDetail = null; + Job job = null; + + boolean interrupted = false; + + while (it.hasNext()) { + jec = (JobExecutionContext)it.next(); + jobDetail = jec.getJobDetail(); + if (jobName.equals(jobDetail.getName()) + && groupName.equals(jobDetail.getGroup())){ + job = jec.getJobInstance(); + if (job instanceof InterruptableJob) { + ((InterruptableJob)job).interrupt(); + interrupted = true; + } else { + throw new UnableToInterruptJobException( + "Job '" + + jobName + + "' of group '" + + groupName + + "' can not be interrupted, since it does not implement " + + InterruptableJob.class.getName()); + + } + } + } + + return interrupted; + } + + private void shutdownPlugins() { + java.util.Iterator itr = resources.getSchedulerPlugins().iterator(); + while (itr.hasNext()) { + SchedulerPlugin plugin = (SchedulerPlugin) itr.next(); + plugin.shutdown(); + } + } + + private void startPlugins() { + java.util.Iterator itr = resources.getSchedulerPlugins().iterator(); + while (itr.hasNext()) { + SchedulerPlugin plugin = (SchedulerPlugin) itr.next(); + plugin.start(); + } + } + +} + +///////////////////////////////////////////////////////////////////////////// +// +// ErrorLogger - Scheduler Listener Class +// +///////////////////////////////////////////////////////////////////////////// + +class ErrorLogger extends SchedulerListenerSupport { + ErrorLogger() { + } + + public void schedulerError(String msg, SchedulerException cause) { + getLog().error(msg, cause); + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// ExecutingJobsManager - Job Listener Class +// +///////////////////////////////////////////////////////////////////////////// + +class ExecutingJobsManager implements JobListener { + HashMap executingJobs = new HashMap(); + + int numJobsFired = 0; + + ExecutingJobsManager() { + } + + public String getName() { + return getClass().getName(); + } + + public int getNumJobsCurrentlyExecuting() { + synchronized (executingJobs) { + return executingJobs.size(); + } + } + + public void jobToBeExecuted(JobExecutionContext context) { + numJobsFired++; + + synchronized (executingJobs) { + executingJobs + .put(context.getTrigger().getFireInstanceId(), context); + } + } + + public void jobWasExecuted(JobExecutionContext context, + JobExecutionException jobException) { + synchronized (executingJobs) { + executingJobs.remove(context.getTrigger().getFireInstanceId()); + } + } + + public int getNumJobsFired() { + return numJobsFired; + } + + public List getExecutingJobs() { + synchronized (executingJobs) { + return java.util.Collections.unmodifiableList(new ArrayList( + executingJobs.values())); + } + } + + public void jobExecutionVetoed(JobExecutionContext context) { + + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/QuartzSchedulerResources.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/QuartzSchedulerResources.java new file mode 100644 index 000000000..113626c3e --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/QuartzSchedulerResources.java @@ -0,0 +1,519 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.core; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.quartz.spi.JobStore; +import com.fr.third.org.quartz.spi.SchedulerPlugin; +import com.fr.third.org.quartz.spi.ThreadPool; + +/** + *

+ * Contains all of the resources (JobStore,ThreadPool, + * etc.) necessary to create a {@link QuartzScheduler} instance. + *

+ * + * @see QuartzScheduler + * + * @author James House + */ +public class QuartzSchedulerResources { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public static final String CREATE_REGISTRY_NEVER = "never"; + + public static final String CREATE_REGISTRY_ALWAYS = "always"; + + public static final String CREATE_REGISTRY_AS_NEEDED = "as_needed"; + + private String name; + + private String instanceId; + + private String threadName; + + private String rmiRegistryHost = null; + + private int rmiRegistryPort = 1099; + + private int rmiServerPort = -1; + + private String rmiCreateRegistryStrategy = CREATE_REGISTRY_NEVER; + + private ThreadPool threadPool; + + private JobStore jobStore; + + private JobRunShellFactory jobRunShellFactory; + + private ArrayList schedulerPlugins = new ArrayList(10); + + private boolean makeSchedulerThreadDaemon = false; + + private boolean threadsInheritInitializersClassLoadContext = false; + + private String rmiBindName; + + private boolean jmxExport; + + private String jmxObjectName; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Create an instance with no properties initialized. + *

+ */ + public QuartzSchedulerResources() { + // do nothing... + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Get the name for the {@link QuartzScheduler}. + *

+ */ + public String getName() { + return name; + } + + /** + *

+ * Set the name for the {@link QuartzScheduler}. + *

+ * + * @exception IllegalArgumentException + * if name is null or empty. + */ + public void setName(String name) { + if (name == null || name.trim().length() == 0) { + throw new IllegalArgumentException( + "Scheduler name cannot be empty."); + } + + this.name = name; + + if (threadName == null) { + // thread name not already set, use default thread name + setThreadName(name + "_QuartzSchedulerThread"); + } + } + + /** + *

+ * Get the instance Id for the {@link QuartzScheduler}. + *

+ */ + public String getInstanceId() { + return instanceId; + } + + /** + *

+ * Set the name for the {@link QuartzScheduler}. + *

+ * + * @exception IllegalArgumentException + * if name is null or empty. + */ + public void setInstanceId(String instanceId) { + if (instanceId == null || instanceId.trim().length() == 0) { + throw new IllegalArgumentException( + "Scheduler instanceId cannot be empty."); + } + + this.instanceId = instanceId; + } + + public static String getUniqueIdentifier(String schedName, + String schedInstId) { + return schedName + "_$_" + schedInstId; + } + + public String getUniqueIdentifier() { + return getUniqueIdentifier(name, instanceId); + } + + /** + *

+ * Get the host name of the RMI Registry that the scheduler should export + * itself to. + *

+ */ + public String getRMIRegistryHost() { + return rmiRegistryHost; + } + + /** + *

+ * Set the host name of the RMI Registry that the scheduler should export + * itself to. + *

+ */ + public void setRMIRegistryHost(String hostName) { + this.rmiRegistryHost = hostName; + } + + /** + *

+ * Get the port number of the RMI Registry that the scheduler should export + * itself to. + *

+ */ + public int getRMIRegistryPort() { + return rmiRegistryPort; + } + + /** + *

+ * Set the port number of the RMI Registry that the scheduler should export + * itself to. + *

+ */ + public void setRMIRegistryPort(int port) { + this.rmiRegistryPort = port; + } + + + /** + *

+ * Get the port number the scheduler server will be bound to. + *

+ */ + public int getRMIServerPort() { + return rmiServerPort; + } + + /** + *

+ * Set the port number the scheduler server will be bound to. + *

+ */ + public void setRMIServerPort(int port) { + this.rmiServerPort = port; + } + + /** + *

+ * Get the setting of whether or not Quartz should create an RMI Registry, + * and if so, how. + *

+ */ + public String getRMICreateRegistryStrategy() { + return rmiCreateRegistryStrategy; + } + + /** + *

+ * Get the name for the {@link QuartzSchedulerThread}. + *

+ */ + public String getThreadName() { + return threadName; + } + + /** + *

+ * Set the name for the {@link QuartzSchedulerThread}. + *

+ * + * @exception IllegalArgumentException + * if name is null or empty. + */ + public void setThreadName(String threadName) { + if (threadName == null || threadName.trim().length() == 0) { + throw new IllegalArgumentException( + "Scheduler thread name cannot be empty."); + } + + this.threadName = threadName; + } + + /** + *

+ * Set whether or not Quartz should create an RMI Registry, and if so, how. + *

+ * + * @see #CREATE_REGISTRY_ALWAYS + * @see #CREATE_REGISTRY_AS_NEEDED + * @see #CREATE_REGISTRY_NEVER + */ + public void setRMICreateRegistryStrategy(String rmiCreateRegistryStrategy) { + if (rmiCreateRegistryStrategy == null + || rmiCreateRegistryStrategy.trim().length() == 0) { + rmiCreateRegistryStrategy = CREATE_REGISTRY_NEVER; + } else if (rmiCreateRegistryStrategy.equalsIgnoreCase("true")) { + rmiCreateRegistryStrategy = CREATE_REGISTRY_AS_NEEDED; + } else if (rmiCreateRegistryStrategy.equalsIgnoreCase("false")) { + rmiCreateRegistryStrategy = CREATE_REGISTRY_NEVER; + } else if (rmiCreateRegistryStrategy.equalsIgnoreCase(CREATE_REGISTRY_ALWAYS)) { + rmiCreateRegistryStrategy = CREATE_REGISTRY_ALWAYS; + } else if (rmiCreateRegistryStrategy.equalsIgnoreCase(CREATE_REGISTRY_AS_NEEDED)) { + rmiCreateRegistryStrategy = CREATE_REGISTRY_AS_NEEDED; + } else if (rmiCreateRegistryStrategy.equalsIgnoreCase(CREATE_REGISTRY_NEVER)) { + rmiCreateRegistryStrategy = CREATE_REGISTRY_NEVER; + } else { + throw new IllegalArgumentException( + "Faild to set RMICreateRegistryStrategy - strategy unknown: '" + + rmiCreateRegistryStrategy + "'"); + } + + this.rmiCreateRegistryStrategy = rmiCreateRegistryStrategy; + } + + /** + *

+ * Get the {@link ThreadPool} for the {@link QuartzScheduler} + * to use. + *

+ */ + public ThreadPool getThreadPool() { + return threadPool; + } + + /** + *

+ * Set the {@link ThreadPool} for the {@link QuartzScheduler} + * to use. + *

+ * + * @exception IllegalArgumentException + * if threadPool is null. + */ + public void setThreadPool(ThreadPool threadPool) { + if (threadPool == null) { + throw new IllegalArgumentException("ThreadPool cannot be null."); + } + + this.threadPool = threadPool; + } + + /** + *

+ * Get the {@link JobStore} for the {@link QuartzScheduler} + * to use. + *

+ */ + public JobStore getJobStore() { + return jobStore; + } + + /** + *

+ * Set the {@link JobStore} for the {@link QuartzScheduler} + * to use. + *

+ * + * @exception IllegalArgumentException + * if jobStore is null. + */ + public void setJobStore(JobStore jobStore) { + if (jobStore == null) { + throw new IllegalArgumentException("JobStore cannot be null."); + } + + this.jobStore = jobStore; + } + + /** + *

+ * Get the {@link JobRunShellFactory} for the {@link QuartzScheduler} + * to use. + *

+ */ + public JobRunShellFactory getJobRunShellFactory() { + return jobRunShellFactory; + } + + /** + *

+ * Set the {@link JobRunShellFactory} for the {@link QuartzScheduler} + * to use. + *

+ * + * @exception IllegalArgumentException + * if jobRunShellFactory is null. + */ + public void setJobRunShellFactory(JobRunShellFactory jobRunShellFactory) { + if (jobRunShellFactory == null) { + throw new IllegalArgumentException( + "JobRunShellFactory cannot be null."); + } + + this.jobRunShellFactory = jobRunShellFactory; + } + + /** + *

+ * Add the given {@link com.fr.third.org.quartz.spi.SchedulerPlugin} for the + * {@link QuartzScheduler} to use. This method expects the plugin's + * "initialize" method to be invoked externally (either before or after + * this method is called). + *

+ */ + public void addSchedulerPlugin(SchedulerPlugin plugin) { + schedulerPlugins.add(plugin); + } + + /** + *

+ * Get the List of all + * {@link com.fr.third.org.quartz.spi.SchedulerPlugin}s for the + * {@link QuartzScheduler} to use. + *

+ */ + public List getSchedulerPlugins() { + return schedulerPlugins; + } + + /** + * Get whether to mark the Quartz scheduling thread as daemon. + * + * @see Thread#setDaemon(boolean) + */ + public boolean getMakeSchedulerThreadDaemon() { + return makeSchedulerThreadDaemon; + } + + /** + * Set whether to mark the Quartz scheduling thread as daemon. + * + * @see Thread#setDaemon(boolean) + */ + public void setMakeSchedulerThreadDaemon(boolean makeSchedulerThreadDaemon) { + this.makeSchedulerThreadDaemon = makeSchedulerThreadDaemon; + } + + /** + * Get whether to set the class load context of spawned threads to that + * of the initializing thread. + */ + public boolean isThreadsInheritInitializersClassLoadContext() { + return threadsInheritInitializersClassLoadContext; + } + + /** + * Set whether to set the class load context of spawned threads to that + * of the initializing thread. + */ + public void setThreadsInheritInitializersClassLoadContext( + boolean threadsInheritInitializersClassLoadContext) { + this.threadsInheritInitializersClassLoadContext = threadsInheritInitializersClassLoadContext; + } + + /** + * Get the name under which to bind the QuartzScheduler in RMI. Will + * return the value of the uniqueIdentifier property if explict RMI bind + * name was never set. + * + * @see #getUniqueIdentifier() + */ + public String getRMIBindName() { + return (rmiBindName == null) ? getUniqueIdentifier() : rmiBindName; + } + + /** + * Set the name under which to bind the QuartzScheduler in RMI. If unset, + * defaults to the value of the uniqueIdentifier property. + * + * @see #getUniqueIdentifier() + */ + public void setRMIBindName(String rmiBindName) { + this.rmiBindName = rmiBindName; + } + + /** + * Get whether the QuartzScheduler should be registered with the local + * MBeanServer. + */ + public boolean getJMXExport() { + return jmxExport; + } + + /** + * Set whether the QuartzScheduler should be registered with the local + * MBeanServer. + */ + public void setJMXExport(boolean jmxExport) { + this.jmxExport = jmxExport; + } + + /** + * Get the name under which the QuartzScheduler should be registered with + * the local MBeanServer. If unset, defaults to the value calculated by + * generateJMXObjectName. + * + * @see #generateJMXObjectName(String, String) + */ + public String getJMXObjectName() { + return (jmxObjectName == null) ? generateJMXObjectName(name, instanceId) : jmxObjectName; + } + + /** + * Set the name under which the QuartzScheduler should be registered with + * the local MBeanServer. If unset, defaults to the value calculated by + * generateJMXObjectName. + * + * @see #generateJMXObjectName(String, String) + */ + public void setJMXObjectName(String jmxObjectName) { + this.jmxObjectName = jmxObjectName; + } + + /** + * Create the name under which this scheduler should be registered in JMX. + *

+ * The name is composed as: + * quartz:type=QuartzScheduler,name=[schedName],instance=[schedInstId] + *

+ */ + public static String generateJMXObjectName(String schedName, String schedInstId) { + return "quartz:type=QuartzScheduler" + + ",name=" + schedName + + ",instance=" + schedInstId; + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/QuartzSchedulerThread.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/QuartzSchedulerThread.java new file mode 100644 index 000000000..6e1f685d5 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/QuartzSchedulerThread.java @@ -0,0 +1,560 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.core; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.JobPersistenceException; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.Trigger; +import com.fr.third.org.quartz.spi.TriggerFiredBundle; + +import java.util.Random; + +/** + *

+ * The thread responsible for performing the work of firing {@link Trigger} + * s that are registered with the {@link QuartzScheduler}. + *

+ * + * @see QuartzScheduler + * @see com.fr.third.org.quartz.Job + * @see Trigger + * + * @author James House + */ +public class QuartzSchedulerThread extends Thread { + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + private QuartzScheduler qs; + + private QuartzSchedulerResources qsRsrcs; + + private Object sigLock = new Object(); + + private boolean signaled; + private long signaledNextFireTime; + + private boolean paused; + + private boolean halted; + + private SchedulingContext ctxt = null; + + private Random random = new Random(System.currentTimeMillis()); + + // When the scheduler finds there is no current trigger to fire, how long + // it should wait until checking again... + private static long DEFAULT_IDLE_WAIT_TIME = 30L * 1000L; + + private long idleWaitTime = DEFAULT_IDLE_WAIT_TIME; + + private int idleWaitVariablness = 7 * 1000; + + private long dbFailureRetryInterval = 15L * 1000L; + + private final Log log = LogFactory.getLog(getClass()); + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Construct a new QuartzSchedulerThread for the given + * QuartzScheduler as a non-daemon Thread + * with normal priority. + *

+ */ + QuartzSchedulerThread(QuartzScheduler qs, QuartzSchedulerResources qsRsrcs, + SchedulingContext ctxt) { + this(qs, qsRsrcs, ctxt, qsRsrcs.getMakeSchedulerThreadDaemon(), Thread.NORM_PRIORITY); + } + + /** + *

+ * Construct a new QuartzSchedulerThread for the given + * QuartzScheduler as a Thread with the given + * attributes. + *

+ */ + QuartzSchedulerThread(QuartzScheduler qs, QuartzSchedulerResources qsRsrcs, + SchedulingContext ctxt, boolean setDaemon, int threadPrio) { + super(qs.getSchedulerThreadGroup(), qsRsrcs.getThreadName()); + this.qs = qs; + this.qsRsrcs = qsRsrcs; + this.ctxt = ctxt; + this.setDaemon(setDaemon); + if(qsRsrcs.isThreadsInheritInitializersClassLoadContext()) { + log.info("QuartzSchedulerThread Inheriting ContextClassLoader of thread: " + Thread.currentThread().getName()); + this.setContextClassLoader(Thread.currentThread().getContextClassLoader()); + } + + this.setPriority(threadPrio); + + // start the underlying thread, but put this object into the 'paused' + // state + // so processing doesn't start yet... + paused = true; + halted = false; + this.start(); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + void setIdleWaitTime(long waitTime) { + idleWaitTime = waitTime; + idleWaitVariablness = (int) (waitTime * 0.2); + } + + private long getDbFailureRetryInterval() { + return dbFailureRetryInterval; + } + + public void setDbFailureRetryInterval(long dbFailureRetryInterval) { + this.dbFailureRetryInterval = dbFailureRetryInterval; + } + + private long getRandomizedIdleWaitTime() { + return idleWaitTime - random.nextInt(idleWaitVariablness); + } + + /** + *

+ * Signals the main processing loop to pause at the next possible point. + *

+ */ + void togglePause(boolean pause) { + synchronized (sigLock) { + paused = pause; + + if (paused) { + signalSchedulingChange(0); + } else { + sigLock.notifyAll(); + } + } + } + + /** + *

+ * Signals the main processing loop to pause at the next possible point. + *

+ */ + void halt() { + synchronized (sigLock) { + halted = true; + + if (paused) { + sigLock.notifyAll(); + } else { + signalSchedulingChange(0); + } + } + } + + boolean isPaused() { + return paused; + } + + /** + *

+ * Signals the main processing loop that a change in scheduling has been + * made - in order to interrupt any sleeping that may be occuring while + * waiting for the fire time to arrive. + *

+ * + * @param newNextTime the time (in millis) when the newly scheduled trigger + * will fire. If this method is being called do to some other even (rather + * than scheduling a trigger), the caller should pass zero (0). + */ + public void signalSchedulingChange(long candidateNewNextFireTime) { + synchronized(sigLock) { + signaled = true; + signaledNextFireTime = candidateNewNextFireTime; + sigLock.notifyAll(); + } + } + + public void clearSignaledSchedulingChange() { + synchronized(sigLock) { + signaled = false; + signaledNextFireTime = 0; + } + } + + public boolean isScheduleChanged() { + synchronized(sigLock) { + return signaled; + } + } + + public long getSignaledNextFireTime() { + synchronized(sigLock) { + return signaledNextFireTime; + } + } + + /** + *

+ * The main processing loop of the QuartzSchedulerThread. + *

+ */ + public void run() { + boolean lastAcquireFailed = false; + + while (!halted) { + try { + // check if we're supposed to pause... + synchronized (sigLock) { + while (paused && !halted) { + try { + // wait until togglePause(false) is called... + sigLock.wait(1000L); + } catch (InterruptedException ignore) { + } + } + + if (halted) { + break; + } + } + + int availTreadCount = qsRsrcs.getThreadPool().blockForAvailableThreads(); + if(availTreadCount > 0) { // will always be true, due to semantics of blockForAvailableThreads... + + Trigger trigger = null; + + long now = System.currentTimeMillis(); + + clearSignaledSchedulingChange(); + try { + trigger = qsRsrcs.getJobStore().acquireNextTrigger( + ctxt, now + idleWaitTime); + lastAcquireFailed = false; + } catch (JobPersistenceException jpe) { + if(!lastAcquireFailed) { + qs.notifySchedulerListenersError( + "An error occured while scanning for the next trigger to fire.", + jpe); + } + lastAcquireFailed = true; + } catch (RuntimeException e) { + if(!lastAcquireFailed) { + getLog().error("quartzSchedulerThreadLoop: RuntimeException " + +e.getMessage(), e); + } + lastAcquireFailed = true; + } + + if (trigger != null) { + + now = System.currentTimeMillis(); + long triggerTime = trigger.getNextFireTime().getTime(); + long timeUntilTrigger = triggerTime - now; + while(timeUntilTrigger > 0) { + synchronized(sigLock) { + try { + // we could have blocked a long while + // on 'synchronize', so we must recompute + now = System.currentTimeMillis(); + timeUntilTrigger = triggerTime - now; + if(timeUntilTrigger > 1) + sigLock.wait(timeUntilTrigger); + } catch (InterruptedException ignore) { + } + } + if (isScheduleChanged()) { + if(isCandidateNewTimeEarlierWithinReason(triggerTime)) { + // above call does a clearSignaledSchedulingChange() + try { + qsRsrcs.getJobStore().releaseAcquiredTrigger( + ctxt, trigger); + } catch (JobPersistenceException jpe) { + qs.notifySchedulerListenersError( + "An error occured while releasing trigger '" + + trigger.getFullName() + "'", + jpe); + // db connection must have failed... keep + // retrying until it's up... + releaseTriggerRetryLoop(trigger); + } catch (RuntimeException e) { + getLog().error( + "releaseTriggerRetryLoop: RuntimeException " + +e.getMessage(), e); + // db connection must have failed... keep + // retrying until it's up... + releaseTriggerRetryLoop(trigger); + } + trigger = null; + break; + } + } + now = System.currentTimeMillis(); + timeUntilTrigger = triggerTime - now; + } + if(trigger == null) + continue; + + // set trigger to 'executing' + TriggerFiredBundle bndle = null; + + boolean goAhead = true; + synchronized(sigLock) { + goAhead = !halted; + } + if(goAhead) { + try { + bndle = qsRsrcs.getJobStore().triggerFired(ctxt, + trigger); + } catch (SchedulerException se) { + qs.notifySchedulerListenersError( + "An error occured while firing trigger '" + + trigger.getFullName() + "'", se); + } catch (RuntimeException e) { + getLog().error( + "RuntimeException while firing trigger " + + trigger.getFullName(), e); + // db connection must have failed... keep + // retrying until it's up... + releaseTriggerRetryLoop(trigger); + } + } + + // it's possible to get 'null' if the trigger was paused, + // blocked, or other similar occurrences that prevent it being + // fired at this time... or if the scheduler was shutdown (halted) + if (bndle == null) { + try { + qsRsrcs.getJobStore().releaseAcquiredTrigger(ctxt, + trigger); + } catch (SchedulerException se) { + qs.notifySchedulerListenersError( + "An error occured while releasing trigger '" + + trigger.getFullName() + "'", se); + // db connection must have failed... keep retrying + // until it's up... + releaseTriggerRetryLoop(trigger); + } + continue; + } + + // TODO: improvements: + // + // 2- make sure we can get a job runshell before firing trigger, or + // don't let that throw an exception (right now it never does, + // but the signature says it can). + // 3- acquire more triggers at a time (based on num threads available?) + + + JobRunShell shell = null; + try { + shell = qsRsrcs.getJobRunShellFactory().borrowJobRunShell(); + shell.initialize(qs, bndle); + } catch (SchedulerException se) { + try { + qsRsrcs.getJobStore().triggeredJobComplete(ctxt, + trigger, bndle.getJobDetail(), Trigger.INSTRUCTION_SET_ALL_JOB_TRIGGERS_ERROR); + } catch (SchedulerException se2) { + qs.notifySchedulerListenersError( + "An error occured while placing job's triggers in error state '" + + trigger.getFullName() + "'", se2); + // db connection must have failed... keep retrying + // until it's up... + errorTriggerRetryLoop(bndle); + } + continue; + } + + if (qsRsrcs.getThreadPool().runInThread(shell) == false) { + try { + // this case should never happen, as it is indicative of the + // scheduler being shutdown or a bug in the thread pool or + // a thread pool being used concurrently - which the docs + // say not to do... + getLog().error("ThreadPool.runInThread() return false!"); + qsRsrcs.getJobStore().triggeredJobComplete(ctxt, + trigger, bndle.getJobDetail(), Trigger.INSTRUCTION_SET_ALL_JOB_TRIGGERS_ERROR); + } catch (SchedulerException se2) { + qs.notifySchedulerListenersError( + "An error occured while placing job's triggers in error state '" + + trigger.getFullName() + "'", se2); + // db connection must have failed... keep retrying + // until it's up... + releaseTriggerRetryLoop(trigger); + } + } + + continue; + } + } else { // if(availTreadCount > 0) + continue; // should never happen, if threadPool.blockForAvailableThreads() follows contract + } + + long now = System.currentTimeMillis(); + long waitTime = now + getRandomizedIdleWaitTime(); + long timeUntilContinue = waitTime - now; + synchronized(sigLock) { + try { + sigLock.wait(timeUntilContinue); + } catch (InterruptedException ignore) { + } + } + + } catch(RuntimeException re) { + getLog().error("Runtime error occured in main trigger firing loop.", re); + } + } // loop... + + // drop references to scheduler stuff to aid garbage collection... + qs = null; + qsRsrcs = null; + } + + private boolean isCandidateNewTimeEarlierWithinReason(long oldTime) { + + // So here's the deal: We know due to being signaled that 'the schedule' + // has changed. We may know (if getSignaledNextFireTime() != 0) the + // new earliest fire time. We may not (in which case we will assume + // that the new time is earlier than the trigger we have acquired). + // In either case, we only want to abandon our acquired trigger and + // go looking for a new one if "it's worth it". It's only worth it if + // the time cost incurred to abandon the trigger and acquire a new one + // is less than the time until the currently acquired trigger will fire, + // otherwise we're just "thrashing" the job store (e.g. database). + // + // So the question becomes when is it "worth it"? This will depend on + // the job store implementation (and of course the particular database + // or whatever behind it). Ideally we would depend on the job store + // implementation to tell us the amount of time in which it "thinks" + // it can abandon the acquired trigger and acquire a new one. However + // we have no current facility for having it tell us that, so we make + // a somewhat educated but arbitrary guess ;-). + + synchronized(sigLock) { + + boolean earlier = false; + + if(getSignaledNextFireTime() == 0) + earlier = true; + else if(getSignaledNextFireTime() < oldTime ) + earlier = true; + + if(earlier) { + // so the new time is considered earlier, but is it enough earlier? + // le + long diff = oldTime - System.currentTimeMillis(); + if(diff < (qsRsrcs.getJobStore().supportsPersistence() ? 80L : 7L)) + earlier = false; + } + + clearSignaledSchedulingChange(); + + return earlier; + } + } + + public void errorTriggerRetryLoop(TriggerFiredBundle bndle) { + int retryCount = 0; + try { + while (!halted) { + try { + Thread.sleep(getDbFailureRetryInterval()); // retry every N + // seconds (the db + // connection must + // be failed) + retryCount++; + qsRsrcs.getJobStore().triggeredJobComplete(ctxt, + bndle.getTrigger(), bndle.getJobDetail(), Trigger.INSTRUCTION_SET_ALL_JOB_TRIGGERS_ERROR); + retryCount = 0; + break; + } catch (JobPersistenceException jpe) { + if(retryCount % 4 == 0) { + qs.notifySchedulerListenersError( + "An error occured while releasing trigger '" + + bndle.getTrigger().getFullName() + "'", jpe); + } + } catch (RuntimeException e) { + getLog().error("releaseTriggerRetryLoop: RuntimeException "+e.getMessage(), e); + } catch (InterruptedException e) { + getLog().error("releaseTriggerRetryLoop: InterruptedException "+e.getMessage(), e); + } + } + } finally { + if(retryCount == 0) { + getLog().info("releaseTriggerRetryLoop: connection restored."); + } + } + } + + public void releaseTriggerRetryLoop(Trigger trigger) { + int retryCount = 0; + try { + while (!halted) { + try { + Thread.sleep(getDbFailureRetryInterval()); // retry every N + // seconds (the db + // connection must + // be failed) + retryCount++; + qsRsrcs.getJobStore().releaseAcquiredTrigger(ctxt, trigger); + retryCount = 0; + break; + } catch (JobPersistenceException jpe) { + if(retryCount % 4 == 0) { + qs.notifySchedulerListenersError( + "An error occured while releasing trigger '" + + trigger.getFullName() + "'", jpe); + } + } catch (RuntimeException e) { + getLog().error("releaseTriggerRetryLoop: RuntimeException "+e.getMessage(), e); + } catch (InterruptedException e) { + getLog().error("releaseTriggerRetryLoop: InterruptedException "+e.getMessage(), e); + } + } + } finally { + if(retryCount == 0) { + getLog().info("releaseTriggerRetryLoop: connection restored."); + } + } + } + + public Log getLog() { + return log; + } + +} // end of QuartzSchedulerThread diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/RemotableQuartzScheduler.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/RemotableQuartzScheduler.java new file mode 100644 index 000000000..fe298bf24 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/RemotableQuartzScheduler.java @@ -0,0 +1,235 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.core; + +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.util.Date; +import java.util.List; +import java.util.Set; + +import com.fr.third.org.quartz.Calendar; +import com.fr.third.org.quartz.JobDataMap; +import com.fr.third.org.quartz.JobDetail; +import com.fr.third.org.quartz.JobListener; +import com.fr.third.org.quartz.SchedulerContext; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.SchedulerListener; +import com.fr.third.org.quartz.Trigger; +import com.fr.third.org.quartz.TriggerListener; +import com.fr.third.org.quartz.UnableToInterruptJobException; + +/** + * @author James House + */ +public interface RemotableQuartzScheduler extends Remote { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + String getSchedulerName() throws RemoteException; + + String getSchedulerInstanceId() throws RemoteException; + + SchedulerContext getSchedulerContext() throws SchedulerException, + RemoteException; + + void start() throws SchedulerException, RemoteException; + + void startDelayed(int seconds) throws SchedulerException, RemoteException; + + void standby() throws RemoteException; + + boolean isInStandbyMode() throws RemoteException; + + void shutdown() throws RemoteException; + + void shutdown(boolean waitForJobsToComplete) throws RemoteException; + + boolean isShutdown() throws RemoteException; + + Date runningSince() throws RemoteException; + + String getVersion() throws RemoteException; + + int numJobsExecuted() throws RemoteException; + + Class getJobStoreClass() throws RemoteException; + + boolean supportsPersistence() throws RemoteException; + + Class getThreadPoolClass() throws RemoteException; + + int getThreadPoolSize() throws RemoteException; + + List getCurrentlyExecutingJobs() throws SchedulerException, + RemoteException; + + Date scheduleJob(SchedulingContext ctxt, JobDetail jobDetail, + Trigger trigger) throws SchedulerException, RemoteException; + + Date scheduleJob(SchedulingContext ctxt, Trigger trigger) + throws SchedulerException, RemoteException; + + void addJob(SchedulingContext ctxt, JobDetail jobDetail, + boolean replace) throws SchedulerException, RemoteException; + + boolean deleteJob(SchedulingContext ctxt, String jobName, + String groupName) throws SchedulerException, RemoteException; + + boolean unscheduleJob(SchedulingContext ctxt, String triggerName, + String groupName) throws SchedulerException, RemoteException; + + Date rescheduleJob(SchedulingContext ctxt, String triggerName, + String groupName, Trigger newTrigger) throws SchedulerException, RemoteException; + + + void triggerJob(SchedulingContext ctxt, String jobName, + String groupName, JobDataMap data) throws SchedulerException, RemoteException; + + void triggerJobWithVolatileTrigger(SchedulingContext ctxt, + String jobName, String groupName, JobDataMap data) throws SchedulerException, + RemoteException; + + void pauseTrigger(SchedulingContext ctxt, String triggerName, + String groupName) throws SchedulerException, RemoteException; + + void pauseTriggerGroup(SchedulingContext ctxt, String groupName) + throws SchedulerException, RemoteException; + + void pauseJob(SchedulingContext ctxt, String jobName, + String groupName) throws SchedulerException, RemoteException; + + void pauseJobGroup(SchedulingContext ctxt, String groupName) + throws SchedulerException, RemoteException; + + void resumeTrigger(SchedulingContext ctxt, String triggerName, + String groupName) throws SchedulerException, RemoteException; + + void resumeTriggerGroup(SchedulingContext ctxt, String groupName) + throws SchedulerException, RemoteException; + + Set getPausedTriggerGroups(SchedulingContext ctxt) + throws SchedulerException, RemoteException; + + void resumeJob(SchedulingContext ctxt, String jobName, + String groupName) throws SchedulerException, RemoteException; + + void resumeJobGroup(SchedulingContext ctxt, String groupName) + throws SchedulerException, RemoteException; + + void pauseAll(SchedulingContext ctxt) throws SchedulerException, + RemoteException; + + void resumeAll(SchedulingContext ctxt) throws SchedulerException, + RemoteException; + + String[] getJobGroupNames(SchedulingContext ctxt) + throws SchedulerException, RemoteException; + + String[] getJobNames(SchedulingContext ctxt, String groupName) + throws SchedulerException, RemoteException; + + Trigger[] getTriggersOfJob(SchedulingContext ctxt, String jobName, + String groupName) throws SchedulerException, RemoteException; + + String[] getTriggerGroupNames(SchedulingContext ctxt) + throws SchedulerException, RemoteException; + + String[] getTriggerNames(SchedulingContext ctxt, String groupName) + throws SchedulerException, RemoteException; + + JobDetail getJobDetail(SchedulingContext ctxt, String jobName, + String jobGroup) throws SchedulerException, RemoteException; + + Trigger getTrigger(SchedulingContext ctxt, String triggerName, + String triggerGroup) throws SchedulerException, RemoteException; + + int getTriggerState(SchedulingContext ctxt, String triggerName, + String triggerGroup) throws SchedulerException, RemoteException; + + void addCalendar(SchedulingContext ctxt, String calName, + Calendar calendar, boolean replace, boolean updateTriggers) throws SchedulerException, + RemoteException; + + boolean deleteCalendar(SchedulingContext ctxt, String calName) + throws SchedulerException, RemoteException; + + Calendar getCalendar(SchedulingContext ctxt, String calName) + throws SchedulerException, RemoteException; + + String[] getCalendarNames(SchedulingContext ctxt) + throws SchedulerException, RemoteException; + + void addGlobalJobListener(JobListener jobListener) + throws RemoteException; + + void addJobListener(JobListener jobListener) throws RemoteException; + + boolean removeGlobalJobListener(String name) throws RemoteException; + + boolean removeJobListener(String name) throws RemoteException; + + List getGlobalJobListeners() throws RemoteException; + + Set getJobListenerNames() throws RemoteException; + + JobListener getGlobalJobListener(String name) throws RemoteException; + + JobListener getJobListener(String name) throws RemoteException; + + void addGlobalTriggerListener(TriggerListener triggerListener) + throws RemoteException; + + void addTriggerListener(TriggerListener triggerListener) + throws RemoteException; + + boolean removeGlobalTriggerListener(String name) + throws RemoteException; + + boolean removeTriggerListener(String name) throws RemoteException; + + List getGlobalTriggerListeners() throws RemoteException; + + Set getTriggerListenerNames() throws RemoteException; + + TriggerListener getGlobalTriggerListener(String name) + throws RemoteException; + + TriggerListener getTriggerListener(String name) + throws RemoteException; + + void addSchedulerListener(SchedulerListener schedulerListener) + throws RemoteException; + + boolean removeSchedulerListener(SchedulerListener schedulerListener) + throws RemoteException; + + List getSchedulerListeners() throws RemoteException; + + boolean interrupt(SchedulingContext ctxt, String jobName, String groupName) throws UnableToInterruptJobException,RemoteException ; +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/SchedulerSignalerImpl.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/SchedulerSignalerImpl.java new file mode 100644 index 000000000..51003eeee --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/SchedulerSignalerImpl.java @@ -0,0 +1,93 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.core; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.Trigger; +import com.fr.third.org.quartz.spi.SchedulerSignaler; + +/** + * An interface to be used by JobStore instances in order to + * communicate signals back to the QuartzScheduler. + * + * @author jhouse + */ +public class SchedulerSignalerImpl implements SchedulerSignaler { + + Log log = LogFactory.getLog(SchedulerSignalerImpl.class); + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + protected QuartzScheduler sched; + protected QuartzSchedulerThread schedThread; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public SchedulerSignalerImpl(QuartzScheduler sched, QuartzSchedulerThread schedThread) { + this.sched = sched; + this.schedThread = schedThread; + + log.info("Initialized Scheduler Signaller of type: " + getClass()); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public void notifyTriggerListenersMisfired(Trigger trigger) { + try { + sched.notifyTriggerListenersMisfired(trigger); + } catch (SchedulerException se) { + sched.getLog().error( + "Error notifying listeners of trigger misfire.", se); + sched.notifySchedulerListenersError( + "Error notifying listeners of trigger misfire.", se); + } + } + + public void notifySchedulerListenersFinalized(Trigger trigger) { + sched.notifySchedulerListenersFinalized(trigger); + } + + public void signalSchedulingChange(long candidateNewNextFireTime) { + schedThread.signalSchedulingChange(candidateNewNextFireTime); + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/SchedulingContext.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/SchedulingContext.java new file mode 100644 index 000000000..58f8f8e03 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/SchedulingContext.java @@ -0,0 +1,87 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.core; + +/** + *

+ * An object used to pass information about the 'client' to the QuartzScheduler. + *

+ * + * @see QuartzScheduler + * + * @author James House + */ +public class SchedulingContext implements java.io.Serializable { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private String instanceId; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Construct a SchedulingContext with default values. + *

+ */ + public SchedulingContext() { + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * get the instanceId in the cluster. + *

+ */ + public String getInstanceId() { + return this.instanceId; + } + + /** + *

+ * Set the instanceId. + *

+ */ + public void setInstanceId(String instanceId) { + this.instanceId = instanceId; + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/mbeans-descriptors.xml b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/mbeans-descriptors.xml new file mode 100644 index 000000000..7a833728c --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/mbeans-descriptors.xml @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/package.html b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/package.html new file mode 100644 index 000000000..3f14da662 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/core/package.html @@ -0,0 +1,15 @@ + + +Package com.fr.third.org.quartz.core + + +

Contains the core classes and interfaces for the Quartz job scheduler.

+ +
+
+
+See the Quartz project + at Open Symphony for more information. + + + \ No newline at end of file diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/ee/jta/JTAJobRunShell.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/ee/jta/JTAJobRunShell.java new file mode 100644 index 000000000..d2c8965ee --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/ee/jta/JTAJobRunShell.java @@ -0,0 +1,166 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.ee.jta; + +import javax.transaction.Status; +import javax.transaction.SystemException; +import javax.transaction.UserTransaction; + +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.core.JobRunShell; +import com.fr.third.org.quartz.core.JobRunShellFactory; +import com.fr.third.org.quartz.core.SchedulingContext; + +/** + *

+ * An extension of {@link com.fr.third.org.quartz.core.JobRunShell} that + * begins an XA transaction before executing the Job, and commits (or + * rolls-back) the transaction after execution completes. + *

+ * + * @see com.fr.third.org.quartz.core.JobRunShell + * + * @author James House + */ +public class JTAJobRunShell extends JobRunShell { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private UserTransaction ut; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Create a JTAJobRunShell instance with the given settings. + *

+ */ + public JTAJobRunShell(JobRunShellFactory jobRunShellFactory, + Scheduler scheduler, SchedulingContext schdCtxt) { + super(jobRunShellFactory, scheduler, schdCtxt); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + protected void begin() throws SchedulerException { + // Don't get a new UserTransaction w/o making sure we cleaned up the old + // one. This is necessary because there are paths through JobRunShell.run() + // where begin() can be called multiple times w/o complete being called in + // between. + cleanupUserTransaction(); + + boolean beganSuccessfully = false; + try { + getLog().debug("Looking up UserTransaction."); + ut = UserTransactionHelper.lookupUserTransaction(); + + getLog().debug("Beginning UserTransaction."); + ut.begin(); + + beganSuccessfully = true; + } catch (SchedulerException se) { + throw se; + } catch (Exception nse) { + + throw new SchedulerException( + "JTAJobRunShell could not start UserTransaction.", nse); + } finally { + if (beganSuccessfully == false) { + cleanupUserTransaction(); + } + } + } + + protected void complete(boolean successfulExecution) + throws SchedulerException { + if (ut == null) { + return; + } + + try { + try { + if (ut.getStatus() == Status.STATUS_MARKED_ROLLBACK) { + getLog().debug("UserTransaction marked for rollback only."); + successfulExecution = false; + } + } catch (SystemException e) { + throw new SchedulerException( + "JTAJobRunShell could not read UserTransaction status.", e); + } + + if (successfulExecution) { + try { + getLog().debug("Committing UserTransaction."); + ut.commit(); + } catch (Exception nse) { + throw new SchedulerException( + "JTAJobRunShell could not commit UserTransaction.", nse); + } + } else { + try { + getLog().debug("Rolling-back UserTransaction."); + ut.rollback(); + } catch (Exception nse) { + throw new SchedulerException( + "JTAJobRunShell could not rollback UserTransaction.", + nse); + } + } + } finally { + cleanupUserTransaction(); + } + } + + /** + * Override passivate() to ensure we always cleanup the UserTransaction. + */ + public void passivate() { + cleanupUserTransaction(); + super.passivate(); + } + + private void cleanupUserTransaction() { + if (ut != null) { + UserTransactionHelper.returnUserTransaction(ut); + ut = null; + } + } +} \ No newline at end of file diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/ee/jta/JTAJobRunShellFactory.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/ee/jta/JTAJobRunShellFactory.java new file mode 100644 index 000000000..bc73b89e4 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/ee/jta/JTAJobRunShellFactory.java @@ -0,0 +1,114 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.ee.jta; + +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SchedulerConfigException; +import com.fr.third.org.quartz.core.JobRunShell; +import com.fr.third.org.quartz.core.JobRunShellFactory; +import com.fr.third.org.quartz.core.SchedulingContext; + +/** + *

+ * Responsible for creating the instances of {@link com.fr.third.org.quartz.ee.jta.JTAJobRunShell} + * to be used within the {@link com.fr.third.org.quartz.core.QuartzScheduler} + * instance. + *

+ * + *

+ * This implementation does not re-use any objects, it simply makes a new + * JTAJobRunShell each time borrowJobRunShell() is called. + *

+ * + * @author James House + */ +public class JTAJobRunShellFactory implements JobRunShellFactory { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private Scheduler scheduler; + + private SchedulingContext schedCtxt; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public JTAJobRunShellFactory() { + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Initialize the factory, providing a handle to the Scheduler + * that should be made available within the JobRunShell and + * the JobExecutionContext s within it, and a handle to the + * SchedulingContext that the shell will use in its own + * operations with the JobStore. + *

+ */ + public void initialize(Scheduler scheduler, SchedulingContext schedCtxt) + throws SchedulerConfigException { + this.scheduler = scheduler; + this.schedCtxt = schedCtxt; + } + + /** + *

+ * Called by the {@link com.fr.third.org.quartz.core.QuartzSchedulerThread} + * to obtain instances of + * {@link com.fr.third.org.quartz.core.JobRunShell}. + *

+ */ + public JobRunShell borrowJobRunShell() { + return new JTAJobRunShell(this, scheduler, schedCtxt); + } + + /** + *

+ * Called by the {@link com.fr.third.org.quartz.core.QuartzSchedulerThread} + * to return instances of + * {@link com.fr.third.org.quartz.core.JobRunShell}. + *

+ */ + public void returnJobRunShell(JobRunShell jobRunShell) { + jobRunShell.passivate(); + } + +} \ No newline at end of file diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/ee/jta/UserTransactionHelper.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/ee/jta/UserTransactionHelper.java new file mode 100644 index 000000000..bc72aa5c1 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/ee/jta/UserTransactionHelper.java @@ -0,0 +1,225 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.ee.jta; + +import javax.naming.InitialContext; +import javax.transaction.HeuristicMixedException; +import javax.transaction.HeuristicRollbackException; +import javax.transaction.NotSupportedException; +import javax.transaction.RollbackException; +import javax.transaction.SystemException; +import javax.transaction.TransactionManager; +import javax.transaction.UserTransaction; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.SchedulerException; + +/** + *

+ * A helper for obtaining a handle to a UserTransaction... + *

+ *

+ * To ensure proper cleanup of the InitalContext used to create/lookup + * the UserTransaction, be sure to always call returnUserTransaction() when + * you are done with the UserTransaction. + *

+ * + * @author James House + */ +public class UserTransactionHelper { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public static final String DEFAULT_USER_TX_LOCATION = "java:comp/UserTransaction"; + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private static String userTxURL = DEFAULT_USER_TX_LOCATION; + + private static Log log = LogFactory.getLog(UserTransactionHelper.class); + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Do not allow the creation of an all static utility class. + */ + private UserTransactionHelper() { + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public static String getUserTxLocation() { + return userTxURL; + } + + /** + * Set the JNDI URL at which the Application Server's UserTransaction can + * be found. If not set, the default value is "java:comp/UserTransaction" - + * which works for nearly all application servers. + */ + public static void setUserTxLocation(String userTxURL) { + if (userTxURL != null) { + UserTransactionHelper.userTxURL = userTxURL; + } + } + + /** + * Create/Lookup a UserTransaction in the InitialContext via the + * name set in setUserTxLocation(). + */ + public static UserTransaction lookupUserTransaction() throws SchedulerException { + return new UserTransactionWithContext(); + } + + /** + * Return a UserTransaction that was retrieved via getUserTransaction(). + * This will make sure that the InitalContext used to lookup/create the + * UserTransaction is properly cleaned up. + */ + public static void returnUserTransaction(UserTransaction userTransaction) { + if ((userTransaction != null) && + (userTransaction instanceof UserTransactionWithContext)) { + UserTransactionWithContext userTransactionWithContext = + (UserTransactionWithContext)userTransaction; + + userTransactionWithContext.closeContext(); + } + } + + + /** + * This class wraps a UserTransaction with the InitialContext that was used + * to look it up, so that when the UserTransaction is returned to the + * UserTransactionHelper the InitialContext can be closed. + */ + private static class UserTransactionWithContext implements UserTransaction { + InitialContext context; + UserTransaction userTransaction; + + public UserTransactionWithContext() throws SchedulerException { + try { + context = new InitialContext(); + } catch (Throwable t) { + throw new SchedulerException( + "UserTransactionHelper failed to create InitialContext to lookup/create UserTransaction.", t); + } + + try { + userTransaction = (UserTransaction)context.lookup(userTxURL); + } catch (Throwable t) { + closeContext(); + throw new SchedulerException( + "UserTransactionHelper could not lookup/create UserTransaction.", + t); + } + + if (userTransaction == null) { + closeContext(); + throw new SchedulerException( + "UserTransactionHelper could not lookup/create UserTransaction from the InitialContext."); + } + } + + /** + * Close the InitialContext that was used to lookup/create the + * underlying UserTransaction. + */ + public void closeContext() { + try { + if (context != null) { + context.close(); + } + } catch (Throwable t) { + getLog().warn("Failed to close InitialContext used to get a UserTransaction.", t); + } + context = null; + } + + /** + * When we are being garbage collected, make sure we were properly + * returned to the UserTransactionHelper. + */ + protected void finalize() throws Throwable { + try { + if (context != null) { + getLog().warn("UserTransaction was never returned to the UserTransactionHelper."); + closeContext(); + } + } finally { + super.finalize(); + } + } + + private static Log getLog() { + return LogFactory.getLog(UserTransactionWithContext.class); + } + + // Wrapper methods that just delegate to the underlying UserTransaction + + public void begin() throws NotSupportedException, SystemException { + userTransaction.begin(); + } + + public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException, IllegalStateException, SystemException { + userTransaction.commit(); + } + + public void rollback() throws IllegalStateException, SecurityException, SystemException { + userTransaction.rollback(); + } + + public void setRollbackOnly() throws IllegalStateException, SystemException { + userTransaction.setRollbackOnly(); + } + + public int getStatus() throws SystemException { + return userTransaction.getStatus(); + } + + public void setTransactionTimeout(int seconds) throws SystemException { + userTransaction.setTransactionTimeout(seconds); + } + } +} \ No newline at end of file diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/helpers/TriggerUtils.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/helpers/TriggerUtils.java new file mode 100644 index 000000000..7f50a2d94 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/helpers/TriggerUtils.java @@ -0,0 +1,1194 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.helpers; + +import java.util.Calendar; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.TimeZone; + +import com.fr.third.org.quartz.CronTrigger; +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SimpleTrigger; +import com.fr.third.org.quartz.Trigger; + +/** + *

+ * Convenience and utility methods for simplifying the construction and + * configuration of {@link Trigger}s. + *

+ * + *

+ * Please submit suggestions for additional convenience methods to either the + * Quartz user forum or the developer's mail list at + * source forge. + *

+ * + * @see CronTrigger + * @see SimpleTrigger + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + * @author James House + */ +public class TriggerUtils { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public static final int SUNDAY = 1; + + public static final int MONDAY = 2; + + public static final int TUESDAY = 3; + + public static final int WEDNESDAY = 4; + + public static final int THURSDAY = 5; + + public static final int FRIDAY = 6; + + public static final int SATURDAY = 7; + + public static final int LAST_DAY_OF_MONTH = -1; + + public static final long MILLISECONDS_IN_MINUTE = 60l * 1000l; + + public static final long MILLISECONDS_IN_HOUR = 60l * 60l * 1000l; + + public static final long SECONDS_IN_DAY = 24l * 60l * 60L; + + public static final long MILLISECONDS_IN_DAY = SECONDS_IN_DAY * 1000l; + + /** + * Private constructor because this is a pure utility class. + */ + private TriggerUtils() { + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private static void validateDayOfWeek(int dayOfWeek) { + if (dayOfWeek < SUNDAY || dayOfWeek > SATURDAY) { + throw new IllegalArgumentException("Invalid day of week."); + } + } + + private static void validateHour(int hour) { + if (hour < 0 || hour > 23) { + throw new IllegalArgumentException( + "Invalid hour (must be >= 0 and <= 23)."); + } + } + + private static void validateMinute(int minute) { + if (minute < 0 || minute > 59) { + throw new IllegalArgumentException( + "Invalid minute (must be >= 0 and <= 59)."); + } + } + + private static void validateSecond(int second) { + if (second < 0 || second > 59) { + throw new IllegalArgumentException( + "Invalid second (must be >= 0 and <= 59)."); + } + } + + private static void validateDayOfMonth(int day) { + if ((day < 1 || day > 31) && day != LAST_DAY_OF_MONTH) { + throw new IllegalArgumentException("Invalid day of month."); + } + } + + private static void validateMonth(int month) { + if (month < 1 || month > 12) { + throw new IllegalArgumentException( + "Invalid month (must be >= 1 and <= 12."); + } + } + + private static void validateYear(int year) { + if (year < 1970 || year > 2099) { + throw new IllegalArgumentException( + "Invalid year (must be >= 1970 and <= 2099."); + } + } + + /** + *

+ * Set the given Trigger's name to the given value, and its + * group to the default group (Scheduler.DEFAULT_GROUP). + *

+ * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static void setTriggerIdentity(Trigger trig, String name) { + trig.setName(name); + trig.setGroup(Scheduler.DEFAULT_GROUP); + } + + /** + *

+ * Set the given Trigger's name to the given value, and its + * group to the given group. + *

+ * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static void setTriggerIdentity(Trigger trig, String name, + String group) { + trig.setName(name); + trig.setGroup(group); + } + + /** + *

+ * Make a trigger that will fire every day at the given time. + *

+ * + *

+ * The generated trigger will still need to have its name, group, + * start-time and end-time set. + *

+ * + * @param hour + * the hour (0-23) upon which to fire + * @param minute + * the minute (0-59) upon which to fire + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Trigger makeDailyTrigger(int hour, int minute) { + validateHour(hour); + validateMinute(minute); + + CronTrigger trig = new CronTrigger(); + + try { + trig.setCronExpression("0 " + minute + " " + hour + " ? * *"); + } catch (Exception ignore) { + return null; /* never happens... */ + } + + return trig; + } + + /** + *

+ * Make a trigger that will fire every day at the given time. + *

+ * + *

+ * The generated trigger will still need to have its name, group, + * start-time and end-time set. + *

+ * + * @param dayOfWeek + * (1-7) the day of week upon which to fire + * @param hour + * the hour (0-23) upon which to fire + * @param minute + * the minute (0-59) upon which to fire + * + * @see #SUNDAY + * @see #MONDAY + * @see #TUESDAY + * @see #WEDNESDAY + * @see #THURSDAY + * @see #FRIDAY + * @see #SATURDAY + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Trigger makeWeeklyTrigger(int dayOfWeek, int hour, int minute) { + validateDayOfWeek(dayOfWeek); + validateHour(hour); + validateMinute(minute); + + CronTrigger trig = new CronTrigger(); + + try { + trig.setCronExpression("0 " + minute + " " + hour + " ? * " + + dayOfWeek); + } catch (Exception ignore) { + return null; /* never happens... */ + } + + return trig; + } + + /** + *

+ * Make a trigger that will fire every day at the given time. + *

+ * + *

+ * The generated trigger will still need to have its name, group, + * start-time and end-time set. + *

+ * + *

+ * If the day of the month specified does not occur in a given month, a + * firing will not occur that month. (i.e. if dayOfMonth is specified as + * 31, no firing will occur in the months of the year with fewer than 31 + * days). + *

+ * + * @param dayOfMonth + * (1-31, or -1) the day of week upon which to fire + * @param hour + * the hour (0-23) upon which to fire + * @param minute + * the minute (0-59) upon which to fire + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Trigger makeMonthlyTrigger(int dayOfMonth, int hour, + int minute) { + validateDayOfMonth(dayOfMonth); + validateHour(hour); + validateMinute(minute); + + CronTrigger trig = new CronTrigger(); + + try { + if (dayOfMonth != LAST_DAY_OF_MONTH) { + trig.setCronExpression("0 " + minute + " " + hour + " " + dayOfMonth + " * ?"); + } else { + trig.setCronExpression("0 " + minute + " " + hour + " L * ?"); + } + } catch (Exception ignore) { + return null; /* never happens... */ + } + + return trig; + } + + /* + *

Make a trigger that will fire every N days at the given time.

+ * + *

The generated trigger will still need to have its name, group, + * start-time and end-time set.

+ * + * @param hour the hour (0-23) upon which to fire @param minute the minute + * (0-59) upon which to fire @param interval the number of days between + * firings public static Trigger makeDailyTrigger(int interval, int hour, + * int minute) { + * + * SimpleTrigger trig = new SimpleTrigger(); + * + * MILLISECONDS_IN_DAY); + * trig.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); + * + * return trig; + * } + */ + + /** + *

+ * Make a trigger that will fire every second, indefinitely. + *

+ * + *

+ * The generated trigger will still need to have its name, group, + * start-time and end-time set. + *

+ * + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Trigger makeSecondlyTrigger() { + return makeSecondlyTrigger(1, SimpleTrigger.REPEAT_INDEFINITELY); + } + + /** + *

+ * Make a trigger that will fire every N seconds, indefinitely. + *

+ * + *

+ * The generated trigger will still need to have its name, group, + * start-time and end-time set. + *

+ * + * @param intervalInSeconds + * the number of seconds between firings + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Trigger makeSecondlyTrigger(int intervalInSeconds) { + return makeSecondlyTrigger(intervalInSeconds, + SimpleTrigger.REPEAT_INDEFINITELY); + } + + /** + *

+ * Make a trigger that will fire every N seconds, with the given number of + * repeats. + *

+ * + *

+ * The generated trigger will still need to have its name, group, + * start-time and end-time set. + *

+ * + * @param intervalInSeconds + * the number of seconds between firings + * @param repeatCount + * the number of times to repeat the firing + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Trigger makeSecondlyTrigger(int intervalInSeconds, + int repeatCount) { + SimpleTrigger trig = new SimpleTrigger(); + + trig.setRepeatInterval(intervalInSeconds * 1000l); + trig.setRepeatCount(repeatCount); + + return trig; + } + + /** + *

+ * Make a trigger that will fire every minute, indefinitely. + *

+ * + *

+ * The generated trigger will still need to have its name, group, + * start-time and end-time set. + *

+ * + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Trigger makeMinutelyTrigger() { + return makeMinutelyTrigger(1, SimpleTrigger.REPEAT_INDEFINITELY); + } + + /** + *

+ * Make a trigger that will fire every N minutes, indefinitely. + *

+ * + *

+ * The generated trigger will still need to have its name, group, + * start-time and end-time set. + *

+ * + * @param intervalInMinutes + * the number of minutes between firings + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Trigger makeMinutelyTrigger(int intervalInMinutes) { + return makeMinutelyTrigger(intervalInMinutes, + SimpleTrigger.REPEAT_INDEFINITELY); + } + + /** + *

+ * Make a trigger that will fire every N minutes, with the given number of + * repeats. + *

+ * + *

+ * The generated trigger will still need to have its name, group, + * start-time and end-time set. + *

+ * + * @param intervalInMinutes + * the number of minutes between firings + * @param repeatCount + * the number of times to repeat the firing + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Trigger makeMinutelyTrigger(int intervalInMinutes, + int repeatCount) { + SimpleTrigger trig = new SimpleTrigger(); + + trig.setRepeatInterval(intervalInMinutes * MILLISECONDS_IN_MINUTE); + trig.setRepeatCount(repeatCount); + + return trig; + } + + /** + *

+ * Make a trigger that will fire every hour, indefinitely. + *

+ * + *

+ * The generated trigger will still need to have its name, group, + * start-time and end-time set. + *

+ * + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Trigger makeHourlyTrigger() { + return makeHourlyTrigger(1, SimpleTrigger.REPEAT_INDEFINITELY); + } + + /** + *

+ * Make a trigger that will fire every N hours, indefinitely. + *

+ * + *

+ * The generated trigger will still need to have its name, group, + * start-time and end-time set. + *

+ * + * @param intervalInHours + * the number of hours between firings + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Trigger makeHourlyTrigger(int intervalInHours) { + return makeHourlyTrigger(intervalInHours, + SimpleTrigger.REPEAT_INDEFINITELY); + } + + /** + *

+ * Make a trigger that will fire every N hours, with the given number of + * repeats. + *

+ * + *

+ * The generated trigger will still need to have its name, group, + * start-time and end-time set. + *

+ * + * @param intervalInHours + * the number of hours between firings + * @param repeatCount + * the number of times to repeat the firing + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Trigger makeHourlyTrigger(int intervalInHours, int repeatCount) { + SimpleTrigger trig = new SimpleTrigger(); + + trig.setRepeatInterval(intervalInHours * MILLISECONDS_IN_HOUR); + trig.setRepeatCount(repeatCount); + + return trig; + } + + /** + *

+ * Returns a date that is rounded to the next even hour above the given + * date. + *

+ * + *

+ * For example an input date with a time of 08:13:54 would result in a date + * with the time of 09:00:00. If the date's time is in the 23rd hour, the + * date's 'day' will be promoted, and the time will be set to 00:00:00. + *

+ * + * @param date + * the Date to round, if null the current time will + * be used + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Date getEvenHourDate(Date date) { + if (date == null) { + date = new Date(); + } + + Calendar c = Calendar.getInstance(); + c.setTime(date); + c.setLenient(true); + + c.set(Calendar.HOUR_OF_DAY, c.get(Calendar.HOUR_OF_DAY) + 1); + c.set(Calendar.MINUTE, 0); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + /** + *

+ * Returns a date that is rounded to the previous even hour below the given + * date. + *

+ * + *

+ * For example an input date with a time of 08:13:54 would result in a date + * with the time of 08:00:00. + *

+ * + * @param date + * the Date to round, if null the current time will + * be used + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Date getEvenHourDateBefore(Date date) { + if (date == null) { + date = new Date(); + } + + Calendar c = Calendar.getInstance(); + c.setTime(date); + + c.set(Calendar.MINUTE, 0); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + /** + *

+ * Returns a date that is rounded to the next even hour above the given + * date. + *

+ * + *

+ * For example an input date with a time of 08:13:54 would result in a date + * with the time of 08:14:00. If the date's time is in the 59th minute, + * then the hour (and possibly the day) will be promoted. + *

+ * + * @param date + * the Date to round, if null the current time will + * be used + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Date getEvenMinuteDate(Date date) { + if (date == null) { + date = new Date(); + } + + Calendar c = Calendar.getInstance(); + c.setTime(date); + c.setLenient(true); + + c.set(Calendar.MINUTE, c.get(Calendar.MINUTE) + 1); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + /** + *

+ * Returns a date that is rounded to the previous even hour below the given + * date. + *

+ * + *

+ * For example an input date with a time of 08:13:54 would result in a date + * with the time of 08:13:00. + *

+ * + * @param date + * the Date to round, if null the current time will + * be used + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Date getEvenMinuteDateBefore(Date date) { + if (date == null) { + date = new Date(); + } + + Calendar c = Calendar.getInstance(); + c.setTime(date); + + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + /** + *

+ * Returns a date that is rounded to the next even second above the given + * date. + *

+ * + * @param date + * the Date to round, if null the current time will + * be used + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Date getEvenSecondDate(Date date) { + if (date == null) { + date = new Date(); + } + + Calendar c = Calendar.getInstance(); + c.setTime(date); + c.setLenient(true); + + c.set(Calendar.SECOND, c.get(Calendar.SECOND) + 1); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + /** + *

+ * Returns a date that is rounded to the previous even second below the + * given date. + *

+ * + *

+ * For example an input date with a time of 08:13:54.341 would result in a + * date with the time of 08:13:00.000. + *

+ * + * @param date + * the Date to round, if null the current time will + * be used + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Date getEvenSecondDateBefore(Date date) { + if (date == null) { + date = new Date(); + } + + Calendar c = Calendar.getInstance(); + c.setTime(date); + + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + /** + *

+ * Returns a date that is rounded to the next even multiple of the given + * minute. + *

+ * + *

+ * For example an input date with a time of 08:13:54, and an input + * minute-base of 5 would result in a date with the time of 08:15:00. The + * same input date with an input minute-base of 10 would result in a date + * with the time of 08:20:00. But a date with the time 08:53:31 and an + * input minute-base of 45 would result in 09:00:00, because the even-hour + * is the next 'base' for 45-minute intervals. + *

+ * + *

+ * More examples: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Input TimeMinute-BaseResult Time
11:16:412011:20:00
11:36:412011:40:00
11:46:412012:00:00
11:26:413011:30:00
11:36:413012:00:00
11:16:411711:17:00
11:17:411711:34:00
11:52:411712:00:00
11:52:41511:55:00
11:57:41512:00:00
11:17:41012:00:00
11:17:41111:08:00
+ *

+ * + * @param date + * the Date to round, if null the current time will + * be used + * @param minuteBase + * the base-minute to set the time on + * + * @see #getNextGivenSecondDate(Date, int) + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Date getNextGivenMinuteDate(Date date, int minuteBase) { + if (minuteBase < 0 || minuteBase > 59) { + throw new IllegalArgumentException( + "minuteBase must be >=0 and <= 59"); + } + + if (date == null) { + date = new Date(); + } + + Calendar c = Calendar.getInstance(); + c.setTime(date); + c.setLenient(true); + + if (minuteBase == 0) { + c.set(Calendar.HOUR_OF_DAY, c.get(Calendar.HOUR_OF_DAY) + 1); + c.set(Calendar.MINUTE, 0); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + int minute = c.get(Calendar.MINUTE); + + int arItr = minute / minuteBase; + + int nextMinuteOccurance = minuteBase * (arItr + 1); + + if (nextMinuteOccurance < 60) { + c.set(Calendar.MINUTE, nextMinuteOccurance); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } else { + c.set(Calendar.HOUR_OF_DAY, c.get(Calendar.HOUR_OF_DAY) + 1); + c.set(Calendar.MINUTE, 0); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + } + + /** + *

+ * Returns a date that is rounded to the next even multiple of the given + * minute. + *

+ * + *

+ * The rules for calculating the second are the same as those for + * calculating the minute in the method getNextGivenMinuteDate(..).

+ * * + * @param date the Date to round, if null the current time will + * be used + * @param secondBase the base-second to set the time on + * + * @see #getNextGivenMinuteDate(Date, int) + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Date getNextGivenSecondDate(Date date, int secondBase) { + if (secondBase < 0 || secondBase > 59) { + throw new IllegalArgumentException( + "secondBase must be >=0 and <= 59"); + } + + if (date == null) { + date = new Date(); + } + + Calendar c = Calendar.getInstance(); + c.setTime(date); + c.setLenient(true); + + if (secondBase == 0) { + c.set(Calendar.MINUTE, c.get(Calendar.MINUTE) + 1); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + int second = c.get(Calendar.SECOND); + + int arItr = second / secondBase; + + int nextSecondOccurance = secondBase * (arItr + 1); + + if (nextSecondOccurance < 60) { + c.set(Calendar.SECOND, nextSecondOccurance); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } else { + c.set(Calendar.MINUTE, c.get(Calendar.MINUTE) + 1); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + } + + /** + *

+ * Get a Date object that represents the given time, on + * today's date. + *

+ * + * @param second + * The value (0-59) to give the seconds field of the date + * @param minute + * The value (0-59) to give the minutes field of the date + * @param hour + * The value (0-23) to give the hours field of the date + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Date getDateOf(int second, int minute, int hour) { + validateSecond(second); + validateMinute(minute); + validateHour(hour); + + Date date = new Date(); + + Calendar c = Calendar.getInstance(); + c.setTime(date); + c.setLenient(true); + + c.set(Calendar.HOUR_OF_DAY, hour); + c.set(Calendar.MINUTE, minute); + c.set(Calendar.SECOND, second); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + /** + *

+ * Get a Date object that represents the given time, on the + * given date. + *

+ * + * @param second + * The value (0-59) to give the seconds field of the date + * @param minute + * The value (0-59) to give the minutes field of the date + * @param hour + * The value (0-23) to give the hours field of the date + * @param dayOfMonth + * The value (1-31) to give the day of month field of the date + * @param month + * The value (1-12) to give the month field of the date + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Date getDateOf(int second, int minute, int hour, + int dayOfMonth, int month) { + validateSecond(second); + validateMinute(minute); + validateHour(hour); + validateDayOfMonth(dayOfMonth); + validateMonth(month); + + Date date = new Date(); + + Calendar c = Calendar.getInstance(); + c.setTime(date); + + c.set(Calendar.MONTH, month - 1); + c.set(Calendar.DAY_OF_MONTH, dayOfMonth); + c.set(Calendar.HOUR_OF_DAY, hour); + c.set(Calendar.MINUTE, minute); + c.set(Calendar.SECOND, second); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + /** + *

+ * Get a Date object that represents the given time, on the + * given date. + *

+ * + * @param second + * The value (0-59) to give the seconds field of the date + * @param minute + * The value (0-59) to give the minutes field of the date + * @param hour + * The value (0-23) to give the hours field of the date + * @param dayOfMonth + * The value (1-31) to give the day of month field of the date + * @param month + * The value (1-12) to give the month field of the date + * @param year + * The value (1970-2099) to give the year field of the date + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static Date getDateOf(int second, int minute, int hour, + int dayOfMonth, int month, int year) { + validateSecond(second); + validateMinute(minute); + validateHour(hour); + validateDayOfMonth(dayOfMonth); + validateMonth(month); + validateYear(year); + + Date date = new Date(); + + Calendar c = Calendar.getInstance(); + c.setTime(date); + + c.set(Calendar.YEAR, year); + c.set(Calendar.MONTH, month - 1); + c.set(Calendar.DAY_OF_MONTH, dayOfMonth); + c.set(Calendar.HOUR_OF_DAY, hour); + c.set(Calendar.MINUTE, minute); + c.set(Calendar.SECOND, second); + c.set(Calendar.MILLISECOND, 0); + + return c.getTime(); + } + + /** + * Returns a list of Dates that are the next fire times of a Trigger. + * The input trigger will be cloned before any work is done, so you need + * not worry about its state being altered by this method. + * + * @param trigg + * The trigger upon which to do the work + * @param cal + * The calendar to apply to the trigger's schedule + * @param numTimes + * The number of next fire times to produce + * @return List of java.util.Date objects + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + */ + public static List computeFireTimes(Trigger trigg, com.fr.third.org.quartz.Calendar cal, + int numTimes) { + LinkedList lst = new LinkedList(); + + Trigger t = (Trigger) trigg.clone(); + + if (t.getNextFireTime() == null) { + t.computeFirstFireTime(cal); + } + + for (int i = 0; i < numTimes; i++) { + Date d = t.getNextFireTime(); + if (d != null) { + lst.add(d); + t.triggered(cal); + } else { + break; + } + } + + return java.util.Collections.unmodifiableList(lst); + } + + /** + * Returns a list of Dates that are the next fire times of a Trigger + * that fall within the given date range. The input trigger will be cloned + * before any work is done, so you need not worry about its state being + * altered by this method. + * + * @param trigg + * The trigger upon which to do the work + * @param cal + * The calendar to apply to the trigger's schedule + * @param from + * The starting date at which to find fire times + * @param to + * The ending date at which to stop finding fire times + * @return List of java.util.Date objects + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + * + * + */ + public static List computeFireTimesBetween(Trigger trigg, + com.fr.third.org.quartz.Calendar cal, Date from, Date to) { + LinkedList lst = new LinkedList(); + + Trigger t = (Trigger) trigg.clone(); + + if (t.getNextFireTime() == null) { + t.computeFirstFireTime(cal); + } + + // TODO: this method could be more efficient by using logic specific + // to the type of trigger ... + while (true) { + Date d = t.getNextFireTime(); + if (d != null) { + if (d.before(from)) { + t.triggered(cal); + continue; + } + if (d.after(to)) { + break; + } + lst.add(d); + t.triggered(cal); + } else { + break; + } + } + + return java.util.Collections.unmodifiableList(lst); + } + + /** + * Translate a date & time from a users timezone to the another + * (probably server) timezone to assist in creating a simple trigger with + * the right date & time. + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + */ + public static Date translateTime(Date date, TimeZone src, TimeZone dest) { + + Date newDate = new Date(); + + int offset = (getOffset(date.getTime(), dest) - getOffset(date.getTime(), src)); + + newDate.setTime(date.getTime() - offset); + + return newDate; + } + + /** + * Gets the offset from UT for the given date in the given timezone, + * taking into account daylight savings. + * + *

+ * Equivalent of TimeZone.getOffset(date) in JDK 1.4, but Quartz is trying + * to support JDK 1.3. + *

+ * + * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + */ + public static int getOffset(long date, TimeZone tz) { + + if (tz.inDaylightTime(new Date(date))) { + return tz.getRawOffset() + getDSTSavings(tz); + } + + return tz.getRawOffset(); + } + + /** + *

+ * Equivalent of TimeZone.getDSTSavings() in JDK 1.4, but Quartz is trying + * to support JDK 1.3. + *

+ * + * @deprecated use com.fr.third.org.quartz.TriggerUtils instead! + */ + public static int getDSTSavings(TimeZone tz) { + + if (tz.useDaylightTime()) { + return 3600000; + } + return 0; + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/helpers/VersionPrinter.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/helpers/VersionPrinter.java new file mode 100644 index 000000000..9b63aeb47 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/helpers/VersionPrinter.java @@ -0,0 +1,54 @@ + +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.helpers; + +import com.fr.third.org.quartz.core.QuartzScheduler; + +/** + *

+ * Prints the version of Quartz on stdout. + *

+ * + * @author James House + */ +public class VersionPrinter { + + /** + * Private constructor because this is a pure utility class. + */ + private VersionPrinter() { + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public static void main(String[] args) { + System.out.println("Quartz version: " + QuartzScheduler.getVersionMajor() + + "." + QuartzScheduler.getVersionMinor() + "." + + QuartzScheduler.getVersionIteration()); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/helpers/package.html b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/helpers/package.html new file mode 100644 index 000000000..09d4f72d5 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/helpers/package.html @@ -0,0 +1,15 @@ + + +Package com.fr.third.org.quartz.helpers + + +

Contains helper classes to make working with Quartz easier.

+ +
+
+
+See the Quartz project + at Open Symphony for more information. + + + \ No newline at end of file diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/DirectSchedulerFactory.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/DirectSchedulerFactory.java new file mode 100644 index 000000000..0bda8dbad --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/DirectSchedulerFactory.java @@ -0,0 +1,482 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.SchedulerFactory; +import com.fr.third.org.quartz.core.JobRunShellFactory; +import com.fr.third.org.quartz.core.QuartzScheduler; +import com.fr.third.org.quartz.core.QuartzSchedulerResources; +import com.fr.third.org.quartz.core.SchedulingContext; +import com.fr.third.org.quartz.simpl.CascadingClassLoadHelper; +import com.fr.third.org.quartz.simpl.RAMJobStore; +import com.fr.third.org.quartz.simpl.SimpleThreadPool; +import com.fr.third.org.quartz.spi.ClassLoadHelper; +import com.fr.third.org.quartz.spi.JobStore; +import com.fr.third.org.quartz.spi.SchedulerPlugin; +import com.fr.third.org.quartz.spi.ThreadPool; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +/** + *

+ * A singleton implementation of {@link com.fr.third.org.quartz.SchedulerFactory}. + *

+ * + *

+ * Here are some examples of using this class: + *

+ *

+ * To create a scheduler that does not write anything to the database (is not + * persistent), you can call createVolatileScheduler: + * + *

+ *  DirectSchedulerFactory.getInstance().createVolatileScheduler(10); // 10 threads * // don't forget to start the scheduler: DirectSchedulerFactory.getInstance().getScheduler().start();
+ * 
+ * + * + *

+ * Several create methods are provided for convenience. All create methods + * eventually end up calling the create method with all the parameters: + *

+ * + *
+ *  public void createScheduler(String schedulerName, String schedulerInstanceId, ThreadPool threadPool, JobStore jobStore, String rmiRegistryHost, int rmiRegistryPort)
+ * 
+ * + * + *

+ * Here is an example of using this method: + *

+ * * + * *
// create the thread pool SimpleThreadPool threadPool = new SimpleThreadPool(maxThreads, Thread.NORM_PRIORITY); threadPool.initialize(); * // create the job store JobStore jobStore = new RAMJobStore(); jobStore.initialize();
+ * 
+ *  DirectSchedulerFactory.getInstance().createScheduler("My Quartz Scheduler", "My Instance", threadPool, jobStore, "localhost", 1099); * // don't forget to start the scheduler: DirectSchedulerFactory.getInstance().getScheduler("My Quartz Scheduler", "My Instance").start();
+ * 
+ * + * + *

+ * You can also use a JDBCJobStore instead of the RAMJobStore: + *

+ * + *
+ *  DBConnectionManager.getInstance().addConnectionProvider("someDatasource", new JNDIConnectionProvider("someDatasourceJNDIName"));
+ * 
+ *  JDBCJobStore jdbcJobStore = new JDBCJobStore(); jdbcJobStore.setDataSource("someDatasource"); jdbcJobStore.setPostgresStyleBlobs(true); jdbcJobStore.setTablePrefix("QRTZ_"); jdbcJobStore.setInstanceId("My Instance"); jdbcJobStore.initialize();
+ * 
+ * + * @author Mohammad Rezaei + * @author James House + * + * @see JobStore + * @see ThreadPool + */ +public class DirectSchedulerFactory implements SchedulerFactory { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + public static final String DEFAULT_INSTANCE_ID = "SIMPLE_NON_CLUSTERED"; + + public static final String DEFAULT_SCHEDULER_NAME = "SimpleQuartzScheduler"; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private boolean initialized = false; + + private static DirectSchedulerFactory instance = new DirectSchedulerFactory(); + + private final Log log = LogFactory.getLog(getClass()); + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + protected Log getLog() { + return log; + } + + /** + * Constructor + */ + protected DirectSchedulerFactory() { + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public static DirectSchedulerFactory getInstance() { + return instance; + } + + /** + * Creates an in memory job store ({@link RAMJobStore}) + * The thread priority is set to Thread.NORM_PRIORITY + * + * @param maxThreads + * The number of threads in the thread pool + * @throws SchedulerException + * if initialization failed. + */ + public void createVolatileScheduler(int maxThreads) + throws SchedulerException { + SimpleThreadPool threadPool = new SimpleThreadPool(maxThreads, + Thread.NORM_PRIORITY); + threadPool.initialize(); + JobStore jobStore = new RAMJobStore(); + this.createScheduler(threadPool, jobStore); + + } + + /** + * @deprecated see correctly spelled method. + * @see #createVolatileScheduler(int) + */ + public void createVolatileSchduler(int maxThreads) + throws SchedulerException { + createVolatileScheduler(maxThreads); + } + + /** + * Creates a proxy to a remote scheduler. This scheduler can be retrieved + * via {@link DirectSchedulerFactory#getScheduler()} + * + * @param rmiHost + * The hostname for remote scheduler + * @param rmiPort + * Port for the remote scheduler. The default RMI port is 1099. + * @throws SchedulerException + * if the remote scheduler could not be reached. + */ + public void createRemoteScheduler(String rmiHost, int rmiPort) + throws SchedulerException { + createRemoteScheduler(DEFAULT_SCHEDULER_NAME, DEFAULT_INSTANCE_ID, + rmiHost, rmiPort); + initialized = true; + } + + /** + * Same as + * {@link DirectSchedulerFactory#createRemoteScheduler(String rmiHost, int rmiPort)}, + * with the addition of specifying the scheduler name and instance ID. This + * scheduler can only be retrieved via + * {@link DirectSchedulerFactory#getScheduler(String)} + * + * @param schedulerName + * The name for the scheduler. + * @param schedulerInstanceId + * The instance ID for the scheduler. + * @param rmiHost + * The hostname for remote scheduler + * @param rmiPort + * Port for the remote scheduler. The default RMI port is 1099. + * @throws SchedulerException + * if the remote scheduler could not be reached. + */ + public void createRemoteScheduler(String schedulerName, + String schedulerInstanceId, String rmiHost, int rmiPort) + throws SchedulerException { + createRemoteScheduler(schedulerName, + schedulerInstanceId, null, rmiHost, rmiPort); + } + + /** + * Same as + * {@link DirectSchedulerFactory#createRemoteScheduler(String rmiHost, int rmiPort)}, + * with the addition of specifying the scheduler name, instance ID, and rmi + * bind name. This scheduler can only be retrieved via + * {@link DirectSchedulerFactory#getScheduler(String)} + * + * @param schedulerName + * The name for the scheduler. + * @param schedulerInstanceId + * The instance ID for the scheduler. + * @param rmiBindName + * The name of the remote scheduler in the RMI repository. If null + * defaults to the generated unique identifier. + * @param rmiHost + * The hostname for remote scheduler + * @param rmiPort + * Port for the remote scheduler. The default RMI port is 1099. + * @throws SchedulerException + * if the remote scheduler could not be reached. + */ + public void createRemoteScheduler(String schedulerName, + String schedulerInstanceId, String rmiBindName, String rmiHost, int rmiPort) + throws SchedulerException { + SchedulingContext schedCtxt = new SchedulingContext(); + schedCtxt.setInstanceId(schedulerInstanceId); + + String uid = (rmiBindName != null) ? rmiBindName : + QuartzSchedulerResources.getUniqueIdentifier( + schedulerName, schedulerInstanceId); + + RemoteScheduler remoteScheduler = new RemoteScheduler(schedCtxt, uid, + rmiHost, rmiPort); + + SchedulerRepository schedRep = SchedulerRepository.getInstance(); + schedRep.bind(remoteScheduler); + } + + /** + * Creates a scheduler using the specified thread pool and job store. This + * scheduler can be retrieved via + * {@link DirectSchedulerFactory#getScheduler()} + * + * @param threadPool + * The thread pool for executing jobs + * @param jobStore + * The type of job store + * @throws SchedulerException + * if initialization failed + */ + public void createScheduler(ThreadPool threadPool, JobStore jobStore) + throws SchedulerException { + createScheduler(DEFAULT_SCHEDULER_NAME, DEFAULT_INSTANCE_ID, + threadPool, jobStore); + initialized = true; + } + + /** + * Same as + * {@link DirectSchedulerFactory#createScheduler(ThreadPool threadPool, JobStore jobStore)}, + * with the addition of specifying the scheduler name and instance ID. This + * scheduler can only be retrieved via + * {@link DirectSchedulerFactory#getScheduler(String)} + * + * @param schedulerName + * The name for the scheduler. + * @param schedulerInstanceId + * The instance ID for the scheduler. + * @param threadPool + * The thread pool for executing jobs + * @param jobStore + * The type of job store + * @throws SchedulerException + * if initialization failed + */ + public void createScheduler(String schedulerName, + String schedulerInstanceId, ThreadPool threadPool, JobStore jobStore) + throws SchedulerException { + createScheduler(schedulerName, schedulerInstanceId, threadPool, + jobStore, null, 0, -1, -1); + } + + /** + * Creates a scheduler using the specified thread pool and job store and + * binds it to RMI. + * + * @param schedulerName + * The name for the scheduler. + * @param schedulerInstanceId + * The instance ID for the scheduler. + * @param threadPool + * The thread pool for executing jobs + * @param jobStore + * The type of job store + * @param rmiRegistryHost + * The hostname to register this scheduler with for RMI. Can use + * "null" if no RMI is required. + * @param rmiRegistryPort + * The port for RMI. Typically 1099. + * @param idleWaitTime + * The idle wait time in milliseconds. You can specify "-1" for + * the default value, which is currently 30000 ms. + * @throws SchedulerException + * if initialization failed + */ + public void createScheduler(String schedulerName, + String schedulerInstanceId, ThreadPool threadPool, + JobStore jobStore, String rmiRegistryHost, int rmiRegistryPort, + long idleWaitTime, long dbFailureRetryInterval) + throws SchedulerException { + createScheduler(schedulerName, + schedulerInstanceId, threadPool, + jobStore, null, // plugins + rmiRegistryHost, rmiRegistryPort, + idleWaitTime, dbFailureRetryInterval); + } + + /** + * Creates a scheduler using the specified thread pool, job store, and + * plugins, and binds it to RMI. + * + * @param schedulerName + * The name for the scheduler. + * @param schedulerInstanceId + * The instance ID for the scheduler. + * @param threadPool + * The thread pool for executing jobs + * @param jobStore + * The type of job store + * @param schedulerPluginMap + * Map from a String plugin names to + * {@link com.fr.third.org.quartz.spi.SchedulerPlugin}s. Can use + * "null" if no plugins are required. + * @param rmiRegistryHost + * The hostname to register this scheduler with for RMI. Can use + * "null" if no RMI is required. + * @param rmiRegistryPort + * The port for RMI. Typically 1099. + * @param idleWaitTime + * The idle wait time in milliseconds. You can specify "-1" for + * the default value, which is currently 30000 ms. + * @throws SchedulerException + * if initialization failed + */ + public void createScheduler(String schedulerName, + String schedulerInstanceId, ThreadPool threadPool, + JobStore jobStore, Map schedulerPluginMap, + String rmiRegistryHost, int rmiRegistryPort, + long idleWaitTime, long dbFailureRetryInterval) + throws SchedulerException { + // Currently only one run-shell factory is available... + JobRunShellFactory jrsf = new StdJobRunShellFactory(); + + // Fire everything up + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SchedulingContext schedCtxt = new SchedulingContext(); + schedCtxt.setInstanceId(schedulerInstanceId); + + QuartzSchedulerResources qrs = new QuartzSchedulerResources(); + + qrs.setName(schedulerName); + qrs.setInstanceId(schedulerInstanceId); + qrs.setJobRunShellFactory(jrsf); + qrs.setThreadPool(threadPool); + qrs.setJobStore(jobStore); + qrs.setRMIRegistryHost(rmiRegistryHost); + qrs.setRMIRegistryPort(rmiRegistryPort); + + // add plugins + if (schedulerPluginMap != null) { + for (Iterator pluginIter = schedulerPluginMap.values().iterator(); pluginIter.hasNext();) { + qrs.addSchedulerPlugin((SchedulerPlugin)pluginIter.next()); + } + } + + QuartzScheduler qs = new QuartzScheduler(qrs, schedCtxt, idleWaitTime, + dbFailureRetryInterval); + + ClassLoadHelper cch = new CascadingClassLoadHelper(); + cch.initialize(); + + jobStore.initialize(cch, qs.getSchedulerSignaler()); + + Scheduler scheduler = new StdScheduler(qs, schedCtxt); + + // Initialize plugins now that we have a Scheduler instance. + if (schedulerPluginMap != null) { + for (Iterator pluginEntryIter = schedulerPluginMap.entrySet().iterator(); pluginEntryIter.hasNext();) { + Map.Entry pluginEntry = (Map.Entry)pluginEntryIter.next(); + + ((SchedulerPlugin)pluginEntry.getValue()).initialize( + (String)pluginEntry.getKey(), scheduler); + } + } + + jrsf.initialize(scheduler, schedCtxt); + + getLog().info("Quartz scheduler '" + scheduler.getSchedulerName()); + + getLog().info("Quartz scheduler version: " + qs.getVersion()); + + SchedulerRepository schedRep = SchedulerRepository.getInstance(); + + qs.addNoGCObject(schedRep); // prevents the repository from being + // garbage collected + + schedRep.bind(scheduler); + } + + /* + * public void registerSchedulerForRmi(String schedulerName, String + * schedulerId, String registryHost, int registryPort) throws + * SchedulerException, RemoteException { QuartzScheduler scheduler = + * (QuartzScheduler) this.getScheduler(); scheduler.bind(registryHost, + * registryPort); } + */ + + /** + *

+ * Returns a handle to the Scheduler produced by this factory. + *

+ * + *

+ * you must call createRemoteScheduler or createScheduler methods before + * calling getScheduler() + *

+ */ + public Scheduler getScheduler() throws SchedulerException { + if (!initialized) { + throw new SchedulerException( + "you must call createRemoteScheduler or createScheduler methods before calling getScheduler()"); + } + + return getScheduler(DEFAULT_SCHEDULER_NAME); + } + + /** + *

+ * Returns a handle to the Scheduler with the given name, if it exists. + *

+ */ + public Scheduler getScheduler(String schedName) throws SchedulerException { + SchedulerRepository schedRep = SchedulerRepository.getInstance(); + + return schedRep.lookup(schedName); + } + + /** + *

+ * Returns a handle to all known Schedulers (made by any + * StdSchedulerFactory instance.). + *

+ */ + public Collection getAllSchedulers() throws SchedulerException { + return SchedulerRepository.getInstance().lookupAll(); + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/QuartzServer.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/QuartzServer.java new file mode 100644 index 000000000..6879b4e90 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/QuartzServer.java @@ -0,0 +1,197 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl; + +import java.io.BufferedReader; +import java.io.InputStreamReader; + +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.SchedulerFactory; +import com.fr.third.org.quartz.listeners.SchedulerListenerSupport; + +/** + *

+ * Instantiates an instance of Quartz Scheduler as a stand-alone program, if + * the scheduler is configured for RMI it will be made available. + *

+ * + *

+ * The main() method of this class currently accepts 0 or 1 arguemtns, if there + * is an argument, and its value is "console", then the program + * will print a short message on the console (std-out) and wait for the user to + * type "exit" - at which time the scheduler will be shutdown. + *

+ * + *

+ * Future versions of this server should allow additional configuration for + * responding to scheduler events by allowing the user to specify {@link com.fr.third.org.quartz.JobListener}, + * {@link com.fr.third.org.quartz.TriggerListener} and {@link com.fr.third.org.quartz.SchedulerListener} + * classes. + *

+ * + *

+ * Please read the Quartz FAQ entries about RMI before asking questions in the + * forums or mail-lists. + *

+ * + * @author James House + */ +public class QuartzServer extends SchedulerListenerSupport { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private Scheduler sched = null; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + QuartzServer() { + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public void serve(SchedulerFactory schedFact, boolean console) + throws Exception { + sched = schedFact.getScheduler(); + + sched.start(); + + try { + Thread.sleep(3000l); + } catch (Exception ignore) { + } + + System.out.println("\n*** The scheduler successfully started."); + + if (console) { + System.out.println("\n"); + System.out + .println("The scheduler will now run until you type \"exit\""); + System.out + .println(" If it was configured to export itself via RMI,"); + System.out.println(" then other process may now use it."); + + BufferedReader rdr = new BufferedReader(new InputStreamReader( + System.in)); + + while (true) { + System.out.print("Type 'exit' to shutdown the server: "); + if ("exit".equals(rdr.readLine())) { + break; + } + } + + System.out.println("\n...Shutting down server..."); + + sched.shutdown(true); + } + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * SchedulerListener Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Called by the {@link Scheduler} when a serious error has + * occured within the scheduler - such as repeated failures in the JobStore, + * or the inability to instantiate a Job instance when its + * Trigger has fired. + *

+ * + *

+ * The getErrorCode() method of the given SchedulerException + * can be used to determine more specific information about the type of + * error that was encountered. + *

+ */ + public void schedulerError(String msg, SchedulerException cause) { + System.err.println("*** " + msg); + cause.printStackTrace(); + } + + /** + *

+ * Called by the {@link Scheduler} to inform the listener + * that it has shutdown. + *

+ */ + public void schedulerShutdown() { + System.out.println("\n*** The scheduler is now shutdown."); + sched = null; + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Main Method. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public static void main(String[] args) throws Exception { + + // //Configure Log4J + // org.apache.log4j.PropertyConfigurator.configure( + // System.getProperty("log4jConfigFile", "log4j.properties")); + + if (System.getSecurityManager() == null) { + System.setSecurityManager(new java.rmi.RMISecurityManager()); + } + + try { + QuartzServer server = new QuartzServer(); + if (args.length == 0) { + server.serve( + new com.fr.third.org.quartz.impl.StdSchedulerFactory(), false); + } else if (args.length == 1 && args[0].equalsIgnoreCase("console")) { + server.serve(new com.fr.third.org.quartz.impl.StdSchedulerFactory(), true); + } else { + System.err.println("\nUsage: QuartzServer [console]"); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/RemoteMBeanScheduler.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/RemoteMBeanScheduler.java new file mode 100644 index 000000000..ef7583170 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/RemoteMBeanScheduler.java @@ -0,0 +1,1108 @@ +/* + * Copyright 2004-2006 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ +package com.fr.third.org.quartz.impl; + +import java.util.Date; +import java.util.List; +import java.util.Set; + +import javax.management.AttributeList; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +import com.fr.third.org.quartz.Calendar; +import com.fr.third.org.quartz.JobDataMap; +import com.fr.third.org.quartz.JobDetail; +import com.fr.third.org.quartz.JobListener; +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SchedulerContext; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.SchedulerListener; +import com.fr.third.org.quartz.SchedulerMetaData; +import com.fr.third.org.quartz.Trigger; +import com.fr.third.org.quartz.TriggerListener; +import com.fr.third.org.quartz.UnableToInterruptJobException; +import com.fr.third.org.quartz.core.SchedulingContext; +import com.fr.third.org.quartz.spi.JobFactory; + +/** + *

+ * An implementation of the Scheduler interface that remotely + * proxies all method calls to the equivalent call on a given QuartzScheduler + * instance, via JMX. + *

+ * + *

+ * A user must create a subclass to implement the actual connection to the remote + * MBeanServer using their application specific connector. + * For example {@link com.fr.third.org.quartz.ee.jmx.jboss.JBoss4RMIRemoteMBeanScheduler}. + *

+ * @see com.fr.third.org.quartz.Scheduler + * @see com.fr.third.org.quartz.core.QuartzScheduler + * @see com.fr.third.org.quartz.core.SchedulingContext + */ +public abstract class RemoteMBeanScheduler implements Scheduler { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private SchedulingContext schedulingContext; + + private ObjectName schedulerObjectName; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public RemoteMBeanScheduler() { + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Properties. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Get the name under which the Scheduler MBean is registered on the + * remote MBean server. + */ + protected ObjectName getSchedulerObjectName() { + return schedulerObjectName; + } + + /** + * Set the name under which the Scheduler MBean is registered on the + * remote MBean server. + */ + public void setSchedulerObjectName(String schedulerObjectName) throws SchedulerException { + try { + this.schedulerObjectName = new ObjectName(schedulerObjectName); + } catch (MalformedObjectNameException e) { + throw new SchedulerException("Failed to parse Scheduler MBean name: " + schedulerObjectName, e); + } + } + + /** + * Set the name under which the Scheduler MBean is registered on the + * remote MBean server. + */ + public void setSchedulerObjectName(ObjectName schedulerObjectName) throws SchedulerException { + this.schedulerObjectName = schedulerObjectName; + } + + /** + * Set the scheduling context of this proxy. + */ + public void setSchedulingContext(SchedulingContext schedulingContext) { + this.schedulingContext = schedulingContext; + } + + + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Abstract methods. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Initialize this RemoteMBeanScheduler instance, connecting to the + * remote MBean server. + */ + public abstract void initialize() throws SchedulerException; + + /** + * Get the given attribute of the remote Scheduler MBean. + */ + protected abstract Object getAttribute( + String attribute) throws SchedulerException; + + /** + * Get the given attributes of the remote Scheduler MBean. + */ + protected abstract AttributeList getAttributes(String[] attributes) + throws SchedulerException; + + /** + * Invoke the given operation on the remote Scheduler MBean. + */ + protected abstract Object invoke( + String operationName, + Object[] params, + String[] signature) throws SchedulerException; + + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Returns the name of the Scheduler. + *

+ */ + public String getSchedulerName() throws SchedulerException { + return (String)getAttribute("schedulerName"); + } + + /** + *

+ * Returns the instance Id of the Scheduler. + *

+ */ + public String getSchedulerInstanceId() throws SchedulerException { + return (String)getAttribute("schedulerInstanceId"); + } + + public SchedulerMetaData getMetaData() throws SchedulerException { + AttributeList attributeList = + getAttributes( + new String[] { + "schedulerName", + "schedulerInstanceId", + "inStandbyMode", + "shutdown", + "jobStoreClass", + "threadPoolClass", + "threadPoolSize", + "version" + }); + + return new SchedulerMetaData( + (String)attributeList.get(0), + (String)attributeList.get(1), + getClass(), true, isStarted(), + ((Boolean)attributeList.get(2)).booleanValue(), + ((Boolean)attributeList.get(3)).booleanValue(), + (Date)invoke("runningSince", new Object[] {}, new String[] {}), + ((Integer)invoke("numJobsExecuted", new Object[] {}, new String[] {})).intValue(), + (Class)attributeList.get(4), + ((Boolean)invoke("supportsPersistence", new Object[] {}, new String[] {})).booleanValue(), + (Class)attributeList.get(5), + ((Integer)attributeList.get(6)).intValue(), + (String)attributeList.get(7)); + } + + /** + *

+ * Returns the SchedulerContext of the Scheduler. + *

+ */ + public SchedulerContext getContext() throws SchedulerException { + return (SchedulerContext)getAttribute("schedulerContext"); + } + + /////////////////////////////////////////////////////////////////////////// + /// + /// Schedululer State Management Methods + /// + /////////////////////////////////////////////////////////////////////////// + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void start() throws SchedulerException { + invoke("start", new Object[] {}, new String[] {}); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void startDelayed(int seconds) throws SchedulerException { + invoke("startDelayed", new Object[] {new Integer(seconds)}, new String[] {int.class.getName()}); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void standby() throws SchedulerException { + invoke("standby", new Object[] {}, new String[] {}); + } + + /** + * @see com.fr.third.org.quartz.Scheduler#pause() + * @deprecated + */ + public void pause() throws SchedulerException { + standby(); + } + + + /** + * Whether the scheduler has been started. + * + *

+ * Note: This only reflects whether {@link #start()} has ever + * been called on this Scheduler, so it will return true even + * if the Scheduler is currently in standby mode or has been + * since shutdown. + *

+ * + * @see #start() + * @see #isShutdown() + * @see #isInStandbyMode() + */ + public boolean isStarted() throws SchedulerException { + return (invoke("runningSince", new Object[] {}, new String[] {}) != null); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean isInStandbyMode() throws SchedulerException { + return ((Boolean)getAttribute("inStandbyMode")).booleanValue(); + } + + /** + * @see com.fr.third.org.quartz.Scheduler#isInStandbyMode() + * @deprecated + */ + public boolean isPaused() throws SchedulerException { + return isInStandbyMode(); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void shutdown() throws SchedulerException { + // Have to get the scheduler name before we actually call shutdown. + String schedulerName = getSchedulerName(); + + invoke("shutdown", new Object[] {}, new String[] {}); + SchedulerRepository.getInstance().remove(schedulerName); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void shutdown(boolean waitForJobsToComplete) + throws SchedulerException { + // Have to get the scheduler name before we actually call shutdown. + String schedulerName = getSchedulerName(); + + invoke( + "shutdown", + new Object[] { toBoolean(waitForJobsToComplete) }, + new String[] { boolean.class.getName() }); + + SchedulerRepository.getInstance().remove(schedulerName); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean isShutdown() throws SchedulerException { + return ((Boolean)getAttribute("shutdown")).booleanValue(); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public List getCurrentlyExecutingJobs() throws SchedulerException { + return (List)invoke("getCurrentlyExecutingJobs", new Object[] {}, new String[] {}); + } + + /////////////////////////////////////////////////////////////////////////// + /// + /// Scheduling-related Methods + /// + /////////////////////////////////////////////////////////////////////////// + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public Date scheduleJob(JobDetail jobDetail, Trigger trigger) + throws SchedulerException { + return (Date)invoke( + "scheduleJob", + new Object[] { schedulingContext, jobDetail, trigger }, + new String[] { SchedulingContext.class.getName(), JobDetail.class.getName(), Trigger.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public Date scheduleJob(Trigger trigger) throws SchedulerException { + return (Date)invoke( + "scheduleJob", + new Object[] { schedulingContext, trigger }, + new String[] { SchedulingContext.class.getName(), Trigger.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void addJob(JobDetail jobDetail, boolean replace) + throws SchedulerException { + invoke( + "addJob", + new Object[] { schedulingContext, jobDetail, toBoolean(replace) }, + new String[] { SchedulingContext.class.getName(), JobDetail.class.getName(), boolean.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public boolean deleteJob(String jobName, String groupName) + throws SchedulerException { + return ((Boolean)invoke( + "deleteJob", + new Object[] { schedulingContext, jobName, groupName}, + new String[] { SchedulingContext.class.getName(), String.class.getName(), String.class.getName() })).booleanValue(); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public boolean unscheduleJob(String triggerName, String groupName) + throws SchedulerException { + return ((Boolean)invoke( + "unscheduleJob", + new Object[] { schedulingContext, triggerName, groupName}, + new String[] { SchedulingContext.class.getName(), String.class.getName(), String.class.getName() })).booleanValue(); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public Date rescheduleJob(String triggerName, + String groupName, Trigger newTrigger) throws SchedulerException { + return (Date)invoke( + "unscheduleJob", + new Object[] { schedulingContext, triggerName, groupName, newTrigger}, + new String[] { SchedulingContext.class.getName(), String.class.getName(), String.class.getName(), Trigger.class.getName() }); + } + + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void triggerJob(String jobName, String groupName) + throws SchedulerException { + triggerJob(jobName, groupName, null); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void triggerJob(String jobName, String groupName, JobDataMap data) + throws SchedulerException { + invoke( + "triggerJob", + new Object[] { schedulingContext, jobName, groupName, data}, + new String[] { SchedulingContext.class.getName(), String.class.getName(), String.class.getName(), JobDataMap.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void triggerJobWithVolatileTrigger(String jobName, String groupName) + throws SchedulerException { + triggerJobWithVolatileTrigger(jobName, groupName, null); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void triggerJobWithVolatileTrigger(String jobName, String groupName, JobDataMap data) + throws SchedulerException { + invoke( + "triggerJobWithVolatileTrigger", + new Object[] { schedulingContext, jobName, groupName, data}, + new String[] { SchedulingContext.class.getName(), String.class.getName(), String.class.getName(), JobDataMap.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void pauseTrigger(String triggerName, String groupName) + throws SchedulerException { + invoke( + "pauseTrigger", + new Object[] { schedulingContext, triggerName, groupName}, + new String[] { SchedulingContext.class.getName(), String.class.getName(), String.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void pauseTriggerGroup(String groupName) throws SchedulerException { + invoke( + "pauseTriggerGroup", + new Object[] { schedulingContext, groupName}, + new String[] { SchedulingContext.class.getName(), String.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void pauseJob(String jobName, String groupName) + throws SchedulerException { + invoke( + "pauseJob", + new Object[] { schedulingContext, jobName, groupName}, + new String[] { SchedulingContext.class.getName(), String.class.getName(), String.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void pauseJobGroup(String groupName) throws SchedulerException { + invoke( + "pauseJobGroup", + new Object[] { schedulingContext, groupName}, + new String[] { SchedulingContext.class.getName(), String.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void resumeTrigger(String triggerName, String groupName) + throws SchedulerException { + invoke( + "resumeTrigger", + new Object[] { schedulingContext, triggerName, groupName}, + new String[] { SchedulingContext.class.getName(), String.class.getName(), String.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void resumeTriggerGroup(String groupName) throws SchedulerException { + invoke( + "resumeTriggerGroup", + new Object[] { schedulingContext, groupName}, + new String[] { SchedulingContext.class.getName(), String.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void resumeJob(String jobName, String groupName) + throws SchedulerException { + invoke( + "resumeJob", + new Object[] { schedulingContext, jobName, groupName}, + new String[] { SchedulingContext.class.getName(), String.class.getName(), String.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void resumeJobGroup(String groupName) throws SchedulerException { + invoke( + "resumeJobGroup", + new Object[] { schedulingContext, groupName}, + new String[] { SchedulingContext.class.getName(), String.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void pauseAll() throws SchedulerException { + invoke( + "pauseAll", + new Object[] { schedulingContext}, + new String[] { SchedulingContext.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void resumeAll() throws SchedulerException { + invoke( + "resumeAll", + new Object[] { schedulingContext}, + new String[] { SchedulingContext.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public String[] getJobGroupNames() throws SchedulerException { + return (String[])invoke( + "getJobGroupNames", + new Object[] { schedulingContext}, + new String[] { SchedulingContext.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public String[] getJobNames(String groupName) throws SchedulerException { + return (String[])invoke( + "getJobNames", + new Object[] { schedulingContext, groupName }, + new String[] { SchedulingContext.class.getName(), String.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public Trigger[] getTriggersOfJob(String jobName, String groupName) + throws SchedulerException { + return (Trigger[])invoke( + "getTriggersOfJob", + new Object[] { schedulingContext, jobName, groupName }, + new String[] { SchedulingContext.class.getName(), String.class.getName(), String.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public String[] getTriggerGroupNames() throws SchedulerException { + return (String[])invoke( + "getTriggerGroupNames", + new Object[] { schedulingContext}, + new String[] { SchedulingContext.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public String[] getTriggerNames(String groupName) throws SchedulerException { + return (String[])invoke( + "getTriggerNames", + new Object[] { schedulingContext, groupName }, + new String[] { SchedulingContext.class.getName(), String.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public JobDetail getJobDetail(String jobName, String jobGroup) + throws SchedulerException { + return (JobDetail)invoke( + "getJobDetail", + new Object[] { schedulingContext, jobName, jobGroup }, + new String[] { SchedulingContext.class.getName(), String.class.getName(), String.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public Trigger getTrigger(String triggerName, String triggerGroup) + throws SchedulerException { + return (Trigger)invoke( + "getTrigger", + new Object[] { schedulingContext, triggerName, triggerGroup }, + new String[] { SchedulingContext.class.getName(), String.class.getName(), String.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public int getTriggerState(String triggerName, String triggerGroup) + throws SchedulerException { + return ((Integer)invoke( + "getTriggerState", + new Object[] { schedulingContext, triggerName, triggerGroup }, + new String[] { SchedulingContext.class.getName(), String.class.getName(), String.class.getName() })).intValue(); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void addCalendar(String calName, Calendar calendar, boolean replace, boolean updateTriggers) + throws SchedulerException { + invoke( + "addCalendar", + new Object[] { schedulingContext, calName, calendar, toBoolean(replace), toBoolean(updateTriggers) }, + new String[] { SchedulingContext.class.getName(), String.class.getName(), + Calendar.class.getName(), boolean.class.getName(), boolean.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public boolean deleteCalendar(String calName) throws SchedulerException { + return ((Boolean)invoke( + "getTriggerState", + new Object[] { schedulingContext, calName }, + new String[] { SchedulingContext.class.getName(), String.class.getName() })).booleanValue(); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public Calendar getCalendar(String calName) throws SchedulerException { + return (Calendar)invoke( + "getCalendar", + new Object[] { schedulingContext, calName }, + new String[] { SchedulingContext.class.getName(), String.class.getName() }); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public String[] getCalendarNames() throws SchedulerException { + return (String[])invoke( + "getCalendarNames", + new Object[] { schedulingContext }, + new String[] { SchedulingContext.class.getName() }); + } + + /////////////////////////////////////////////////////////////////////////// + /// + /// Listener-related Methods + /// + /////////////////////////////////////////////////////////////////////////// + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void addGlobalJobListener(JobListener jobListener) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void addJobListener(JobListener jobListener) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ * @deprecated Use {@link #removeGlobalJobListener(String)} + */ + public boolean removeGlobalJobListener(JobListener jobListener) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean removeGlobalJobListener(String name) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean removeJobListener(String name) throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public List getGlobalJobListeners() throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public Set getJobListenerNames() throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public JobListener getGlobalJobListener(String name) throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public JobListener getJobListener(String name) throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void addGlobalTriggerListener(TriggerListener triggerListener) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void addTriggerListener(TriggerListener triggerListener) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ * @deprecated Use {@link #removeGlobalTriggerListener(String)} + */ + public boolean removeGlobalTriggerListener(TriggerListener triggerListener) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean removeGlobalTriggerListener(String name) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean removeTriggerListener(String name) throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public List getGlobalTriggerListeners() throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public Set getTriggerListenerNames() throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public TriggerListener getGlobalTriggerListener(String name) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public TriggerListener getTriggerListener(String name) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void addSchedulerListener(SchedulerListener schedulerListener) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean removeSchedulerListener(SchedulerListener schedulerListener) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public List getSchedulerListeners() throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + * @see com.fr.third.org.quartz.Scheduler#getPausedTriggerGroups() + */ + public Set getPausedTriggerGroups() throws SchedulerException { + return (Set)invoke( + "getPausedTriggerGroups", + new Object[] { schedulingContext }, + new String[] { SchedulingContext.class.getName() }); + } + + /** + * @see com.fr.third.org.quartz.Scheduler#interrupt(java.lang.String, java.lang.String) + */ + public boolean interrupt(String jobName, String groupName) throws UnableToInterruptJobException { + try { + return ((Boolean)invoke( + "interrupt", + new Object[] { schedulingContext, jobName, groupName}, + new String[] { SchedulingContext.class.getName(), String.class.getName(), String.class.getName() })).booleanValue(); + } catch (SchedulerException se) { + throw new UnableToInterruptJobException(se); + } + } + + /** + * @see com.fr.third.org.quartz.Scheduler#setJobFactory(com.fr.third.org.quartz.spi.JobFactory) + */ + public void setJobFactory(JobFactory factory) throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + protected Boolean toBoolean(boolean bool) { + return (bool) ? Boolean.TRUE : Boolean.FALSE; + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/RemoteScheduler.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/RemoteScheduler.java new file mode 100644 index 000000000..2c816237d --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/RemoteScheduler.java @@ -0,0 +1,1188 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl; + +import java.rmi.RemoteException; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.util.Date; +import java.util.List; +import java.util.Set; + +import com.fr.third.org.quartz.Calendar; +import com.fr.third.org.quartz.JobDataMap; +import com.fr.third.org.quartz.JobDetail; +import com.fr.third.org.quartz.JobListener; +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SchedulerContext; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.SchedulerListener; +import com.fr.third.org.quartz.SchedulerMetaData; +import com.fr.third.org.quartz.Trigger; +import com.fr.third.org.quartz.TriggerListener; +import com.fr.third.org.quartz.UnableToInterruptJobException; +import com.fr.third.org.quartz.core.RemotableQuartzScheduler; +import com.fr.third.org.quartz.core.SchedulingContext; +import com.fr.third.org.quartz.spi.JobFactory; + +/** + *

+ * An implementation of the Scheduler interface that remotely + * proxies all method calls to the equivalent call on a given QuartzScheduler + * instance, via RMI. + *

+ * + * @see com.fr.third.org.quartz.Scheduler + * @see com.fr.third.org.quartz.core.QuartzScheduler + * @see com.fr.third.org.quartz.core.SchedulingContext + * + * @author James House + */ +public class RemoteScheduler implements Scheduler { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private RemotableQuartzScheduler rsched; + + private SchedulingContext schedCtxt; + + private String schedId; + + private String rmiHost; + + private int rmiPort; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Construct a RemoteScheduler instance to proxy the given + * RemoteableQuartzScheduler instance, and with the given + * SchedulingContext. + *

+ */ + public RemoteScheduler(SchedulingContext schedCtxt, String schedId, + String host, int port) { + + this.schedCtxt = schedCtxt; + this.schedId = schedId; + this.rmiHost = host; + this.rmiPort = port; + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + protected RemotableQuartzScheduler getRemoteScheduler() + throws SchedulerException { + if (rsched != null) { + return rsched; + } + + try { + Registry registry = LocateRegistry.getRegistry(rmiHost, rmiPort); + + rsched = (RemotableQuartzScheduler) registry.lookup(schedId); + + } catch (Exception e) { + SchedulerException initException = new SchedulerException( + "Could not get handle to remote scheduler: " + + e.getMessage(), e); + initException + .setErrorCode(SchedulerException.ERR_COMMUNICATION_FAILURE); + throw initException; + } + + return rsched; + } + + protected SchedulerException invalidateHandleCreateException(String msg, + Exception cause) { + rsched = null; + SchedulerException ex = new SchedulerException(msg, cause); + ex.setErrorCode(SchedulerException.ERR_COMMUNICATION_FAILURE); + return ex; + } + + /** + *

+ * Returns the name of the Scheduler. + *

+ */ + public String getSchedulerName() throws SchedulerException { + try { + return getRemoteScheduler().getSchedulerName(); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Returns the instance Id of the Scheduler. + *

+ */ + public String getSchedulerInstanceId() throws SchedulerException { + try { + return getRemoteScheduler().getSchedulerInstanceId(); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + public SchedulerMetaData getMetaData() throws SchedulerException { + try { + RemotableQuartzScheduler sched = getRemoteScheduler(); + return new SchedulerMetaData(getSchedulerName(), + getSchedulerInstanceId(), getClass(), true, isStarted(), + isInStandbyMode(), isShutdown(), sched.runningSince(), + sched.numJobsExecuted(), sched.getJobStoreClass(), + sched.supportsPersistence(), sched.getThreadPoolClass(), + sched.getThreadPoolSize(), sched.getVersion()); + + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + + } + + /** + *

+ * Returns the SchedulerContext of the Scheduler. + *

+ */ + public SchedulerContext getContext() throws SchedulerException { + try { + return getRemoteScheduler().getSchedulerContext(); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /////////////////////////////////////////////////////////////////////////// + /// + /// Schedululer State Management Methods + /// + /////////////////////////////////////////////////////////////////////////// + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void start() throws SchedulerException { + try { + getRemoteScheduler().start(); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void startDelayed(int seconds) throws SchedulerException { + try { + getRemoteScheduler().startDelayed(seconds); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void standby() throws SchedulerException { + try { + getRemoteScheduler().standby(); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + * @see com.fr.third.org.quartz.Scheduler#pause() + * @deprecated + */ + public void pause() throws SchedulerException { + this.standby(); + } + + + + /** + * Whether the scheduler has been started. + * + *

+ * Note: This only reflects whether {@link #start()} has ever + * been called on this Scheduler, so it will return true even + * if the Scheduler is currently in standby mode or has been + * since shutdown. + *

+ * + * @see #start() + * @see #isShutdown() + * @see #isInStandbyMode() + */ + public boolean isStarted() throws SchedulerException { + try { + return (getRemoteScheduler().runningSince() != null); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean isInStandbyMode() throws SchedulerException { + try { + return getRemoteScheduler().isInStandbyMode(); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + public boolean isPaused() throws SchedulerException { + return this.isInStandbyMode(); + } + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void shutdown() throws SchedulerException { + try { + String schedulerName = getSchedulerName(); + + getRemoteScheduler().shutdown(); + + SchedulerRepository.getInstance().remove(schedulerName); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void shutdown(boolean waitForJobsToComplete) + throws SchedulerException { + try { + String schedulerName = getSchedulerName(); + + getRemoteScheduler().shutdown(waitForJobsToComplete); + + SchedulerRepository.getInstance().remove(schedulerName); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean isShutdown() throws SchedulerException { + try { + return getRemoteScheduler().isShutdown(); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public List getCurrentlyExecutingJobs() throws SchedulerException { + try { + return getRemoteScheduler().getCurrentlyExecutingJobs(); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /////////////////////////////////////////////////////////////////////////// + /// + /// Scheduling-related Methods + /// + /////////////////////////////////////////////////////////////////////////// + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public Date scheduleJob(JobDetail jobDetail, Trigger trigger) + throws SchedulerException { + try { + return getRemoteScheduler().scheduleJob(schedCtxt, jobDetail, + trigger); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public Date scheduleJob(Trigger trigger) throws SchedulerException { + try { + return getRemoteScheduler().scheduleJob(schedCtxt, trigger); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void addJob(JobDetail jobDetail, boolean replace) + throws SchedulerException { + try { + getRemoteScheduler().addJob(schedCtxt, jobDetail, replace); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public boolean deleteJob(String jobName, String groupName) + throws SchedulerException { + try { + return getRemoteScheduler() + .deleteJob(schedCtxt, jobName, groupName); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public boolean unscheduleJob(String triggerName, String groupName) + throws SchedulerException { + try { + return getRemoteScheduler().unscheduleJob(schedCtxt, triggerName, + groupName); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public Date rescheduleJob(String triggerName, + String groupName, Trigger newTrigger) throws SchedulerException { + try { + return getRemoteScheduler().rescheduleJob(schedCtxt, triggerName, + groupName, newTrigger); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void triggerJob(String jobName, String groupName) + throws SchedulerException { + triggerJob(jobName, groupName, null); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void triggerJob(String jobName, String groupName, JobDataMap data) + throws SchedulerException { + try { + getRemoteScheduler().triggerJob(schedCtxt, jobName, groupName, data); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void triggerJobWithVolatileTrigger(String jobName, String groupName) + throws SchedulerException { + triggerJobWithVolatileTrigger(jobName, groupName, null); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void triggerJobWithVolatileTrigger(String jobName, String groupName, JobDataMap data) + throws SchedulerException { + try { + getRemoteScheduler().triggerJobWithVolatileTrigger(schedCtxt, + jobName, groupName, data); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void pauseTrigger(String triggerName, String groupName) + throws SchedulerException { + try { + getRemoteScheduler() + .pauseTrigger(schedCtxt, triggerName, groupName); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void pauseTriggerGroup(String groupName) throws SchedulerException { + try { + getRemoteScheduler().pauseTriggerGroup(schedCtxt, groupName); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void pauseJob(String jobName, String groupName) + throws SchedulerException { + try { + getRemoteScheduler().pauseJob(schedCtxt, jobName, groupName); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void pauseJobGroup(String groupName) throws SchedulerException { + try { + getRemoteScheduler().pauseJobGroup(schedCtxt, groupName); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void resumeTrigger(String triggerName, String groupName) + throws SchedulerException { + try { + getRemoteScheduler().resumeTrigger(schedCtxt, triggerName, + groupName); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void resumeTriggerGroup(String groupName) throws SchedulerException { + try { + getRemoteScheduler().resumeTriggerGroup(schedCtxt, groupName); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void resumeJob(String jobName, String groupName) + throws SchedulerException { + try { + getRemoteScheduler().resumeJob(schedCtxt, jobName, groupName); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void resumeJobGroup(String groupName) throws SchedulerException { + try { + getRemoteScheduler().resumeJobGroup(schedCtxt, groupName); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void pauseAll() throws SchedulerException { + try { + getRemoteScheduler().pauseAll(schedCtxt); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void resumeAll() throws SchedulerException { + try { + getRemoteScheduler().resumeAll(schedCtxt); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public String[] getJobGroupNames() throws SchedulerException { + try { + return getRemoteScheduler().getJobGroupNames(schedCtxt); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public String[] getJobNames(String groupName) throws SchedulerException { + try { + return getRemoteScheduler().getJobNames(schedCtxt, groupName); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public Trigger[] getTriggersOfJob(String jobName, String groupName) + throws SchedulerException { + try { + return getRemoteScheduler().getTriggersOfJob(schedCtxt, jobName, + groupName); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public String[] getTriggerGroupNames() throws SchedulerException { + try { + return getRemoteScheduler().getTriggerGroupNames(schedCtxt); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public String[] getTriggerNames(String groupName) throws SchedulerException { + try { + return getRemoteScheduler().getTriggerNames(schedCtxt, groupName); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public JobDetail getJobDetail(String jobName, String jobGroup) + throws SchedulerException { + try { + return getRemoteScheduler().getJobDetail(schedCtxt, jobName, + jobGroup); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public Trigger getTrigger(String triggerName, String triggerGroup) + throws SchedulerException { + try { + return getRemoteScheduler().getTrigger(schedCtxt, triggerName, + triggerGroup); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public int getTriggerState(String triggerName, String triggerGroup) + throws SchedulerException { + try { + return getRemoteScheduler().getTriggerState(schedCtxt, triggerName, + triggerGroup); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void addCalendar(String calName, Calendar calendar, boolean replace, boolean updateTriggers) + throws SchedulerException { + try { + getRemoteScheduler().addCalendar(schedCtxt, calName, calendar, + replace, updateTriggers); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public boolean deleteCalendar(String calName) throws SchedulerException { + try { + return getRemoteScheduler().deleteCalendar(schedCtxt, calName); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public Calendar getCalendar(String calName) throws SchedulerException { + try { + return getRemoteScheduler().getCalendar(schedCtxt, calName); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public String[] getCalendarNames() throws SchedulerException { + try { + return getRemoteScheduler().getCalendarNames(schedCtxt); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /////////////////////////////////////////////////////////////////////////// + /// + /// Listener-related Methods + /// + /////////////////////////////////////////////////////////////////////////// + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void addGlobalJobListener(JobListener jobListener) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void addJobListener(JobListener jobListener) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ * @deprecated Use {@link #removeGlobalJobListener(String)} + */ + public boolean removeGlobalJobListener(JobListener jobListener) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean removeGlobalJobListener(String name) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean removeJobListener(String name) throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public List getGlobalJobListeners() throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public Set getJobListenerNames() throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public JobListener getGlobalJobListener(String name) throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public JobListener getJobListener(String name) throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void addGlobalTriggerListener(TriggerListener triggerListener) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void addTriggerListener(TriggerListener triggerListener) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ * @deprecated Use {@link #removeGlobalTriggerListener(String)} + */ + public boolean removeGlobalTriggerListener(TriggerListener triggerListener) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean removeGlobalTriggerListener(String name) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean removeTriggerListener(String name) throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public List getGlobalTriggerListeners() throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public Set getTriggerListenerNames() throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public TriggerListener getGlobalTriggerListener(String name) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public TriggerListener getTriggerListener(String name) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void addSchedulerListener(SchedulerListener schedulerListener) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean removeSchedulerListener(SchedulerListener schedulerListener) + throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public List getSchedulerListeners() throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } + + /** + * @see com.fr.third.org.quartz.Scheduler#getPausedTriggerGroups() + */ + public Set getPausedTriggerGroups() throws SchedulerException { + try { + return getRemoteScheduler().getPausedTriggerGroups(schedCtxt); + } catch (RemoteException re) { + throw invalidateHandleCreateException( + "Error communicating with remote scheduler.", re); + } + } + + /** + * @see com.fr.third.org.quartz.Scheduler#interrupt(java.lang.String, java.lang.String) + */ + public boolean interrupt(String jobName, String groupName) throws UnableToInterruptJobException { + try { + return getRemoteScheduler().interrupt(schedCtxt, jobName, groupName); + } catch (RemoteException re) { + throw new UnableToInterruptJobException(invalidateHandleCreateException( + "Error communicating with remote scheduler.", re)); + } catch (SchedulerException se) { + throw new UnableToInterruptJobException(se); + } + } + + /** + * @see com.fr.third.org.quartz.Scheduler#setJobFactory(com.fr.third.org.quartz.spi.JobFactory) + */ + public void setJobFactory(JobFactory factory) throws SchedulerException { + throw new SchedulerException( + "Operation not supported for remote schedulers.", + SchedulerException.ERR_UNSUPPORTED_FUNCTION_IN_THIS_CONFIGURATION); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/SchedulerRepository.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/SchedulerRepository.java new file mode 100644 index 000000000..5c4406a58 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/SchedulerRepository.java @@ -0,0 +1,104 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl; + +import java.util.Collection; +import java.util.HashMap; + +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SchedulerException; + +/** + *

+ * Holds references to Scheduler instances - ensuring uniqueness, and + * preventing garbage collection, and allowing 'global' lookups - all within a + * ClassLoader space. + *

+ * + * @author James House + */ +public class SchedulerRepository { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private HashMap schedulers; + + private static SchedulerRepository inst; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private SchedulerRepository() { + schedulers = new HashMap(); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public static synchronized SchedulerRepository getInstance() { + if (inst == null) { + inst = new SchedulerRepository(); + } + + return inst; + } + + public synchronized void bind(Scheduler sched) throws SchedulerException { + + if ((Scheduler) schedulers.get(sched.getSchedulerName()) != null) { + throw new SchedulerException("Scheduler with name '" + + sched.getSchedulerName() + "' already exists.", + SchedulerException.ERR_BAD_CONFIGURATION); + } + + schedulers.put(sched.getSchedulerName(), sched); + } + + public synchronized boolean remove(String schedName) { + return (schedulers.remove(schedName) != null); + } + + public synchronized Scheduler lookup(String schedName) { + return (Scheduler) schedulers.get(schedName); + } + + public synchronized Collection lookupAll() { + return java.util.Collections + .unmodifiableCollection(schedulers.values()); + } + +} \ No newline at end of file diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/StdJobRunShellFactory.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/StdJobRunShellFactory.java new file mode 100644 index 000000000..ff50e97c6 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/StdJobRunShellFactory.java @@ -0,0 +1,100 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl; + +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.core.JobRunShell; +import com.fr.third.org.quartz.core.JobRunShellFactory; +import com.fr.third.org.quartz.core.SchedulingContext; + +/** + *

+ * Responsible for creating the instances of {@link com.fr.third.org.quartz.core.JobRunShell} + * to be used within the {@link com.fr.third.org.quartz.core.QuartzScheduler} + * instance. + *

+ * + *

+ * This implementation does not re-use any objects, it simply makes a new + * JobRunShell each time borrowJobRunShell() is called. + *

+ * + * @author James House + */ +public class StdJobRunShellFactory implements JobRunShellFactory { + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private Scheduler scheduler; + + private SchedulingContext schedCtxt; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Initialize the factory, providing a handle to the Scheduler + * that should be made available within the JobRunShell and + * the JobExecutionCOntext s within it, and a handle to the + * SchedulingContext that the shell will use in its own + * operations with the JobStore. + *

+ */ + public void initialize(Scheduler scheduler, SchedulingContext schedCtxt) { + this.scheduler = scheduler; + this.schedCtxt = schedCtxt; + } + + /** + *

+ * Called by the {@link com.fr.third.org.quartz.core.QuartzSchedulerThread} + * to obtain instances of + * {@link com.fr.third.org.quartz.core.JobRunShell}. + *

+ */ + public JobRunShell borrowJobRunShell() throws SchedulerException { + return new JobRunShell(this, scheduler, schedCtxt); + } + + /** + *

+ * Called by the {@link com.fr.third.org.quartz.core.QuartzSchedulerThread} + * to return instances of + * {@link com.fr.third.org.quartz.core.JobRunShell}. + *

+ */ + public void returnJobRunShell(JobRunShell jobRunShell) { + jobRunShell.passivate(); + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/StdScheduler.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/StdScheduler.java new file mode 100644 index 000000000..83130f2be --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/StdScheduler.java @@ -0,0 +1,844 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl; + +import java.util.Date; +import java.util.List; +import java.util.Set; + +import com.fr.third.org.quartz.Calendar; +import com.fr.third.org.quartz.JobDataMap; +import com.fr.third.org.quartz.JobDetail; +import com.fr.third.org.quartz.JobListener; +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SchedulerContext; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.SchedulerListener; +import com.fr.third.org.quartz.SchedulerMetaData; +import com.fr.third.org.quartz.Trigger; +import com.fr.third.org.quartz.TriggerListener; +import com.fr.third.org.quartz.UnableToInterruptJobException; +import com.fr.third.org.quartz.core.QuartzScheduler; +import com.fr.third.org.quartz.core.SchedulingContext; +import com.fr.third.org.quartz.spi.JobFactory; + +/** + *

+ * An implementation of the Scheduler interface that directly + * proxies all method calls to the equivalent call on a given QuartzScheduler + * instance. + *

+ * + * @see com.fr.third.org.quartz.Scheduler + * @see com.fr.third.org.quartz.core.QuartzScheduler + * @see com.fr.third.org.quartz.core.SchedulingContext + * + * @author James House + */ +public class StdScheduler implements Scheduler { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private QuartzScheduler sched; + + private SchedulingContext schedCtxt; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Construct a StdScheduler instance to proxy the given + * QuartzScheduler instance, and with the given SchedulingContext. + *

+ */ + public StdScheduler(QuartzScheduler sched, SchedulingContext schedCtxt) { + this.sched = sched; + this.schedCtxt = schedCtxt; + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Returns the name of the Scheduler. + *

+ */ + public String getSchedulerName() { + return sched.getSchedulerName(); + } + + /** + *

+ * Returns the instance Id of the Scheduler. + *

+ */ + public String getSchedulerInstanceId() { + return sched.getSchedulerInstanceId(); + } + + public SchedulerMetaData getMetaData() { + return new SchedulerMetaData(getSchedulerName(), + getSchedulerInstanceId(), getClass(), false, isStarted(), + isInStandbyMode(), isShutdown(), sched.runningSince(), + sched.numJobsExecuted(), sched.getJobStoreClass(), + sched.supportsPersistence(), sched.getThreadPoolClass(), + sched.getThreadPoolSize(), sched.getVersion()); + + } + + /** + *

+ * Returns the SchedulerContext of the Scheduler. + *

+ */ + public SchedulerContext getContext() throws SchedulerException { + return sched.getSchedulerContext(); + } + + /////////////////////////////////////////////////////////////////////////// + /// + /// Schedululer State Management Methods + /// + /////////////////////////////////////////////////////////////////////////// + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void start() throws SchedulerException { + sched.start(); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void startDelayed(int seconds) throws SchedulerException { + sched.startDelayed(seconds); + } + + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ * + * @deprecated + * @see #standby() + */ + public void pause() { + this.standby(); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void standby() { + sched.standby(); + } + + /** + * Whether the scheduler has been started. + * + *

+ * Note: This only reflects whether {@link #start()} has ever + * been called on this Scheduler, so it will return true even + * if the Scheduler is currently in standby mode or has been + * since shutdown. + *

+ * + * @see #start() + * @see #isShutdown() + * @see #isInStandbyMode() + */ + public boolean isStarted() { + return (sched.runningSince() != null); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean isInStandbyMode() { + return sched.isInStandbyMode(); + } + + /** + * @deprecated + */ + public boolean isPaused() { + return isInStandbyMode(); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void shutdown() { + sched.shutdown(); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void shutdown(boolean waitForJobsToComplete) { + sched.shutdown(waitForJobsToComplete); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean isShutdown() { + return sched.isShutdown(); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public List getCurrentlyExecutingJobs() { + return sched.getCurrentlyExecutingJobs(); + } + + /////////////////////////////////////////////////////////////////////////// + /// + /// Scheduling-related Methods + /// + /////////////////////////////////////////////////////////////////////////// + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public Date scheduleJob(JobDetail jobDetail, Trigger trigger) + throws SchedulerException { + return sched.scheduleJob(schedCtxt, jobDetail, trigger); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public Date scheduleJob(Trigger trigger) throws SchedulerException { + return sched.scheduleJob(schedCtxt, trigger); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void addJob(JobDetail jobDetail, boolean replace) + throws SchedulerException { + sched.addJob(schedCtxt, jobDetail, replace); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public boolean deleteJob(String jobName, String groupName) + throws SchedulerException { + return sched.deleteJob(schedCtxt, jobName, groupName); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public boolean unscheduleJob(String triggerName, String groupName) + throws SchedulerException { + return sched.unscheduleJob(schedCtxt, triggerName, groupName); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public Date rescheduleJob(String triggerName, + String groupName, Trigger newTrigger) throws SchedulerException { + return sched.rescheduleJob(schedCtxt, triggerName, groupName, newTrigger); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void triggerJob(String jobName, String groupName) + throws SchedulerException { + triggerJob(jobName, groupName, null); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void triggerJob(String jobName, String groupName, JobDataMap data) + throws SchedulerException { + sched.triggerJob(schedCtxt, jobName, groupName, data); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void triggerJobWithVolatileTrigger(String jobName, String groupName) + throws SchedulerException { + triggerJobWithVolatileTrigger(jobName, groupName, null); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void triggerJobWithVolatileTrigger(String jobName, String groupName, JobDataMap data) + throws SchedulerException { + sched.triggerJobWithVolatileTrigger(schedCtxt, jobName, groupName, data); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void pauseTrigger(String triggerName, String groupName) + throws SchedulerException { + sched.pauseTrigger(schedCtxt, triggerName, groupName); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void pauseTriggerGroup(String groupName) throws SchedulerException { + sched.pauseTriggerGroup(schedCtxt, groupName); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void pauseJob(String jobName, String groupName) + throws SchedulerException { + sched.pauseJob(schedCtxt, jobName, groupName); + } + + /** + * @see com.fr.third.org.quartz.Scheduler#getPausedTriggerGroups() + */ + public Set getPausedTriggerGroups() throws SchedulerException { + return sched.getPausedTriggerGroups(schedCtxt); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void pauseJobGroup(String groupName) throws SchedulerException { + sched.pauseJobGroup(schedCtxt, groupName); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void resumeTrigger(String triggerName, String groupName) + throws SchedulerException { + sched.resumeTrigger(schedCtxt, triggerName, groupName); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void resumeTriggerGroup(String groupName) throws SchedulerException { + sched.resumeTriggerGroup(schedCtxt, groupName); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void resumeJob(String jobName, String groupName) + throws SchedulerException { + sched.resumeJob(schedCtxt, jobName, groupName); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void resumeJobGroup(String groupName) throws SchedulerException { + sched.resumeJobGroup(schedCtxt, groupName); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void pauseAll() throws SchedulerException { + sched.pauseAll(schedCtxt); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void resumeAll() throws SchedulerException { + sched.resumeAll(schedCtxt); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public String[] getJobGroupNames() throws SchedulerException { + return sched.getJobGroupNames(schedCtxt); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public Trigger[] getTriggersOfJob(String jobName, String groupName) + throws SchedulerException { + return sched.getTriggersOfJob(schedCtxt, jobName, groupName); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public String[] getJobNames(String groupName) throws SchedulerException { + return sched.getJobNames(schedCtxt, groupName); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public String[] getTriggerGroupNames() throws SchedulerException { + return sched.getTriggerGroupNames(schedCtxt); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public String[] getTriggerNames(String groupName) throws SchedulerException { + return sched.getTriggerNames(schedCtxt, groupName); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public JobDetail getJobDetail(String jobName, String jobGroup) + throws SchedulerException { + return sched.getJobDetail(schedCtxt, jobName, jobGroup); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public Trigger getTrigger(String triggerName, String triggerGroup) + throws SchedulerException { + return sched.getTrigger(schedCtxt, triggerName, triggerGroup); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public int getTriggerState(String triggerName, String triggerGroup) + throws SchedulerException { + return sched.getTriggerState(schedCtxt, triggerName, triggerGroup); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public void addCalendar(String calName, Calendar calendar, boolean replace, boolean updateTriggers) + throws SchedulerException { + sched.addCalendar(schedCtxt, calName, calendar, replace, updateTriggers); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public boolean deleteCalendar(String calName) throws SchedulerException { + return sched.deleteCalendar(schedCtxt, calName); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public Calendar getCalendar(String calName) throws SchedulerException { + return sched.getCalendar(schedCtxt, calName); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler, + * passing the SchedulingContext associated with this + * instance. + *

+ */ + public String[] getCalendarNames() throws SchedulerException { + return sched.getCalendarNames(schedCtxt); + } + + /////////////////////////////////////////////////////////////////////////// + /// + /// Listener-related Methods + /// + /////////////////////////////////////////////////////////////////////////// + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void addGlobalJobListener(JobListener jobListener) { + sched.addGlobalJobListener(jobListener); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void addJobListener(JobListener jobListener) { + sched.addJobListener(jobListener); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ * @deprecated Use {@link #removeGlobalJobListener(String)} + */ + public boolean removeGlobalJobListener(JobListener jobListener) { + return sched.removeGlobalJobListener( + (jobListener == null) ? null : jobListener.getName()); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean removeGlobalJobListener(String name) { + return sched.removeGlobalJobListener(name); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean removeJobListener(String name) { + return sched.removeJobListener(name); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public List getGlobalJobListeners() { + return sched.getGlobalJobListeners(); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public Set getJobListenerNames() { + return sched.getJobListenerNames(); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public JobListener getGlobalJobListener(String name) { + return sched.getGlobalJobListener(name); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public JobListener getJobListener(String name) { + return sched.getJobListener(name); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void addGlobalTriggerListener(TriggerListener triggerListener) { + sched.addGlobalTriggerListener(triggerListener); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void addTriggerListener(TriggerListener triggerListener) { + sched.addTriggerListener(triggerListener); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ * + * @deprecated Use {@link #removeGlobalTriggerListener(String)} + */ + public boolean removeGlobalTriggerListener(TriggerListener triggerListener) { + return sched.removeGlobalTriggerListener( + (triggerListener == null) ? null : triggerListener.getName()); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean removeGlobalTriggerListener(String name) { + return sched.removeGlobalTriggerListener(name); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean removeTriggerListener(String name) { + return sched.removeTriggerListener(name); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public List getGlobalTriggerListeners() { + return sched.getGlobalTriggerListeners(); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public Set getTriggerListenerNames() { + return sched.getTriggerListenerNames(); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public TriggerListener getGlobalTriggerListener(String name) { + return sched.getGlobalTriggerListener(name); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public TriggerListener getTriggerListener(String name) { + return sched.getTriggerListener(name); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public void addSchedulerListener(SchedulerListener schedulerListener) { + sched.addSchedulerListener(schedulerListener); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public boolean removeSchedulerListener(SchedulerListener schedulerListener) { + return sched.removeSchedulerListener(schedulerListener); + } + + /** + *

+ * Calls the equivalent method on the 'proxied' QuartzScheduler. + *

+ */ + public List getSchedulerListeners() { + return sched.getSchedulerListeners(); + } + + public boolean interrupt(String jobName, String groupName) throws UnableToInterruptJobException { + return sched.interrupt(schedCtxt, jobName, groupName); + } + + /** + * @see com.fr.third.org.quartz.Scheduler#setJobFactory(com.fr.third.org.quartz.spi.JobFactory) + */ + public void setJobFactory(JobFactory factory) throws SchedulerException { + sched.setJobFactory(factory); + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/StdSchedulerFactory.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/StdSchedulerFactory.java new file mode 100644 index 000000000..85e917ddc --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/StdSchedulerFactory.java @@ -0,0 +1,1414 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.security.AccessControlException; +import java.sql.SQLException; +import java.util.Collection; +import java.util.Iterator; +import java.util.Locale; +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.JobListener; +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SchedulerConfigException; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.SchedulerFactory; +import com.fr.third.org.quartz.TriggerListener; +import com.fr.third.org.quartz.core.JobRunShellFactory; +import com.fr.third.org.quartz.core.QuartzScheduler; +import com.fr.third.org.quartz.core.QuartzSchedulerResources; +import com.fr.third.org.quartz.core.SchedulingContext; +import com.fr.third.org.quartz.ee.jta.JTAJobRunShellFactory; +import com.fr.third.org.quartz.ee.jta.UserTransactionHelper; +import com.fr.third.org.quartz.impl.jdbcjobstore.JobStoreSupport; +import com.fr.third.org.quartz.impl.jdbcjobstore.Semaphore; +import com.fr.third.org.quartz.impl.jdbcjobstore.TablePrefixAware; +import com.fr.third.org.quartz.simpl.RAMJobStore; +import com.fr.third.org.quartz.simpl.SimpleThreadPool; +import com.fr.third.org.quartz.spi.ClassLoadHelper; +import com.fr.third.org.quartz.spi.InstanceIdGenerator; +import com.fr.third.org.quartz.spi.JobFactory; +import com.fr.third.org.quartz.spi.JobStore; +import com.fr.third.org.quartz.spi.SchedulerPlugin; +import com.fr.third.org.quartz.spi.ThreadPool; +import com.fr.third.org.quartz.utils.ConnectionProvider; +import com.fr.third.org.quartz.utils.DBConnectionManager; +import com.fr.third.org.quartz.utils.JNDIConnectionProvider; +import com.fr.third.org.quartz.utils.PoolingConnectionProvider; +import com.fr.third.org.quartz.utils.PropertiesParser; + +/** + *

+ * An implementation of {@link com.fr.third.org.quartz.SchedulerFactory} that + * does all of its work of creating a QuartzScheduler instance + * based on the contenents of a Properties file. + *

+ * + *

+ * By default a properties file named "quartz.properties" is loaded from the + * 'current working directory'. If that fails, then the "quartz.properties" + * file located (as a resource) in the org/quartz package is loaded. If you + * wish to use a file other than these defaults, you must define the system + * property 'com.fr.third.org.quartz.properties' to point to the file you want. + *

+ * + *

+ * See the sample properties files that are distributed with Quartz for + * information about the various settings available within the file. + *

+ * + *

+ * Alternatively, you can explicitly initialize the factory by calling one of + * the initialize(xx) methods before calling getScheduler(). + *

+ * + *

+ * Instances of the specified {@link com.fr.third.org.quartz.spi.JobStore}, + * {@link com.fr.third.org.quartz.spi.ThreadPool}, classes will be created + * by name, and then any additional properties specified for them in the config + * file will be set on the instance by calling an equivalent 'set' method. For + * example if the properties file contains the property + * 'com.fr.third.org.quartz.jobStore.myProp = 10' then after the JobStore class has been + * instantiated, the method 'setMyProp()' will be called on it. Type conversion + * to primitive Java types (int, long, float, double, boolean, and String) are + * performed before calling the property's setter method. + *

+ * + * @author James House + * @author Anthony Eden + * @author Mohammad Rezaei + */ +public class StdSchedulerFactory implements SchedulerFactory { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public static final String PROPERTIES_FILE = "com.fr.third.org.quartz.properties"; + + public static final String PROP_SCHED_INSTANCE_NAME = "com.fr.third.org.quartz.scheduler.instanceName"; + + public static final String PROP_SCHED_INSTANCE_ID = "com.fr.third.org.quartz.scheduler.instanceId"; + + public static final String PROP_SCHED_INSTANCE_ID_GENERATOR_PREFIX = "com.fr.third.org.quartz.scheduler.instanceIdGenerator"; + + public static final String PROP_SCHED_INSTANCE_ID_GENERATOR_CLASS = + PROP_SCHED_INSTANCE_ID_GENERATOR_PREFIX + ".class"; + + public static final String PROP_SCHED_THREAD_NAME = "com.fr.third.org.quartz.scheduler.threadName"; + + public static final String PROP_SCHED_JMX_EXPORT = "com.fr.third.org.quartz.scheduler.jmx.export"; + + public static final String PROP_SCHED_JMX_PROXY = "com.fr.third.org.quartz.scheduler.jmx.proxy"; + + public static final String PROP_SCHED_JMX_PROXY_CLASS = "com.fr.third.org.quartz.scheduler.jmx.proxy.class"; + + public static final String PROP_SCHED_JMX_OBJECT_NAME = "com.fr.third.org.quartz.scheduler.jmx.objectName"; + + public static final String PROP_SCHED_RMI_EXPORT = "com.fr.third.org.quartz.scheduler.rmi.export"; + + public static final String PROP_SCHED_RMI_PROXY = "com.fr.third.org.quartz.scheduler.rmi.proxy"; + + public static final String PROP_SCHED_RMI_HOST = "com.fr.third.org.quartz.scheduler.rmi.registryHost"; + + public static final String PROP_SCHED_RMI_PORT = "com.fr.third.org.quartz.scheduler.rmi.registryPort"; + + public static final String PROP_SCHED_RMI_SERVER_PORT = "com.fr.third.org.quartz.scheduler.rmi.serverPort"; + + public static final String PROP_SCHED_RMI_CREATE_REGISTRY = "com.fr.third.org.quartz.scheduler.rmi.createRegistry"; + + public static final String PROP_SCHED_RMI_BIND_NAME = "com.fr.third.org.quartz.scheduler.rmi.bindName"; + + public static final String PROP_SCHED_WRAP_JOB_IN_USER_TX = "com.fr.third.org.quartz.scheduler.wrapJobExecutionInUserTransaction"; + + public static final String PROP_SCHED_USER_TX_URL = "com.fr.third.org.quartz.scheduler.userTransactionURL"; + + public static final String PROP_SCHED_IDLE_WAIT_TIME = "com.fr.third.org.quartz.scheduler.idleWaitTime"; + + public static final String PROP_SCHED_DB_FAILURE_RETRY_INTERVAL = "com.fr.third.org.quartz.scheduler.dbFailureRetryInterval"; + + public static final String PROP_SCHED_MAKE_SCHEDULER_THREAD_DAEMON = "com.fr.third.org.quartz.scheduler.makeSchedulerThreadDaemon"; + + public static final String PROP_SCHED_SCHEDULER_THREADS_INHERIT_CONTEXT_CLASS_LOADER_OF_INITIALIZING_THREAD = "com.fr.third.org.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer"; + + public static final String PROP_SCHED_CLASS_LOAD_HELPER_CLASS = "com.fr.third.org.quartz.scheduler.classLoadHelper.class"; + + public static final String PROP_SCHED_JOB_FACTORY_CLASS = "com.fr.third.org.quartz.scheduler.jobFactory.class"; + + public static final String PROP_SCHED_JOB_FACTORY_PREFIX = "com.fr.third.org.quartz.scheduler.jobFactory"; + + public static final String PROP_SCHED_CONTEXT_PREFIX = "com.fr.third.org.quartz.context.key"; + + public static final String PROP_THREAD_POOL_PREFIX = "com.fr.third.org.quartz.threadPool"; + + public static final String PROP_THREAD_POOL_CLASS = "com.fr.third.org.quartz.threadPool.class"; + + public static final String PROP_JOB_STORE_PREFIX = "com.fr.third.org.quartz.jobStore"; + + public static final String PROP_JOB_STORE_LOCK_HANDLER_PREFIX = PROP_JOB_STORE_PREFIX + ".lockHandler"; + + public static final String PROP_JOB_STORE_LOCK_HANDLER_CLASS = PROP_JOB_STORE_LOCK_HANDLER_PREFIX + ".class"; + + public static final String PROP_TABLE_PREFIX = "tablePrefix"; + + public static final String PROP_JOB_STORE_CLASS = "com.fr.third.org.quartz.jobStore.class"; + + public static final String PROP_JOB_STORE_USE_PROP = "com.fr.third.org.quartz.jobStore.useProperties"; + + public static final String PROP_DATASOURCE_PREFIX = "com.fr.third.org.quartz.dataSource"; + + public static final String PROP_CONNECTION_PROVIDER_CLASS = "connectionProvider.class"; + + public static final String PROP_DATASOURCE_DRIVER = "driver"; + + public static final String PROP_DATASOURCE_URL = "URL"; + + public static final String PROP_DATASOURCE_USER = "user"; + + public static final String PROP_DATASOURCE_PASSWORD = "password"; + + public static final String PROP_DATASOURCE_MAX_CONNECTIONS = "maxConnections"; + + public static final String PROP_DATASOURCE_VALIDATION_QUERY = "validationQuery"; + + public static final String PROP_DATASOURCE_JNDI_URL = "jndiURL"; + + public static final String PROP_DATASOURCE_JNDI_ALWAYS_LOOKUP = "jndiAlwaysLookup"; + + public static final String PROP_DATASOURCE_JNDI_INITIAL = "java.naming.factory.initial"; + + public static final String PROP_DATASOURCE_JNDI_PROVDER = "java.naming.provider.url"; + + public static final String PROP_DATASOURCE_JNDI_PRINCIPAL = "java.naming.security.principal"; + + public static final String PROP_DATASOURCE_JNDI_CREDENTIALS = "java.naming.security.credentials"; + + public static final String PROP_PLUGIN_PREFIX = "com.fr.third.org.quartz.plugin"; + + public static final String PROP_PLUGIN_CLASS = "class"; + + public static final String PROP_JOB_LISTENER_PREFIX = "com.fr.third.org.quartz.jobListener"; + + public static final String PROP_TRIGGER_LISTENER_PREFIX = "com.fr.third.org.quartz.triggerListener"; + + public static final String PROP_LISTENER_CLASS = "class"; + + public static final String DEFAULT_INSTANCE_ID = "NON_CLUSTERED"; + + public static final String AUTO_GENERATE_INSTANCE_ID = "AUTO"; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private SchedulerException initException = null; + + private String propSrc = null; + + private PropertiesParser cfg; + + private final Log log = LogFactory.getLog(getClass()); + + // private Scheduler scheduler; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Create an uninitialized StdSchedulerFactory. + */ + public StdSchedulerFactory() { + } + + /** + * Create a StdSchedulerFactory that has been initialized via + * {@link #initialize(Properties)}. + * + * @see #initialize(Properties) + */ + public StdSchedulerFactory(Properties props) throws SchedulerException { + initialize(props); + } + + /** + * Create a StdSchedulerFactory that has been initialized via + * {@link #initialize(String)}. + * + * @see #initialize(String) + */ + public StdSchedulerFactory(String fileName) throws SchedulerException { + initialize(fileName); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public Log getLog() { + return log; + } + + /** + *

+ * Initialize the {@link com.fr.third.org.quartz.SchedulerFactory} with + * the contents of a Properties file and overriding System + * properties. + *

+ * + *

+ * By default a properties file named "quartz.properties" is loaded from + * the 'current working directory'. If that fails, then the + * "quartz.properties" file located (as a resource) in the org/quartz + * package is loaded. If you wish to use a file other than these defaults, + * you must define the system property 'com.fr.third.org.quartz.properties' to point to + * the file you want. + *

+ * + *

+ * System properties (environment variables, and -D definitions on the + * command-line when running the JVM) override any properties in the + * loaded file. For this reason, you may want to use a different initialize() + * method if your application security policy prohibits access to + * {@link java.lang.System#getProperties()}. + *

+ */ + public void initialize() throws SchedulerException { + // short-circuit if already initialized + if (cfg != null) { + return; + } + if (initException != null) { + throw initException; + } + + String requestedFile = System.getProperty(PROPERTIES_FILE); + String propFileName = requestedFile != null ? requestedFile + : "quartz.properties"; + File propFile = new File(propFileName); + + Properties props = new Properties(); + + InputStream in = null; + + try { + if (propFile.exists()) { + try { + if (requestedFile != null) { + propSrc = "specified file: '" + requestedFile + "'"; + } else { + propSrc = "default file in current working dir: 'quartz.properties'"; + } + + in = new BufferedInputStream(new FileInputStream(propFileName)); + props.load(in); + + } catch (IOException ioe) { + initException = new SchedulerException("Properties file: '" + + propFileName + "' could not be read.", ioe); + throw initException; + } + } else if (requestedFile != null) { + in = + Thread.currentThread().getContextClassLoader().getResourceAsStream(requestedFile); + + if(in == null) { + initException = new SchedulerException("Properties file: '" + + requestedFile + "' could not be found."); + throw initException; + } + + propSrc = "specified file: '" + requestedFile + "' in the class resource path."; + + in = new BufferedInputStream(in); + try { + props.load(in); + } catch (IOException ioe) { + initException = new SchedulerException("Properties file: '" + + requestedFile + "' could not be read.", ioe); + throw initException; + } + + } else { + propSrc = "default resource file in Quartz package: 'quartz.properties'"; + + in = getClass().getClassLoader().getResourceAsStream( + "quartz.properties"); + + if (in == null) { + in = getClass().getClassLoader().getResourceAsStream( + "/quartz.properties"); + } + if (in == null) { + in = getClass().getClassLoader().getResourceAsStream( + "com/fr/third/org/quartz/quartz.properties"); + } + if (in == null) { + initException = new SchedulerException( + "Default quartz.properties not found in class path"); + throw initException; + } + try { + props.load(in); + } catch (IOException ioe) { + initException = new SchedulerException( + "Resource properties file: 'com/fr/third/org/quartz/quartz.properties' " + + "could not be read from the classpath.", ioe); + throw initException; + } + } + } finally { + if(in != null) { + try { in.close(); } catch(IOException ignore) { /* ignore */ } + } + } + + initialize(overrideWithSysProps(props)); + } + + /** + * Add all System properties to the given props. Will override + * any properties that already exist in the given props. + */ + private Properties overrideWithSysProps(Properties props) { + Properties sysProps = null; + try { + sysProps = System.getProperties(); + } catch (AccessControlException e) { + getLog().warn( + "Skipping overriding quartz properties with System properties " + + "during initialization because of an AccessControlException. " + + "This is likely due to not having read/write access for " + + "java.util.PropertyPermission as required by java.lang.System.getProperties(). " + + "To resolve this warning, either add this permission to your policy file or " + + "use a non-default version of initialize().", + e); + } + + if (sysProps != null) { + props.putAll(sysProps); + } + + return props; + } + + /** + *

+ * Initialize the {@link com.fr.third.org.quartz.SchedulerFactory} with + * the contenents of the Properties file with the given + * name. + *

+ */ + public void initialize(String filename) throws SchedulerException { + // short-circuit if already initialized + if (cfg != null) { + return; + } + + if (initException != null) { + throw initException; + } + + InputStream is = null; + Properties props = new Properties(); + + is = Thread.currentThread().getContextClassLoader().getResourceAsStream(filename); + + try { + if(is != null) { + is = new BufferedInputStream(is); + propSrc = "the specified file : '" + filename + "' from the class resource path."; + } else { + is = new BufferedInputStream(new FileInputStream(filename)); + propSrc = "the specified file : '" + filename + "'"; + } + props.load(is); + } catch (IOException ioe) { + initException = new SchedulerException("Properties file: '" + + filename + "' could not be read.", ioe); + throw initException; + } + finally { + if(is != null) + try { is.close(); } catch(IOException ignore) {} + } + + initialize(props); + } + + /** + *

+ * Initialize the {@link com.fr.third.org.quartz.SchedulerFactory} with + * the contenents of the Properties file opened with the + * given InputStream. + *

+ */ + public void initialize(InputStream propertiesStream) + throws SchedulerException { + // short-circuit if already initialized + if (cfg != null) { + return; + } + + if (initException != null) { + throw initException; + } + + Properties props = new Properties(); + + if (propertiesStream != null) { + try { + props.load(propertiesStream); + propSrc = "an externally opened InputStream."; + } catch (IOException e) { + initException = new SchedulerException( + "Error loading property data from InputStream", e); + throw initException; + } + } else { + initException = new SchedulerException( + "Error loading property data from InputStream - InputStream is null."); + throw initException; + } + + initialize(props); + } + + /** + *

+ * Initialize the {@link com.fr.third.org.quartz.SchedulerFactory} with + * the contenents of the given Properties object. + *

+ */ + public void initialize(Properties props) throws SchedulerException { + if (propSrc == null) { + propSrc = "an externally provided properties instance."; + } + + this.cfg = new PropertiesParser(props); + } + + private Scheduler instantiate() throws SchedulerException { + if (cfg == null) { + initialize(); + } + + if (initException != null) { + throw initException; + } + + JobStore js = null; + ThreadPool tp = null; + QuartzScheduler qs = null; + SchedulingContext schedCtxt = null; + DBConnectionManager dbMgr = null; + String instanceIdGeneratorClass = null; + Properties tProps = null; + String userTXLocation = null; + boolean wrapJobInTx = false; + boolean autoId = false; + long idleWaitTime = -1; + long dbFailureRetry = -1; + String classLoadHelperClass; + String jobFactoryClass; + + SchedulerRepository schedRep = SchedulerRepository.getInstance(); + + // Get Scheduler Properties + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + String schedName = cfg.getStringProperty(PROP_SCHED_INSTANCE_NAME, + "QuartzScheduler"); + + String threadName = cfg.getStringProperty(PROP_SCHED_THREAD_NAME, + schedName + "_QuartzSchedulerThread"); + + String schedInstId = cfg.getStringProperty(PROP_SCHED_INSTANCE_ID, + DEFAULT_INSTANCE_ID); + + if (schedInstId.equals(AUTO_GENERATE_INSTANCE_ID)) { + autoId = true; + instanceIdGeneratorClass = cfg.getStringProperty( + PROP_SCHED_INSTANCE_ID_GENERATOR_CLASS, + "com.fr.third.org.quartz.simpl.SimpleInstanceIdGenerator"); + } + + userTXLocation = cfg.getStringProperty(PROP_SCHED_USER_TX_URL, + userTXLocation); + if (userTXLocation != null && userTXLocation.trim().length() == 0) { + userTXLocation = null; + } + + classLoadHelperClass = cfg.getStringProperty( + PROP_SCHED_CLASS_LOAD_HELPER_CLASS, + "com.fr.third.org.quartz.simpl.CascadingClassLoadHelper"); + wrapJobInTx = cfg.getBooleanProperty(PROP_SCHED_WRAP_JOB_IN_USER_TX, + wrapJobInTx); + + jobFactoryClass = cfg.getStringProperty( + PROP_SCHED_JOB_FACTORY_CLASS, null); + + idleWaitTime = cfg.getLongProperty(PROP_SCHED_IDLE_WAIT_TIME, + idleWaitTime); + dbFailureRetry = cfg.getLongProperty( + PROP_SCHED_DB_FAILURE_RETRY_INTERVAL, dbFailureRetry); + + boolean makeSchedulerThreadDaemon = + cfg.getBooleanProperty(PROP_SCHED_MAKE_SCHEDULER_THREAD_DAEMON); + + boolean threadsInheritInitalizersClassLoader = + cfg.getBooleanProperty(PROP_SCHED_SCHEDULER_THREADS_INHERIT_CONTEXT_CLASS_LOADER_OF_INITIALIZING_THREAD); + + boolean jmxExport = cfg.getBooleanProperty(PROP_SCHED_JMX_EXPORT); + boolean jmxProxy = cfg.getBooleanProperty(PROP_SCHED_JMX_PROXY); + String jmxProxyClass = cfg.getStringProperty(PROP_SCHED_JMX_PROXY_CLASS); + String jmxObjectName = cfg.getStringProperty(PROP_SCHED_JMX_OBJECT_NAME); + + boolean rmiExport = cfg.getBooleanProperty(PROP_SCHED_RMI_EXPORT, false); + boolean rmiProxy = cfg.getBooleanProperty(PROP_SCHED_RMI_PROXY, false); + String rmiHost = cfg.getStringProperty(PROP_SCHED_RMI_HOST, "localhost"); + int rmiPort = cfg.getIntProperty(PROP_SCHED_RMI_PORT, 1099); + int rmiServerPort = cfg.getIntProperty(PROP_SCHED_RMI_SERVER_PORT, -1); + String rmiCreateRegistry = cfg.getStringProperty( + PROP_SCHED_RMI_CREATE_REGISTRY, + QuartzSchedulerResources.CREATE_REGISTRY_NEVER); + String rmiBindName = cfg.getStringProperty(PROP_SCHED_RMI_BIND_NAME); + + if (jmxProxy && rmiProxy) { + throw new SchedulerConfigException("Cannot proxy both RMI and JMX."); + } + + Properties schedCtxtProps = cfg.getPropertyGroup(PROP_SCHED_CONTEXT_PREFIX, true); + + // If Proxying to remote scheduler, short-circuit here... + // ~~~~~~~~~~~~~~~~~~ + if (rmiProxy) { + + if (autoId) { + schedInstId = DEFAULT_INSTANCE_ID; + } + + schedCtxt = new SchedulingContext(); + schedCtxt.setInstanceId(schedInstId); + + String uid = (rmiBindName == null) ? QuartzSchedulerResources.getUniqueIdentifier( + schedName, schedInstId) : rmiBindName; + + RemoteScheduler remoteScheduler = new RemoteScheduler(schedCtxt, + uid, rmiHost, rmiPort); + + schedRep.bind(remoteScheduler); + + return remoteScheduler; + } + + + // Create class load helper + ClassLoadHelper loadHelper = null; + try { + loadHelper = (ClassLoadHelper) loadClass(classLoadHelperClass) + .newInstance(); + } catch (Exception e) { + throw new SchedulerConfigException( + "Unable to instantiate class load helper class: " + + e.getMessage(), e); + } + loadHelper.initialize(); + + // If Proxying to remote JMX scheduler, short-circuit here... + // ~~~~~~~~~~~~~~~~~~ + if (jmxProxy) { + if (autoId) { + schedInstId = DEFAULT_INSTANCE_ID; + } + + if (jmxProxyClass == null) { + throw new SchedulerConfigException("No JMX Proxy Scheduler class provided"); + } + + RemoteMBeanScheduler jmxScheduler = null; + try { + jmxScheduler = (RemoteMBeanScheduler)loadHelper.loadClass(jmxProxyClass) + .newInstance(); + } catch (Exception e) { + throw new SchedulerConfigException( + "Unable to instantiate RemoteMBeanScheduler class.", e); + } + + schedCtxt = new SchedulingContext(); + schedCtxt.setInstanceId(schedInstId); + + if (jmxObjectName == null) { + jmxObjectName = QuartzSchedulerResources.generateJMXObjectName(schedName, schedInstId); + } + + jmxScheduler.setSchedulingContext(schedCtxt); + jmxScheduler.setSchedulerObjectName(jmxObjectName); + + tProps = cfg.getPropertyGroup(PROP_SCHED_JMX_PROXY, true); + try { + setBeanProps(jmxScheduler, tProps); + } catch (Exception e) { + initException = new SchedulerException("RemoteMBeanScheduler class '" + + jmxProxyClass + "' props could not be configured.", e); + initException.setErrorCode(SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + + jmxScheduler.initialize(); + + schedRep.bind(jmxScheduler); + + return jmxScheduler; + } + + JobFactory jobFactory = null; + if(jobFactoryClass != null) { + try { + jobFactory = (JobFactory) loadHelper.loadClass(jobFactoryClass) + .newInstance(); + } catch (Exception e) { + throw new SchedulerConfigException( + "Unable to instantiate JobFactory class: " + + e.getMessage(), e); + } + + tProps = cfg.getPropertyGroup(PROP_SCHED_JOB_FACTORY_PREFIX, true); + try { + setBeanProps(jobFactory, tProps); + } catch (Exception e) { + initException = new SchedulerException("JobFactory class '" + + jobFactoryClass + "' props could not be configured.", e); + initException + .setErrorCode(SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + } + + InstanceIdGenerator instanceIdGenerator = null; + if(instanceIdGeneratorClass != null) { + try { + instanceIdGenerator = (InstanceIdGenerator) loadHelper.loadClass(instanceIdGeneratorClass) + .newInstance(); + } catch (Exception e) { + throw new SchedulerConfigException( + "Unable to instantiate InstanceIdGenerator class: " + + e.getMessage(), e); + } + + tProps = cfg.getPropertyGroup(PROP_SCHED_INSTANCE_ID_GENERATOR_PREFIX, true); + try { + setBeanProps(instanceIdGenerator, tProps); + } catch (Exception e) { + initException = new SchedulerException("InstanceIdGenerator class '" + + instanceIdGeneratorClass + "' props could not be configured.", e); + initException + .setErrorCode(SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + } + + // Get ThreadPool Properties + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + String tpClass = cfg.getStringProperty(PROP_THREAD_POOL_CLASS, null); + + if (tpClass == null) { + initException = new SchedulerException( + "ThreadPool class not specified. ", + SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + + try { + tp = (ThreadPool) loadHelper.loadClass(tpClass).newInstance(); + } catch (Exception e) { + initException = new SchedulerException("ThreadPool class '" + + tpClass + "' could not be instantiated.", e); + initException + .setErrorCode(SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + tProps = cfg.getPropertyGroup(PROP_THREAD_POOL_PREFIX, true); + try { + setBeanProps(tp, tProps); + } catch (Exception e) { + initException = new SchedulerException("ThreadPool class '" + + tpClass + "' props could not be configured.", e); + initException + .setErrorCode(SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + + // Get JobStore Properties + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + String jsClass = cfg.getStringProperty(PROP_JOB_STORE_CLASS, + RAMJobStore.class.getName()); + + if (jsClass == null) { + initException = new SchedulerException( + "JobStore class not specified. ", + SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + + try { + js = (JobStore) loadHelper.loadClass(jsClass).newInstance(); + } catch (Exception e) { + initException = new SchedulerException("JobStore class '" + jsClass + + "' could not be instantiated.", e); + initException + .setErrorCode(SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + tProps = cfg.getPropertyGroup(PROP_JOB_STORE_PREFIX, true, new String[] {PROP_JOB_STORE_LOCK_HANDLER_PREFIX}); + try { + setBeanProps(js, tProps); + } catch (Exception e) { + initException = new SchedulerException("JobStore class '" + jsClass + + "' props could not be configured.", e); + initException + .setErrorCode(SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + + if (js instanceof JobStoreSupport) { + ((JobStoreSupport)js).setInstanceId(schedInstId); + ((JobStoreSupport)js).setInstanceName(schedName); + + // Install custom lock handler (Semaphore) + String lockHandlerClass = cfg.getStringProperty(PROP_JOB_STORE_LOCK_HANDLER_CLASS); + if (lockHandlerClass != null) { + try { + Semaphore lockHandler = (Semaphore)loadHelper.loadClass(lockHandlerClass).newInstance(); + + tProps = cfg.getPropertyGroup(PROP_JOB_STORE_LOCK_HANDLER_PREFIX, true); + + // If this lock handler requires the table prefix, add it to its properties. + if (lockHandler instanceof TablePrefixAware) { + tProps.setProperty( + PROP_TABLE_PREFIX, ((JobStoreSupport)js).getTablePrefix()); + } + + try { + setBeanProps(lockHandler, tProps); + } catch (Exception e) { + initException = new SchedulerException("JobStore LockHandler class '" + lockHandlerClass + + "' props could not be configured.", e); + initException.setErrorCode(SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + + ((JobStoreSupport)js).setLockHandler(lockHandler); + getLog().info("Using custom data access locking (synchronization): " + lockHandlerClass); + } catch (Exception e) { + initException = new SchedulerException("JobStore LockHandler class '" + lockHandlerClass + + "' could not be instantiated.", e); + initException.setErrorCode(SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + } + } + + // Set up any DataSources + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + String[] dsNames = cfg.getPropertyGroups(PROP_DATASOURCE_PREFIX); + for (int i = 0; i < dsNames.length; i++) { + PropertiesParser pp = new PropertiesParser(cfg.getPropertyGroup( + PROP_DATASOURCE_PREFIX + "." + dsNames[i], true)); + + String cpClass = pp.getStringProperty(PROP_CONNECTION_PROVIDER_CLASS, null); + + // custom connectionProvider... + if(cpClass != null) { + ConnectionProvider cp = null; + try { + cp = (ConnectionProvider) loadHelper.loadClass(cpClass).newInstance(); + } catch (Exception e) { + initException = new SchedulerException("ConnectionProvider class '" + cpClass + + "' could not be instantiated.", e); + initException + .setErrorCode(SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + + try { + // remove the class name, so it isn't attempted to be set + pp.getUnderlyingProperties().remove( + PROP_CONNECTION_PROVIDER_CLASS); + + setBeanProps(cp, pp.getUnderlyingProperties()); + } catch (Exception e) { + initException = new SchedulerException("ConnectionProvider class '" + cpClass + + "' props could not be configured.", e); + initException + .setErrorCode(SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + + dbMgr = DBConnectionManager.getInstance(); + dbMgr.addConnectionProvider(dsNames[i], cp); + } else { + String dsJndi = pp.getStringProperty(PROP_DATASOURCE_JNDI_URL, null); + + if (dsJndi != null) { + boolean dsAlwaysLookup = pp.getBooleanProperty( + PROP_DATASOURCE_JNDI_ALWAYS_LOOKUP); + String dsJndiInitial = pp.getStringProperty( + PROP_DATASOURCE_JNDI_INITIAL); + String dsJndiProvider = pp.getStringProperty( + PROP_DATASOURCE_JNDI_PROVDER); + String dsJndiPrincipal = pp.getStringProperty( + PROP_DATASOURCE_JNDI_PRINCIPAL); + String dsJndiCredentials = pp.getStringProperty( + PROP_DATASOURCE_JNDI_CREDENTIALS); + Properties props = null; + if (null != dsJndiInitial || null != dsJndiProvider + || null != dsJndiPrincipal || null != dsJndiCredentials) { + props = new Properties(); + if (dsJndiInitial != null) { + props.put(PROP_DATASOURCE_JNDI_INITIAL, + dsJndiInitial); + } + if (dsJndiProvider != null) { + props.put(PROP_DATASOURCE_JNDI_PROVDER, + dsJndiProvider); + } + if (dsJndiPrincipal != null) { + props.put(PROP_DATASOURCE_JNDI_PRINCIPAL, + dsJndiPrincipal); + } + if (dsJndiCredentials != null) { + props.put(PROP_DATASOURCE_JNDI_CREDENTIALS, + dsJndiCredentials); + } + } + JNDIConnectionProvider cp = new JNDIConnectionProvider(dsJndi, + props, dsAlwaysLookup); + dbMgr = DBConnectionManager.getInstance(); + dbMgr.addConnectionProvider(dsNames[i], cp); + } else { + String dsDriver = pp.getStringProperty(PROP_DATASOURCE_DRIVER); + String dsURL = pp.getStringProperty(PROP_DATASOURCE_URL); + String dsUser = pp.getStringProperty(PROP_DATASOURCE_USER, ""); + String dsPass = pp.getStringProperty(PROP_DATASOURCE_PASSWORD, ""); + int dsCnt = pp.getIntProperty(PROP_DATASOURCE_MAX_CONNECTIONS, 10); + String dsValidation = pp.getStringProperty(PROP_DATASOURCE_VALIDATION_QUERY); + + if (dsDriver == null) { + initException = new SchedulerException( + "Driver not specified for DataSource: " + + dsNames[i]); + throw initException; + } + if (dsURL == null) { + initException = new SchedulerException( + "DB URL not specified for DataSource: " + + dsNames[i]); + throw initException; + } + try { + PoolingConnectionProvider cp = new PoolingConnectionProvider( + dsDriver, dsURL, dsUser, dsPass, dsCnt, + dsValidation); + dbMgr = DBConnectionManager.getInstance(); + dbMgr.addConnectionProvider(dsNames[i], cp); + } catch (SQLException sqle) { + initException = new SchedulerException( + "Could not initialize DataSource: " + dsNames[i], + sqle); + throw initException; + } + } + + } + + } + + // Set up any SchedulerPlugins + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + String[] pluginNames = cfg.getPropertyGroups(PROP_PLUGIN_PREFIX); + SchedulerPlugin[] plugins = new SchedulerPlugin[pluginNames.length]; + for (int i = 0; i < pluginNames.length; i++) { + Properties pp = cfg.getPropertyGroup(PROP_PLUGIN_PREFIX + "." + + pluginNames[i], true); + + String plugInClass = pp.getProperty(PROP_PLUGIN_CLASS, null); + + if (plugInClass == null) { + initException = new SchedulerException( + "SchedulerPlugin class not specified for plugin '" + + pluginNames[i] + "'", + SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + SchedulerPlugin plugin = null; + try { + plugin = (SchedulerPlugin) + loadHelper.loadClass(plugInClass).newInstance(); + } catch (Exception e) { + initException = new SchedulerException( + "SchedulerPlugin class '" + plugInClass + + "' could not be instantiated.", e); + initException + .setErrorCode(SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + try { + setBeanProps(plugin, pp); + } catch (Exception e) { + initException = new SchedulerException( + "JobStore SchedulerPlugin '" + plugInClass + + "' props could not be configured.", e); + initException + .setErrorCode(SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + + plugins[i] = plugin; + } + + // Set up any JobListeners + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Class[] strArg = new Class[] { String.class }; + String[] jobListenerNames = cfg.getPropertyGroups(PROP_JOB_LISTENER_PREFIX); + JobListener[] jobListeners = new JobListener[jobListenerNames.length]; + for (int i = 0; i < jobListenerNames.length; i++) { + Properties lp = cfg.getPropertyGroup(PROP_JOB_LISTENER_PREFIX + "." + + jobListenerNames[i], true); + + String listenerClass = lp.getProperty(PROP_LISTENER_CLASS, null); + + if (listenerClass == null) { + initException = new SchedulerException( + "JobListener class not specified for listener '" + + jobListenerNames[i] + "'", + SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + JobListener listener = null; + try { + listener = (JobListener) + loadHelper.loadClass(listenerClass).newInstance(); + } catch (Exception e) { + initException = new SchedulerException( + "JobListener class '" + listenerClass + + "' could not be instantiated.", e); + initException + .setErrorCode(SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + try { + Method nameSetter = listener.getClass().getMethod("setName", strArg); + if(nameSetter != null) { + nameSetter.invoke(listener, new Object[] {jobListenerNames[i] } ); + } + setBeanProps(listener, lp); + } catch (Exception e) { + initException = new SchedulerException( + "JobListener '" + listenerClass + + "' props could not be configured.", e); + initException + .setErrorCode(SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + jobListeners[i] = listener; + } + + // Set up any TriggerListeners + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + String[] triggerListenerNames = cfg.getPropertyGroups(PROP_TRIGGER_LISTENER_PREFIX); + TriggerListener[] triggerListeners = new TriggerListener[triggerListenerNames.length]; + for (int i = 0; i < triggerListenerNames.length; i++) { + Properties lp = cfg.getPropertyGroup(PROP_TRIGGER_LISTENER_PREFIX + "." + + triggerListenerNames[i], true); + + String listenerClass = lp.getProperty(PROP_LISTENER_CLASS, null); + + if (listenerClass == null) { + initException = new SchedulerException( + "TriggerListener class not specified for listener '" + + triggerListenerNames[i] + "'", + SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + TriggerListener listener = null; + try { + listener = (TriggerListener) + loadHelper.loadClass(listenerClass).newInstance(); + } catch (Exception e) { + initException = new SchedulerException( + "TriggerListener class '" + listenerClass + + "' could not be instantiated.", e); + initException + .setErrorCode(SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + try { + Method nameSetter = listener.getClass().getMethod("setName", strArg); + if(nameSetter != null) { + nameSetter.invoke(listener, new Object[] {triggerListenerNames[i] } ); + } + setBeanProps(listener, lp); + } catch (Exception e) { + initException = new SchedulerException( + "TriggerListener '" + listenerClass + + "' props could not be configured.", e); + initException + .setErrorCode(SchedulerException.ERR_BAD_CONFIGURATION); + throw initException; + } + triggerListeners[i] = listener; + } + + + // Fire everything up + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + JobRunShellFactory jrsf = null; // Create correct run-shell factory... + + if (userTXLocation != null) { + UserTransactionHelper.setUserTxLocation(userTXLocation); + } + + if (wrapJobInTx) { + jrsf = new JTAJobRunShellFactory(); + } else { + jrsf = new StdJobRunShellFactory(); + } + + if (autoId) { + try { + schedInstId = DEFAULT_INSTANCE_ID; + if (js instanceof JobStoreSupport) { + if(((JobStoreSupport)js).isClustered()) { + schedInstId = instanceIdGenerator.generateInstanceId(); + } + } + } catch (Exception e) { + getLog().error("Couldn't generate instance Id!", e); + throw new IllegalStateException( + "Cannot run without an instance id."); + } + } + + if (js instanceof JobStoreSupport) { + JobStoreSupport jjs = (JobStoreSupport)js; + jjs.setInstanceId(schedInstId); + jjs.setDbRetryInterval(dbFailureRetry); + if(threadsInheritInitalizersClassLoader) + jjs.setThreadsInheritInitializersClassLoadContext(threadsInheritInitalizersClassLoader); + } + + QuartzSchedulerResources rsrcs = new QuartzSchedulerResources(); + rsrcs.setName(schedName); + rsrcs.setThreadName(threadName); + rsrcs.setInstanceId(schedInstId); + rsrcs.setJobRunShellFactory(jrsf); + rsrcs.setMakeSchedulerThreadDaemon(makeSchedulerThreadDaemon); + rsrcs.setThreadsInheritInitializersClassLoadContext(threadsInheritInitalizersClassLoader); + rsrcs.setJMXExport(jmxExport); + rsrcs.setJMXObjectName(jmxObjectName); + + if (rmiExport) { + rsrcs.setRMIRegistryHost(rmiHost); + rsrcs.setRMIRegistryPort(rmiPort); + rsrcs.setRMIServerPort(rmiServerPort); + rsrcs.setRMICreateRegistryStrategy(rmiCreateRegistry); + rsrcs.setRMIBindName(rmiBindName); + } + + rsrcs.setThreadPool(tp); + if(tp instanceof SimpleThreadPool) { + ((SimpleThreadPool)tp).setThreadNamePrefix(schedName + "_Worker"); + if(threadsInheritInitalizersClassLoader) + ((SimpleThreadPool)tp).setThreadsInheritContextClassLoaderOfInitializingThread(threadsInheritInitalizersClassLoader); + } + tp.initialize(); + + rsrcs.setJobStore(js); + + // add plugins + for (int i = 0; i < plugins.length; i++) { + rsrcs.addSchedulerPlugin(plugins[i]); + } + + schedCtxt = new SchedulingContext(); + schedCtxt.setInstanceId(rsrcs.getInstanceId()); + + qs = new QuartzScheduler(rsrcs, schedCtxt, idleWaitTime, dbFailureRetry); + + // Create Scheduler ref... + Scheduler scheduler = instantiate(rsrcs, qs); + + // set job factory if specified + if(jobFactory != null) { + qs.setJobFactory(jobFactory); + } + + // Initialize plugins now that we have a Scheduler instance. + for (int i = 0; i < plugins.length; i++) { + plugins[i].initialize(pluginNames[i], scheduler); + } + + // add listeners + for (int i = 0; i < jobListeners.length; i++) { + qs.addGlobalJobListener(jobListeners[i]); + } + for (int i = 0; i < triggerListeners.length; i++) { + qs.addGlobalTriggerListener(triggerListeners[i]); + } + + // set scheduler context data... + Iterator itr = schedCtxtProps.keySet().iterator(); + while(itr.hasNext()) { + String key = (String) itr.next(); + String val = schedCtxtProps.getProperty(key); + + scheduler.getContext().put(key, val); + } + + // fire up job store, and runshell factory + + js.initialize(loadHelper, qs.getSchedulerSignaler()); + + jrsf.initialize(scheduler, schedCtxt); + + getLog().info( + "Quartz scheduler '" + scheduler.getSchedulerName() + + "' initialized from " + propSrc); + + getLog().info("Quartz scheduler version: " + qs.getVersion()); + + // prevents the repository from being garbage collected + qs.addNoGCObject(schedRep); + // prevents the db manager from being garbage collected + if (dbMgr != null) { + qs.addNoGCObject(dbMgr); + } + + schedRep.bind(scheduler); + + return scheduler; + } + + protected Scheduler instantiate(QuartzSchedulerResources rsrcs, QuartzScheduler qs) { + SchedulingContext schedCtxt = new SchedulingContext(); + schedCtxt.setInstanceId(rsrcs.getInstanceId()); + + Scheduler scheduler = new StdScheduler(qs, schedCtxt); + return scheduler; + } + + + private void setBeanProps(Object obj, Properties props) + throws NoSuchMethodException, IllegalAccessException, + java.lang.reflect.InvocationTargetException, + IntrospectionException, SchedulerConfigException { + props.remove("class"); + + BeanInfo bi = Introspector.getBeanInfo(obj.getClass()); + PropertyDescriptor[] propDescs = bi.getPropertyDescriptors(); + PropertiesParser pp = new PropertiesParser(props); + + java.util.Enumeration keys = props.keys(); + while (keys.hasMoreElements()) { + String name = (String) keys.nextElement(); + String c = name.substring(0, 1).toUpperCase(Locale.US); + String methName = "set" + c + name.substring(1); + + java.lang.reflect.Method setMeth = getSetMethod(methName, propDescs); + + try { + if (setMeth == null) { + throw new NoSuchMethodException( + "No setter for property '" + name + "'"); + } + + Class[] params = setMeth.getParameterTypes(); + if (params.length != 1) { + throw new NoSuchMethodException( + "No 1-argument setter for property '" + name + "'"); + } + if (params[0].equals(int.class)) { + setMeth.invoke(obj, new Object[]{new Integer(pp + .getIntProperty(name))}); + } else if (params[0].equals(long.class)) { + setMeth.invoke(obj, new Object[]{new Long(pp + .getLongProperty(name))}); + } else if (params[0].equals(float.class)) { + setMeth.invoke(obj, new Object[]{new Float(pp + .getFloatProperty(name))}); + } else if (params[0].equals(double.class)) { + setMeth.invoke(obj, new Object[]{new Double(pp + .getDoubleProperty(name))}); + } else if (params[0].equals(boolean.class)) { + setMeth.invoke(obj, new Object[]{new Boolean(pp + .getBooleanProperty(name))}); + } else if (params[0].equals(String.class)) { + setMeth.invoke(obj, + new Object[]{pp.getStringProperty(name)}); + } else { + throw new NoSuchMethodException( + "No primitive-type setter for property '" + name + + "'"); + } + } catch (NumberFormatException nfe) { + throw new SchedulerConfigException("Could not parse property '" + + name + "' into correct data type: " + nfe.toString()); + } + } + } + + private java.lang.reflect.Method getSetMethod(String name, + PropertyDescriptor[] props) { + for (int i = 0; i < props.length; i++) { + java.lang.reflect.Method wMeth = props[i].getWriteMethod(); + + if (wMeth != null && wMeth.getName().equals(name)) { + return wMeth; + } + } + + return null; + } + + private Class loadClass(String className) throws ClassNotFoundException { + + try { + return Thread.currentThread().getContextClassLoader().loadClass( + className); + } catch (ClassNotFoundException e) { + return getClass().getClassLoader().loadClass(className); + } + } + + private String getSchedulerName() { + return cfg.getStringProperty(PROP_SCHED_INSTANCE_NAME, + "QuartzScheduler"); + } + + private String getSchedulerInstId() { + return cfg.getStringProperty(PROP_SCHED_INSTANCE_ID, + DEFAULT_INSTANCE_ID); + } + + /** + *

+ * Returns a handle to the Scheduler produced by this factory. + *

+ * + *

+ * If one of the initialize methods has not be previously + * called, then the default (no-arg) initialize() method + * will be called by this method. + *

+ */ + public Scheduler getScheduler() throws SchedulerException { + if (cfg == null) { + initialize(); + } + + SchedulerRepository schedRep = SchedulerRepository.getInstance(); + + Scheduler sched = schedRep.lookup(getSchedulerName()); + + if (sched != null) { + if (sched.isShutdown()) { + schedRep.remove(getSchedulerName()); + } else { + return sched; + } + } + + sched = instantiate(); + + return sched; + } + + /** + *

+ * Returns a handle to the default Scheduler, creating it if it does not + * yet exist. + *

+ * + * @see #initialize() + */ + public static Scheduler getDefaultScheduler() throws SchedulerException { + StdSchedulerFactory fact = new StdSchedulerFactory(); + + return fact.getScheduler(); + } + + /** + *

+ * Returns a handle to the Scheduler with the given name, if it exists (if + * it has already been instantiated). + *

+ */ + public Scheduler getScheduler(String schedName) throws SchedulerException { + return SchedulerRepository.getInstance().lookup(schedName); + } + + /** + *

+ * Returns a handle to all known Schedulers (made by any + * StdSchedulerFactory instance.). + *

+ */ + public Collection getAllSchedulers() throws SchedulerException { + return SchedulerRepository.getInstance().lookupAll(); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/AnnualCalendar.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/AnnualCalendar.java new file mode 100644 index 000000000..e703402fe --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/AnnualCalendar.java @@ -0,0 +1,294 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + * and Juergen Donnerstag (c) 2002, EDS 2002 + */ + +package com.fr.third.org.quartz.impl.calendar; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.TimeZone; + +import com.fr.third.org.quartz.Calendar; + +/** + *

+ * This implementation of the Calendar excludes a set of days of the year. You + * may use it to exclude bank holidays which are on the same date every year. + *

+ * + * @see com.fr.third.org.quartz.Calendar + * @see com.fr.third.org.quartz.impl.calendar.BaseCalendar + * + * @author Juergen Donnerstag + */ +public class AnnualCalendar extends BaseCalendar implements Calendar, + Serializable { + + static final long serialVersionUID = 7346867105876610961L; + + private ArrayList excludeDays = new ArrayList(); + + // true, if excludeDays is sorted + private boolean dataSorted = false; + + public AnnualCalendar() { + } + + public AnnualCalendar(Calendar baseCalendar) { + super(baseCalendar); + } + + public AnnualCalendar(TimeZone timeZone) { + super(timeZone); + } + + public AnnualCalendar(Calendar baseCalendar, TimeZone timeZone) { + super(baseCalendar, timeZone); + } + + /** + *

+ * Get the array which defines the exclude-value of each day of month + *

+ */ + public ArrayList getDaysExcluded() { + return excludeDays; + } + + /** + *

+ * Return true, if day is defined to be exluded. + *

+ */ + public boolean isDayExcluded(java.util.Calendar day) { + + if (day == null) { + throw new IllegalArgumentException( + "Parameter day must not be null"); + } + + // Check baseCalendar first + if (! super.isTimeIncluded(day.getTime().getTime())) { + return true; + } + + int dmonth = day.get(java.util.Calendar.MONTH); + int dday = day.get(java.util.Calendar.DAY_OF_MONTH); + + if (dataSorted == false) { + Collections.sort(excludeDays, new CalendarComparator()); + dataSorted = true; + } + + Iterator iter = excludeDays.iterator(); + while (iter.hasNext()) { + java.util.Calendar cl = (java.util.Calendar) iter.next(); + + // remember, the list is sorted + if (dmonth < cl.get(java.util.Calendar.MONTH)) { + return false; + } + + if (dday != cl.get(java.util.Calendar.DAY_OF_MONTH)) { + continue; + } + + if (dmonth != cl.get(java.util.Calendar.MONTH)) { + continue; + } + + return true; + } + + return false; + } + + /** + *

+ * Redefine the list of days excluded. The ArrayList + * should contain java.util.Calendar objects. + *

+ */ + public void setDaysExcluded(ArrayList days) { + if (days == null) { + excludeDays = new ArrayList(); + } else { + excludeDays = days; + } + + dataSorted = false; + } + + /** + *

+ * Redefine a certain day to be excluded (true) or included (false). + *

+ */ + public void setDayExcluded(java.util.Calendar day, boolean exclude) { + if (exclude) { + if (isDayExcluded(day)) { + return; + } + + excludeDays.add(day); + dataSorted = false; + } else { + if (!isDayExcluded(day)) { + return; + } + + removeExcludedDay(day, true); + } + } + + /** + * Remove the given day from the list of excluded days + * + * @param day + * @return + */ + public void removeExcludedDay(java.util.Calendar day) { + removeExcludedDay(day, false); + } + + private void removeExcludedDay(java.util.Calendar day, boolean isChecked) { + if (! isChecked && + ! isDayExcluded(day)) { + return; + } + + // Fast way, see if exact day object was already in list + if (this.excludeDays.remove(day)) { + return; + } + + int dmonth = day.get(java.util.Calendar.MONTH); + int dday = day.get(java.util.Calendar.DAY_OF_MONTH); + + // Since there is no guarantee that the given day is in the arraylist with the exact same year + // search for the object based on month and day of month in the list and remove it + Iterator iter = excludeDays.iterator(); + while (iter.hasNext()) { + java.util.Calendar cl = (java.util.Calendar) iter.next(); + + if (dmonth != cl.get(java.util.Calendar.MONTH)) { + continue; + } + + if (dday != cl.get(java.util.Calendar.DAY_OF_MONTH)) { + continue; + } + + day = cl; + break; + } + + this.excludeDays.remove(day); + } + + + /** + *

+ * Determine whether the given time (in milliseconds) is 'included' by the + * Calendar. + *

+ * + *

+ * Note that this Calendar is only has full-day precision. + *

+ */ + public boolean isTimeIncluded(long timeStamp) { + // Test the base calendar first. Only if the base calendar not already + // excludes the time/date, continue evaluating this calendar instance. + if (super.isTimeIncluded(timeStamp) == false) { return false; } + + java.util.Calendar day = createJavaCalendar(timeStamp); + + return !(isDayExcluded(day)); + } + + /** + *

+ * Determine the next time (in milliseconds) that is 'included' by the + * Calendar after the given time. Return the original value if timeStamp is + * included. Return 0 if all days are excluded. + *

+ * + *

+ * Note that this Calendar is only has full-day precision. + *

+ */ + public long getNextIncludedTime(long timeStamp) { + // Call base calendar implementation first + long baseTime = super.getNextIncludedTime(timeStamp); + if ((baseTime > 0) && (baseTime > timeStamp)) { + timeStamp = baseTime; + } + + // Get timestamp for 00:00:00 + java.util.Calendar day = getStartOfDayJavaCalendar(timeStamp); + if (isDayExcluded(day) == false) { + return timeStamp; // return the original value + } + + while (isDayExcluded(day) == true) { + day.add(java.util.Calendar.DATE, 1); + } + + return day.getTime().getTime(); + } +} + +class CalendarComparator implements Comparator { + public CalendarComparator() { + } + + /** + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + public int compare(Object arg0, Object arg1) { + java.util.Calendar c1 = (java.util.Calendar) arg0; + java.util.Calendar c2 = (java.util.Calendar) arg1; + + int month1 = c1.get(java.util.Calendar.MONTH); + int month2 = c2.get(java.util.Calendar.MONTH); + + int day1 = c1.get(java.util.Calendar.DAY_OF_MONTH); + int day2 = c2.get(java.util.Calendar.DAY_OF_MONTH); + + if (month1 < month2) { + return -1; + } + if (month1 > month2) { + return 1; + } + if (day1 < day2) { + return -1; + } + if (day1 > day2) { + return 1; + } + return 0; + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/BaseCalendar.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/BaseCalendar.java new file mode 100644 index 000000000..2e315231e --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/BaseCalendar.java @@ -0,0 +1,283 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + * and Juergen Donnerstag (c) 2002, EDS 2002 + */ + +package com.fr.third.org.quartz.impl.calendar; + +import java.io.Serializable; +import java.util.Date; +import java.util.TimeZone; + +import com.fr.third.org.quartz.Calendar; + +/** + *

+ * This implementation of the Calendar may be used (you don't have to) as a + * base class for more sophisticated one's. It merely implements the base + * functionality required by each Calendar. + *

+ * + *

+ * Regarded as base functionality is the treatment of base calendars. Base + * calendar allow you to chain (stack) as much calendars as you may need. For + * example to exclude weekends you may use WeeklyCalendar. In order to exclude + * holidays as well you may define a WeeklyCalendar instance to be the base + * calendar for HolidayCalendar instance. + *

+ * + * @see com.fr.third.org.quartz.Calendar + * + * @author Juergen Donnerstag + * @author James House + */ +public class BaseCalendar implements Calendar, Serializable { + + static final long serialVersionUID = 3106623404629760239L; + + //

A optional base calendar.

+ private Calendar baseCalendar; + + private String description; + + private TimeZone timeZone; + + public BaseCalendar() { + } + + public BaseCalendar(Calendar baseCalendar) { + setBaseCalendar(baseCalendar); + } + + /** + * @param timeZone The time zone to use for this Calendar, null + * if {@link TimeZone#getDefault()} should be used + */ + public BaseCalendar(TimeZone timeZone) { + setTimeZone(timeZone); + } + + /** + * @param timeZone The time zone to use for this Calendar, null + * if {@link TimeZone#getDefault()} should be used + */ + public BaseCalendar(Calendar baseCalendar, TimeZone timeZone) { + setBaseCalendar(baseCalendar); + setTimeZone(timeZone); + } + + /** + *

+ * Set a new base calendar or remove the existing one + *

+ */ + public void setBaseCalendar(Calendar baseCalendar) { + this.baseCalendar = baseCalendar; + } + + /** + *

+ * Get the base calendar. Will be null, if not set. + *

+ */ + public Calendar getBaseCalendar() { + return this.baseCalendar; + } + + /** + *

+ * Return the description given to the Calendar instance by + * its creator (if any). + *

+ * + * @return null if no description was set. + */ + public String getDescription() { + return description; + } + + /** + *

+ * Set a description for the Calendar instance - may be + * useful for remembering/displaying the purpose of the calendar, though + * the description has no meaning to Quartz. + *

+ */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Returns the time zone for which this Calendar will be + * resolved. + * + * @return This Calendar's timezone, null if Calendar should + * use the {@link TimeZone#getDefault()} + */ + public TimeZone getTimeZone() { + return timeZone; + } + + /** + * Sets the time zone for which this Calendar will be resolved. + * + * @param timeZone The time zone to use for this Calendar, null + * if {@link TimeZone#getDefault()} should be used + */ + public void setTimeZone(TimeZone timeZone) { + this.timeZone = timeZone; + } + + /** + *

+ * Check if date/time represented by timeStamp is included. If included + * return true. The implementation of BaseCalendar simply calls the base + * calendars isTimeIncluded() method if base calendar is set. + *

+ * + * @see com.fr.third.org.quartz.Calendar#isTimeIncluded(long) + */ + public boolean isTimeIncluded(long timeStamp) { + + if (timeStamp <= 0) { + throw new IllegalArgumentException( + "timeStamp must be greater 0"); + } + + if (baseCalendar != null) { + if (baseCalendar.isTimeIncluded(timeStamp) == false) { return false; } + } + + return true; + } + + /** + *

+ * Determine the next time (in milliseconds) that is 'included' by the + * Calendar after the given time. Return the original value if timeStamp is + * included. Return 0 if all days are excluded. + *

+ * + * @see com.fr.third.org.quartz.Calendar#getNextIncludedTime(long) + */ + public long getNextIncludedTime(long timeStamp) { + + if (timeStamp <= 0) { + throw new IllegalArgumentException( + "timeStamp must be greater 0"); + } + + if (baseCalendar != null) { + return baseCalendar.getNextIncludedTime(timeStamp); + } + + return timeStamp; + } + + /** + * Utility method. Return the date of excludeDate. The time fraction will + * be reset to 00.00:00. + * + * @deprecated Always uses the default time zone. + */ + public static Date buildHoliday(Date excludedDate) { + return new BaseCalendar().getStartOfDayJavaCalendar(excludedDate.getTime()).getTime(); + } + + /** + * Utility method. Return just the date of timeStamp. The time fraction + * will be reset to 00.00:00. + * + * @deprecated Always uses the default time zone. + */ + public static long buildHoliday(long timeStamp) { + return new BaseCalendar().getStartOfDayJavaCalendar(timeStamp).getTime().getTime(); + } + + /** + * Utility method. Return a java.util.Calendar for timeStamp. + * + * @deprecated Always uses the default time zone. + */ + public static java.util.Calendar getJavaCalendar(long timeStamp) { + return new BaseCalendar().createJavaCalendar(timeStamp); + } + + /** + * Build a {@link java.util.Calendar} for the given timeStamp. + * The new Calendar will use the BaseCalendar time zone if it + * is not null. + */ + protected java.util.Calendar createJavaCalendar(long timeStamp) { + java.util.Calendar calendar = createJavaCalendar(); + calendar.setTime(new Date(timeStamp)); + return calendar; + } + + /** + * Build a {@link java.util.Calendar} with the current time. + * The new Calendar will use the BaseCalendar time zone if + * it is not null. + */ + protected java.util.Calendar createJavaCalendar() { + return + (getTimeZone() == null) ? + java.util.Calendar.getInstance() : + java.util.Calendar.getInstance(getTimeZone()); + } + + /** + * Returns the start of the given day as a {@link java.util.Calendar}. + * This calculation will take the BaseCalendar + * time zone into account if it is not null. + * + * @param timeInMillis A time containing the desired date for the + * start-of-day time + * @return A {@link java.util.Calendar} set to the start of + * the given day. + */ + protected java.util.Calendar getStartOfDayJavaCalendar(long timeInMillis) { + java.util.Calendar startOfDay = createJavaCalendar(timeInMillis); + startOfDay.set(java.util.Calendar.HOUR_OF_DAY, 0); + startOfDay.set(java.util.Calendar.MINUTE, 0); + startOfDay.set(java.util.Calendar.SECOND, 0); + startOfDay.set(java.util.Calendar.MILLISECOND, 0); + return startOfDay; + } + + /** + * Returns the end of the given day {@link java.util.Calendar}. + * This calculation will take the BaseCalendar + * time zone into account if it is not null. + * + * @param timeInMillis a time containing the desired date for the + * end-of-day time. + * @return A {@link java.util.Calendar} set to the end of + * the given day. + */ + protected java.util.Calendar getEndOfDayJavaCalendar(long timeInMillis) { + java.util.Calendar endOfDay = createJavaCalendar(timeInMillis); + endOfDay.set(java.util.Calendar.HOUR_OF_DAY, 23); + endOfDay.set(java.util.Calendar.MINUTE, 59); + endOfDay.set(java.util.Calendar.SECOND, 59); + endOfDay.set(java.util.Calendar.MILLISECOND, 999); + return endOfDay; + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/CronCalendar.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/CronCalendar.java new file mode 100644 index 000000000..8ddf8ce83 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/CronCalendar.java @@ -0,0 +1,262 @@ +package com.fr.third.org.quartz.impl.calendar; + +import java.text.ParseException; +import java.util.Date; +import java.util.TimeZone; + +import com.fr.third.org.quartz.Calendar; +import com.fr.third.org.quartz.CronExpression; + +/** + * This implementation of the Calendar excludes the set of times expressed by a + * given {@link com.fr.third.org.quartz.CronExpression CronExpression}. For example, you + * could use this calendar to exclude all but business hours (8AM - 5PM) every + * day using the expression "* * 0-7,18-23 ? * *". + *

+ * It is important to remember that the cron expression here describes a set of + * times to be excluded from firing. Whereas the cron expression in + * {@link com.fr.third.org.quartz.CronTrigger CronTrigger} describes a set of times that can + * be included for firing. Thus, if a CronTrigger has a + * given cron expression and is associated with a CronCalendar with + * the same expression, the calendar will exclude all the times the + * trigger includes, and they will cancel each other out. + * + * @author Aaron Craven + */ +public class CronCalendar extends BaseCalendar { + static final long serialVersionUID = -8172103999750856831L; + + /** @deprecated The use of name is no longer supported. */ + private String name; + + CronExpression cronExpression; + + /** + * Create a CronCalendar with the given cron expression and no + * baseCalendar. + * + * @param expression a String representation of the desired cron expression + */ + public CronCalendar(String expression) + throws ParseException { + this(null, expression, null); + } + + /** + * Create a CronCalendar with the given cron expression and + * baseCalendar. + * + * @param baseCalendar the base calendar for this calendar instance – + * see {@link BaseCalendar} for more information on base + * calendar functionality + * @param expression a String representation of the desired cron expression + */ + public CronCalendar(Calendar baseCalendar, + String expression) throws ParseException { + this(baseCalendar, expression, null); + } + + /** + * Create a CronCalendar with the given cron exprssion, + * baseCalendar, and TimeZone. + * + * @param baseCalendar the base calendar for this calendar instance – + * see {@link BaseCalendar} for more information on base + * calendar functionality + * @param expression a String representation of the desired cron expression + * @param timeZone + * Specifies for which time zone the expression + * should be interpreted, i.e. the expression 0 0 10 * * ?, is + * resolved to 10:00 am in this time zone. If + * timeZone is null then + * TimeZone.getDefault() will be used. + */ + public CronCalendar(Calendar baseCalendar, + String expression, TimeZone timeZone) throws ParseException { + super(baseCalendar); + this.cronExpression = new CronExpression(expression); + this.cronExpression.setTimeZone(timeZone); + } + + /** + * @deprecated The use of name is no longer supported. + * + * @see #CronCalendar(String) + */ + public CronCalendar(String name, String expression) + throws ParseException { + this(expression); + this.name = name; + } + + /** + * @deprecated The use of name is no longer supported. + * + * @see #CronCalendar(Calendar, String) + */ + public CronCalendar(String name, Calendar baseCalendar, + String expression) throws ParseException { + this(baseCalendar, expression); + this.name = name; + } + + /** + * @deprecated The use of name is no longer supported. + * + * @see #CronCalendar(Calendar, String, TimeZone) + */ + public CronCalendar(String name, Calendar baseCalendar, + String expression, TimeZone timeZone) throws ParseException { + this(baseCalendar, expression, timeZone); + this.name = name; + } + + /** + * Returns the time zone for which the CronExpression of + * this CronCalendar will be resolved. + *

+ * Overrides {@link BaseCalendar#getTimeZone()} to + * defer to its CronExpression. + *

+ */ + public TimeZone getTimeZone() { + return cronExpression.getTimeZone(); + } + + /** + * Sets the time zone for which the CronExpression of this + * CronCalendar will be resolved. If timeZone + * is null then TimeZone.getDefault() will be + * used. + *

+ * Overrides {@link BaseCalendar#setTimeZone(TimeZone)} to + * defer to its CronExpression. + *

+ */ + public void setTimeZone(TimeZone timeZone) { + cronExpression.setTimeZone(timeZone); + } + + /** + * Returns the name of the CronCalendar + * + * @return the name of the CronCalendar + * + * @deprecated The use of name is no longer supported. + */ + public String getName() { + return name; + } + + /** + * Determines whether the given time (in milliseconds) is 'included' by the + * BaseCalendar + * + * @param timeInMillis the date/time to test + * @return a boolean indicating whether the specified time is 'included' by + * the CronCalendar + */ + public boolean isTimeIncluded(long timeInMillis) { + if ((getBaseCalendar() != null) && + (getBaseCalendar().isTimeIncluded(timeInMillis) == false)) { + return false; + } + + return (!(cronExpression.isSatisfiedBy(new Date(timeInMillis)))); + } + + /** + * Determines the next time included by the CronCalendar + * after the specified time. + * + * @param timeInMillis the initial date/time after which to find an + * included time + * @return the time in milliseconds representing the next time included + * after the specified time. + */ + public long getNextIncludedTime(long timeInMillis) { + long nextIncludedTime = timeInMillis + 1; //plus on millisecond + + while (!isTimeIncluded(nextIncludedTime)) { + + //If the time is in a range excluded by this calendar, we can + // move to the end of the excluded time range and continue testing + // from there. Otherwise, if nextIncludedTime is excluded by the + // baseCalendar, ask it the next time it includes and begin testing + // from there. Failing this, add one millisecond and continue + // testing. + if (cronExpression.isSatisfiedBy(new Date(nextIncludedTime))) { + nextIncludedTime = cronExpression.getNextInvalidTimeAfter( + new Date(nextIncludedTime)).getTime(); + } else if ((getBaseCalendar() != null) && + (!getBaseCalendar().isTimeIncluded(nextIncludedTime))){ + nextIncludedTime = + getBaseCalendar().getNextIncludedTime(nextIncludedTime); + } else { + nextIncludedTime++; + } + } + + return nextIncludedTime; + } + + /** + * Returns a string representing the properties of the + * CronCalendar + * + * @return the properteis of the CronCalendar in a String format + */ + public String toString() { + StringBuffer buffer = new StringBuffer(); + if (name != null) { + buffer.append(name).append(": "); + } + buffer.append("base calendar: ["); + if (getBaseCalendar() != null) { + buffer.append(getBaseCalendar().toString()); + } else { + buffer.append("null"); + } + buffer.append("], excluded cron expression: '"); + buffer.append(cronExpression); + buffer.append("'"); + return buffer.toString(); + } + + /** + * Returns the object representation of the cron expression that defines the + * dates and times this calendar excludes. + * + * @return the cron expression + * @see com.fr.third.org.quartz.CronExpression + */ + public CronExpression getCronExpression() { + return cronExpression; + } + + /** + * Sets the cron expression for the calendar to a new value + * + * @param expression the new string value to build a cron expression from + * @throws ParseException + * if the string expression cannot be parsed + */ + public void setCronExpression(String expression) throws ParseException { + CronExpression newExp = new CronExpression(expression); + + this.cronExpression = newExp; + } + + /** + * Sets the cron expression for the calendar to a new value + * + * @param expression the new cron expression + */ + public void setCronExpression(CronExpression expression) { + if (expression == null) { + throw new IllegalArgumentException("expression cannot be null"); + } + + this.cronExpression = expression; + } +} \ No newline at end of file diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/DailyCalendar.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/DailyCalendar.java new file mode 100644 index 000000000..496f26915 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/DailyCalendar.java @@ -0,0 +1,1034 @@ +package com.fr.third.org.quartz.impl.calendar; + +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.StringTokenizer; +import java.util.TimeZone; + +/** + * This implementation of the Calendar excludes (or includes - see below) a + * specified time range each day. For example, you could use this calendar to + * exclude business hours (8AM - 5PM) every day. Each DailyCalendar + * only allows a single time range to be specified, and that time range may not + * cross daily boundaries (i.e. you cannot specify a time range from 8PM - 5AM). + * If the property invertTimeRange is false (default), + * the time range defines a range of times in which triggers are not allowed to + * fire. If invertTimeRange is true, the time range + * is inverted – that is, all times outside the defined time range + * are excluded. + *

+ * Note when using DailyCalendar, it behaves on the same principals + * as, for example, {@link com.fr.third.org.quartz.impl.calendar.WeeklyCalendar + * WeeklyCalendar}. WeeklyCalendar defines a set of days that are + * excluded every week. Likewise, DailyCalendar defines a + * set of times that are excluded every day. + * + * @author Mike Funk, Aaron Craven + */ +public class DailyCalendar extends BaseCalendar { + static final long serialVersionUID = -7561220099904944039L; + + private static final String invalidHourOfDay = "Invalid hour of day: "; + private static final String invalidMinute = "Invalid minute: "; + private static final String invalidSecond = "Invalid second: "; + private static final String invalidMillis = "Invalid millis: "; + private static final String invalidTimeRange = "Invalid time range: "; + private static final String separator = " - "; + private static final long oneMillis = 1; + private static final String colon = ":"; + + /** @deprecated The use of name is no longer supported. */ + private String name; + + private int rangeStartingHourOfDay; + private int rangeStartingMinute; + private int rangeStartingSecond; + private int rangeStartingMillis; + private int rangeEndingHourOfDay; + private int rangeEndingMinute; + private int rangeEndingSecond; + private int rangeEndingMillis; + + private boolean invertTimeRange = false; + + /** + * Create a DailyCalendar with a time range defined by the + * specified strings and no baseCalendar. + * rangeStartingTime and rangeEndingTime + * must be in the format "HH:MM[:SS[:mmm]]" where: + *

  • HH is the hour of the specified time. The hour should be + * specified using military (24-hour) time and must be in the range + * 0 to 23.
  • + *
  • MM is the minute of the specified time and must be in the range + * 0 to 59.
  • + *
  • SS is the second of the specified time and must be in the range + * 0 to 59.
  • + *
  • mmm is the millisecond of the specified time and must be in the + * range 0 to 999.
  • + *
  • items enclosed in brackets ('[', ']') are optional.
  • + *
  • The time range starting time must be before the time range ending + * time. Note this means that a time range may not cross daily + * boundaries (10PM - 2AM)
  • + *
+ * + *

+ * Note: This DailyCalendar will use the + * {@link TimeZone#getDefault()} time zone unless an explicit + * time zone is set via {@link BaseCalendar#setTimeZone(TimeZone)} + *

+ * + * @param rangeStartingTime a String representing the starting time for the + * time range + * @param rangeEndingTime a String representing the ending time for the + * the time range + */ + public DailyCalendar(String rangeStartingTime, + String rangeEndingTime) { + super(); + setTimeRange(rangeStartingTime, rangeEndingTime); + } + + /** + * Create a DailyCalendar with a time range defined by the + * specified strings and the specified baseCalendar. + * rangeStartingTime and rangeEndingTime + * must be in the format "HH:MM[:SS[:mmm]]" where: + *
  • HH is the hour of the specified time. The hour should be + * specified using military (24-hour) time and must be in the range + * 0 to 23.
  • + *
  • MM is the minute of the specified time and must be in the range + * 0 to 59.
  • + *
  • SS is the second of the specified time and must be in the range + * 0 to 59.
  • + *
  • mmm is the millisecond of the specified time and must be in the + * range 0 to 999.
  • + *
  • items enclosed in brackets ('[', ']') are optional.
  • + *
  • The time range starting time must be before the time range ending + * time. Note this means that a time range may not cross daily + * boundaries (10PM - 2AM)
  • + *
+ * + *

+ * Note: This DailyCalendar will use the + * {@link TimeZone#getDefault()} time zone unless an explicit + * time zone is set via {@link BaseCalendar#setTimeZone(TimeZone)} + *

+ * + * @param baseCalendar the base calendar for this calendar instance + * – see {@link BaseCalendar} for more + * information on base calendar functionality + * @param rangeStartingTime a String representing the starting time for the + * time range + * @param rangeEndingTime a String representing the ending time for the + * time range + */ + public DailyCalendar(com.fr.third.org.quartz.Calendar baseCalendar, + String rangeStartingTime, + String rangeEndingTime) { + super(baseCalendar); + setTimeRange(rangeStartingTime, rangeEndingTime); + } + + /** + * Create a DailyCalendar with a time range defined by the + * specified values and no baseCalendar. Values are subject to + * the following validations: + *
  • Hours must be in the range 0-23 and are expressed using military + * (24-hour) time.
  • + *
  • Minutes must be in the range 0-59
  • + *
  • Seconds must be in the range 0-59
  • + *
  • Milliseconds must be in the range 0-999
  • + *
  • The time range starting time must be before the time range ending + * time. Note this means that a time range may not cross daily + * boundaries (10PM - 2AM)
  • + *
+ * + *

+ * Note: This DailyCalendar will use the + * {@link TimeZone#getDefault()} time zone unless an explicit + * time zone is set via {@link BaseCalendar#setTimeZone(TimeZone)} + *

+ * + * @param rangeStartingHourOfDay the hour of the start of the time range + * @param rangeStartingMinute the minute of the start of the time range + * @param rangeStartingSecond the second of the start of the time range + * @param rangeStartingMillis the millisecond of the start of the time + * range + * @param rangeEndingHourOfDay the hour of the end of the time range + * @param rangeEndingMinute the minute of the end of the time range + * @param rangeEndingSecond the second of the end of the time range + * @param rangeEndingMillis the millisecond of the start of the time + * range + */ + public DailyCalendar(int rangeStartingHourOfDay, + int rangeStartingMinute, + int rangeStartingSecond, + int rangeStartingMillis, + int rangeEndingHourOfDay, + int rangeEndingMinute, + int rangeEndingSecond, + int rangeEndingMillis) { + super(); + setTimeRange(rangeStartingHourOfDay, + rangeStartingMinute, + rangeStartingSecond, + rangeStartingMillis, + rangeEndingHourOfDay, + rangeEndingMinute, + rangeEndingSecond, + rangeEndingMillis); + } + + /** + * Create a DailyCalendar with a time range defined by the + * specified values and the specified baseCalendar. Values are + * subject to the following validations: + *
  • Hours must be in the range 0-23 and are expressed using military + * (24-hour) time.
  • + *
  • Minutes must be in the range 0-59
  • + *
  • Seconds must be in the range 0-59
  • + *
  • Milliseconds must be in the range 0-999
  • + *
  • The time range starting time must be before the time range ending + * time. Note this means that a time range may not cross daily + * boundaries (10PM - 2AM)
  • + *
+ * + *

+ * Note: This DailyCalendar will use the + * {@link TimeZone#getDefault()} time zone unless an explicit + * time zone is set via {@link BaseCalendar#setTimeZone(TimeZone)} + *

+ * + * @param baseCalendar the base calendar for this calendar + * instance – see + * {@link BaseCalendar} for more + * information on base calendar + * functionality + * @param rangeStartingHourOfDay the hour of the start of the time range + * @param rangeStartingMinute the minute of the start of the time range + * @param rangeStartingSecond the second of the start of the time range + * @param rangeStartingMillis the millisecond of the start of the time + * range + * @param rangeEndingHourOfDay the hour of the end of the time range + * @param rangeEndingMinute the minute of the end of the time range + * @param rangeEndingSecond the second of the end of the time range + * @param rangeEndingMillis the millisecond of the start of the time + * range + */ + public DailyCalendar(com.fr.third.org.quartz.Calendar baseCalendar, + int rangeStartingHourOfDay, + int rangeStartingMinute, + int rangeStartingSecond, + int rangeStartingMillis, + int rangeEndingHourOfDay, + int rangeEndingMinute, + int rangeEndingSecond, + int rangeEndingMillis) { + super(baseCalendar); + setTimeRange(rangeStartingHourOfDay, + rangeStartingMinute, + rangeStartingSecond, + rangeStartingMillis, + rangeEndingHourOfDay, + rangeEndingMinute, + rangeEndingSecond, + rangeEndingMillis); + } + + /** + * Create a DailyCalendar with a time range defined by the + * specified java.util.Calendars and no + * baseCalendar. The Calendars are subject to the following + * considerations: + *
  • Only the time-of-day fields of the specified Calendars will be + * used (the date fields will be ignored)
  • + *
  • The starting time must be before the ending time of the defined + * time range. Note this means that a time range may not cross + * daily boundaries (10PM - 2AM). (because only time fields are + * are used, it is possible for two Calendars to represent a valid + * time range and + * rangeStartingCalendar.after(rangeEndingCalendar) == + * true)
  • + *
+ * + *

+ * Note: This DailyCalendar will use the + * {@link TimeZone#getDefault()} time zone unless an explicit + * time zone is set via {@link BaseCalendar#setTimeZone(TimeZone)} + *

+ * + * @param rangeStartingCalendar a java.util.Calendar representing the + * starting time for the time range + * @param rangeEndingCalendar a java.util.Calendar representing the ending + * time for the time range + */ + public DailyCalendar( + Calendar rangeStartingCalendar, + Calendar rangeEndingCalendar) { + super(); + setTimeRange(rangeStartingCalendar, rangeEndingCalendar); + } + + /** + * Create a DailyCalendar with a time range defined by the + * specified java.util.Calendars and the specified + * baseCalendar. The Calendars are subject to the following + * considerations: + *
  • Only the time-of-day fields of the specified Calendars will be + * used (the date fields will be ignored)
  • + *
  • The starting time must be before the ending time of the defined + * time range. Note this means that a time range may not cross + * daily boundaries (10PM - 2AM). (because only time fields are + * are used, it is possible for two Calendars to represent a valid + * time range and + * rangeStartingCalendar.after(rangeEndingCalendar) == + * true)
  • + *
+ * + *

+ * Note: This DailyCalendar will use the + * {@link TimeZone#getDefault()} time zone unless an explicit + * time zone is set via {@link BaseCalendar#setTimeZone(TimeZone)} + *

+ * + * @param baseCalendar the base calendar for this calendar instance + * – see {@link BaseCalendar} for more + * information on base calendar functionality + * @param rangeStartingCalendar a java.util.Calendar representing the + * starting time for the time range + * @param rangeEndingCalendar a java.util.Calendar representing the ending + * time for the time range + */ + public DailyCalendar(com.fr.third.org.quartz.Calendar baseCalendar, + Calendar rangeStartingCalendar, + Calendar rangeEndingCalendar) { + super(baseCalendar); + setTimeRange(rangeStartingCalendar, rangeEndingCalendar); + } + + /** + * Create a DailyCalendar with a time range defined by the + * specified values and no baseCalendar. The values are + * subject to the following considerations: + *
  • Only the time-of-day portion of the specified values will be + * used
  • + *
  • The starting time must be before the ending time of the defined + * time range. Note this means that a time range may not cross + * daily boundaries (10PM - 2AM). (because only time value are + * are used, it is possible for the two values to represent a valid + * time range and rangeStartingTime > + * rangeEndingTime)
  • + *
+ * + *

+ * Note: This DailyCalendar will use the + * {@link TimeZone#getDefault()} time zone unless an explicit + * time zone is set via {@link BaseCalendar#setTimeZone(TimeZone)}. + * You should use {@link #DailyCalendar(String, TimeZone, long, long)} + * if you don't want the given rangeStartingTimeInMillis and + * rangeEndingTimeInMillis to be evaluated in the default + * time zone. + *

+ * + * @param rangeStartingTimeInMillis a long representing the starting time + * for the time range + * @param rangeEndingTimeInMillis a long representing the ending time for + * the time range + */ + public DailyCalendar(long rangeStartingTimeInMillis, + long rangeEndingTimeInMillis) { + super(); + setTimeRange(rangeStartingTimeInMillis, + rangeEndingTimeInMillis); + } + + /** + * Create a DailyCalendar with a time range defined by the + * specified values and the specified baseCalendar. The values + * are subject to the following considerations: + *
  • Only the time-of-day portion of the specified values will be + * used
  • + *
  • The starting time must be before the ending time of the defined + * time range. Note this means that a time range may not cross + * daily boundaries (10PM - 2AM). (because only time value are + * are used, it is possible for the two values to represent a valid + * time range and rangeStartingTime > + * rangeEndingTime)
  • + *
+ * + *

+ * Note: This DailyCalendar will use the + * {@link TimeZone#getDefault()} time zone unless an explicit + * time zone is set via {@link BaseCalendar#setTimeZone(TimeZone)}. + * You should use {@link #DailyCalendar(String, Calendar, TimeZone, long, long)} + * if you don't want the given rangeStartingTimeInMillis and + * rangeEndingTimeInMillis to be evaluated in the default + * time zone. + *

+ * + * @param baseCalendar the base calendar for this calendar + * instance – see {@link + * BaseCalendar} for more information on + * base calendar functionality + * @param rangeStartingTimeInMillis a long representing the starting time + * for the time range + * @param rangeEndingTimeInMillis a long representing the ending time for + * the time range + */ + public DailyCalendar(com.fr.third.org.quartz.Calendar baseCalendar, + long rangeStartingTimeInMillis, + long rangeEndingTimeInMillis) { + super(baseCalendar); + setTimeRange(rangeStartingTimeInMillis, + rangeEndingTimeInMillis); + } + + /** + * Create a DailyCalendar with a time range defined by the + * specified values and no baseCalendar. The values are + * subject to the following considerations: + *
  • Only the time-of-day portion of the specified values will be + * used
  • + *
  • The starting time must be before the ending time of the defined + * time range. Note this means that a time range may not cross + * daily boundaries (10PM - 2AM). (because only time value are + * are used, it is possible for the two values to represent a valid + * time range and rangeStartingTime > + * rangeEndingTime)
  • + *
+ * + * @param timeZone the time zone for of the + * DailyCalendar which will + * also be used to resolve the given + * start/end times. + * @param rangeStartingTimeInMillis a long representing the starting time + * for the time range + * @param rangeEndingTimeInMillis a long representing the ending time for + * the time range + */ + public DailyCalendar(TimeZone timeZone, + long rangeStartingTimeInMillis, + long rangeEndingTimeInMillis) { + super(timeZone); + setTimeRange(rangeStartingTimeInMillis, + rangeEndingTimeInMillis); + } + + /** + * Create a DailyCalendar with a time range defined by the + * specified values and the specified baseCalendar. The values + * are subject to the following considerations: + *
  • Only the time-of-day portion of the specified values will be + * used
  • + *
  • The starting time must be before the ending time of the defined + * time range. Note this means that a time range may not cross + * daily boundaries (10PM - 2AM). (because only time value are + * are used, it is possible for the two values to represent a valid + * time range and rangeStartingTime > + * rangeEndingTime)
  • + *
+ * + * @param baseCalendar the base calendar for this calendar + * instance – see {@link + * BaseCalendar} for more information on + * base calendar functionality + * @param timeZone the time zone for of the + * DailyCalendar which will + * also be used to resolve the given + * start/end times. + * @param rangeStartingTimeInMillis a long representing the starting time + * for the time range + * @param rangeEndingTimeInMillis a long representing the ending time for + * the time range + */ + public DailyCalendar(com.fr.third.org.quartz.Calendar baseCalendar, + TimeZone timeZone, + long rangeStartingTimeInMillis, + long rangeEndingTimeInMillis) { + super(baseCalendar, timeZone); + setTimeRange(rangeStartingTimeInMillis, + rangeEndingTimeInMillis); + } + + /** + * @deprecated The use of name is no longer supported. + * + * @see DailyCalendar#DailyCalendar(String, String) + */ + public DailyCalendar(String name, + String rangeStartingTime, + String rangeEndingTime) { + this(rangeStartingTime, rangeEndingTime); + this.name = name; + } + + /** + * @deprecated The use of name is no longer supported. + * + * @see DailyCalendar#DailyCalendar(com.fr.third.org.quartz.Calendar, String, String) + */ + public DailyCalendar(String name, + com.fr.third.org.quartz.Calendar baseCalendar, + String rangeStartingTime, + String rangeEndingTime) { + this(baseCalendar, rangeStartingTime, rangeEndingTime); + this.name = name; + } + + /** + * @deprecated The use of name is no longer supported. + * + * @see DailyCalendar#DailyCalendar(int, int, int, int, int, int, int, int) + */ + public DailyCalendar(String name, + int rangeStartingHourOfDay, + int rangeStartingMinute, + int rangeStartingSecond, + int rangeStartingMillis, + int rangeEndingHourOfDay, + int rangeEndingMinute, + int rangeEndingSecond, + int rangeEndingMillis) { + this(rangeStartingHourOfDay, + rangeStartingMinute, + rangeStartingSecond, + rangeStartingMillis, + rangeEndingHourOfDay, + rangeEndingMinute, + rangeEndingSecond, + rangeEndingMillis); + this.name = name; + } + + /** + * @deprecated The use of name is no longer supported. + * + * @see DailyCalendar#DailyCalendar(com.fr.third.org.quartz.Calendar, int, int, int, int, int, int, int, int) + */ + public DailyCalendar(String name, + com.fr.third.org.quartz.Calendar baseCalendar, + int rangeStartingHourOfDay, + int rangeStartingMinute, + int rangeStartingSecond, + int rangeStartingMillis, + int rangeEndingHourOfDay, + int rangeEndingMinute, + int rangeEndingSecond, + int rangeEndingMillis) { + this(baseCalendar, + rangeStartingHourOfDay, + rangeStartingMinute, + rangeStartingSecond, + rangeStartingMillis, + rangeEndingHourOfDay, + rangeEndingMinute, + rangeEndingSecond, + rangeEndingMillis); + this.name = name; + } + + /** + * @deprecated The use of name is no longer supported. + * + * @see DailyCalendar#DailyCalendar(Calendar, Calendar) + */ + public DailyCalendar(String name, + Calendar rangeStartingCalendar, + Calendar rangeEndingCalendar) { + this(rangeStartingCalendar, rangeEndingCalendar); + this.name = name; + } + + /** + * @deprecated The use of name is no longer supported. + * + * @see DailyCalendar#DailyCalendar(com.fr.third.org.quartz.Calendar, Calendar, Calendar) + */ + public DailyCalendar(String name, + com.fr.third.org.quartz.Calendar baseCalendar, + Calendar rangeStartingCalendar, + Calendar rangeEndingCalendar) { + this(baseCalendar, rangeStartingCalendar, rangeEndingCalendar); + this.name = name; + } + + /** + * @deprecated The use of name is no longer supported. + * + * @see DailyCalendar#DailyCalendar(long, long) + */ + public DailyCalendar(String name, + long rangeStartingTimeInMillis, + long rangeEndingTimeInMillis) { + this(rangeStartingTimeInMillis, rangeEndingTimeInMillis); + this.name = name; + } + + /** + * @deprecated The use of name is no longer supported. + * + * @see DailyCalendar#DailyCalendar(com.fr.third.org.quartz.Calendar, long, long) + */ + public DailyCalendar(String name, + com.fr.third.org.quartz.Calendar baseCalendar, + long rangeStartingTimeInMillis, + long rangeEndingTimeInMillis) { + this(baseCalendar, rangeStartingTimeInMillis, rangeEndingTimeInMillis); + this.name = name; + } + + /** + * @deprecated The use of name is no longer supported. + * + * @see DailyCalendar#DailyCalendar(TimeZone, long, long) + */ + public DailyCalendar(String name, + TimeZone timeZone, + long rangeStartingTimeInMillis, + long rangeEndingTimeInMillis) { + this(timeZone, + rangeStartingTimeInMillis, + rangeEndingTimeInMillis); + this.name = name; + } + + /** + * @deprecated The use of name is no longer supported. + * + * @see DailyCalendar#DailyCalendar(com.fr.third.org.quartz.Calendar, TimeZone, long, long) + */ + public DailyCalendar(String name, + com.fr.third.org.quartz.Calendar baseCalendar, + TimeZone timeZone, + long rangeStartingTimeInMillis, + long rangeEndingTimeInMillis) { + this(baseCalendar, + timeZone, + rangeStartingTimeInMillis, + rangeEndingTimeInMillis); + this.name = name; + } + + /** + * Returns the name of the DailyCalendar + * + * @return the name of the DailyCalendar + * + * @deprecated The use of name is no longer supported. + */ + public String getName() { + return name; + } + + /** + * Determines whether the given time (in milliseconds) is 'included' by the + * BaseCalendar + * + * @param timeInMillis the date/time to test + * @return a boolean indicating whether the specified time is 'included' by + * the BaseCalendar + */ + public boolean isTimeIncluded(long timeInMillis) { + if ((getBaseCalendar() != null) && + (getBaseCalendar().isTimeIncluded(timeInMillis) == false)) { + return false; + } + + long startOfDayInMillis = getStartOfDayJavaCalendar(timeInMillis).getTime().getTime(); + long endOfDayInMillis = getEndOfDayJavaCalendar(timeInMillis).getTime().getTime(); + long timeRangeStartingTimeInMillis = + getTimeRangeStartingTimeInMillis(timeInMillis); + long timeRangeEndingTimeInMillis = + getTimeRangeEndingTimeInMillis(timeInMillis); + if (!invertTimeRange) { + return + ((timeInMillis > startOfDayInMillis && + timeInMillis < timeRangeStartingTimeInMillis) || + (timeInMillis > timeRangeEndingTimeInMillis && + timeInMillis < endOfDayInMillis)); + } else { + return ((timeInMillis >= timeRangeStartingTimeInMillis) && + (timeInMillis <= timeRangeEndingTimeInMillis)); + } + } + + /** + * Determines the next time included by the DailyCalendar + * after the specified time. + * + * @param timeInMillis the initial date/time after which to find an + * included time + * @return the time in milliseconds representing the next time included + * after the specified time. + */ + public long getNextIncludedTime(long timeInMillis) { + long nextIncludedTime = timeInMillis + oneMillis; + + while (!isTimeIncluded(nextIncludedTime)) { + if (!invertTimeRange) { + //If the time is in a range excluded by this calendar, we can + // move to the end of the excluded time range and continue + // testing from there. Otherwise, if nextIncludedTime is + // excluded by the baseCalendar, ask it the next time it + // includes and begin testing from there. Failing this, add one + // millisecond and continue testing. + if ((nextIncludedTime >= + getTimeRangeStartingTimeInMillis(nextIncludedTime)) && + (nextIncludedTime <= + getTimeRangeEndingTimeInMillis(nextIncludedTime))) { + + nextIncludedTime = + getTimeRangeEndingTimeInMillis(nextIncludedTime) + + oneMillis; + } else if ((getBaseCalendar() != null) && + (!getBaseCalendar().isTimeIncluded(nextIncludedTime))){ + nextIncludedTime = + getBaseCalendar().getNextIncludedTime(nextIncludedTime); + } else { + nextIncludedTime++; + } + } else { + //If the time is in a range excluded by this calendar, we can + // move to the end of the excluded time range and continue + // testing from there. Otherwise, if nextIncludedTime is + // excluded by the baseCalendar, ask it the next time it + // includes and begin testing from there. Failing this, add one + // millisecond and continue testing. + if (nextIncludedTime < + getTimeRangeStartingTimeInMillis(nextIncludedTime)) { + nextIncludedTime = + getTimeRangeStartingTimeInMillis(nextIncludedTime); + } else if (nextIncludedTime > + getTimeRangeEndingTimeInMillis(nextIncludedTime)) { + //(move to start of next day) + nextIncludedTime = getEndOfDayJavaCalendar(nextIncludedTime).getTime().getTime(); + nextIncludedTime += 1l; + } else if ((getBaseCalendar() != null) && + (!getBaseCalendar().isTimeIncluded(nextIncludedTime))){ + nextIncludedTime = + getBaseCalendar().getNextIncludedTime(nextIncludedTime); + } else { + nextIncludedTime++; + } + } + } + + return nextIncludedTime; + } + + /** + * Returns the start time of the time range (in milliseconds) of the day + * specified in timeInMillis + * + * @param timeInMillis a time containing the desired date for the starting + * time of the time range. + * @return a date/time (in milliseconds) representing the start time of the + * time range for the specified date. + */ + public long getTimeRangeStartingTimeInMillis(long timeInMillis) { + Calendar rangeStartingTime = createJavaCalendar(timeInMillis); + rangeStartingTime.set(Calendar.HOUR_OF_DAY, rangeStartingHourOfDay); + rangeStartingTime.set(Calendar.MINUTE, rangeStartingMinute); + rangeStartingTime.set(Calendar.SECOND, rangeStartingSecond); + rangeStartingTime.set(Calendar.MILLISECOND, rangeStartingMillis); + return rangeStartingTime.getTime().getTime(); + } + + /** + * Returns the end time of the time range (in milliseconds) of the day + * specified in timeInMillis + * + * @param timeInMillis a time containing the desired date for the ending + * time of the time range. + * @return a date/time (in milliseconds) representing the end time of the + * time range for the specified date. + */ + public long getTimeRangeEndingTimeInMillis(long timeInMillis) { + Calendar rangeEndingTime = createJavaCalendar(timeInMillis); + rangeEndingTime.set(Calendar.HOUR_OF_DAY, rangeEndingHourOfDay); + rangeEndingTime.set(Calendar.MINUTE, rangeEndingMinute); + rangeEndingTime.set(Calendar.SECOND, rangeEndingSecond); + rangeEndingTime.set(Calendar.MILLISECOND, rangeEndingMillis); + return rangeEndingTime.getTime().getTime(); + } + + /** + * Indicates whether the time range represents an inverted time range (see + * class description). + * + * @return a boolean indicating whether the time range is inverted + */ + public boolean getInvertTimeRange() { + return invertTimeRange; + } + + /** + * Indicates whether the time range represents an inverted time range (see + * class description). + * + * @param flag the new value for the invertTimeRange flag. + */ + public void setInvertTimeRange(boolean flag) { + this.invertTimeRange = flag; + } + + /** + * Returns a string representing the properties of the + * DailyCalendar + * + * @return the properteis of the DailyCalendar in a String format + */ + public String toString() { + NumberFormat numberFormatter = NumberFormat.getNumberInstance(); + numberFormatter.setMaximumFractionDigits(0); + numberFormatter.setMinimumIntegerDigits(2); + StringBuffer buffer = new StringBuffer(); + if (name != null) { + buffer.append(name).append(": "); + } + buffer.append("base calendar: ["); + if (getBaseCalendar() != null) { + buffer.append(getBaseCalendar().toString()); + } else { + buffer.append("null"); + } + buffer.append("], time range: '"); + buffer.append(numberFormatter.format(rangeStartingHourOfDay)); + buffer.append(":"); + buffer.append(numberFormatter.format(rangeStartingMinute)); + buffer.append(":"); + buffer.append(numberFormatter.format(rangeStartingSecond)); + buffer.append(":"); + numberFormatter.setMinimumIntegerDigits(3); + buffer.append(numberFormatter.format(rangeStartingMillis)); + numberFormatter.setMinimumIntegerDigits(2); + buffer.append(" - "); + buffer.append(numberFormatter.format(rangeEndingHourOfDay)); + buffer.append(":"); + buffer.append(numberFormatter.format(rangeEndingMinute)); + buffer.append(":"); + buffer.append(numberFormatter.format(rangeEndingSecond)); + buffer.append(":"); + numberFormatter.setMinimumIntegerDigits(3); + buffer.append(numberFormatter.format(rangeEndingMillis)); + buffer.append("', inverted: " + invertTimeRange + "]"); + return buffer.toString(); + } + + /** + * Helper method to split the given string by the given delimiter. + */ + private String[] split(String string, String delim) { + ArrayList result = new ArrayList(); + + StringTokenizer stringTokenizer = new StringTokenizer(string, delim); + while (stringTokenizer.hasMoreTokens()) { + result.add(stringTokenizer.nextToken()); + } + + return (String[])result.toArray(new String[result.size()]); + } + + /** + * Sets the time range for the DailyCalendar to the times + * represented in the specified Strings. + * + * @param rangeStartingTimeString a String representing the start time of + * the time range + * @param rangeEndingTimeString a String representing the end time of the + * excluded time range + */ + public void setTimeRange(String rangeStartingTimeString, + String rangeEndingTimeString) { + String[] rangeStartingTime; + int rangeStartingHourOfDay; + int rangeStartingMinute; + int rangeStartingSecond; + int rangeStartingMillis; + + String[] rangeEndingTime; + int rangeEndingHourOfDay; + int rangeEndingMinute; + int rangeEndingSecond; + int rangeEndingMillis; + + rangeStartingTime = split(rangeStartingTimeString, colon); + + if ((rangeStartingTime.length < 2) || (rangeStartingTime.length > 4)) { + throw new IllegalArgumentException("Invalid time string '" + + rangeStartingTimeString + "'"); + } + + rangeStartingHourOfDay = Integer.parseInt(rangeStartingTime[0]); + rangeStartingMinute = Integer.parseInt(rangeStartingTime[1]); + if (rangeStartingTime.length > 2) { + rangeStartingSecond = Integer.parseInt(rangeStartingTime[2]); + } else { + rangeStartingSecond = 0; + } + if (rangeStartingTime.length == 4) { + rangeStartingMillis = Integer.parseInt(rangeStartingTime[3]); + } else { + rangeStartingMillis = 0; + } + + rangeEndingTime = split(rangeEndingTimeString, colon); + + if ((rangeEndingTime.length < 2) || (rangeEndingTime.length > 4)) { + throw new IllegalArgumentException("Invalid time string '" + + rangeEndingTimeString + "'"); + } + + rangeEndingHourOfDay = Integer.parseInt(rangeEndingTime[0]); + rangeEndingMinute = Integer.parseInt(rangeEndingTime[1]); + if (rangeEndingTime.length > 2) { + rangeEndingSecond = Integer.parseInt(rangeEndingTime[2]); + } else { + rangeEndingSecond = 0; + } + if (rangeEndingTime.length == 4) { + rangeEndingMillis = Integer.parseInt(rangeEndingTime[3]); + } else { + rangeEndingMillis = 0; + } + + setTimeRange(rangeStartingHourOfDay, + rangeStartingMinute, + rangeStartingSecond, + rangeStartingMillis, + rangeEndingHourOfDay, + rangeEndingMinute, + rangeEndingSecond, + rangeEndingMillis); + } + + /** + * Sets the time range for the DailyCalendar to the times + * represented in the specified values. + * + * @param rangeStartingHourOfDay the hour of the start of the time range + * @param rangeStartingMinute the minute of the start of the time range + * @param rangeStartingSecond the second of the start of the time range + * @param rangeStartingMillis the millisecond of the start of the time + * range + * @param rangeEndingHourOfDay the hour of the end of the time range + * @param rangeEndingMinute the minute of the end of the time range + * @param rangeEndingSecond the second of the end of the time range + * @param rangeEndingMillis the millisecond of the start of the time + * range + */ + public void setTimeRange(int rangeStartingHourOfDay, + int rangeStartingMinute, + int rangeStartingSecond, + int rangeStartingMillis, + int rangeEndingHourOfDay, + int rangeEndingMinute, + int rangeEndingSecond, + int rangeEndingMillis) { + validate(rangeStartingHourOfDay, + rangeStartingMinute, + rangeStartingSecond, + rangeStartingMillis); + + validate(rangeEndingHourOfDay, + rangeEndingMinute, + rangeEndingSecond, + rangeEndingMillis); + + Calendar startCal = createJavaCalendar(); + startCal.set(Calendar.HOUR_OF_DAY, rangeStartingHourOfDay); + startCal.set(Calendar.MINUTE, rangeStartingMinute); + startCal.set(Calendar.SECOND, rangeStartingSecond); + startCal.set(Calendar.MILLISECOND, rangeStartingMillis); + + Calendar endCal = createJavaCalendar(); + endCal.set(Calendar.HOUR_OF_DAY, rangeEndingHourOfDay); + endCal.set(Calendar.MINUTE, rangeEndingMinute); + endCal.set(Calendar.SECOND, rangeEndingSecond); + endCal.set(Calendar.MILLISECOND, rangeEndingMillis); + + if (!startCal.before(endCal)) { + throw new IllegalArgumentException(invalidTimeRange + + rangeStartingHourOfDay + ":" + + rangeStartingMinute + ":" + + rangeStartingSecond + ":" + + rangeStartingMillis + separator + + rangeEndingHourOfDay + ":" + + rangeEndingMinute + ":" + + rangeEndingSecond + ":" + + rangeEndingMillis); + } + + this.rangeStartingHourOfDay = rangeStartingHourOfDay; + this.rangeStartingMinute = rangeStartingMinute; + this.rangeStartingSecond = rangeStartingSecond; + this.rangeStartingMillis = rangeStartingMillis; + this.rangeEndingHourOfDay = rangeEndingHourOfDay; + this.rangeEndingMinute = rangeEndingMinute; + this.rangeEndingSecond = rangeEndingSecond; + this.rangeEndingMillis = rangeEndingMillis; + } + + /** + * Sets the time range for the DailyCalendar to the times + * represented in the specified java.util.Calendars. + * + * @param rangeStartingCalendar a Calendar containing the start time for + * the DailyCalendar + * @param rangeEndingCalendar a Calendar containing the end time for + * the DailyCalendar + */ + public void setTimeRange(Calendar rangeStartingCalendar, + Calendar rangeEndingCalendar) { + setTimeRange( + rangeStartingCalendar.get(Calendar.HOUR_OF_DAY), + rangeStartingCalendar.get(Calendar.MINUTE), + rangeStartingCalendar.get(Calendar.SECOND), + rangeStartingCalendar.get(Calendar.MILLISECOND), + rangeEndingCalendar.get(Calendar.HOUR_OF_DAY), + rangeEndingCalendar.get(Calendar.MINUTE), + rangeEndingCalendar.get(Calendar.SECOND), + rangeEndingCalendar.get(Calendar.MILLISECOND)); + } + + /** + * Sets the time range for the DailyCalendar to the times + * represented in the specified values. + * + * @param rangeStartingTime the starting time (in milliseconds) for the + * time range + * @param rangeEndingTime the ending time (in milliseconds) for the time + * range + */ + public void setTimeRange(long rangeStartingTime, + long rangeEndingTime) { + setTimeRange( + createJavaCalendar(rangeStartingTime), + createJavaCalendar(rangeEndingTime)); + } + + /** + * Checks the specified values for validity as a set of time values. + * + * @param hourOfDay the hour of the time to check (in military (24-hour) + * time) + * @param minute the minute of the time to check + * @param second the second of the time to check + * @param millis the millisecond of the time to check + */ + private void validate(int hourOfDay, int minute, int second, int millis) { + if (hourOfDay < 0 || hourOfDay > 23) { + throw new IllegalArgumentException(invalidHourOfDay + hourOfDay); + } + if (minute < 0 || minute > 59) { + throw new IllegalArgumentException(invalidMinute + minute); + } + if (second < 0 || second > 59) { + throw new IllegalArgumentException(invalidSecond + second); + } + if (millis < 0 || millis > 999) { + throw new IllegalArgumentException(invalidMillis + millis); + } + } +} \ No newline at end of file diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/HolidayCalendar.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/HolidayCalendar.java new file mode 100644 index 000000000..9f511842a --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/HolidayCalendar.java @@ -0,0 +1,146 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.calendar; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Date; +import java.util.SortedSet; +import java.util.TimeZone; +import java.util.TreeSet; + +import com.fr.third.org.quartz.Calendar; + +/** + *

+ * This implementation of the Calendar stores a list of holidays (full days + * that are excluded from scheduling). + *

+ * + *

+ * The implementation DOES take the year into consideration, so if you want to + * exclude July 4th for the next 10 years, you need to add 10 entries to the + * exclude list. + *

+ * + * @author Sharada Jambula + * @author Juergen Donnerstag + */ +public class HolidayCalendar extends BaseCalendar implements Calendar, + Serializable { + static final long serialVersionUID = -7590908752291814693L; + + // A sorted set to store the holidays + private TreeSet dates = new TreeSet(); + + public HolidayCalendar() { + } + + public HolidayCalendar(Calendar baseCalendar) { + super(baseCalendar); + } + + public HolidayCalendar(TimeZone timeZone) { + super(timeZone); + } + + public HolidayCalendar(Calendar baseCalendar, TimeZone timeZone) { + super(baseCalendar, timeZone); + } + + /** + *

+ * Determine whether the given time (in milliseconds) is 'included' by the + * Calendar. + *

+ * + *

+ * Note that this Calendar is only has full-day precision. + *

+ */ + public boolean isTimeIncluded(long timeStamp) { + if (super.isTimeIncluded(timeStamp) == false) { + return false; + } + + Date lookFor = getStartOfDayJavaCalendar(timeStamp).getTime(); + + return !(dates.contains(lookFor)); + } + + /** + *

+ * Determine the next time (in milliseconds) that is 'included' by the + * Calendar after the given time. + *

+ * + *

+ * Note that this Calendar is only has full-day precision. + *

+ */ + public long getNextIncludedTime(long timeStamp) { + + // Call base calendar implementation first + long baseTime = super.getNextIncludedTime(timeStamp); + if ((baseTime > 0) && (baseTime > timeStamp)) { + timeStamp = baseTime; + } + + // Get timestamp for 00:00:00 + java.util.Calendar day = getStartOfDayJavaCalendar(timeStamp); + while (isTimeIncluded(day.getTime().getTime()) == false) { + day.add(java.util.Calendar.DATE, 1); + } + + return day.getTime().getTime(); + } + + /** + *

+ * Add the given Date to the list of excluded days. Only the month, day and + * year of the returned dates are significant. + *

+ */ + public void addExcludedDate(Date excludedDate) { + Date date = getStartOfDayJavaCalendar(excludedDate.getTime()).getTime(); + /* + * System.err.println( "HolidayCalendar.add(): date=" + + * excludedDate.toLocaleString()); + */ + this.dates.add(date); + } + + public void removeExcludedDate(Date dateToRemove) { + Date date = getStartOfDayJavaCalendar(dateToRemove.getTime()).getTime(); + dates.remove(date); + } + + /** + *

+ * Returns a SortedSet of Dates representing the excluded + * days. Only the month, day and year of the returned dates are + * significant. + *

+ */ + public SortedSet getExcludedDates() { + return Collections.unmodifiableSortedSet(dates); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/MonthlyCalendar.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/MonthlyCalendar.java new file mode 100644 index 000000000..8a0a396ba --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/MonthlyCalendar.java @@ -0,0 +1,218 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + * and Juergen Donnerstag (c) 2002, EDS 2002 + */ + +package com.fr.third.org.quartz.impl.calendar; + +import java.io.Serializable; +import java.util.TimeZone; + +import com.fr.third.org.quartz.Calendar; + +/** + *

+ * This implementation of the Calendar excludes a set of days of the month. You + * may use it to exclude every first day of each month for example. But you may define + * any day of a month. + *

+ * + * @see com.fr.third.org.quartz.Calendar + * @see com.fr.third.org.quartz.impl.calendar.BaseCalendar + * + * @author Juergen Donnerstag + */ +public class MonthlyCalendar extends BaseCalendar implements Calendar, + Serializable { + + static final long serialVersionUID = 419164961091807944L; + + private static final int MAX_DAYS_IN_MONTH = 31; + + // An array to store a months days which are to be excluded. + // java.util.Calendar.get( ) as index. + private boolean[] excludeDays = new boolean[MAX_DAYS_IN_MONTH]; + + // Will be set to true, if all week days are excluded + private boolean excludeAll = false; + + public MonthlyCalendar() { + this(null, null); + } + + public MonthlyCalendar(Calendar baseCalendar) { + this(baseCalendar, null); + } + + public MonthlyCalendar(TimeZone timeZone) { + this(null, timeZone); + } + + public MonthlyCalendar(Calendar baseCalendar, TimeZone timeZone) { + super(baseCalendar, timeZone); + + // all days are included by default + excludeAll = areAllDaysExcluded(); + } + + /** + *

+ * Get the array which defines the exclude-value of each day of month. + * Only the first 31 elements of the array are relevant, with the 0 index + * element representing the first day of the month. + *

+ */ + public boolean[] getDaysExcluded() { + return excludeDays; + } + + /** + *

+ * Return true, if day is defined to be excluded. + *

+ * + * @param day The day of the month (from 1 to 31) to check. + */ + public boolean isDayExcluded(int day) { + if ((day < 1) || (day > MAX_DAYS_IN_MONTH)) { + throw new IllegalArgumentException( + "The day parameter must be in the range of 1 to " + MAX_DAYS_IN_MONTH); + } + + return excludeDays[day - 1]; + } + + /** + *

+ * Redefine the array of days excluded. The array must non-null and of size + * greater or equal to 31. The 0 index element represents the first day of + * the month. + *

+ */ + public void setDaysExcluded(boolean[] days) { + if (days == null) { + throw new IllegalArgumentException("The days parameter cannot be null."); + } + + if (days.length < MAX_DAYS_IN_MONTH) { + throw new IllegalArgumentException( + "The days parameter must have a length of at least " + MAX_DAYS_IN_MONTH + " elements."); + } + + excludeDays = days; + excludeAll = areAllDaysExcluded(); + } + + /** + *

+ * Redefine a certain day of the month to be excluded (true) or included + * (false). + *

+ * + * @param day The day of the month (from 1 to 31) to set. + */ + public void setDayExcluded(int day, boolean exclude) { + if ((day < 1) || (day > MAX_DAYS_IN_MONTH)) { + throw new IllegalArgumentException( + "The day parameter must be in the range of 1 to " + MAX_DAYS_IN_MONTH); + } + + excludeDays[day - 1] = exclude; + excludeAll = areAllDaysExcluded(); + } + + /** + *

+ * Check if all days are excluded. That is no day is included. + *

+ */ + public boolean areAllDaysExcluded() { + for (int i = 1; i <= MAX_DAYS_IN_MONTH; i++) { + if (isDayExcluded(i) == false) { + return false; + } + } + + return true; + } + + /** + *

+ * Determine whether the given time (in milliseconds) is 'included' by the + * Calendar. + *

+ * + *

+ * Note that this Calendar is only has full-day precision. + *

+ */ + public boolean isTimeIncluded(long timeStamp) { + if (excludeAll == true) { + return false; + } + + // Test the base calendar first. Only if the base calendar not already + // excludes the time/date, continue evaluating this calendar instance. + if (super.isTimeIncluded(timeStamp) == false) { return false; } + + java.util.Calendar cl = createJavaCalendar(timeStamp); + int day = cl.get(java.util.Calendar.DAY_OF_MONTH); + + return !(isDayExcluded(day)); + } + + /** + *

+ * Determine the next time (in milliseconds) that is 'included' by the + * Calendar after the given time. Return the original value if timeStamp is + * included. Return 0 if all days are excluded. + *

+ * + *

+ * Note that this Calendar is only has full-day precision. + *

+ */ + public long getNextIncludedTime(long timeStamp) { + if (excludeAll == true) { + return 0; + } + + // Call base calendar implementation first + long baseTime = super.getNextIncludedTime(timeStamp); + if ((baseTime > 0) && (baseTime > timeStamp)) { + timeStamp = baseTime; + } + + // Get timestamp for 00:00:00 + java.util.Calendar cl = getStartOfDayJavaCalendar(timeStamp); + int day = cl.get(java.util.Calendar.DAY_OF_MONTH); + + if (!isDayExcluded(day)) { + return timeStamp; // return the original value + } + + while (isDayExcluded(day) == true) { + cl.add(java.util.Calendar.DATE, 1); + day = cl.get(java.util.Calendar.DAY_OF_MONTH); + } + + return cl.getTime().getTime(); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/WeeklyCalendar.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/WeeklyCalendar.java new file mode 100644 index 000000000..f24872d51 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/calendar/WeeklyCalendar.java @@ -0,0 +1,200 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + * and Juergen Donnerstag (c) 2002, EDS 2002 + */ + +package com.fr.third.org.quartz.impl.calendar; + +import java.io.Serializable; +import java.util.TimeZone; + +import com.fr.third.org.quartz.Calendar; + +/** + *

+ * This implementation of the Calendar excludes a set of days of the week. You + * may use it to exclude weekends for example. But you may define any day of + * the week. By default it excludes SATURDAY and SUNDAY. + *

+ * + * @see com.fr.third.org.quartz.Calendar + * @see com.fr.third.org.quartz.impl.calendar.BaseCalendar + * + * @author Juergen Donnerstag + */ +public class WeeklyCalendar extends BaseCalendar implements Calendar, + Serializable { + static final long serialVersionUID = -6809298821229007586L; + + // An array to store the week days which are to be excluded. + // java.util.Calendar.MONDAY etc. are used as index. + private boolean[] excludeDays = new boolean[8]; + + // Will be set to true, if all week days are excluded + private boolean excludeAll = false; + + public WeeklyCalendar() { + this(null, null); + } + + public WeeklyCalendar(Calendar baseCalendar) { + this(baseCalendar, null); + } + + public WeeklyCalendar(TimeZone timeZone) { + super(null, timeZone); + } + + public WeeklyCalendar(Calendar baseCalendar, TimeZone timeZone) { + super(baseCalendar, timeZone); + + excludeDays[java.util.Calendar.SUNDAY] = true; + excludeDays[java.util.Calendar.SATURDAY] = true; + excludeAll = areAllDaysExcluded(); + } + + /** + *

+ * Get the array with the week days + *

+ */ + public boolean[] getDaysExcluded() { + return excludeDays; + } + + /** + *

+ * Return true, if wday (see Calendar.get()) is defined to be exluded. E. g. + * saturday and sunday. + *

+ */ + public boolean isDayExcluded(int wday) { + return excludeDays[wday]; + } + + /** + *

+ * Redefine the array of days excluded. The array must of size greater or + * equal 8. java.util.Calendar's constants like MONDAY should be used as + * index. A value of true is regarded as: exclude it. + *

+ */ + public void setDaysExcluded(boolean[] weekDays) { + if (weekDays == null) { + return; + } + + excludeDays = weekDays; + excludeAll = areAllDaysExcluded(); + } + + /** + *

+ * Redefine a certain day of the week to be excluded (true) or included + * (false). Use java.util.Calendar's constants like MONDAY to determine the + * wday. + *

+ */ + public void setDayExcluded(int wday, boolean exclude) { + excludeDays[wday] = exclude; + excludeAll = areAllDaysExcluded(); + } + + /** + *

+ * Check if all week days are excluded. That is no day is included. + *

+ * + * @return boolean + */ + public boolean areAllDaysExcluded() { + return + isDayExcluded(java.util.Calendar.SUNDAY) && + isDayExcluded(java.util.Calendar.MONDAY) && + isDayExcluded(java.util.Calendar.TUESDAY) && + isDayExcluded(java.util.Calendar.WEDNESDAY) && + isDayExcluded(java.util.Calendar.THURSDAY) && + isDayExcluded(java.util.Calendar.FRIDAY) && + isDayExcluded(java.util.Calendar.SATURDAY); + } + + /** + *

+ * Determine whether the given time (in milliseconds) is 'included' by the + * Calendar. + *

+ * + *

+ * Note that this Calendar is only has full-day precision. + *

+ */ + public boolean isTimeIncluded(long timeStamp) { + if (excludeAll == true) { + return false; + } + + // Test the base calendar first. Only if the base calendar not already + // excludes the time/date, continue evaluating this calendar instance. + if (super.isTimeIncluded(timeStamp) == false) { return false; } + + java.util.Calendar cl = createJavaCalendar(timeStamp); + int wday = cl.get(java.util.Calendar.DAY_OF_WEEK); + + return !(isDayExcluded(wday)); + } + + /** + *

+ * Determine the next time (in milliseconds) that is 'included' by the + * Calendar after the given time. Return the original value if timeStamp is + * included. Return 0 if all days are excluded. + *

+ * + *

+ * Note that this Calendar is only has full-day precision. + *

+ */ + public long getNextIncludedTime(long timeStamp) { + if (excludeAll == true) { + return 0; + } + + // Call base calendar implementation first + long baseTime = super.getNextIncludedTime(timeStamp); + if ((baseTime > 0) && (baseTime > timeStamp)) { + timeStamp = baseTime; + } + + // Get timestamp for 00:00:00 + java.util.Calendar cl = getStartOfDayJavaCalendar(timeStamp); + int wday = cl.get(java.util.Calendar.DAY_OF_WEEK); + + if (!isDayExcluded(wday)) { + return timeStamp; // return the original value + } + + while (isDayExcluded(wday) == true) { + cl.add(java.util.Calendar.DATE, 1); + wday = cl.get(java.util.Calendar.DAY_OF_WEEK); + } + + return cl.getTime().getTime(); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/AttributeRestoringConnectionInvocationHandler.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/AttributeRestoringConnectionInvocationHandler.java new file mode 100644 index 000000000..7d3906ab1 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/AttributeRestoringConnectionInvocationHandler.java @@ -0,0 +1,159 @@ +/* + * Copyright 2004-2006 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.sql.Connection; +import java.sql.SQLException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + *

+ * Protects a {@link java.sql.Connection}'s attributes from being permanently modfied. + *

+ * + *

+ * Wraps a provided {@link java.sql.Connection} such that its auto + * commit and transaction isolation attributes can be overwritten, but + * will automatically restored to their original values when the connection + * is actually closed (and potentially returned to a pool for reuse). + *

+ * + * @see com.fr.third.org.quartz.impl.jdbcjobstore.JobStoreSupport#getConnection() + * @see com.fr.third.org.quartz.impl.jdbcjobstore.JobStoreCMT#getNonManagedTXConnection() + */ +public class AttributeRestoringConnectionInvocationHandler implements InvocationHandler { + private Connection conn; + + private boolean overwroteOriginalAutoCommitValue; + private boolean overwroteOriginalTxIsolationValue; + + // Set if overwroteOriginalAutoCommitValue is true + private boolean originalAutoCommitValue; + + // Set if overwroteOriginalTxIsolationValue is true + private int originalTxIsolationValue; + + public AttributeRestoringConnectionInvocationHandler( + Connection conn) { + this.conn = conn; + } + + protected Log getLog() { + return LogFactory.getLog(getClass()); + } + + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + if (method.getName().equals("setAutoCommit")) { + setAutoCommit(((Boolean)args[0]).booleanValue()); + } else if (method.getName().equals("setTransactionIsolation")) { + setTransactionIsolation(((Integer)args[0]).intValue()); + } else if (method.getName().equals("close")) { + close(); + } else { + return method.invoke(conn, args); + } + + return null; + } + + /** + * Sets this connection's auto-commit mode to the given state, saving + * the original mode. The connection's original auto commit mode is restored + * when the connection is closed. + */ + public void setAutoCommit(boolean autoCommit) throws SQLException { + boolean currentAutoCommitValue = conn.getAutoCommit(); + + if (autoCommit != currentAutoCommitValue) { + if (overwroteOriginalAutoCommitValue == false) { + overwroteOriginalAutoCommitValue = true; + originalAutoCommitValue = currentAutoCommitValue; + } + + conn.setAutoCommit(autoCommit); + } + } + + /** + * Attempts to change the transaction isolation level to the given level, saving + * the original level. The connection's original transaction isolation level is + * restored when the connection is closed. + */ + public void setTransactionIsolation(int level) throws SQLException { + int currentLevel = conn.getTransactionIsolation(); + + if (level != currentLevel) { + if (overwroteOriginalTxIsolationValue == false) { + overwroteOriginalTxIsolationValue = true; + originalTxIsolationValue = currentLevel; + } + + conn.setTransactionIsolation(level); + } + } + + /** + * Gets the underlying connection to which all operations ultimately + * defer. This is provided in case a user ever needs to punch through + * the wrapper to access vendor specific methods outside of the + * standard java.sql.Connection interface. + * + * @return The underlying connection to which all operations + * ultimately defer. + */ + public Connection getWrappedConnection() { + return conn; + } + + /** + * Attempts to restore the auto commit and transaction isolation connection + * attributes of the wrapped connection to their original values (if they + * were overwritten). + */ + public void restoreOriginalAtributes() { + try { + if (overwroteOriginalAutoCommitValue) { + conn.setAutoCommit(originalAutoCommitValue); + } + } catch (Throwable t) { + getLog().warn("Failed restore connection's original auto commit setting.", t); + } + + try { + if (overwroteOriginalTxIsolationValue) { + conn.setTransactionIsolation(originalTxIsolationValue); + } + } catch (Throwable t) { + getLog().warn("Failed restore connection's original transaction isolation setting.", t); + } + } + + /** + * Attempts to restore the auto commit and transaction isolation connection + * attributes of the wrapped connection to their original values (if they + * were overwritten), before finally actually closing the wrapped connection. + */ + public void close() throws SQLException { + restoreOriginalAtributes(); + + conn.close(); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/CloudscapeDelegate.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/CloudscapeDelegate.java new file mode 100644 index 000000000..9a938c62b --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/CloudscapeDelegate.java @@ -0,0 +1,118 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ + +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.apache.commons.logging.Log; + +/** + *

+ * This is a driver delegate for the Cloudscape database, not surprisingly, + * it is known to work with Derby as well. Though later versions of Derby + * may simply use StdJDBCDelegate. + *

+ * + * @author James House + * @author Sridhar Jawaharlal, Srinivas Venkatarangaiah + * @deprecated Use the StdJDBCDelegate for latest versions of Derby + */ +public class CloudscapeDelegate extends StdJDBCDelegate { + /** + *

+ * Create new CloudscapeDelegate instance. + *

+ * + * @param log + * the logger to use during execution + * @param tablePrefix + * the prefix of all table names + */ + public CloudscapeDelegate(Log log, String tablePrefix, String instanceId) { + super(log, tablePrefix, instanceId); + } + + /** + *

+ * Create new CloudscapeDelegate instance. + *

+ * + * @param log + * the logger to use during execution + * @param tablePrefix + * the prefix of all table names + * @param useProperties + * useProperties flag + */ + public CloudscapeDelegate(Log log, String tablePrefix, String instanceId, + Boolean useProperties) { + super(log, tablePrefix, instanceId, useProperties); + } + + //--------------------------------------------------------------------------- + // protected methods that can be overridden by subclasses + //--------------------------------------------------------------------------- + + /** + *

+ * This method should be overridden by any delegate subclasses that need + * special handling for BLOBs. The default implementation uses standard + * JDBC java.sql.Blob operations. + *

+ * + * @param rs + * the result set, already queued to the correct row + * @param colName + * the column name for the BLOB + * @return the deserialized Object from the ResultSet BLOB + * @throws ClassNotFoundException + * if a class found during deserialization cannot be found + * @throws IOException + * if deserialization causes an error + */ + protected Object getObjectFromBlob(ResultSet rs, String colName) + throws ClassNotFoundException, IOException, SQLException { + Object obj = null; + + byte[] inputBytes = rs.getBytes(colName); + + if (null != inputBytes && inputBytes.length != 0) { + ByteArrayInputStream bais = new + ByteArrayInputStream(inputBytes); + + ObjectInputStream in = new ObjectInputStream(bais); + try { + obj = in.readObject(); + } finally { + in.close(); + } + } + + return obj; + } +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/Constants.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/Constants.java new file mode 100644 index 000000000..0a57a7c71 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/Constants.java @@ -0,0 +1,193 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ + +package com.fr.third.org.quartz.impl.jdbcjobstore; + +/** + *

+ * This interface can be implemented by any {@link + * com.fr.third.org.quartz.impl.jdbcjobstore.DriverDelegate} + * class that needs to use the constants contained herein. + *

+ * + * @author Jeffrey Wescott + * @author James House + */ +public interface Constants { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + // Table names + String TABLE_JOB_DETAILS = "JOB_DETAILS"; + + String TABLE_TRIGGERS = "TRIGGERS"; + + String TABLE_SIMPLE_TRIGGERS = "SIMPLE_TRIGGERS"; + + String TABLE_CRON_TRIGGERS = "CRON_TRIGGERS"; + + String TABLE_BLOB_TRIGGERS = "BLOB_TRIGGERS"; + + String TABLE_FIRED_TRIGGERS = "FIRED_TRIGGERS"; + + String TABLE_JOB_LISTENERS = "JOB_LISTENERS"; + + String TABLE_TRIGGER_LISTENERS = "TRIGGER_LISTENERS"; + + String TABLE_CALENDARS = "CALENDARS"; + + String TABLE_PAUSED_TRIGGERS = "PAUSED_TRIGGER_GRPS"; + + String TABLE_LOCKS = "LOCKS"; + + String TABLE_SCHEDULER_STATE = "SCHEDULER_STATE"; + + // TABLE_JOB_DETAILS columns names + String COL_JOB_NAME = "JOB_NAME"; + + String COL_JOB_GROUP = "JOB_GROUP"; + + String COL_IS_DURABLE = "IS_DURABLE"; + + String COL_IS_VOLATILE = "IS_VOLATILE"; + + String COL_IS_STATEFUL = "IS_STATEFUL"; + + String COL_REQUESTS_RECOVERY = "REQUESTS_RECOVERY"; + + String COL_JOB_DATAMAP = "JOB_DATA"; + + String COL_JOB_CLASS = "JOB_CLASS_NAME"; + + String COL_DESCRIPTION = "DESCRIPTION"; + + // TABLE_JOB_LISTENERS columns names + String COL_JOB_LISTENER = "JOB_LISTENER"; + + // TABLE_TRIGGERS columns names + String COL_TRIGGER_NAME = "TRIGGER_NAME"; + + String COL_TRIGGER_GROUP = "TRIGGER_GROUP"; + + String COL_NEXT_FIRE_TIME = "NEXT_FIRE_TIME"; + + String COL_PREV_FIRE_TIME = "PREV_FIRE_TIME"; + + String COL_TRIGGER_STATE = "TRIGGER_STATE"; + + String COL_TRIGGER_TYPE = "TRIGGER_TYPE"; + + String COL_START_TIME = "START_TIME"; + + String COL_END_TIME = "END_TIME"; + + String COL_PRIORITY = "PRIORITY"; + + String COL_MISFIRE_INSTRUCTION = "MISFIRE_INSTR"; + + String ALIAS_COL_NEXT_FIRE_TIME = "ALIAS_NXT_FR_TM"; + + // TABLE_SIMPLE_TRIGGERS columns names + String COL_REPEAT_COUNT = "REPEAT_COUNT"; + + String COL_REPEAT_INTERVAL = "REPEAT_INTERVAL"; + + String COL_TIMES_TRIGGERED = "TIMES_TRIGGERED"; + + // TABLE_CRON_TRIGGERS columns names + String COL_CRON_EXPRESSION = "CRON_EXPRESSION"; + + // TABLE_BLOB_TRIGGERS columns names + String COL_BLOB = "BLOB_DATA"; + + String COL_TIME_ZONE_ID = "TIME_ZONE_ID"; + + // TABLE_TRIGGER_LISTENERS + String COL_TRIGGER_LISTENER = "TRIGGER_LISTENER"; + + // TABLE_FIRED_TRIGGERS columns names + String COL_INSTANCE_NAME = "INSTANCE_NAME"; + + String COL_FIRED_TIME = "FIRED_TIME"; + + String COL_ENTRY_ID = "ENTRY_ID"; + + String COL_ENTRY_STATE = "STATE"; + + // TABLE_CALENDARS columns names + String COL_CALENDAR_NAME = "CALENDAR_NAME"; + + String COL_CALENDAR = "CALENDAR"; + + // TABLE_LOCKS columns names + String COL_LOCK_NAME = "LOCK_NAME"; + + // TABLE_LOCKS columns names + String COL_LAST_CHECKIN_TIME = "LAST_CHECKIN_TIME"; + + String COL_CHECKIN_INTERVAL = "CHECKIN_INTERVAL"; + + // MISC CONSTANTS + String DEFAULT_TABLE_PREFIX = "QRTZ_"; + + // STATES + String STATE_WAITING = "WAITING"; + + String STATE_ACQUIRED = "ACQUIRED"; + + String STATE_EXECUTING = "EXECUTING"; + + String STATE_COMPLETE = "COMPLETE"; + + String STATE_BLOCKED = "BLOCKED"; + + String STATE_ERROR = "ERROR"; + + String STATE_PAUSED = "PAUSED"; + + String STATE_PAUSED_BLOCKED = "PAUSED_BLOCKED"; + + String STATE_DELETED = "DELETED"; + + /** + * @deprecated Whether a trigger has misfired is no longer a state, but + * rather now identified dynamically by whether the trigger's next fire + * time is more than the misfire threshold time in the past. + */ + String STATE_MISFIRED = "MISFIRED"; + + String ALL_GROUPS_PAUSED = "_$_ALL_GROUPS_PAUSED_$_"; + + // TRIGGER TYPES + String TTYPE_SIMPLE = "SIMPLE"; + + String TTYPE_CRON = "CRON"; + + String TTYPE_BLOB = "BLOB"; +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/DB2v6Delegate.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/DB2v6Delegate.java new file mode 100644 index 000000000..5f5efefcc --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/DB2v6Delegate.java @@ -0,0 +1,146 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.apache.commons.logging.Log; + +/** + * Quartz JDBC delegate for DB2 v6 databases. select count(name) + * had to be replaced with select count(*). + * + * @author Martin Renner + * @author James House + */ +public class DB2v6Delegate extends StdJDBCDelegate { + public static final String SELECT_NUM_JOBS = "SELECT COUNT(*) FROM " + + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS; + + public static final String SELECT_NUM_TRIGGERS_FOR_JOB = "SELECT COUNT(*) FROM " + + TABLE_PREFIX_SUBST + + TABLE_TRIGGERS + + " WHERE " + + COL_JOB_NAME + + " = ? AND " + COL_JOB_GROUP + " = ?"; + + public static final String SELECT_NUM_TRIGGERS = "SELECT COUNT(*) FROM " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS; + + public static final String SELECT_NUM_CALENDARS = "SELECT COUNT(*) FROM " + + TABLE_PREFIX_SUBST + TABLE_CALENDARS; + + public DB2v6Delegate(Log logger, String tablePrefix, String instanceId) { + super(logger, tablePrefix, instanceId); + } + + public DB2v6Delegate(Log logger, String tablePrefix, String instanceId, + Boolean useProperties) { + super(logger, tablePrefix, instanceId, useProperties); + } + + public int selectNumJobs(Connection conn) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + int count = 0; + ps = conn.prepareStatement(rtp(SELECT_NUM_JOBS)); + rs = ps.executeQuery(); + + if (rs.next()) { + count = rs.getInt(1); + } + + return count; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + public int selectNumTriggersForJob(Connection conn, String jobName, + String groupName) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_NUM_TRIGGERS_FOR_JOB)); + ps.setString(1, jobName); + ps.setString(2, groupName); + rs = ps.executeQuery(); + + if (rs.next()) { + return rs.getInt(1); + } else { + return 0; + } + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + public int selectNumTriggers(Connection conn) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + int count = 0; + ps = conn.prepareStatement(rtp(SELECT_NUM_TRIGGERS)); + rs = ps.executeQuery(); + + if (rs.next()) { + count = rs.getInt(1); + } + + return count; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + public int selectNumCalendars(Connection conn) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + int count = 0; + ps = conn.prepareStatement(rtp(SELECT_NUM_CALENDARS)); + rs = ps.executeQuery(); + + if (rs.next()) { + count = rs.getInt(1); + } + + return count; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/DB2v7Delegate.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/DB2v7Delegate.java new file mode 100644 index 000000000..6585ffbef --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/DB2v7Delegate.java @@ -0,0 +1,72 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.io.ByteArrayOutputStream; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import org.apache.commons.logging.Log; + +/** + * Quartz JDBC delegate for DB2 v7 databases. + *

+ * This differs from the StdJDBCDelegate in that it stores + * boolean values in an varchar(1) column, and saves + * serialized data in a byte array using + * {@link PreparedStatement#setObject(int, java.lang.Object, int)} + * rather than {@link PreparedStatement#setBytes(int, byte[])}. + *

+ * + * @author Blair Jensen + */ +public class DB2v7Delegate extends StdJDBCDelegate { + + public DB2v7Delegate(Log logger, String tablePrefix, String instanceId) { + super(logger, tablePrefix, instanceId); + } + + public DB2v7Delegate(Log log, String tablePrefix, String instanceId, + Boolean useProperties) { + super(log, tablePrefix, instanceId, useProperties); + } + + /** + * Sets the designated parameter to the byte array of the given + * ByteArrayOutputStream. Will set parameter value to null if the + * ByteArrayOutputStream is null. + * Wraps {@link PreparedStatement#setObject(int, java.lang.Object, int)} rather than + * {@link PreparedStatement#setBytes(int, byte[])} as required by the + * DB2 v7 database. + */ + protected void setBytes(PreparedStatement ps, int index, ByteArrayOutputStream baos) throws SQLException { + ps.setObject(index, ((baos == null) ? null : baos.toByteArray()), java.sql.Types.BLOB); + } + + /** + * Sets the designated parameter to the given Java boolean value. + * This translates the boolean to 1/0 for true/false. + */ + protected void setBoolean(PreparedStatement ps, int index, boolean val) throws SQLException { + ps.setString(index, ((val) ? "1" : "0")); + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/DB2v8Delegate.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/DB2v8Delegate.java new file mode 100644 index 000000000..f8b93966b --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/DB2v8Delegate.java @@ -0,0 +1,51 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import org.apache.commons.logging.Log; + +/** + * Quartz JDBC delegate for DB2 v8 databases. + *

+ * This differs from the StdJDBCDelegate in that it stores + * boolean values in an integer column. + *

+ * + * @author Blair Jensen + */ +public class DB2v8Delegate extends StdJDBCDelegate { + + public DB2v8Delegate(Log logger, String tablePrefix, String instanceId) { + super(logger, tablePrefix, instanceId); + } + + public DB2v8Delegate(Log log, String tablePrefix, String instanceId, + Boolean useProperties) { + super(log, tablePrefix, instanceId, useProperties); + } + + /** + * Sets the designated parameter to the given Java boolean value. + * This translates the boolean to 1/0 for true/false. + */ + protected void setBoolean(PreparedStatement ps, int index, boolean val) throws SQLException { + ps.setInt(index, ((val) ? 1 : 0)); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/DBSemaphore.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/DBSemaphore.java new file mode 100644 index 000000000..092cce504 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/DBSemaphore.java @@ -0,0 +1,201 @@ +/* + * Copyright 2004-2006 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.sql.Connection; +import java.util.HashSet; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Base class for database based lock handlers for providing thread/resource locking + * in order to protect resources from being altered by multiple threads at the + * same time. + */ +public abstract class DBSemaphore implements Semaphore, Constants, + StdJDBCConstants, TablePrefixAware { + + private final Log log = LogFactory.getLog(getClass()); + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + ThreadLocal lockOwners = new ThreadLocal(); + + private String sql; + + private String tablePrefix; + + private String expandedSQL; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public DBSemaphore(String tablePrefix, String sql, String defaultSQL) { + this.sql = defaultSQL; + this.tablePrefix = tablePrefix; + setSQL(sql); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + protected Log getLog() { + return log; + } + + private HashSet getThreadLocks() { + HashSet threadLocks = (HashSet) lockOwners.get(); + if (threadLocks == null) { + threadLocks = new HashSet(); + lockOwners.set(threadLocks); + } + return threadLocks; + } + + /** + * Execute the SQL that will lock the proper database row. + */ + protected abstract void executeSQL( + Connection conn, String lockName, String expandedSQL) throws LockException; + + /** + * Grants a lock on the identified resource to the calling thread (blocking + * until it is available). + * + * @return true if the lock was obtained. + */ + public boolean obtainLock(Connection conn, String lockName) + throws LockException { + + lockName = lockName.intern(); + + Log log = getLog(); + + if(log.isDebugEnabled()) { + log.debug( + "Lock '" + lockName + "' is desired by: " + + Thread.currentThread().getName()); + } + if (!isLockOwner(conn, lockName)) { + + executeSQL(conn, lockName, expandedSQL); + + if(log.isDebugEnabled()) { + log.debug( + "Lock '" + lockName + "' given to: " + + Thread.currentThread().getName()); + } + getThreadLocks().add(lockName); + //getThreadLocksObtainer().put(lockName, new + // Exception("Obtainer...")); + } else if(log.isDebugEnabled()) { + log.debug( + "Lock '" + lockName + "' Is already owned by: " + + Thread.currentThread().getName()); + } + + return true; + } + + + /** + * Release the lock on the identified resource if it is held by the calling + * thread. + */ + public void releaseLock(Connection conn, String lockName) { + + lockName = lockName.intern(); + + if (isLockOwner(conn, lockName)) { + if(getLog().isDebugEnabled()) { + getLog().debug( + "Lock '" + lockName + "' returned by: " + + Thread.currentThread().getName()); + } + getThreadLocks().remove(lockName); + //getThreadLocksObtainer().remove(lockName); + } else if (getLog().isDebugEnabled()) { + getLog().warn( + "Lock '" + lockName + "' attempt to return by: " + + Thread.currentThread().getName() + + " -- but not owner!", + new Exception("stack-trace of wrongful returner")); + } + } + + /** + * Determine whether the calling thread owns a lock on the identified + * resource. + */ + public boolean isLockOwner(Connection conn, String lockName) { + lockName = lockName.intern(); + + return getThreadLocks().contains(lockName); + } + + /** + * This Semaphore implementation does use the database. + */ + public boolean requiresConnection() { + return true; + } + + protected String getSQL() { + return sql; + } + + protected void setSQL(String sql) { + if ((sql != null) && (sql.trim().length() != 0)) { + this.sql = sql; + } + + setExpandedSQL(); + } + + private void setExpandedSQL() { + if (getTablePrefix() != null) { + expandedSQL = Util.rtp(this.sql, getTablePrefix()); + } + } + + protected String getTablePrefix() { + return tablePrefix; + } + + public void setTablePrefix(String tablePrefix) { + this.tablePrefix = tablePrefix; + + setExpandedSQL(); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/DriverDelegate.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/DriverDelegate.java new file mode 100644 index 000000000..6fd861976 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/DriverDelegate.java @@ -0,0 +1,1435 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; +import java.util.Set; + +import com.fr.third.org.quartz.Calendar; +import com.fr.third.org.quartz.CronTrigger; +import com.fr.third.org.quartz.JobDataMap; +import com.fr.third.org.quartz.JobDetail; +import com.fr.third.org.quartz.SimpleTrigger; +import com.fr.third.org.quartz.Trigger; +import com.fr.third.org.quartz.spi.ClassLoadHelper; +import com.fr.third.org.quartz.utils.Key; +import com.fr.third.org.quartz.utils.TriggerStatus; + +/** + *

+ * This is the base interface for all driver delegate classes. + *

+ * + *

+ * This interface is very similar to the {@link + * com.fr.third.org.quartz.spi.JobStore} + * interface except each method has an additional {@link java.sql.Connection} + * parameter. + *

+ * + *

+ * Unless a database driver has some extremely-DB-specific + * requirements, any DriverDelegate implementation classes should extend the + * {@link com.fr.third.org.quartz.impl.jdbcjobstore.StdJDBCDelegate} class. + *

+ * + * @author Jeffrey Wescott + * @author James House + */ +interface DriverDelegate { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + //--------------------------------------------------------------------------- + // startup / recovery + //--------------------------------------------------------------------------- + /** + *

+ * Update all triggers having one of the two given states, to the given new + * state. + *

+ * + * @param conn + * the DB Connection + * @param newState + * the new state for the triggers + * @param oldState1 + * the first old state to update + * @param oldState2 + * the second old state to update + * @return number of rows updated + */ + int updateTriggerStatesFromOtherStates(Connection conn, + String newState, String oldState1, String oldState2) + throws SQLException; + + /** + *

+ * Get the names of all of the triggers that have misfired - according to + * the given timestamp. + *

+ * + * @param conn + * the DB Connection + * @return an array of {@link + * com.fr.third.org.quartz.utils.Key} objects + */ + Key[] selectMisfiredTriggers(Connection conn, long ts) + throws SQLException; + + /** + *

+ * Get the names of all of the triggers in the given state that have + * misfired - according to the given timestamp. + *

+ * + * @param conn + * the DB Connection + * @return an array of {@link + * com.fr.third.org.quartz.utils.Key} objects + */ + Key[] selectMisfiredTriggersInState(Connection conn, String state, + long ts) throws SQLException; + + /** + *

+ * Get the names of all of the triggers in the given states that have + * misfired - according to the given timestamp. No more than count will + * be returned. + *

+ * + * @param conn the DB Connection + * @param count the most misfired triggers to return, negative for all + * @param resultList Output parameter. A List of + * {@link com.fr.third.org.quartz.utils.Key} objects. Must not be null. + * + * @return Whether there are more misfired triggers left to find beyond + * the given count. + */ + boolean selectMisfiredTriggersInStates(Connection conn, String state1, String state2, + long ts, int count, List resultList) throws SQLException; + + /** + *

+ * Get the number of triggers in the given states that have + * misfired - according to the given timestamp. + *

+ * + * @param conn the DB Connection + */ + int countMisfiredTriggersInStates( + Connection conn, String state1, String state2, long ts) throws SQLException; + + /** + *

+ * Get the names of all of the triggers in the given group and state that + * have misfired - according to the given timestamp. + *

+ * + * @param conn + * the DB Connection + * @return an array of {@link + * com.fr.third.org.quartz.utils.Key} objects + */ + Key[] selectMisfiredTriggersInGroupInState(Connection conn, + String groupName, String state, long ts) throws SQLException; + + + /** + *

+ * Select all of the triggers for jobs that are requesting recovery. The + * returned trigger objects will have unique "recoverXXX" trigger names and + * will be in the {@link + * com.fr.third.org.quartz.Scheduler}.DEFAULT_RECOVERY_GROUP + * trigger group. + *

+ * + *

+ * In order to preserve the ordering of the triggers, the fire time will be + * set from the COL_FIRED_TIME column in the TABLE_FIRED_TRIGGERS + * table. The caller is responsible for calling computeFirstFireTime + * on each returned trigger. It is also up to the caller to insert the + * returned triggers to ensure that they are fired. + *

+ * + * @param conn + * the DB Connection + * @return an array of {@link com.fr.third.org.quartz.Trigger} objects + */ + Trigger[] selectTriggersForRecoveringJobs(Connection conn) + throws SQLException, IOException, ClassNotFoundException; + + /** + *

+ * Delete all fired triggers. + *

+ * + * @param conn + * the DB Connection + * @return the number of rows deleted + */ + int deleteFiredTriggers(Connection conn) throws SQLException; + + /** + *

+ * Delete all fired triggers of the given instance. + *

+ * + * @param conn + * the DB Connection + * @return the number of rows deleted + */ + int deleteFiredTriggers(Connection conn, String instanceId) + throws SQLException; + + /** + *

+ * Delete all volatile fired triggers. + *

+ * + * @param conn + * the DB Connection + * @return the number of rows deleted + */ + int deleteVolatileFiredTriggers(Connection conn) throws SQLException; + + /** + *

+ * Get the names of all of the triggers that are volatile. + *

+ * + * @param conn + * the DB Connection + * @return an array of {@link + * com.fr.third.org.quartz.utils.Key} objects + */ + Key[] selectVolatileTriggers(Connection conn) throws SQLException; + + /** + *

+ * Get the names of all of the jobs that are volatile. + *

+ * + * @param conn + * the DB Connection + * @return an array of {@link + * com.fr.third.org.quartz.utils.Key} objects + */ + Key[] selectVolatileJobs(Connection conn) throws SQLException; + + //--------------------------------------------------------------------------- + // jobs + //--------------------------------------------------------------------------- + + /** + *

+ * Insert the job detail record. + *

+ * + * @param conn + * the DB Connection + * @param job + * the job to insert + * @return number of rows inserted + * @throws IOException + * if there were problems serializing the JobDataMap + */ + int insertJobDetail(Connection conn, JobDetail job) + throws IOException, SQLException; + + /** + *

+ * Update the job detail record. + *

+ * + * @param conn + * the DB Connection + * @param job + * the job to update + * @return number of rows updated + * @throws IOException + * if there were problems serializing the JobDataMap + */ + int updateJobDetail(Connection conn, JobDetail job) + throws IOException, SQLException; + + /** + *

+ * Get the names of all of the triggers associated with the given job. + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the job name + * @param groupName + * the job group + * @return an array of {@link + * com.fr.third.org.quartz.utils.Key} objects + */ + Key[] selectTriggerNamesForJob(Connection conn, String jobName, + String groupName) throws SQLException; + + /** + *

+ * Delete all job listeners for the given job. + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the name of the job + * @param groupName + * the group containing the job + * @return the number of rows deleted + */ + int deleteJobListeners(Connection conn, String jobName, + String groupName) throws SQLException; + + /** + *

+ * Delete the job detail record for the given job. + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the name of the job + * @param groupName + * the group containing the job + * @return the number of rows deleted + */ + int deleteJobDetail(Connection conn, String jobName, String groupName) + throws SQLException; + + /** + *

+ * Check whether or not the given job is stateful. + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the name of the job + * @param groupName + * the group containing the job + * @return true if the job exists and is stateful, false otherwise + */ + boolean isJobStateful(Connection conn, String jobName, + String groupName) throws SQLException; + + /** + *

+ * Check whether or not the given job exists. + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the name of the job + * @param groupName + * the group containing the job + * @return true if the job exists, false otherwise + */ + boolean jobExists(Connection conn, String jobName, String groupName) + throws SQLException; + + /** + *

+ * Update the job data map for the given job. + *

+ * + * @param conn + * the DB Connection + * @param job + * the job to update + * @return the number of rows updated + * @throws IOException + * if there were problems serializing the JobDataMap + */ + int updateJobData(Connection conn, JobDetail job) + throws IOException, SQLException; + + /** + *

+ * Associate a listener with a job. + *

+ * + * @param conn + * the DB Connection + * @param job + * the job to associate with the listener + * @param listener + * the listener to insert + * @return the number of rows inserted + */ + int insertJobListener(Connection conn, JobDetail job, String listener) + throws SQLException; + + /** + *

+ * Get all of the listeners for a given job. + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the job name whose listeners are wanted + * @param groupName + * the group containing the job + * @return array of String listener names + */ + String[] selectJobListeners(Connection conn, String jobName, + String groupName) throws SQLException; + + /** + *

+ * Select the JobDetail object for a given job name / group name. + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the job name whose listeners are wanted + * @param groupName + * the group containing the job + * @return the populated JobDetail object + * @throws ClassNotFoundException + * if a class found during deserialization cannot be found or if + * the job class could not be found + * @throws IOException + * if deserialization causes an error + */ + JobDetail selectJobDetail(Connection conn, String jobName, + String groupName, ClassLoadHelper loadHelper) + throws ClassNotFoundException, IOException, SQLException; + + /** + *

+ * Select the total number of jobs stored. + *

+ * + * @param conn + * the DB Connection + * @return the total number of jobs stored + */ + int selectNumJobs(Connection conn) throws SQLException; + + /** + *

+ * Select all of the job group names that are stored. + *

+ * + * @param conn + * the DB Connection + * @return an array of String group names + */ + String[] selectJobGroups(Connection conn) throws SQLException; + + /** + *

+ * Select all of the jobs contained in a given group. + *

+ * + * @param conn + * the DB Connection + * @param groupName + * the group containing the jobs + * @return an array of String job names + */ + String[] selectJobsInGroup(Connection conn, String groupName) + throws SQLException; + + //--------------------------------------------------------------------------- + // triggers + //--------------------------------------------------------------------------- + + /** + *

+ * Insert the base trigger data. + *

+ * + * @param conn + * the DB Connection + * @param trigger + * the trigger to insert + * @param state + * the state that the trigger should be stored in + * @return the number of rows inserted + */ + int insertTrigger(Connection conn, Trigger trigger, String state, + JobDetail jobDetail) throws SQLException, IOException; + + /** + *

+ * Insert the simple trigger data. + *

+ * + * @param conn + * the DB Connection + * @param trigger + * the trigger to insert + * @return the number of rows inserted + */ + int insertSimpleTrigger(Connection conn, SimpleTrigger trigger) + throws SQLException; + + /** + *

+ * Insert the blob trigger data. + *

+ * + * @param conn + * the DB Connection + * @param trigger + * the trigger to insert + * @return the number of rows inserted + */ + int insertBlobTrigger(Connection conn, Trigger trigger) + throws SQLException, IOException; + + /** + *

+ * Insert the cron trigger data. + *

+ * + * @param conn + * the DB Connection + * @param trigger + * the trigger to insert + * @return the number of rows inserted + */ + int insertCronTrigger(Connection conn, CronTrigger trigger) + throws SQLException; + + /** + *

+ * Update the base trigger data. + *

+ * + * @param conn + * the DB Connection + * @param trigger + * the trigger to insert + * @param state + * the state that the trigger should be stored in + * @return the number of rows updated + */ + int updateTrigger(Connection conn, Trigger trigger, String state, + JobDetail jobDetail) throws SQLException, IOException; + + /** + *

+ * Update the simple trigger data. + *

+ * + * @param conn + * the DB Connection + * @param trigger + * the trigger to insert + * @return the number of rows updated + */ + int updateSimpleTrigger(Connection conn, SimpleTrigger trigger) + throws SQLException; + + /** + *

+ * Update the cron trigger data. + *

+ * + * @param conn + * the DB Connection + * @param trigger + * the trigger to insert + * @return the number of rows updated + */ + int updateCronTrigger(Connection conn, CronTrigger trigger) + throws SQLException; + + /** + *

+ * Update the blob trigger data. + *

+ * + * @param conn + * the DB Connection + * @param trigger + * the trigger to insert + * @return the number of rows updated + */ + int updateBlobTrigger(Connection conn, Trigger trigger) + throws SQLException, IOException; + + /** + *

+ * Check whether or not a trigger exists. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return the number of rows updated + */ + boolean triggerExists(Connection conn, String triggerName, + String groupName) throws SQLException; + + /** + *

+ * Update the state for a given trigger. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @param state + * the new state for the trigger + * @return the number of rows updated + */ + int updateTriggerState(Connection conn, String triggerName, + String groupName, String state) throws SQLException; + + /** + *

+ * Update the given trigger to the given new state, if it is in the given + * old state. + *

+ * + * @param conn + * the DB connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @param newState + * the new state for the trigger + * @param oldState + * the old state the trigger must be in + * @return int the number of rows updated + * @throws SQLException + */ + int updateTriggerStateFromOtherState(Connection conn, + String triggerName, String groupName, String newState, + String oldState) throws SQLException; + + /** + *

+ * Update the given trigger to the given new state, if it is one of the + * given old states. + *

+ * + * @param conn + * the DB connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @param newState + * the new state for the trigger + * @param oldState1 + * one of the old state the trigger must be in + * @param oldState2 + * one of the old state the trigger must be in + * @param oldState3 + * one of the old state the trigger must be in + * @return int the number of rows updated + * @throws SQLException + */ + int updateTriggerStateFromOtherStates(Connection conn, + String triggerName, String groupName, String newState, + String oldState1, String oldState2, String oldState3) + throws SQLException; + + /** + *

+ * Update the all triggers to the given new state, if they are in one of + * the given old states AND its next fire time is before the given time. + *

+ * + * @param conn + * the DB connection + * @param newState + * the new state for the trigger + * @param oldState1 + * one of the old state the trigger must be in + * @param oldState2 + * one of the old state the trigger must be in + * @param time + * the time before which the trigger's next fire time must be + * @return int the number of rows updated + * @throws SQLException + */ + int updateTriggerStateFromOtherStatesBeforeTime(Connection conn, + String newState, String oldState1, String oldState2, long time) + throws SQLException; + + /** + *

+ * Update all triggers in the given group to the given new state, if they + * are in one of the given old states. + *

+ * + * @param conn + * the DB connection + * @param groupName + * the group containing the trigger + * @param newState + * the new state for the trigger + * @param oldState1 + * one of the old state the trigger must be in + * @param oldState2 + * one of the old state the trigger must be in + * @param oldState3 + * one of the old state the trigger must be in + * @return int the number of rows updated + * @throws SQLException + */ + int updateTriggerGroupStateFromOtherStates(Connection conn, + String groupName, String newState, String oldState1, + String oldState2, String oldState3) throws SQLException; + + /** + *

+ * Update all of the triggers of the given group to the given new state, if + * they are in the given old state. + *

+ * + * @param conn + * the DB connection + * @param groupName + * the group containing the triggers + * @param newState + * the new state for the trigger group + * @param oldState + * the old state the triggers must be in + * @return int the number of rows updated + * @throws SQLException + */ + int updateTriggerGroupStateFromOtherState(Connection conn, + String groupName, String newState, String oldState) + throws SQLException; + + /** + *

+ * Update the states of all triggers associated with the given job. + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the name of the job + * @param groupName + * the group containing the job + * @param state + * the new state for the triggers + * @return the number of rows updated + */ + int updateTriggerStatesForJob(Connection conn, String jobName, + String groupName, String state) throws SQLException; + + /** + *

+ * Update the states of any triggers associated with the given job, that + * are the given current state. + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the name of the job + * @param groupName + * the group containing the job + * @param state + * the new state for the triggers + * @param oldState + * the old state of the triggers + * @return the number of rows updated + */ + int updateTriggerStatesForJobFromOtherState(Connection conn, + String jobName, String groupName, String state, String oldState) + throws SQLException; + + /** + *

+ * Delete all of the listeners associated with a given trigger. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger whose listeners will be deleted + * @param groupName + * the name of the group containing the trigger + * @return the number of rows deleted + */ + int deleteTriggerListeners(Connection conn, String triggerName, + String groupName) throws SQLException; + + /** + *

+ * Associate a listener with the given trigger. + *

+ * + * @param conn + * the DB Connection + * @param trigger + * the trigger + * @param listener + * the name of the listener to associate with the trigger + * @return the number of rows inserted + */ + int insertTriggerListener(Connection conn, Trigger trigger, + String listener) throws SQLException; + + /** + *

+ * Select the listeners associated with a given trigger. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return array of String trigger listener names + */ + String[] selectTriggerListeners(Connection conn, String triggerName, + String groupName) throws SQLException; + + /** + *

+ * Delete the simple trigger data for a trigger. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return the number of rows deleted + */ + int deleteSimpleTrigger(Connection conn, String triggerName, + String groupName) throws SQLException; + + /** + *

+ * Delete the BLOB trigger data for a trigger. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return the number of rows deleted + */ + int deleteBlobTrigger(Connection conn, String triggerName, + String groupName) throws SQLException; + + /** + *

+ * Delete the cron trigger data for a trigger. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return the number of rows deleted + */ + int deleteCronTrigger(Connection conn, String triggerName, + String groupName) throws SQLException; + + /** + *

+ * Delete the base trigger data for a trigger. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return the number of rows deleted + */ + int deleteTrigger(Connection conn, String triggerName, + String groupName) throws SQLException; + + /** + *

+ * Select the number of triggers associated with a given job. + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the name of the job + * @param groupName + * the group containing the job + * @return the number of triggers for the given job + */ + int selectNumTriggersForJob(Connection conn, String jobName, + String groupName) throws SQLException; + + /** + *

+ * Select the job to which the trigger is associated. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return the {@link com.fr.third.org.quartz.JobDetail} object + * associated with the given trigger + */ + JobDetail selectJobForTrigger(Connection conn, String triggerName, + String groupName, ClassLoadHelper loadHelper) + throws ClassNotFoundException, SQLException; + + /** + *

+ * Select the stateful jobs which are referenced by triggers in the given + * trigger group. + *

+ * + * @param conn + * the DB Connection + * @param groupName + * the trigger group + * @return a List of Keys to jobs. + */ + List selectStatefulJobsOfTriggerGroup(Connection conn, + String groupName) throws SQLException; + + /** + *

+ * Select the triggers for a job + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return an array of (@link com.fr.third.org.quartz.Trigger) objects + * associated with a given job. + * @throws SQLException + */ + Trigger[] selectTriggersForJob(Connection conn, String jobName, + String groupName) throws SQLException, ClassNotFoundException, + IOException; + + /** + *

+ * Select the triggers for a calendar + *

+ * + * @param conn + * the DB Connection + * @param calName + * the name of the calendar + * @return an array of (@link com.fr.third.org.quartz.Trigger) objects + * associated with the given calendar. + * @throws SQLException + */ + Trigger[] selectTriggersForCalendar(Connection conn, String calName) + throws SQLException, ClassNotFoundException, IOException; + /** + *

+ * Select a trigger. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return the {@link com.fr.third.org.quartz.Trigger} object + */ + Trigger selectTrigger(Connection conn, String triggerName, + String groupName) throws SQLException, ClassNotFoundException, + IOException; + + /** + *

+ * Select a trigger's JobDataMap. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return the {@link com.fr.third.org.quartz.JobDataMap} of the Trigger, + * never null, but possibly empty. + */ + JobDataMap selectTriggerJobDataMap(Connection conn, String triggerName, + String groupName) throws SQLException, ClassNotFoundException, + IOException; + + /** + *

+ * Select a trigger' state value. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return the {@link com.fr.third.org.quartz.Trigger} object + */ + String selectTriggerState(Connection conn, String triggerName, + String groupName) throws SQLException; + + /** + *

+ * Select a trigger' status (state & next fire time). + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return a TriggerStatus object, or null + */ + TriggerStatus selectTriggerStatus(Connection conn, + String triggerName, String groupName) throws SQLException; + + /** + *

+ * Select the total number of triggers stored. + *

+ * + * @param conn + * the DB Connection + * @return the total number of triggers stored + */ + int selectNumTriggers(Connection conn) throws SQLException; + + /** + *

+ * Select all of the trigger group names that are stored. + *

+ * + * @param conn + * the DB Connection + * @return an array of String group names + */ + String[] selectTriggerGroups(Connection conn) throws SQLException; + + /** + *

+ * Select all of the triggers contained in a given group. + *

+ * + * @param conn + * the DB Connection + * @param groupName + * the group containing the triggers + * @return an array of String trigger names + */ + String[] selectTriggersInGroup(Connection conn, String groupName) + throws SQLException; + + /** + *

+ * Select all of the triggers in a given state. + *

+ * + * @param conn + * the DB Connection + * @param state + * the state the triggers must be in + * @return an array of trigger Key s + */ + Key[] selectTriggersInState(Connection conn, String state) + throws SQLException; + + int insertPausedTriggerGroup(Connection conn, String groupName) + throws SQLException; + + int deletePausedTriggerGroup(Connection conn, String groupName) + throws SQLException; + + int deleteAllPausedTriggerGroups(Connection conn) + throws SQLException; + + boolean isTriggerGroupPaused(Connection conn, String groupName) + throws SQLException; + + Set selectPausedTriggerGroups(Connection conn) + throws SQLException; + + boolean isExistingTriggerGroup(Connection conn, String groupName) + throws SQLException; + + //--------------------------------------------------------------------------- + // calendars + //--------------------------------------------------------------------------- + + /** + *

+ * Insert a new calendar. + *

+ * + * @param conn + * the DB Connection + * @param calendarName + * the name for the new calendar + * @param calendar + * the calendar + * @return the number of rows inserted + * @throws IOException + * if there were problems serializing the calendar + */ + int insertCalendar(Connection conn, String calendarName, + Calendar calendar) throws IOException, SQLException; + + /** + *

+ * Update a calendar. + *

+ * + * @param conn + * the DB Connection + * @param calendarName + * the name for the new calendar + * @param calendar + * the calendar + * @return the number of rows updated + * @throws IOException + * if there were problems serializing the calendar + */ + int updateCalendar(Connection conn, String calendarName, + Calendar calendar) throws IOException, SQLException; + + /** + *

+ * Check whether or not a calendar exists. + *

+ * + * @param conn + * the DB Connection + * @param calendarName + * the name of the calendar + * @return true if the trigger exists, false otherwise + */ + boolean calendarExists(Connection conn, String calendarName) + throws SQLException; + + /** + *

+ * Select a calendar. + *

+ * + * @param conn + * the DB Connection + * @param calendarName + * the name of the calendar + * @return the Calendar + * @throws ClassNotFoundException + * if a class found during deserialization cannot be found be + * found + * @throws IOException + * if there were problems deserializing the calendar + */ + Calendar selectCalendar(Connection conn, String calendarName) + throws ClassNotFoundException, IOException, SQLException; + + /** + *

+ * Check whether or not a calendar is referenced by any triggers. + *

+ * + * @param conn + * the DB Connection + * @param calendarName + * the name of the calendar + * @return true if any triggers reference the calendar, false otherwise + */ + boolean calendarIsReferenced(Connection conn, String calendarName) + throws SQLException; + + /** + *

+ * Delete a calendar. + *

+ * + * @param conn + * the DB Connection + * @param calendarName + * the name of the trigger + * @return the number of rows deleted + */ + int deleteCalendar(Connection conn, String calendarName) + throws SQLException; + + /** + *

+ * Select the total number of calendars stored. + *

+ * + * @param conn + * the DB Connection + * @return the total number of calendars stored + */ + int selectNumCalendars(Connection conn) throws SQLException; + + /** + *

+ * Select all of the stored calendars. + *

+ * + * @param conn + * the DB Connection + * @return an array of String calendar names + */ + String[] selectCalendars(Connection conn) throws SQLException; + + //--------------------------------------------------------------------------- + // trigger firing + //--------------------------------------------------------------------------- + + /** + *

+ * Select the next time that a trigger will be fired. + *

+ * + * @param conn + * the DB Connection + * @return the next fire time, or 0 if no trigger will be fired + * + * @deprecated Does not account for misfires. + */ + long selectNextFireTime(Connection conn) throws SQLException; + + /** + *

+ * Select the trigger that will be fired at the given fire time. + *

+ * + * @param conn + * the DB Connection + * @param fireTime + * the time that the trigger will be fired + * @return a {@link com.fr.third.org.quartz.utils.Key} representing the + * trigger that will be fired at the given fire time, or null if no + * trigger will be fired at that time + */ + Key selectTriggerForFireTime(Connection conn, long fireTime) + throws SQLException; + + /** + *

+ * Select the next trigger which will fire to fire between the two given timestamps + * in ascending order of fire time, and then descending by priority. + *

+ * + * @param conn + * the DB Connection + * @param noLaterThan + * highest value of getNextFireTime() of the triggers (exclusive) + * @param noEarlierThan + * highest value of getNextFireTime() of the triggers (inclusive) + * + * @return A (never null, possibly empty) list of the identifiers (Key objects) of the next triggers to be fired. + */ + List selectTriggerToAcquire(Connection conn, long noLaterThan, long noEarlierThan) + throws SQLException; + + /** + *

+ * Insert a fired trigger. + *

+ * + * @param conn + * the DB Connection + * @param trigger + * the trigger + * @param state + * the state that the trigger should be stored in + * @return the number of rows inserted + */ + int insertFiredTrigger(Connection conn, Trigger trigger, + String state, JobDetail jobDetail) throws SQLException; + + /** + *

+ * Select the states of all fired-trigger records for a given trigger, or + * trigger group if trigger name is null. + *

+ * + * @return a List of FiredTriggerRecord objects. + */ + List selectFiredTriggerRecords(Connection conn, String triggerName, + String groupName) throws SQLException; + + /** + *

+ * Select the states of all fired-trigger records for a given job, or job + * group if job name is null. + *

+ * + * @return a List of FiredTriggerRecord objects. + */ + List selectFiredTriggerRecordsByJob(Connection conn, String jobName, + String groupName) throws SQLException; + + /** + *

+ * Select the states of all fired-trigger records for a given scheduler + * instance. + *

+ * + * @return a List of FiredTriggerRecord objects. + */ + List selectInstancesFiredTriggerRecords(Connection conn, + String instanceName) throws SQLException; + + + /** + *

+ * Select the distinct instance names of all fired-trigger records. + *

+ * + *

+ * This is useful when trying to identify orphaned fired triggers (a + * fired trigger without a scheduler state record.) + *

+ * + * @return a Set of String objects. + */ + Set selectFiredTriggerInstanceNames(Connection conn) + throws SQLException; + + /** + *

+ * Delete a fired trigger. + *

+ * + * @param conn + * the DB Connection + * @param entryId + * the fired trigger entry to delete + * @return the number of rows deleted + */ + int deleteFiredTrigger(Connection conn, String entryId) + throws SQLException; + + /** + *

+ * Get the number instances of the identified job currently executing. + *

+ * + * @param conn + * the DB Connection + * @return the number instances of the identified job currently executing. + */ + int selectJobExecutionCount(Connection conn, String jobName, + String jobGroup) throws SQLException; + + /** + *

+ * Insert a scheduler-instance state record. + *

+ * + * @param conn + * the DB Connection + * @return the number of inserted rows. + */ + int insertSchedulerState(Connection conn, String instanceId, + long checkInTime, long interval) + throws SQLException; + + /** + *

+ * Delete a scheduler-instance state record. + *

+ * + * @param conn + * the DB Connection + * @return the number of deleted rows. + */ + int deleteSchedulerState(Connection conn, String instanceId) + throws SQLException; + + + /** + *

+ * Update a scheduler-instance state record. + *

+ * + * @param conn + * the DB Connection + * @return the number of updated rows. + */ + int updateSchedulerState(Connection conn, String instanceId, long checkInTime) + throws SQLException; + + /** + *

+ * A List of all current SchedulerStateRecords. + *

+ * + *

+ * If instanceId is not null, then only the record for the identified + * instance will be returned. + *

+ * + * @param conn + * the DB Connection + */ + List selectSchedulerStateRecords(Connection conn, String instanceId) + throws SQLException; + +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/FiredTriggerRecord.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/FiredTriggerRecord.java new file mode 100644 index 000000000..05532f0c7 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/FiredTriggerRecord.java @@ -0,0 +1,154 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import com.fr.third.org.quartz.utils.Key; + +/** + *

+ * Conveys the state of a fired-trigger record. + *

+ * + * @author James House + */ +public class FiredTriggerRecord implements java.io.Serializable { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private String fireInstanceId; + + private long fireTimestamp; + + private String schedulerInstanceId; + + private Key triggerKey; + + private String fireInstanceState; + + private boolean triggerIsVolatile; + + private Key jobKey; + + private boolean jobIsStateful; + + private boolean jobRequestsRecovery; + + private int priority; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public String getFireInstanceId() { + return fireInstanceId; + } + + public long getFireTimestamp() { + return fireTimestamp; + } + + public boolean isJobIsStateful() { + return jobIsStateful; + } + + public Key getJobKey() { + return jobKey; + } + + public String getSchedulerInstanceId() { + return schedulerInstanceId; + } + + public Key getTriggerKey() { + return triggerKey; + } + + public String getFireInstanceState() { + return fireInstanceState; + } + + public void setFireInstanceId(String string) { + fireInstanceId = string; + } + + public void setFireTimestamp(long l) { + fireTimestamp = l; + } + + public void setJobIsStateful(boolean b) { + jobIsStateful = b; + } + + public void setJobKey(Key key) { + jobKey = key; + } + + public void setSchedulerInstanceId(String string) { + schedulerInstanceId = string; + } + + public void setTriggerKey(Key key) { + triggerKey = key; + } + + public void setFireInstanceState(String string) { + fireInstanceState = string; + } + + public boolean isJobRequestsRecovery() { + return jobRequestsRecovery; + } + + public boolean isTriggerIsVolatile() { + return triggerIsVolatile; + } + + public void setJobRequestsRecovery(boolean b) { + jobRequestsRecovery = b; + } + + public void setTriggerIsVolatile(boolean b) { + triggerIsVolatile = b; + } + + public int getPriority() { + return priority; + } + + + public void setPriority(int priority) { + this.priority = priority; + } + + +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/HSQLDBDelegate.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/HSQLDBDelegate.java new file mode 100644 index 000000000..5b5b32ce0 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/HSQLDBDelegate.java @@ -0,0 +1,122 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.apache.commons.logging.Log; + +/** + *

+ * This is a driver delegate for the HSQLDB database. + *

+ * + * @author James House + * @author Jeffrey Wescott + */ +public class HSQLDBDelegate extends StdJDBCDelegate { + /** + *

+ * Create new HSQLDBDelegate instance. + *

+ * + * @param log + * the logger to use during execution + * @param tablePrefix + * the prefix of all table names + */ + public HSQLDBDelegate(Log log, String tablePrefix, String instanceId) { + super(log, tablePrefix, instanceId); + } + + /** + *

+ * Create new MSSQLDelegate instance. + *

+ * + * @param log + * the logger to use during execution + * @param tablePrefix + * the prefix of all table names + * @param useProperties + * use java.util.Properties for storage + */ + public HSQLDBDelegate(Log log, String tablePrefix, String instanceId, + Boolean useProperties) { + super(log, tablePrefix, instanceId, useProperties); + } + + //--------------------------------------------------------------------------- + // protected methods that can be overridden by subclasses + //--------------------------------------------------------------------------- + + /** + *

+ * This method should be overridden by any delegate subclasses that need + * special handling for BLOBs. The default implementation uses standard + * JDBC java.sql.Blob operations. + *

+ * + * @param rs + * the result set, already queued to the correct row + * @param colName + * the column name for the BLOB + * @return the deserialized Object from the ResultSet BLOB + * @throws ClassNotFoundException + * if a class found during deserialization cannot be found + * @throws IOException + * if deserialization causes an error + */ + protected Object getObjectFromBlob(ResultSet rs, String colName) + throws ClassNotFoundException, IOException, SQLException { + InputStream binaryInput = rs.getBinaryStream(colName); + + if(binaryInput == null || binaryInput.available() == 0) { + return null; + } + + Object obj = null; + + ObjectInputStream in = new ObjectInputStream(binaryInput); + try { + obj = in.readObject(); + } finally { + in.close(); + } + + return obj; + } + + protected Object getJobDetailFromBlob(ResultSet rs, String colName) + throws ClassNotFoundException, IOException, SQLException { + if (canUseProperties()) { + InputStream binaryInput = rs.getBinaryStream(colName); + return binaryInput; + } + return getObjectFromBlob(rs, colName); + } +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/InvalidConfigurationException.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/InvalidConfigurationException.java new file mode 100644 index 000000000..725f8a59d --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/InvalidConfigurationException.java @@ -0,0 +1,50 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +/** + *

+ * Exception class for when a driver delegate cannot be found for a given + * configuration, or lack thereof. + *

+ * + * @author Jeffrey Wescott + */ +public class InvalidConfigurationException extends Exception { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public InvalidConfigurationException(String msg) { + super(msg); + } + + public InvalidConfigurationException() { + super(); + } +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/JTANonClusteredSemaphore.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/JTANonClusteredSemaphore.java new file mode 100644 index 000000000..1c5b9e85c --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/JTANonClusteredSemaphore.java @@ -0,0 +1,304 @@ +/* + * Copyright 2004-2006 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.sql.Connection; +import java.util.HashSet; + +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.transaction.Synchronization; +import javax.transaction.SystemException; +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Provides in memory thread/resource locking that is JTA + * {@link javax.transaction.Transaction} aware. + * It is most appropriate for use when using + * {@link com.fr.third.org.quartz.impl.jdbcjobstore.JobStoreCMT} without clustering. + * + *

+ * This Semaphore implementation is not Quartz cluster safe. + *

+ * + *

+ * When a lock is obtained/released but there is no active JTA + * {@link javax.transaction.Transaction}, then this Semaphore operates + * just like {@link com.fr.third.org.quartz.impl.jdbcjobstore.SimpleSemaphore}. + *

+ * + *

+ * By default, this class looks for the {@link javax.transaction.TransactionManager} + * in JNDI under name "java:TransactionManager". If this is not where your Application Server + * registers it, you can modify the JNDI lookup location using the + * "transactionManagerJNDIName" property. + *

+ * + *

+ * IMPORTANT: This Semaphore implementation is currently experimental. + * It has been tested a limited amount on JBoss 4.0.3SP1. If you do choose to + * use it, any feedback would be most appreciated! + *

+ * + * @see com.fr.third.org.quartz.impl.jdbcjobstore.SimpleSemaphore + */ +public class JTANonClusteredSemaphore implements Semaphore { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public static final String DEFAULT_TRANSACTION_MANANGER_LOCATION = "java:TransactionManager"; + + ThreadLocal lockOwners = new ThreadLocal(); + + HashSet locks = new HashSet(); + + private final Log log = LogFactory.getLog(getClass()); + + private String transactionManagerJNDIName = DEFAULT_TRANSACTION_MANANGER_LOCATION; + + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + protected Log getLog() { + return log; + } + + public void setTransactionManagerJNDIName(String transactionManagerJNDIName) { + this.transactionManagerJNDIName = transactionManagerJNDIName; + } + + private HashSet getThreadLocks() { + HashSet threadLocks = (HashSet) lockOwners.get(); + if (threadLocks == null) { + threadLocks = new HashSet(); + lockOwners.set(threadLocks); + } + return threadLocks; + } + + /** + * Grants a lock on the identified resource to the calling thread (blocking + * until it is available). + * + * @return true if the lock was obtained. + */ + public synchronized boolean obtainLock(Connection conn, String lockName) throws LockException { + + lockName = lockName.intern(); + + Log log = getLog(); + + if(log.isDebugEnabled()) { + log.debug( + "Lock '" + lockName + "' is desired by: " + + Thread.currentThread().getName()); + } + + if (!isLockOwner(conn, lockName)) { + if(log.isDebugEnabled()) { + log.debug( + "Lock '" + lockName + "' is being obtained: " + + Thread.currentThread().getName()); + } + + while (locks.contains(lockName)) { + try { + this.wait(); + } catch (InterruptedException ie) { + if(log.isDebugEnabled()) { + log.debug( + "Lock '" + lockName + "' was not obtained by: " + + Thread.currentThread().getName()); + } + } + } + + // If we are in a transaction, register a callback to actually release + // the lock when the transaction completes + Transaction t = getTransaction(); + if (t != null) { + try { + t.registerSynchronization(new SemaphoreSynchronization(lockName)); + } catch (Exception e) { + throw new LockException("Failed to register semaphore with Transaction.", e); + } + } + + if(log.isDebugEnabled()) { + log.debug( + "Lock '" + lockName + "' given to: " + + Thread.currentThread().getName()); + } + + + getThreadLocks().add(lockName); + locks.add(lockName); + } else if(log.isDebugEnabled()) { + log.debug( + "Lock '" + lockName + "' already owned by: " + + Thread.currentThread().getName() + + " -- but not owner!", + new Exception("stack-trace of wrongful returner")); + } + + return true; + } + + /** + * Helper method to get the current {@link javax.transaction.Transaction} + * from the {@link javax.transaction.TransactionManager} in JNDI. + * + * @return The current {@link javax.transaction.Transaction}, null if + * not currently in a transaction. + */ + protected Transaction getTransaction() throws LockException{ + InitialContext ic = null; + try { + ic = new InitialContext(); + TransactionManager tm = (TransactionManager)ic.lookup(transactionManagerJNDIName); + + return tm.getTransaction(); + } catch (SystemException e) { + throw new LockException("Failed to get Transaction from TransactionManager", e); + } catch (NamingException e) { + throw new LockException("Failed to find TransactionManager in JNDI under name: " + transactionManagerJNDIName, e); + } finally { + if (ic != null) { + try { + ic.close(); + } catch (NamingException ignored) { + } + } + } + } + + /** + * Release the lock on the identified resource if it is held by the calling + * thread, unless currently in a JTA transaction. + */ + public synchronized void releaseLock(Connection conn, String lockName) throws LockException { + releaseLock(lockName, false); + } + + /** + * Release the lock on the identified resource if it is held by the calling + * thread, unless currently in a JTA transaction. + * + * @param fromSynchronization True if this method is being invoked from + * {@link Synchronization} notified of the enclosing + * transaction having completed. + * + * @throws LockException Thrown if there was a problem accessing the JTA + * Transaction. Only relevant if fromSynchronization + * is false. + */ + protected synchronized void releaseLock( + String lockName, boolean fromSynchronization) throws LockException { + lockName = lockName.intern(); + + if (isLockOwner(null, lockName)) { + + if (fromSynchronization == false) { + Transaction t = getTransaction(); + if (t != null) { + if(getLog().isDebugEnabled()) { + getLog().debug( + "Lock '" + lockName + "' is in a JTA transaction. " + + "Return deferred by: " + Thread.currentThread().getName()); + } + + // If we are still in a transaction, then we don't want to + // actually release the lock. + return; + } + } + + if(getLog().isDebugEnabled()) { + getLog().debug( + "Lock '" + lockName + "' returned by: " + + Thread.currentThread().getName()); + } + getThreadLocks().remove(lockName); + locks.remove(lockName); + this.notify(); + } else if (getLog().isDebugEnabled()) { + getLog().debug( + "Lock '" + lockName + "' attempt to return by: " + + Thread.currentThread().getName() + + " -- but not owner!", + new Exception("stack-trace of wrongful returner")); + } + } + + /** + * Determine whether the calling thread owns a lock on the identified + * resource. + */ + public synchronized boolean isLockOwner(Connection conn, String lockName) { + lockName = lockName.intern(); + + return getThreadLocks().contains(lockName); + } + + /** + * This Semaphore implementation does not use the database. + */ + public boolean requiresConnection() { + return false; + } + + /** + * Helper class that is registered with the active + * {@link javax.transaction.Transaction} so that the lock + * will be released when the transaction completes. + */ + private class SemaphoreSynchronization implements Synchronization { + private String lockName; + + public SemaphoreSynchronization(String lockName) { + this.lockName = lockName; + } + + public void beforeCompletion() { + // nothing to do... + } + + public void afterCompletion(int status) { + try { + releaseLock(lockName, true); + } catch (LockException e) { + // Ignore as can't be thrown with fromSynchronization set to true + } + } + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/JobStoreCMT.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/JobStoreCMT.java new file mode 100644 index 000000000..da194bc76 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/JobStoreCMT.java @@ -0,0 +1,255 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.sql.Connection; +import java.sql.SQLException; + +import com.fr.third.org.quartz.JobPersistenceException; +import com.fr.third.org.quartz.SchedulerConfigException; +import com.fr.third.org.quartz.spi.ClassLoadHelper; +import com.fr.third.org.quartz.spi.SchedulerSignaler; +import com.fr.third.org.quartz.utils.DBConnectionManager; + +/** + *

+ * JobStoreCMT is meant to be used in an application-server + * environment that provides container-managed-transactions. No commit / + * rollback will be1 handled by this class. + *

+ * + *

+ * If you need commit / rollback, use {@link + * com.fr.third.org.quartz.impl.jdbcjobstore.JobStoreTX} + * instead. + *

+ * + * @author Jeffrey Wescott + * @author James House + * @author Srinivas Venkatarangaiah + * + */ +public class JobStoreCMT extends JobStoreSupport { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + protected String nonManagedTxDsName; + + // Great name huh? + protected boolean dontSetNonManagedTXConnectionAutoCommitFalse = false; + + + protected boolean setTxIsolationLevelReadCommitted = false; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Set the name of the DataSource that should be used for + * performing database functions. + *

+ */ + public void setNonManagedTXDataSource(String nonManagedTxDsName) { + this.nonManagedTxDsName = nonManagedTxDsName; + } + + /** + *

+ * Get the name of the DataSource that should be used for + * performing database functions. + *

+ */ + public String getNonManagedTXDataSource() { + return nonManagedTxDsName; + } + + public boolean isDontSetNonManagedTXConnectionAutoCommitFalse() { + return dontSetNonManagedTXConnectionAutoCommitFalse; + } + + /** + * Don't call set autocommit(false) on connections obtained from the + * DataSource. This can be helpfull in a few situations, such as if you + * have a driver that complains if it is called when it is already off. + * + * @param b + */ + public void setDontSetNonManagedTXConnectionAutoCommitFalse(boolean b) { + dontSetNonManagedTXConnectionAutoCommitFalse = b; + } + + + public boolean isTxIsolationLevelReadCommitted() { + return setTxIsolationLevelReadCommitted; + } + + /** + * Set the transaction isolation level of DB connections to sequential. + * + * @param b + */ + public void setTxIsolationLevelReadCommitted(boolean b) { + setTxIsolationLevelReadCommitted = b; + } + + + public void initialize(ClassLoadHelper loadHelper, + SchedulerSignaler signaler) throws SchedulerConfigException { + + if (nonManagedTxDsName == null) { + throw new SchedulerConfigException( + "Non-ManagedTX DataSource name not set! " + + "If your 'com.fr.third.org.quartz.jobStore.dataSource' is XA, then set " + + "'com.fr.third.org.quartz.jobStore.nonManagedTXDataSource' to a non-XA "+ + "datasource (for the same DB). " + + "Otherwise, you can set them to be the same."); + } + + if (getLockHandler() == null) { + // If the user hasn't specified an explicit lock handler, + // then we *must* use DB locks with CMT... + setUseDBLocks(true); + } + + super.initialize(loadHelper, signaler); + + getLog().info("JobStoreCMT initialized."); + } + + public void shutdown() { + + super.shutdown(); + + try { + DBConnectionManager.getInstance().shutdown(getNonManagedTXDataSource()); + } catch (SQLException sqle) { + getLog().warn("Database connection shutdown unsuccessful.", sqle); + } + } + + protected Connection getNonManagedTXConnection() + throws JobPersistenceException { + Connection conn = null; + try { + conn = DBConnectionManager.getInstance().getConnection( + getNonManagedTXDataSource()); + } catch (SQLException sqle) { + throw new JobPersistenceException( + "Failed to obtain DB connection from data source '" + + getNonManagedTXDataSource() + "': " + + sqle.toString(), sqle); + } catch (Throwable e) { + throw new JobPersistenceException( + "Failed to obtain DB connection from data source '" + + getNonManagedTXDataSource() + "': " + + e.toString(), e, + JobPersistenceException.ERR_PERSISTENCE_CRITICAL_FAILURE); + } + + if (conn == null) { + throw new JobPersistenceException( + "Could not get connection from DataSource '" + + getNonManagedTXDataSource() + "'"); + } + + // Protect connection attributes we might change. + conn = getAttributeRestoringConnection(conn); + + // Set any connection connection attributes we are to override. + try { + if (!isDontSetNonManagedTXConnectionAutoCommitFalse()) { + conn.setAutoCommit(false); + } + + if (isTxIsolationLevelReadCommitted()) { + conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); + } + } catch (SQLException sqle) { + getLog().warn("Failed to override connection auto commit/transaction isolation.", sqle); + } catch (Throwable e) { + try { conn.close(); } catch(Throwable tt) {} + + throw new JobPersistenceException( + "Failure setting up connection.", e); + } + + return conn; + } + + /** + * Execute the given callback having optionally aquired the given lock. + * Because CMT assumes that the connection is already part of a managed + * transaction, it does not attempt to commit or rollback the + * enclosing transaction. + * + * @param lockName The name of the lock to aquire, for example + * "TRIGGER_ACCESS". If null, then no lock is aquired, but the + * txCallback is still executed in a transaction. + * + * @see JobStoreSupport#executeInNonManagedTXLock(String, TransactionCallback) + * @see JobStoreTX#executeInLock(String, TransactionCallback) + * @see JobStoreSupport#getNonManagedTXConnection() + * @see JobStoreSupport#getConnection() + */ + protected Object executeInLock( + String lockName, + TransactionCallback txCallback) throws JobPersistenceException { + boolean transOwner = false; + Connection conn = null; + try { + if (lockName != null) { + // If we aren't using db locks, then delay getting DB connection + // until after aquiring the lock since it isn't needed. + if (getLockHandler().requiresConnection()) { + conn = getConnection(); + } + + transOwner = getLockHandler().obtainLock(conn, lockName); + } + + if (conn == null) { + conn = getConnection(); + } + + return txCallback.execute(conn); + } finally { + try { + releaseLock(conn, LOCK_TRIGGER_ACCESS, transOwner); + } finally { + cleanupConnection(conn); + } + } + } +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/JobStoreSupport.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/JobStoreSupport.java new file mode 100644 index 000000000..a456b79c9 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/JobStoreSupport.java @@ -0,0 +1,3936 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Proxy; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.Calendar; +import com.fr.third.org.quartz.CronTrigger; +import com.fr.third.org.quartz.JobDataMap; +import com.fr.third.org.quartz.JobDetail; +import com.fr.third.org.quartz.JobPersistenceException; +import com.fr.third.org.quartz.ObjectAlreadyExistsException; +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SchedulerConfigException; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.SimpleTrigger; +import com.fr.third.org.quartz.Trigger; +import com.fr.third.org.quartz.core.SchedulingContext; +import com.fr.third.org.quartz.spi.ClassLoadHelper; +import com.fr.third.org.quartz.spi.JobStore; +import com.fr.third.org.quartz.spi.SchedulerSignaler; +import com.fr.third.org.quartz.spi.TriggerFiredBundle; +import com.fr.third.org.quartz.utils.DBConnectionManager; +import com.fr.third.org.quartz.utils.Key; +import com.fr.third.org.quartz.utils.TriggerStatus; + + +/** + *

+ * Contains base functionality for JDBC-based JobStore implementations. + *

+ * + * @author Jeffrey Wescott + * @author James House + */ +public abstract class JobStoreSupport implements JobStore, Constants { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + protected static final String LOCK_TRIGGER_ACCESS = "TRIGGER_ACCESS"; + + protected static final String LOCK_JOB_ACCESS = "JOB_ACCESS"; + + protected static final String LOCK_CALENDAR_ACCESS = "CALENDAR_ACCESS"; + + protected static final String LOCK_STATE_ACCESS = "STATE_ACCESS"; + + protected static final String LOCK_MISFIRE_ACCESS = "MISFIRE_ACCESS"; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + protected String dsName; + + protected String tablePrefix = DEFAULT_TABLE_PREFIX; + + protected boolean useProperties = false; + + protected String instanceId; + + protected String instanceName; + + protected String delegateClassName; + protected Class delegateClass = StdJDBCDelegate.class; + + protected HashMap calendarCache = new HashMap(); + + private DriverDelegate delegate; + + private long misfireThreshold = 60000L; // one minute + + private boolean dontSetAutoCommitFalse = false; + + private boolean isClustered = false; + + private boolean useDBLocks = false; + + private boolean lockOnInsert = true; + + private Semaphore lockHandler = null; // set in initialize() method... + + private String selectWithLockSQL = null; + + private long clusterCheckinInterval = 7500L; + + private ClusterManager clusterManagementThread = null; + + private MisfireHandler misfireHandler = null; + + private ClassLoadHelper classLoadHelper; + + private SchedulerSignaler signaler; + + protected int maxToRecoverAtATime = 20; + + private boolean setTxIsolationLevelSequential = false; + + private boolean acquireTriggersWithinLock = false; + + private long dbRetryInterval = 10000; + + private boolean makeThreadsDaemons = false; + + private boolean threadsInheritInitializersClassLoadContext = false; + private ClassLoader initializersLoader = null; + + private boolean doubleCheckLockMisfireHandler = true; + + private final Log log = LogFactory.getLog(getClass()); + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Set the name of the DataSource that should be used for + * performing database functions. + *

+ */ + public void setDataSource(String dsName) { + this.dsName = dsName; + } + + /** + *

+ * Get the name of the DataSource that should be used for + * performing database functions. + *

+ */ + public String getDataSource() { + return dsName; + } + + /** + *

+ * Set the prefix that should be pre-pended to all table names. + *

+ */ + public void setTablePrefix(String prefix) { + if (prefix == null) { + prefix = ""; + } + + this.tablePrefix = prefix; + } + + /** + *

+ * Get the prefix that should be pre-pended to all table names. + *

+ */ + public String getTablePrefix() { + return tablePrefix; + } + + /** + *

+ * Set whether String-only properties will be handled in JobDataMaps. + *

+ */ + public void setUseProperties(String useProp) { + if (useProp == null) { + useProp = "false"; + } + + this.useProperties = Boolean.valueOf(useProp).booleanValue(); + } + + /** + *

+ * Get whether String-only properties will be handled in JobDataMaps. + *

+ */ + public boolean canUseProperties() { + return useProperties; + } + + /** + *

+ * Set the instance Id of the Scheduler (must be unique within a cluster). + *

+ */ + public void setInstanceId(String instanceId) { + this.instanceId = instanceId; + } + + /** + *

+ * Get the instance Id of the Scheduler (must be unique within a cluster). + *

+ */ + public String getInstanceId() { + + return instanceId; + } + + /** + * Set the instance name of the Scheduler (must be unique within this server instance). + */ + public void setInstanceName(String instanceName) { + this.instanceName = instanceName; + } + + /** + * Get the instance name of the Scheduler (must be unique within this server instance). + */ + public String getInstanceName() { + + return instanceName; + } + + /** + *

+ * Set whether this instance is part of a cluster. + *

+ */ + public void setIsClustered(boolean isClustered) { + this.isClustered = isClustered; + } + + /** + *

+ * Get whether this instance is part of a cluster. + *

+ */ + public boolean isClustered() { + return isClustered; + } + + /** + *

+ * Get the frequency (in milliseconds) at which this instance "checks-in" + * with the other instances of the cluster. -- Affects the rate of + * detecting failed instances. + *

+ */ + public long getClusterCheckinInterval() { + return clusterCheckinInterval; + } + + /** + *

+ * Set the frequency (in milliseconds) at which this instance "checks-in" + * with the other instances of the cluster. -- Affects the rate of + * detecting failed instances. + *

+ */ + public void setClusterCheckinInterval(long l) { + clusterCheckinInterval = l; + } + + /** + *

+ * Get the maximum number of misfired triggers that the misfire handling + * thread will try to recover at one time (within one transaction). The + * default is 20. + *

+ */ + public int getMaxMisfiresToHandleAtATime() { + return maxToRecoverAtATime; + } + + /** + *

+ * Set the maximum number of misfired triggers that the misfire handling + * thread will try to recover at one time (within one transaction). The + * default is 20. + *

+ */ + public void setMaxMisfiresToHandleAtATime(int maxToRecoverAtATime) { + this.maxToRecoverAtATime = maxToRecoverAtATime; + } + + /** + * @return Returns the dbRetryInterval. + */ + public long getDbRetryInterval() { + return dbRetryInterval; + } + /** + * @param dbRetryInterval The dbRetryInterval to set. + */ + public void setDbRetryInterval(long dbRetryInterval) { + this.dbRetryInterval = dbRetryInterval; + } + + /** + *

+ * Set whether this instance should use database-based thread + * synchronization. + *

+ */ + public void setUseDBLocks(boolean useDBLocks) { + this.useDBLocks = useDBLocks; + } + + /** + *

+ * Get whether this instance should use database-based thread + * synchronization. + *

+ */ + public boolean getUseDBLocks() { + return useDBLocks; + } + + public boolean isLockOnInsert() { + return lockOnInsert; + } + + /** + * Whether or not to obtain locks when inserting new jobs/triggers. + * Defaults to true, which is safest - some db's (such as + * MS SQLServer) seem to require this to avoid deadlocks under high load, + * while others seem to do fine without. + * + *

Setting this property to false will provide a + * significant performance increase during the addition of new jobs + * and triggers.

+ * + * @param lockOnInsert + */ + public void setLockOnInsert(boolean lockOnInsert) { + this.lockOnInsert = lockOnInsert; + } + + public long getMisfireThreshold() { + return misfireThreshold; + } + + /** + * The the number of milliseconds by which a trigger must have missed its + * next-fire-time, in order for it to be considered "misfired" and thus + * have its misfire instruction applied. + * + * @param misfireThreshold + */ + public void setMisfireThreshold(long misfireThreshold) { + if (misfireThreshold < 1) { + throw new IllegalArgumentException( + "Misfirethreshold must be larger than 0"); + } + this.misfireThreshold = misfireThreshold; + } + + public boolean isDontSetAutoCommitFalse() { + return dontSetAutoCommitFalse; + } + + /** + * Don't call set autocommit(false) on connections obtained from the + * DataSource. This can be helpfull in a few situations, such as if you + * have a driver that complains if it is called when it is already off. + * + * @param b + */ + public void setDontSetAutoCommitFalse(boolean b) { + dontSetAutoCommitFalse = b; + } + + public boolean isTxIsolationLevelSerializable() { + return setTxIsolationLevelSequential; + } + + /** + * Set the transaction isolation level of DB connections to sequential. + * + * @param b + */ + public void setTxIsolationLevelSerializable(boolean b) { + setTxIsolationLevelSequential = b; + } + + /** + * Whether or not the query and update to acquire a Trigger for firing + * should be performed after obtaining an explicit DB lock (to avoid + * possible race conditions on the trigger's db row). This is the + * behavior prior to Quartz 1.6.3, but is considered unnecessary for most + * databases (due to the nature of the SQL update that is performed), + * and therefore a superfluous performance hit. + */ + public boolean isAcquireTriggersWithinLock() { + return acquireTriggersWithinLock; + } + + /** + * Whether or not the query and update to acquire a Trigger for firing + * should be performed after obtaining an explicit DB lock. This is the + * behavior prior to Quartz 1.6.3, but is considered unnecessary for most + * databases, and therefore a superfluous performance hit. + */ + public void setAcquireTriggersWithinLock(boolean acquireTriggersWithinLock) { + this.acquireTriggersWithinLock = acquireTriggersWithinLock; + } + + + /** + *

+ * Set the JDBC driver delegate class. + *

+ * + * @param delegateClassName + * the delegate class name + */ + public void setDriverDelegateClass(String delegateClassName) + throws InvalidConfigurationException { + this.delegateClassName = delegateClassName; + } + + /** + *

+ * Get the JDBC driver delegate class name. + *

+ * + * @return the delegate class name + */ + public String getDriverDelegateClass() { + return delegateClassName; + } + + public String getSelectWithLockSQL() { + return selectWithLockSQL; + } + + /** + *

+ * set the SQL statement to use to select and lock a row in the "locks" + * table. + *

+ * + * @see StdRowLockSemaphore + */ + public void setSelectWithLockSQL(String string) { + selectWithLockSQL = string; + } + + protected ClassLoadHelper getClassLoadHelper() { + return classLoadHelper; + } + + /** + * Get whether the threads spawned by this JobStore should be + * marked as daemon. Possible threads include the MisfireHandler + * and the ClusterManager. + * + * @see Thread#setDaemon(boolean) + */ + public boolean getMakeThreadsDaemons() { + return makeThreadsDaemons; + } + + /** + * Set whether the threads spawned by this JobStore should be + * marked as daemon. Possible threads include the MisfireHandler + * and the ClusterManager. + * + * @see Thread#setDaemon(boolean) + */ + public void setMakeThreadsDaemons(boolean makeThreadsDaemons) { + this.makeThreadsDaemons = makeThreadsDaemons; + } + + /** + * Get whether to set the class load context of spawned threads to that + * of the initializing thread. + */ + public boolean isThreadsInheritInitializersClassLoadContext() { + return threadsInheritInitializersClassLoadContext; + } + + /** + * Set whether to set the class load context of spawned threads to that + * of the initializing thread. + */ + public void setThreadsInheritInitializersClassLoadContext( + boolean threadsInheritInitializersClassLoadContext) { + this.threadsInheritInitializersClassLoadContext = threadsInheritInitializersClassLoadContext; + } + + /** + * Get whether to check to see if there are Triggers that have misfired + * before actually acquiring the lock to recover them. This should be + * set to false if the majority of the time, there are are misfired + * Triggers. + */ + public boolean getDoubleCheckLockMisfireHandler() { + return doubleCheckLockMisfireHandler; + } + + /** + * Set whether to check to see if there are Triggers that have misfired + * before actually acquiring the lock to recover them. This should be + * set to false if the majority of the time, there are are misfired + * Triggers. + */ + public void setDoubleCheckLockMisfireHandler( + boolean doubleCheckLockMisfireHandler) { + this.doubleCheckLockMisfireHandler = doubleCheckLockMisfireHandler; + } + + //--------------------------------------------------------------------------- + // interface methods + //--------------------------------------------------------------------------- + + protected Log getLog() { + return log; + } + + /** + *

+ * Called by the QuartzScheduler before the JobStore is + * used, in order to give it a chance to initialize. + *

+ */ + public void initialize(ClassLoadHelper loadHelper, + SchedulerSignaler signaler) throws SchedulerConfigException { + + if (dsName == null) { + throw new SchedulerConfigException("DataSource name not set."); + } + + classLoadHelper = loadHelper; + if(isThreadsInheritInitializersClassLoadContext()) { + log.info("JDBCJobStore threads will inherit ContextClassLoader of thread: " + Thread.currentThread().getName()); + initializersLoader = Thread.currentThread().getContextClassLoader(); + } + + this.signaler = signaler; + + // If the user hasn't specified an explicit lock handler, then + // choose one based on CMT/Clustered/UseDBLocks. + if (getLockHandler() == null) { + + // If the user hasn't specified an explicit lock handler, + // then we *must* use DB locks with clustering + if (isClustered()) { + setUseDBLocks(true); + } + + if (getUseDBLocks()) { + getLog().info( + "Using db table-based data access locking (synchronization)."); + setLockHandler( + new StdRowLockSemaphore(getTablePrefix(), getSelectWithLockSQL())); + } else { + getLog().info( + "Using thread monitor-based data access locking (synchronization)."); + setLockHandler(new SimpleSemaphore()); + } + } + + if (!isClustered()) { + try { + cleanVolatileTriggerAndJobs(); + } catch (SchedulerException se) { + throw new SchedulerConfigException( + "Failure occured during job recovery.", se); + } + } + } + + /** + * @see com.fr.third.org.quartz.spi.JobStore#schedulerStarted() + */ + public void schedulerStarted() throws SchedulerException { + + if (isClustered()) { + clusterManagementThread = new ClusterManager(); + if(initializersLoader != null) + clusterManagementThread.setContextClassLoader(initializersLoader); + clusterManagementThread.initialize(); + } else { + try { + recoverJobs(); + } catch (SchedulerException se) { + throw new SchedulerConfigException( + "Failure occured during job recovery.", se); + } + } + + misfireHandler = new MisfireHandler(); + if(initializersLoader != null) + misfireHandler.setContextClassLoader(initializersLoader); + misfireHandler.initialize(); + } + + /** + *

+ * Called by the QuartzScheduler to inform the JobStore that + * it should free up all of it's resources because the scheduler is + * shutting down. + *

+ */ + public void shutdown() { + if (clusterManagementThread != null) { + clusterManagementThread.shutdown(); + } + + if (misfireHandler != null) { + misfireHandler.shutdown(); + } + + try { + DBConnectionManager.getInstance().shutdown(getDataSource()); + } catch (SQLException sqle) { + getLog().warn("Database connection shutdown unsuccessful.", sqle); + } + } + + public boolean supportsPersistence() { + return true; + } + + //--------------------------------------------------------------------------- + // helper methods for subclasses + //--------------------------------------------------------------------------- + + protected abstract Connection getNonManagedTXConnection() + throws JobPersistenceException; + + /** + * Wrap the given Connection in a Proxy such that attributes + * that might be set will be restored before the connection is closed + * (and potentially restored to a pool). + */ + protected Connection getAttributeRestoringConnection(Connection conn) { + return (Connection)Proxy.newProxyInstance( + Thread.currentThread().getContextClassLoader(), + new Class[] { Connection.class }, + new AttributeRestoringConnectionInvocationHandler(conn)); + } + + protected Connection getConnection() throws JobPersistenceException { + Connection conn = null; + try { + conn = DBConnectionManager.getInstance().getConnection( + getDataSource()); + } catch (SQLException sqle) { + throw new JobPersistenceException( + "Failed to obtain DB connection from data source '" + + getDataSource() + "': " + sqle.toString(), sqle); + } catch (Throwable e) { + throw new JobPersistenceException( + "Failed to obtain DB connection from data source '" + + getDataSource() + "': " + e.toString(), e, + JobPersistenceException.ERR_PERSISTENCE_CRITICAL_FAILURE); + } + + if (conn == null) { + throw new JobPersistenceException( + "Could not get connection from DataSource '" + + getDataSource() + "'"); + } + + // Protect connection attributes we might change. + conn = getAttributeRestoringConnection(conn); + + // Set any connection connection attributes we are to override. + try { + if (!isDontSetAutoCommitFalse()) { + conn.setAutoCommit(false); + } + + if(isTxIsolationLevelSerializable()) { + conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); + } + } catch (SQLException sqle) { + getLog().warn("Failed to override connection auto commit/transaction isolation.", sqle); + } catch (Throwable e) { + try { conn.close(); } catch(Throwable tt) {} + + throw new JobPersistenceException( + "Failure setting up connection.", e); + } + + return conn; + } + + protected void releaseLock(Connection conn, String lockName, boolean doIt) { + if (doIt && conn != null) { + try { + getLockHandler().releaseLock(conn, lockName); + } catch (LockException le) { + getLog().error("Error returning lock: " + le.getMessage(), le); + } + } + } + + /** + * Removes all volatile data. + * + * @throws JobPersistenceException If jobs could not be recovered. + */ + protected void cleanVolatileTriggerAndJobs() + throws JobPersistenceException { + executeInNonManagedTXLock( + LOCK_TRIGGER_ACCESS, + new VoidTransactionCallback() { + public void execute(Connection conn) throws JobPersistenceException { + cleanVolatileTriggerAndJobs(conn); + } + }); + } + + /** + *

+ * Removes all volatile data. + *

+ * + * @throws JobPersistenceException + * if jobs could not be recovered + */ + protected void cleanVolatileTriggerAndJobs(Connection conn) + throws JobPersistenceException { + try { + // find volatile jobs & triggers... + Key[] volatileTriggers = getDelegate().selectVolatileTriggers(conn); + Key[] volatileJobs = getDelegate().selectVolatileJobs(conn); + + for (int i = 0; i < volatileTriggers.length; i++) { + removeTrigger(conn, null, volatileTriggers[i].getName(), + volatileTriggers[i].getGroup()); + } + getLog().info( + "Removed " + volatileTriggers.length + + " Volatile Trigger(s)."); + + for (int i = 0; i < volatileJobs.length; i++) { + removeJob(conn, null, volatileJobs[i].getName(), + volatileJobs[i].getGroup(), true); + } + getLog().info( + "Removed " + volatileJobs.length + " Volatile Job(s)."); + + // clean up any fired trigger entries + getDelegate().deleteVolatileFiredTriggers(conn); + + } catch (Exception e) { + throw new JobPersistenceException("Couldn't clean volatile data: " + + e.getMessage(), e); + } + } + + /** + * Recover any failed or misfired jobs and clean up the data store as + * appropriate. + * + * @throws JobPersistenceException if jobs could not be recovered + */ + protected void recoverJobs() throws JobPersistenceException { + executeInNonManagedTXLock( + LOCK_TRIGGER_ACCESS, + new VoidTransactionCallback() { + public void execute(Connection conn) throws JobPersistenceException { + recoverJobs(conn); + } + }); + } + + /** + *

+ * Will recover any failed or misfired jobs and clean up the data store as + * appropriate. + *

+ * + * @throws JobPersistenceException + * if jobs could not be recovered + */ + protected void recoverJobs(Connection conn) throws JobPersistenceException { + try { + // update inconsistent job states + int rows = getDelegate().updateTriggerStatesFromOtherStates(conn, + STATE_WAITING, STATE_ACQUIRED, STATE_BLOCKED); + + rows += getDelegate().updateTriggerStatesFromOtherStates(conn, + STATE_PAUSED, STATE_PAUSED_BLOCKED, STATE_PAUSED_BLOCKED); + + getLog().info( + "Freed " + rows + + " triggers from 'acquired' / 'blocked' state."); + + // clean up misfired jobs + recoverMisfiredJobs(conn, true); + + // recover jobs marked for recovery that were not fully executed + Trigger[] recoveringJobTriggers = getDelegate() + .selectTriggersForRecoveringJobs(conn); + getLog() + .info( + "Recovering " + + recoveringJobTriggers.length + + " jobs that were in-progress at the time of the last shut-down."); + + for (int i = 0; i < recoveringJobTriggers.length; ++i) { + if (jobExists(conn, recoveringJobTriggers[i].getJobName(), + recoveringJobTriggers[i].getJobGroup())) { + recoveringJobTriggers[i].computeFirstFireTime(null); + storeTrigger(conn, null, recoveringJobTriggers[i], null, false, + STATE_WAITING, false, true); + } + } + getLog().info("Recovery complete."); + + // remove lingering 'complete' triggers... + Key[] ct = getDelegate().selectTriggersInState(conn, STATE_COMPLETE); + for(int i=0; ct != null && i < ct.length; i++) { + removeTrigger(conn, null, ct[i].getName(), ct[i].getGroup()); + } + getLog().info( + "Removed " + (ct != null ? ct.length : 0) + + " 'complete' triggers."); + + // clean up any fired trigger entries + int n = getDelegate().deleteFiredTriggers(conn); + getLog().info("Removed " + n + " stale fired job entries."); + } catch (JobPersistenceException e) { + throw e; + } catch (Exception e) { + throw new JobPersistenceException("Couldn't recover jobs: " + + e.getMessage(), e); + } + } + + protected long getMisfireTime() { + long misfireTime = System.currentTimeMillis(); + if (getMisfireThreshold() > 0) { + misfireTime -= getMisfireThreshold(); + } + + return (misfireTime > 0) ? misfireTime : 0; + } + + /** + * Helper class for returning the composite result of trying + * to recover misfired jobs. + */ + protected static class RecoverMisfiredJobsResult { + public static final RecoverMisfiredJobsResult NO_OP = + new RecoverMisfiredJobsResult(false, 0, Long.MAX_VALUE); + + private boolean _hasMoreMisfiredTriggers; + private int _processedMisfiredTriggerCount; + private long _earliestNewTime; + + public RecoverMisfiredJobsResult( + boolean hasMoreMisfiredTriggers, int processedMisfiredTriggerCount, long earliestNewTime) { + _hasMoreMisfiredTriggers = hasMoreMisfiredTriggers; + _processedMisfiredTriggerCount = processedMisfiredTriggerCount; + _earliestNewTime = earliestNewTime; + } + + public boolean hasMoreMisfiredTriggers() { + return _hasMoreMisfiredTriggers; + } + public int getProcessedMisfiredTriggerCount() { + return _processedMisfiredTriggerCount; + } + public long getEarliestNewTime() { + return _earliestNewTime; + } + } + + protected RecoverMisfiredJobsResult recoverMisfiredJobs( + Connection conn, boolean recovering) + throws JobPersistenceException, SQLException { + + // If recovering, we want to handle all of the misfired + // triggers right away. + int maxMisfiresToHandleAtATime = + (recovering) ? -1 : getMaxMisfiresToHandleAtATime(); + + List misfiredTriggers = new ArrayList(); + long earliestNewTime = Long.MAX_VALUE; + // We must still look for the MISFIRED state in case triggers were left + // in this state when upgrading to this version that does not support it. + boolean hasMoreMisfiredTriggers = + getDelegate().selectMisfiredTriggersInStates( + conn, STATE_MISFIRED, STATE_WAITING, getMisfireTime(), + maxMisfiresToHandleAtATime, misfiredTriggers); + + if (hasMoreMisfiredTriggers) { + getLog().info( + "Handling the first " + misfiredTriggers.size() + + " triggers that missed their scheduled fire-time. " + + "More misfired triggers remain to be processed."); + } else if (misfiredTriggers.size() > 0) { + getLog().info( + "Handling " + misfiredTriggers.size() + + " trigger(s) that missed their scheduled fire-time."); + } else { + getLog().debug( + "Found 0 triggers that missed their scheduled fire-time."); + return RecoverMisfiredJobsResult.NO_OP; + } + + for (Iterator misfiredTriggerIter = misfiredTriggers.iterator(); misfiredTriggerIter.hasNext();) { + Key triggerKey = (Key) misfiredTriggerIter.next(); + + Trigger trig = + retrieveTrigger(conn, triggerKey.getName(), triggerKey.getGroup()); + + if (trig == null) { + continue; + } + + doUpdateOfMisfiredTrigger(conn, null, trig, false, STATE_WAITING, recovering); + + if(trig.getNextFireTime() != null && trig.getNextFireTime().getTime() < earliestNewTime) + earliestNewTime = trig.getNextFireTime().getTime(); + + signaler.notifyTriggerListenersMisfired(trig); + } + + return new RecoverMisfiredJobsResult( + hasMoreMisfiredTriggers, misfiredTriggers.size(), earliestNewTime); + } + + protected boolean updateMisfiredTrigger(Connection conn, + SchedulingContext ctxt, String triggerName, String groupName, + String newStateIfNotComplete, boolean forceState) // TODO: probably + // get rid of + // this + throws JobPersistenceException { + try { + + Trigger trig = getDelegate().selectTrigger(conn, triggerName, + groupName); + + long misfireTime = System.currentTimeMillis(); + if (getMisfireThreshold() > 0) { + misfireTime -= getMisfireThreshold(); + } + + if (trig.getNextFireTime().getTime() > misfireTime) { + return false; + } + + doUpdateOfMisfiredTrigger(conn, ctxt, trig, forceState, newStateIfNotComplete, false); + + signaler.notifySchedulerListenersFinalized(trig); + + return true; + + } catch (Exception e) { + throw new JobPersistenceException( + "Couldn't update misfired trigger '" + groupName + "." + + triggerName + "': " + e.getMessage(), e); + } + } + + private void doUpdateOfMisfiredTrigger(Connection conn, SchedulingContext ctxt, Trigger trig, boolean forceState, String newStateIfNotComplete, boolean recovering) throws JobPersistenceException { + Calendar cal = null; + if (trig.getCalendarName() != null) { + cal = retrieveCalendar(conn, ctxt, trig.getCalendarName()); + } + + signaler.notifyTriggerListenersMisfired(trig); + + trig.updateAfterMisfire(cal); + + if (trig.getNextFireTime() == null) { + storeTrigger(conn, ctxt, trig, + null, true, STATE_COMPLETE, forceState, recovering); + } else { + storeTrigger(conn, ctxt, trig, null, true, newStateIfNotComplete, + forceState, false); + } + } + + /** + *

+ * Store the given {@link com.fr.third.org.quartz.JobDetail} and {@link com.fr.third.org.quartz.Trigger}. + *

+ * + * @param newJob + * The JobDetail to be stored. + * @param newTrigger + * The Trigger to be stored. + * @throws ObjectAlreadyExistsException + * if a Job with the same name/group already + * exists. + */ + public void storeJobAndTrigger(final SchedulingContext ctxt, final JobDetail newJob, + final Trigger newTrigger) + throws ObjectAlreadyExistsException, JobPersistenceException { + executeInLock( + (isLockOnInsert()) ? LOCK_TRIGGER_ACCESS : null, + new VoidTransactionCallback() { + public void execute(Connection conn) throws JobPersistenceException { + if (newJob.isVolatile() && !newTrigger.isVolatile()) { + JobPersistenceException jpe = + new JobPersistenceException( + "Cannot associate non-volatile trigger with a volatile job!"); + jpe.setErrorCode(SchedulerException.ERR_CLIENT_ERROR); + throw jpe; + } + + storeJob(conn, ctxt, newJob, false); + storeTrigger(conn, ctxt, newTrigger, newJob, false, + Constants.STATE_WAITING, false, false); + } + }); + } + + /** + *

+ * Store the given {@link com.fr.third.org.quartz.JobDetail}. + *

+ * + * @param newJob + * The JobDetail to be stored. + * @param replaceExisting + * If true, any Job existing in the + * JobStore with the same name & group should be + * over-written. + * @throws ObjectAlreadyExistsException + * if a Job with the same name/group already + * exists, and replaceExisting is set to false. + */ + public void storeJob(final SchedulingContext ctxt, final JobDetail newJob, + final boolean replaceExisting) throws ObjectAlreadyExistsException, JobPersistenceException { + executeInLock( + (isLockOnInsert() || replaceExisting) ? LOCK_TRIGGER_ACCESS : null, + new VoidTransactionCallback() { + public void execute(Connection conn) throws JobPersistenceException { + storeJob(conn, ctxt, newJob, replaceExisting); + } + }); + } + + /** + *

+ * Insert or update a job. + *

+ */ + protected void storeJob(Connection conn, SchedulingContext ctxt, + JobDetail newJob, boolean replaceExisting) + throws ObjectAlreadyExistsException, JobPersistenceException { + if (newJob.isVolatile() && isClustered()) { + getLog().info( + "note: volatile jobs are effectively non-volatile in a clustered environment."); + } + + boolean existingJob = jobExists(conn, newJob.getName(), newJob + .getGroup()); + try { + if (existingJob) { + if (!replaceExisting) { + throw new ObjectAlreadyExistsException(newJob); + } + getDelegate().updateJobDetail(conn, newJob); + } else { + getDelegate().insertJobDetail(conn, newJob); + } + } catch (IOException e) { + throw new JobPersistenceException("Couldn't store job: " + + e.getMessage(), e); + } catch (SQLException e) { + throw new JobPersistenceException("Couldn't store job: " + + e.getMessage(), e); + } + } + + /** + *

+ * Check existence of a given job. + *

+ */ + protected boolean jobExists(Connection conn, String jobName, + String groupName) throws JobPersistenceException { + try { + return getDelegate().jobExists(conn, jobName, groupName); + } catch (SQLException e) { + throw new JobPersistenceException( + "Couldn't determine job existence (" + groupName + "." + + jobName + "): " + e.getMessage(), e); + } + } + + + /** + *

+ * Store the given {@link com.fr.third.org.quartz.Trigger}. + *

+ * + * @param newTrigger + * The Trigger to be stored. + * @param replaceExisting + * If true, any Trigger existing in + * the JobStore with the same name & group should + * be over-written. + * @throws ObjectAlreadyExistsException + * if a Trigger with the same name/group already + * exists, and replaceExisting is set to false. + */ + public void storeTrigger(final SchedulingContext ctxt, final Trigger newTrigger, + final boolean replaceExisting) throws ObjectAlreadyExistsException, + JobPersistenceException { + executeInLock( + (isLockOnInsert() || replaceExisting) ? LOCK_TRIGGER_ACCESS : null, + new VoidTransactionCallback() { + public void execute(Connection conn) throws JobPersistenceException { + storeTrigger(conn, ctxt, newTrigger, null, replaceExisting, + STATE_WAITING, false, false); + } + }); + } + + /** + *

+ * Insert or update a trigger. + *

+ */ + protected void storeTrigger(Connection conn, SchedulingContext ctxt, + Trigger newTrigger, JobDetail job, boolean replaceExisting, String state, + boolean forceState, boolean recovering) + throws ObjectAlreadyExistsException, JobPersistenceException { + if (newTrigger.isVolatile() && isClustered()) { + getLog().info( + "note: volatile triggers are effectively non-volatile in a clustered environment."); + } + + boolean existingTrigger = triggerExists(conn, newTrigger.getName(), + newTrigger.getGroup()); + + if ((existingTrigger) && (!replaceExisting)) { + throw new ObjectAlreadyExistsException(newTrigger); + } + + try { + + boolean shouldBepaused = false; + + if (!forceState) { + shouldBepaused = getDelegate().isTriggerGroupPaused( + conn, newTrigger.getGroup()); + + if(!shouldBepaused) { + shouldBepaused = getDelegate().isTriggerGroupPaused(conn, + ALL_GROUPS_PAUSED); + + if (shouldBepaused) { + getDelegate().insertPausedTriggerGroup(conn, newTrigger.getGroup()); + } + } + + if (shouldBepaused && (state.equals(STATE_WAITING) || state.equals(STATE_ACQUIRED))) { + state = STATE_PAUSED; + } + } + + if(job == null) { + job = getDelegate().selectJobDetail(conn, + newTrigger.getJobName(), newTrigger.getJobGroup(), + getClassLoadHelper()); + } + if (job == null) { + throw new JobPersistenceException("The job (" + + newTrigger.getFullJobName() + + ") referenced by the trigger does not exist."); + } + if (job.isVolatile() && !newTrigger.isVolatile()) { + throw new JobPersistenceException( + "It does not make sense to " + + "associate a non-volatile Trigger with a volatile Job!"); + } + + if (job.isStateful() && !recovering) { + state = checkBlockedState(conn, ctxt, job.getName(), + job.getGroup(), state); + } + + if (existingTrigger) { + if (newTrigger instanceof SimpleTrigger && ((SimpleTrigger)newTrigger).hasAdditionalProperties() == false ) { + getDelegate().updateSimpleTrigger(conn, + (SimpleTrigger) newTrigger); + } else if (newTrigger instanceof CronTrigger && ((CronTrigger)newTrigger).hasAdditionalProperties() == false ) { + getDelegate().updateCronTrigger(conn, + (CronTrigger) newTrigger); + } else { + getDelegate().updateBlobTrigger(conn, newTrigger); + } + getDelegate().updateTrigger(conn, newTrigger, state, job); + } else { + getDelegate().insertTrigger(conn, newTrigger, state, job); + if (newTrigger instanceof SimpleTrigger && ((SimpleTrigger)newTrigger).hasAdditionalProperties() == false ) { + getDelegate().insertSimpleTrigger(conn, + (SimpleTrigger) newTrigger); + } else if (newTrigger instanceof CronTrigger && ((CronTrigger)newTrigger).hasAdditionalProperties() == false ) { + getDelegate().insertCronTrigger(conn, + (CronTrigger) newTrigger); + } else { + getDelegate().insertBlobTrigger(conn, newTrigger); + } + } + } catch (Exception e) { + throw new JobPersistenceException("Couldn't store trigger '" + newTrigger.getName() + "' for '" + + newTrigger.getJobName() + "' job:" + e.getMessage(), e); + } + } + + /** + *

+ * Check existence of a given trigger. + *

+ */ + protected boolean triggerExists(Connection conn, String triggerName, + String groupName) throws JobPersistenceException { + try { + return getDelegate().triggerExists(conn, triggerName, groupName); + } catch (SQLException e) { + throw new JobPersistenceException( + "Couldn't determine trigger existence (" + groupName + "." + + triggerName + "): " + e.getMessage(), e); + } + } + + /** + *

+ * Remove (delete) the {@link com.fr.third.org.quartz.Job} with the given + * name, and any {@link com.fr.third.org.quartz.Trigger} s that reference + * it. + *

+ * + *

+ * If removal of the Job results in an empty group, the + * group should be removed from the JobStore's list of + * known group names. + *

+ * + * @param jobName + * The name of the Job to be removed. + * @param groupName + * The group name of the Job to be removed. + * @return true if a Job with the given name & + * group was found and removed from the store. + */ + public boolean removeJob(final SchedulingContext ctxt, final String jobName, + final String groupName) throws JobPersistenceException { + return ((Boolean)executeInLock( + LOCK_TRIGGER_ACCESS, + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + return removeJob(conn, ctxt, jobName, groupName, true) ? + Boolean.TRUE : Boolean.FALSE; + } + })).booleanValue(); + } + + protected boolean removeJob(Connection conn, SchedulingContext ctxt, + String jobName, String groupName, boolean activeDeleteSafe) + throws JobPersistenceException { + + try { + Key[] jobTriggers = getDelegate().selectTriggerNamesForJob(conn, + jobName, groupName); + for (int i = 0; i < jobTriggers.length; ++i) { + deleteTriggerAndChildren( + conn, jobTriggers[i].getName(), jobTriggers[i].getGroup()); + } + + return deleteJobAndChildren(conn, ctxt, jobName, groupName); + } catch (SQLException e) { + throw new JobPersistenceException("Couldn't remove job: " + + e.getMessage(), e); + } + } + + /** + * Delete a job and its listeners. + * + * @see #removeJob(Connection, SchedulingContext, String, String, boolean) + * @see #removeTrigger(Connection, SchedulingContext, String, String) + */ + private boolean deleteJobAndChildren(Connection conn, + SchedulingContext ctxt, String jobName, String groupName) + throws NoSuchDelegateException, SQLException { + getDelegate().deleteJobListeners(conn, jobName, groupName); + + return (getDelegate().deleteJobDetail(conn, jobName, groupName) > 0); + } + + /** + * Delete a trigger, its listeners, and its Simple/Cron/BLOB sub-table entry. + * + * @see #removeJob(Connection, SchedulingContext, String, String, boolean) + * @see #removeTrigger(Connection, SchedulingContext, String, String) + * @see #replaceTrigger(Connection, SchedulingContext, String, String, Trigger) + */ + private boolean deleteTriggerAndChildren( + Connection conn, String triggerName, String triggerGroupName) + throws SQLException, NoSuchDelegateException { + DriverDelegate delegate = getDelegate(); + + // Once it succeeds in deleting one sub-table entry it will not try the others. + if ((delegate.deleteSimpleTrigger(conn, triggerName, triggerGroupName) == 0) && + (delegate.deleteCronTrigger(conn, triggerName, triggerGroupName) == 0)) { + delegate.deleteBlobTrigger(conn, triggerName, triggerGroupName); + } + + delegate.deleteTriggerListeners(conn, triggerName, triggerGroupName); + + return (delegate.deleteTrigger(conn, triggerName, triggerGroupName) > 0); + } + + /** + *

+ * Retrieve the {@link com.fr.third.org.quartz.JobDetail} for the given + * {@link com.fr.third.org.quartz.Job}. + *

+ * + * @param jobName + * The name of the Job to be retrieved. + * @param groupName + * The group name of the Job to be retrieved. + * @return The desired Job, or null if there is no match. + */ + public JobDetail retrieveJob(final SchedulingContext ctxt, final String jobName, + final String groupName) throws JobPersistenceException { + return (JobDetail)executeWithoutLock( // no locks necessary for read... + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + return retrieveJob(conn, ctxt, jobName, groupName); + } + }); + } + + protected JobDetail retrieveJob(Connection conn, SchedulingContext ctxt, + String jobName, String groupName) throws JobPersistenceException { + try { + JobDetail job = getDelegate().selectJobDetail(conn, jobName, + groupName, getClassLoadHelper()); + if (job != null) { + String[] listeners = getDelegate().selectJobListeners(conn, + jobName, groupName); + for (int i = 0; i < listeners.length; ++i) { + job.addJobListener(listeners[i]); + } + } + + return job; + } catch (ClassNotFoundException e) { + throw new JobPersistenceException( + "Couldn't retrieve job because a required class was not found: " + + e.getMessage(), e, + SchedulerException.ERR_PERSISTENCE_JOB_DOES_NOT_EXIST); + } catch (IOException e) { + throw new JobPersistenceException( + "Couldn't retrieve job because the BLOB couldn't be deserialized: " + + e.getMessage(), e, + SchedulerException.ERR_PERSISTENCE_JOB_DOES_NOT_EXIST); + } catch (SQLException e) { + throw new JobPersistenceException("Couldn't retrieve job: " + + e.getMessage(), e); + } + } + + /** + *

+ * Remove (delete) the {@link com.fr.third.org.quartz.Trigger} with the + * given name. + *

+ * + *

+ * If removal of the Trigger results in an empty group, the + * group should be removed from the JobStore's list of + * known group names. + *

+ * + *

+ * If removal of the Trigger results in an 'orphaned' Job + * that is not 'durable', then the Job should be deleted + * also. + *

+ * + * @param triggerName + * The name of the Trigger to be removed. + * @param groupName + * The group name of the Trigger to be removed. + * @return true if a Trigger with the given + * name & group was found and removed from the store. + */ + public boolean removeTrigger(final SchedulingContext ctxt, final String triggerName, + final String groupName) throws JobPersistenceException { + return ((Boolean)executeInLock( + LOCK_TRIGGER_ACCESS, + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + return removeTrigger(conn, ctxt, triggerName, groupName) ? + Boolean.TRUE : Boolean.FALSE; + } + })).booleanValue(); + } + + protected boolean removeTrigger(Connection conn, SchedulingContext ctxt, + String triggerName, String groupName) + throws JobPersistenceException { + boolean removedTrigger = false; + try { + // this must be called before we delete the trigger, obviously + JobDetail job = getDelegate().selectJobForTrigger(conn, + triggerName, groupName, getClassLoadHelper()); + + removedTrigger = + deleteTriggerAndChildren(conn, triggerName, groupName); + + if (null != job && !job.isDurable()) { + int numTriggers = getDelegate().selectNumTriggersForJob(conn, + job.getName(), job.getGroup()); + if (numTriggers == 0) { + // Don't call removeJob() because we don't want to check for + // triggers again. + deleteJobAndChildren(conn, ctxt, job.getName(), job.getGroup()); + } + } + } catch (ClassNotFoundException e) { + throw new JobPersistenceException("Couldn't remove trigger: " + + e.getMessage(), e); + } catch (SQLException e) { + throw new JobPersistenceException("Couldn't remove trigger: " + + e.getMessage(), e); + } + + return removedTrigger; + } + + /** + * @see com.fr.third.org.quartz.spi.JobStore#replaceTrigger(com.fr.third.org.quartz.core.SchedulingContext, java.lang.String, java.lang.String, com.fr.third.org.quartz.Trigger) + */ + public boolean replaceTrigger(final SchedulingContext ctxt, final String triggerName, + final String groupName, final Trigger newTrigger) throws JobPersistenceException { + return ((Boolean)executeInLock( + LOCK_TRIGGER_ACCESS, + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + return replaceTrigger(conn, ctxt, triggerName, groupName, newTrigger) ? + Boolean.TRUE : Boolean.FALSE; + } + })).booleanValue(); + } + + protected boolean replaceTrigger(Connection conn, SchedulingContext ctxt, + String triggerName, String groupName, Trigger newTrigger) + throws JobPersistenceException { + try { + // this must be called before we delete the trigger, obviously + JobDetail job = getDelegate().selectJobForTrigger(conn, + triggerName, groupName, getClassLoadHelper()); + + if (job == null) { + return false; + } + + if (!newTrigger.getJobName().equals(job.getName()) || + !newTrigger.getJobGroup().equals(job.getGroup())) { + throw new JobPersistenceException("New trigger is not related to the same job as the old trigger."); + } + + boolean removedTrigger = + deleteTriggerAndChildren(conn, triggerName, groupName); + + storeTrigger(conn, ctxt, newTrigger, job, false, STATE_WAITING, false, false); + + return removedTrigger; + } catch (ClassNotFoundException e) { + throw new JobPersistenceException("Couldn't remove trigger: " + + e.getMessage(), e); + } catch (SQLException e) { + throw new JobPersistenceException("Couldn't remove trigger: " + + e.getMessage(), e); + } + } + + /** + *

+ * Retrieve the given {@link com.fr.third.org.quartz.Trigger}. + *

+ * + * @param triggerName + * The name of the Trigger to be retrieved. + * @param groupName + * The group name of the Trigger to be retrieved. + * @return The desired Trigger, or null if there is no + * match. + */ + public Trigger retrieveTrigger(final SchedulingContext ctxt, final String triggerName, + final String groupName) throws JobPersistenceException { + return (Trigger)executeWithoutLock( // no locks necessary for read... + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + return retrieveTrigger(conn, ctxt, triggerName, groupName); + } + }); + } + + protected Trigger retrieveTrigger(Connection conn, SchedulingContext ctxt, + String triggerName, String groupName) + throws JobPersistenceException { + return retrieveTrigger(conn, triggerName, groupName); + } + + protected Trigger retrieveTrigger(Connection conn, String triggerName, String groupName) + throws JobPersistenceException { + try { + Trigger trigger = getDelegate().selectTrigger(conn, triggerName, + groupName); + if (trigger == null) { + return null; + } + + // In case Trigger was BLOB, clear out any listeners that might + // have been serialized. + trigger.clearAllTriggerListeners(); + + String[] listeners = getDelegate().selectTriggerListeners(conn, + triggerName, groupName); + for (int i = 0; i < listeners.length; ++i) { + trigger.addTriggerListener(listeners[i]); + } + + return trigger; + } catch (Exception e) { + throw new JobPersistenceException("Couldn't retrieve trigger: " + + e.getMessage(), e); + } + } + + /** + *

+ * Get the current state of the identified {@link Trigger}. + *

+ * + * @see Trigger#STATE_NORMAL + * @see Trigger#STATE_PAUSED + * @see Trigger#STATE_COMPLETE + * @see Trigger#STATE_ERROR + * @see Trigger#STATE_NONE + */ + public int getTriggerState(final SchedulingContext ctxt, final String triggerName, + final String groupName) throws JobPersistenceException { + return ((Integer)executeWithoutLock( // no locks necessary for read... + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + return new Integer(getTriggerState(conn, ctxt, triggerName, groupName)); + } + })).intValue(); + } + + public int getTriggerState(Connection conn, SchedulingContext ctxt, + String triggerName, String groupName) + throws JobPersistenceException { + try { + String ts = getDelegate().selectTriggerState(conn, triggerName, + groupName); + + if (ts == null) { + return Trigger.STATE_NONE; + } + + if (ts.equals(STATE_DELETED)) { + return Trigger.STATE_NONE; + } + + if (ts.equals(STATE_COMPLETE)) { + return Trigger.STATE_COMPLETE; + } + + if (ts.equals(STATE_PAUSED)) { + return Trigger.STATE_PAUSED; + } + + if (ts.equals(STATE_PAUSED_BLOCKED)) { + return Trigger.STATE_PAUSED; + } + + if (ts.equals(STATE_ERROR)) { + return Trigger.STATE_ERROR; + } + + if (ts.equals(STATE_BLOCKED)) { + return Trigger.STATE_BLOCKED; + } + + return Trigger.STATE_NORMAL; + + } catch (SQLException e) { + throw new JobPersistenceException( + "Couldn't determine state of trigger (" + groupName + "." + + triggerName + "): " + e.getMessage(), e); + } + } + + /** + *

+ * Store the given {@link com.fr.third.org.quartz.Calendar}. + *

+ * + * @param calName + * The name of the calendar. + * @param calendar + * The Calendar to be stored. + * @param replaceExisting + * If true, any Calendar existing + * in the JobStore with the same name & group + * should be over-written. + * @throws ObjectAlreadyExistsException + * if a Calendar with the same name already + * exists, and replaceExisting is set to false. + */ + public void storeCalendar(final SchedulingContext ctxt, final String calName, + final Calendar calendar, final boolean replaceExisting, final boolean updateTriggers) + throws ObjectAlreadyExistsException, JobPersistenceException { + executeInLock( + (isLockOnInsert() || updateTriggers) ? LOCK_TRIGGER_ACCESS : null, + new VoidTransactionCallback() { + public void execute(Connection conn) throws JobPersistenceException { + storeCalendar(conn, ctxt, calName, calendar, replaceExisting, updateTriggers); + } + }); + } + + protected void storeCalendar(Connection conn, SchedulingContext ctxt, + String calName, Calendar calendar, boolean replaceExisting, boolean updateTriggers) + throws ObjectAlreadyExistsException, JobPersistenceException { + try { + boolean existingCal = calendarExists(conn, calName); + if (existingCal && !replaceExisting) { + throw new ObjectAlreadyExistsException( + "Calendar with name '" + calName + "' already exists."); + } + + if (existingCal) { + if (getDelegate().updateCalendar(conn, calName, calendar) < 1) { + throw new JobPersistenceException( + "Couldn't store calendar. Update failed."); + } + + if(updateTriggers) { + Trigger[] trigs = getDelegate().selectTriggersForCalendar(conn, calName); + + for(int i=0; i < trigs.length; i++) { + trigs[i].updateWithNewCalendar(calendar, getMisfireThreshold()); + storeTrigger(conn, ctxt, trigs[i], null, true, STATE_WAITING, false, false); + } + } + } else { + if (getDelegate().insertCalendar(conn, calName, calendar) < 1) { + throw new JobPersistenceException( + "Couldn't store calendar. Insert failed."); + } + } + + if (isClustered == false) { + calendarCache.put(calName, calendar); // lazy-cache + } + + } catch (IOException e) { + throw new JobPersistenceException( + "Couldn't store calendar because the BLOB couldn't be serialized: " + + e.getMessage(), e); + } catch (ClassNotFoundException e) { + throw new JobPersistenceException("Couldn't store calendar: " + + e.getMessage(), e); + }catch (SQLException e) { + throw new JobPersistenceException("Couldn't store calendar: " + + e.getMessage(), e); + } + } + + protected boolean calendarExists(Connection conn, String calName) + throws JobPersistenceException { + try { + return getDelegate().calendarExists(conn, calName); + } catch (SQLException e) { + throw new JobPersistenceException( + "Couldn't determine calendar existence (" + calName + "): " + + e.getMessage(), e); + } + } + + /** + *

+ * Remove (delete) the {@link com.fr.third.org.quartz.Calendar} with the + * given name. + *

+ * + *

+ * If removal of the Calendar would result in + * s pointing to non-existent calendars, then a + * JobPersistenceException will be thrown.

+ * * + * @param calName The name of the Calendar to be removed. + * @return true if a Calendar with the given name + * was found and removed from the store. + */ + public boolean removeCalendar(final SchedulingContext ctxt, final String calName) + throws JobPersistenceException { + return ((Boolean)executeInLock( + LOCK_TRIGGER_ACCESS, + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + return removeCalendar(conn, ctxt, calName) ? + Boolean.TRUE : Boolean.FALSE; + } + })).booleanValue(); + } + + protected boolean removeCalendar(Connection conn, SchedulingContext ctxt, + String calName) throws JobPersistenceException { + try { + if (getDelegate().calendarIsReferenced(conn, calName)) { + throw new JobPersistenceException( + "Calender cannot be removed if it referenced by a trigger!"); + } + + if (isClustered == false) { + calendarCache.remove(calName); + } + + return (getDelegate().deleteCalendar(conn, calName) > 0); + } catch (SQLException e) { + throw new JobPersistenceException("Couldn't remove calendar: " + + e.getMessage(), e); + } + } + + /** + *

+ * Retrieve the given {@link com.fr.third.org.quartz.Trigger}. + *

+ * + * @param calName + * The name of the Calendar to be retrieved. + * @return The desired Calendar, or null if there is no + * match. + */ + public Calendar retrieveCalendar(final SchedulingContext ctxt, final String calName) + throws JobPersistenceException { + return (Calendar)executeWithoutLock( // no locks necessary for read... + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + return retrieveCalendar(conn, ctxt, calName); + } + }); + } + + protected Calendar retrieveCalendar(Connection conn, + SchedulingContext ctxt, String calName) + throws JobPersistenceException { + // all calendars are persistent, but we can lazy-cache them during run + // time as long as we aren't running clustered. + Calendar cal = (isClustered) ? null : (Calendar) calendarCache.get(calName); + if (cal != null) { + return cal; + } + + try { + cal = getDelegate().selectCalendar(conn, calName); + if (isClustered == false) { + calendarCache.put(calName, cal); // lazy-cache... + } + return cal; + } catch (ClassNotFoundException e) { + throw new JobPersistenceException( + "Couldn't retrieve calendar because a required class was not found: " + + e.getMessage(), e); + } catch (IOException e) { + throw new JobPersistenceException( + "Couldn't retrieve calendar because the BLOB couldn't be deserialized: " + + e.getMessage(), e); + } catch (SQLException e) { + throw new JobPersistenceException("Couldn't retrieve calendar: " + + e.getMessage(), e); + } + } + + /** + *

+ * Get the number of {@link com.fr.third.org.quartz.Job} s that are + * stored in the JobStore. + *

+ */ + public int getNumberOfJobs(final SchedulingContext ctxt) + throws JobPersistenceException { + return ((Integer)executeWithoutLock( // no locks necessary for read... + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + return new Integer(getNumberOfJobs(conn, ctxt)); + } + })).intValue(); + } + + protected int getNumberOfJobs(Connection conn, SchedulingContext ctxt) + throws JobPersistenceException { + try { + return getDelegate().selectNumJobs(conn); + } catch (SQLException e) { + throw new JobPersistenceException( + "Couldn't obtain number of jobs: " + e.getMessage(), e); + } + } + + /** + *

+ * Get the number of {@link com.fr.third.org.quartz.Trigger} s that are + * stored in the JobsStore. + *

+ */ + public int getNumberOfTriggers(final SchedulingContext ctxt) + throws JobPersistenceException { + return ((Integer)executeWithoutLock( // no locks necessary for read... + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + return new Integer(getNumberOfTriggers(conn, ctxt)); + } + })).intValue(); + } + + protected int getNumberOfTriggers(Connection conn, SchedulingContext ctxt) + throws JobPersistenceException { + try { + return getDelegate().selectNumTriggers(conn); + } catch (SQLException e) { + throw new JobPersistenceException( + "Couldn't obtain number of triggers: " + e.getMessage(), e); + } + } + + /** + *

+ * Get the number of {@link com.fr.third.org.quartz.Calendar} s that are + * stored in the JobsStore. + *

+ */ + public int getNumberOfCalendars(final SchedulingContext ctxt) + throws JobPersistenceException { + return ((Integer)executeWithoutLock( // no locks necessary for read... + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + return new Integer(getNumberOfCalendars(conn, ctxt)); + } + })).intValue(); + } + + protected int getNumberOfCalendars(Connection conn, SchedulingContext ctxt) + throws JobPersistenceException { + try { + return getDelegate().selectNumCalendars(conn); + } catch (SQLException e) { + throw new JobPersistenceException( + "Couldn't obtain number of calendars: " + e.getMessage(), e); + } + } + + /** + *

+ * Get the names of all of the {@link com.fr.third.org.quartz.Job} s that + * have the given group name. + *

+ * + *

+ * If there are no jobs in the given group name, the result should be a + * zero-length array (not null). + *

+ */ + public String[] getJobNames(final SchedulingContext ctxt, final String groupName) + throws JobPersistenceException { + return (String[])executeWithoutLock( // no locks necessary for read... + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + return getJobNames(conn, ctxt, groupName); + } + }); + } + + protected String[] getJobNames(Connection conn, SchedulingContext ctxt, + String groupName) throws JobPersistenceException { + String[] jobNames = null; + + try { + jobNames = getDelegate().selectJobsInGroup(conn, groupName); + } catch (SQLException e) { + throw new JobPersistenceException("Couldn't obtain job names: " + + e.getMessage(), e); + } + + return jobNames; + } + + /** + *

+ * Get the names of all of the {@link com.fr.third.org.quartz.Trigger} s + * that have the given group name. + *

+ * + *

+ * If there are no triggers in the given group name, the result should be a + * zero-length array (not null). + *

+ */ + public String[] getTriggerNames(final SchedulingContext ctxt, final String groupName) + throws JobPersistenceException { + return (String[])executeWithoutLock( // no locks necessary for read... + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + return getTriggerNames(conn, ctxt, groupName); + } + }); + } + + protected String[] getTriggerNames(Connection conn, SchedulingContext ctxt, + String groupName) throws JobPersistenceException { + + String[] trigNames = null; + + try { + trigNames = getDelegate().selectTriggersInGroup(conn, groupName); + } catch (SQLException e) { + throw new JobPersistenceException("Couldn't obtain trigger names: " + + e.getMessage(), e); + } + + return trigNames; + } + + + /** + *

+ * Get the names of all of the {@link com.fr.third.org.quartz.Job} + * groups. + *

+ * + *

+ * If there are no known group names, the result should be a zero-length + * array (not null). + *

+ */ + public String[] getJobGroupNames(final SchedulingContext ctxt) + throws JobPersistenceException { + return (String[])executeWithoutLock( // no locks necessary for read... + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + return getJobGroupNames(conn, ctxt); + } + }); + } + + protected String[] getJobGroupNames(Connection conn, SchedulingContext ctxt) + throws JobPersistenceException { + + String[] groupNames = null; + + try { + groupNames = getDelegate().selectJobGroups(conn); + } catch (SQLException e) { + throw new JobPersistenceException("Couldn't obtain job groups: " + + e.getMessage(), e); + } + + return groupNames; + } + + /** + *

+ * Get the names of all of the {@link com.fr.third.org.quartz.Trigger} + * groups. + *

+ * + *

+ * If there are no known group names, the result should be a zero-length + * array (not null). + *

+ */ + public String[] getTriggerGroupNames(final SchedulingContext ctxt) + throws JobPersistenceException { + return (String[])executeWithoutLock( // no locks necessary for read... + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + return getTriggerGroupNames(conn, ctxt); + } + }); + } + + protected String[] getTriggerGroupNames(Connection conn, + SchedulingContext ctxt) throws JobPersistenceException { + + String[] groupNames = null; + + try { + groupNames = getDelegate().selectTriggerGroups(conn); + } catch (SQLException e) { + throw new JobPersistenceException( + "Couldn't obtain trigger groups: " + e.getMessage(), e); + } + + return groupNames; + } + + /** + *

+ * Get the names of all of the {@link com.fr.third.org.quartz.Calendar} s + * in the JobStore. + *

+ * + *

+ * If there are no Calendars in the given group name, the result should be + * a zero-length array (not null). + *

+ */ + public String[] getCalendarNames(final SchedulingContext ctxt) + throws JobPersistenceException { + return (String[])executeWithoutLock( // no locks necessary for read... + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + return getCalendarNames(conn, ctxt); + } + }); + } + + protected String[] getCalendarNames(Connection conn, SchedulingContext ctxt) + throws JobPersistenceException { + try { + return getDelegate().selectCalendars(conn); + } catch (SQLException e) { + throw new JobPersistenceException( + "Couldn't obtain trigger groups: " + e.getMessage(), e); + } + } + + /** + *

+ * Get all of the Triggers that are associated to the given Job. + *

+ * + *

+ * If there are no matches, a zero-length array should be returned. + *

+ */ + public Trigger[] getTriggersForJob(final SchedulingContext ctxt, final String jobName, + final String groupName) throws JobPersistenceException { + return (Trigger[])executeWithoutLock( // no locks necessary for read... + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + return getTriggersForJob(conn, ctxt, jobName, groupName); + } + }); + } + + protected Trigger[] getTriggersForJob(Connection conn, + SchedulingContext ctxt, String jobName, String groupName) + throws JobPersistenceException { + Trigger[] array = null; + + try { + array = getDelegate() + .selectTriggersForJob(conn, jobName, groupName); + } catch (Exception e) { + throw new JobPersistenceException( + "Couldn't obtain triggers for job: " + e.getMessage(), e); + } + + return array; + } + + /** + *

+ * Pause the {@link com.fr.third.org.quartz.Trigger} with the given name. + *

+ * + * @see #resumeTrigger(SchedulingContext, String, String) + */ + public void pauseTrigger(final SchedulingContext ctxt, final String triggerName, + final String groupName) throws JobPersistenceException { + executeInLock( + LOCK_TRIGGER_ACCESS, + new VoidTransactionCallback() { + public void execute(Connection conn) throws JobPersistenceException { + pauseTrigger(conn, ctxt, triggerName, groupName); + } + }); + } + + /** + *

+ * Pause the {@link com.fr.third.org.quartz.Trigger} with the given name. + *

+ * + * @see #resumeTrigger(Connection, SchedulingContext, String, String) + */ + public void pauseTrigger(Connection conn, SchedulingContext ctxt, + String triggerName, String groupName) + throws JobPersistenceException { + + try { + String oldState = getDelegate().selectTriggerState(conn, + triggerName, groupName); + + if (oldState.equals(STATE_WAITING) + || oldState.equals(STATE_ACQUIRED)) { + + getDelegate().updateTriggerState(conn, triggerName, + groupName, STATE_PAUSED); + } else if (oldState.equals(STATE_BLOCKED)) { + getDelegate().updateTriggerState(conn, triggerName, + groupName, STATE_PAUSED_BLOCKED); + } + } catch (SQLException e) { + throw new JobPersistenceException("Couldn't pause trigger '" + + groupName + "." + triggerName + "': " + e.getMessage(), e); + } + } + + /** + *

+ * Pause the {@link com.fr.third.org.quartz.Job} with the given name - by + * pausing all of its current Triggers. + *

+ * + * @see #resumeJob(SchedulingContext, String, String) + */ + public void pauseJob(final SchedulingContext ctxt, final String jobName, + final String groupName) throws JobPersistenceException { + executeInLock( + LOCK_TRIGGER_ACCESS, + new VoidTransactionCallback() { + public void execute(Connection conn) throws JobPersistenceException { + Trigger[] triggers = getTriggersForJob(conn, ctxt, jobName, groupName); + for (int j = 0; j < triggers.length; j++) { + pauseTrigger(conn, ctxt, triggers[j].getName(), triggers[j].getGroup()); + } + } + }); + } + + /** + *

+ * Pause all of the {@link com.fr.third.org.quartz.Job}s in the given + * group - by pausing all of their Triggers. + *

+ * + * @see #resumeJobGroup(SchedulingContext, String) + */ + public void pauseJobGroup(final SchedulingContext ctxt, final String groupName) + throws JobPersistenceException { + executeInLock( + LOCK_TRIGGER_ACCESS, + new VoidTransactionCallback() { + public void execute(Connection conn) throws JobPersistenceException { + String[] jobNames = getJobNames(conn, ctxt, groupName); + + for (int i = 0; i < jobNames.length; i++) { + Trigger[] triggers = getTriggersForJob(conn, ctxt, jobNames[i], groupName); + for (int j = 0; j < triggers.length; j++) { + pauseTrigger(conn, ctxt, triggers[j].getName(), triggers[j].getGroup()); + } + } + } + }); + } + + /** + * Determines if a Trigger for the given job should be blocked. + * State can only transition to STATE_PAUSED_BLOCKED/STATE_BLOCKED from + * STATE_PAUSED/STATE_WAITING respectively. + * + * @return STATE_PAUSED_BLOCKED, STATE_BLOCKED, or the currentState. + */ + protected String checkBlockedState( + Connection conn, SchedulingContext ctxt, String jobName, + String jobGroupName, String currentState) + throws JobPersistenceException { + + // State can only transition to BLOCKED from PAUSED or WAITING. + if ((currentState.equals(STATE_WAITING) == false) && + (currentState.equals(STATE_PAUSED) == false)) { + return currentState; + } + + try { + List lst = getDelegate().selectFiredTriggerRecordsByJob(conn, + jobName, jobGroupName); + + if (lst.size() > 0) { + FiredTriggerRecord rec = (FiredTriggerRecord) lst.get(0); + if (rec.isJobIsStateful()) { // TODO: worry about + // failed/recovering/volatile job + // states? + return (STATE_PAUSED.equals(currentState)) ? STATE_PAUSED_BLOCKED : STATE_BLOCKED; + } + } + + return currentState; + } catch (SQLException e) { + throw new JobPersistenceException( + "Couldn't determine if trigger should be in a blocked state '" + + jobGroupName + "." + + jobName + "': " + + e.getMessage(), e); + } + + } + + /* + * private List findTriggersToBeBlocked(Connection conn, SchedulingContext + * ctxt, String groupName) throws JobPersistenceException { + * + * try { List blockList = new LinkedList(); + * + * List affectingJobs = + * getDelegate().selectStatefulJobsOfTriggerGroup(conn, groupName); + * + * Iterator itr = affectingJobs.iterator(); while(itr.hasNext()) { Key + * jobKey = (Key) itr.next(); + * + * List lst = getDelegate().selectFiredTriggerRecordsByJob(conn, + * jobKey.getName(), jobKey.getGroup()); + * + * This logic is BROKEN... + * + * if(lst.size() > 0) { FiredTriggerRecord rec = + * (FiredTriggerRecord)lst.get(0); if(rec.isJobIsStateful()) // TODO: worry + * about failed/recovering/volatile job states? blockList.add( + * rec.getTriggerKey() ); } } + * + * + * return blockList; } catch (SQLException e) { throw new + * JobPersistenceException ("Couldn't determine states of resumed triggers + * in group '" + groupName + "': " + e.getMessage(), e); } } + */ + + /** + *

+ * Resume (un-pause) the {@link com.fr.third.org.quartz.Trigger} with the + * given name. + *

+ * + *

+ * If the Trigger missed one or more fire-times, then the + * Trigger's misfire instruction will be applied. + *

+ * + * @see #pauseTrigger(SchedulingContext, String, String) + */ + public void resumeTrigger(final SchedulingContext ctxt, final String triggerName, + final String groupName) throws JobPersistenceException { + executeInLock( + LOCK_TRIGGER_ACCESS, + new VoidTransactionCallback() { + public void execute(Connection conn) throws JobPersistenceException { + resumeTrigger(conn, ctxt, triggerName, groupName); + } + }); + } + + /** + *

+ * Resume (un-pause) the {@link com.fr.third.org.quartz.Trigger} with the + * given name. + *

+ * + *

+ * If the Trigger missed one or more fire-times, then the + * Trigger's misfire instruction will be applied. + *

+ * + * @see #pauseTrigger(Connection, SchedulingContext, String, String) + */ + public void resumeTrigger(Connection conn, SchedulingContext ctxt, + String triggerName, String groupName) + throws JobPersistenceException { + try { + + TriggerStatus status = getDelegate().selectTriggerStatus(conn, + triggerName, groupName); + + if (status == null || status.getNextFireTime() == null) { + return; + } + + boolean blocked = false; + if(STATE_PAUSED_BLOCKED.equals(status.getStatus())) { + blocked = true; + } + + String newState = checkBlockedState(conn, ctxt, status.getJobKey().getName(), + status.getJobKey().getGroup(), STATE_WAITING); + + boolean misfired = false; + + if (status.getNextFireTime().before(new Date())) { + misfired = updateMisfiredTrigger(conn, ctxt, triggerName, groupName, + newState, true); + } + + if(!misfired) { + if(blocked) { + getDelegate().updateTriggerStateFromOtherState(conn, + triggerName, groupName, newState, STATE_PAUSED_BLOCKED); + } else { + getDelegate().updateTriggerStateFromOtherState(conn, + triggerName, groupName, newState, STATE_PAUSED); + } + } + + } catch (SQLException e) { + throw new JobPersistenceException("Couldn't resume trigger '" + + groupName + "." + triggerName + "': " + e.getMessage(), e); + } + } + + /** + *

+ * Resume (un-pause) the {@link com.fr.third.org.quartz.Job} with the + * given name. + *

+ * + *

+ * If any of the Job'sTrigger s missed one + * or more fire-times, then the Trigger's misfire + * instruction will be applied. + *

+ * + * @see #pauseJob(SchedulingContext, String, String) + */ + public void resumeJob(final SchedulingContext ctxt, final String jobName, + final String groupName) throws JobPersistenceException { + executeInLock( + LOCK_TRIGGER_ACCESS, + new VoidTransactionCallback() { + public void execute(Connection conn) throws JobPersistenceException { + Trigger[] triggers = getTriggersForJob(conn, ctxt, jobName, groupName); + for (int j = 0; j < triggers.length; j++) { + resumeTrigger(conn, ctxt, triggers[j].getName(), triggers[j].getGroup()); + } + } + }); + } + + /** + *

+ * Resume (un-pause) all of the {@link com.fr.third.org.quartz.Job}s in + * the given group. + *

+ * + *

+ * If any of the Job s had Trigger s that + * missed one or more fire-times, then the Trigger's + * misfire instruction will be applied. + *

+ * + * @see #pauseJobGroup(SchedulingContext, String) + */ + public void resumeJobGroup(final SchedulingContext ctxt, final String groupName) + throws JobPersistenceException { + executeInLock( + LOCK_TRIGGER_ACCESS, + new VoidTransactionCallback() { + public void execute(Connection conn) throws JobPersistenceException { + String[] jobNames = getJobNames(conn, ctxt, groupName); + + for (int i = 0; i < jobNames.length; i++) { + Trigger[] triggers = getTriggersForJob(conn, ctxt, jobNames[i], groupName); + for (int j = 0; j < triggers.length; j++) { + resumeTrigger(conn, ctxt, triggers[j].getName(), triggers[j].getGroup()); + } + } + } + }); + } + + /** + *

+ * Pause all of the {@link com.fr.third.org.quartz.Trigger}s in the + * given group. + *

+ * + * @see #resumeTriggerGroup(SchedulingContext, String) + */ + public void pauseTriggerGroup(final SchedulingContext ctxt, final String groupName) + throws JobPersistenceException { + executeInLock( + LOCK_TRIGGER_ACCESS, + new VoidTransactionCallback() { + public void execute(Connection conn) throws JobPersistenceException { + pauseTriggerGroup(conn, ctxt, groupName); + } + }); + } + + /** + *

+ * Pause all of the {@link com.fr.third.org.quartz.Trigger}s in the + * given group. + *

+ * + * @see #resumeTriggerGroup(Connection, SchedulingContext, String) + */ + public void pauseTriggerGroup(Connection conn, SchedulingContext ctxt, + String groupName) throws JobPersistenceException { + + try { + + getDelegate().updateTriggerGroupStateFromOtherStates( + conn, groupName, STATE_PAUSED, STATE_ACQUIRED, + STATE_WAITING, STATE_WAITING); + + getDelegate().updateTriggerGroupStateFromOtherState( + conn, groupName, STATE_PAUSED_BLOCKED, STATE_BLOCKED); + + if (!getDelegate().isTriggerGroupPaused(conn, groupName)) { + getDelegate().insertPausedTriggerGroup(conn, groupName); + } + + } catch (SQLException e) { + throw new JobPersistenceException("Couldn't pause trigger group '" + + groupName + "': " + e.getMessage(), e); + } + } + + public Set getPausedTriggerGroups(final SchedulingContext ctxt) + throws JobPersistenceException { + return (Set)executeWithoutLock( // no locks necessary for read... + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + return getPausedTriggerGroups(conn, ctxt); + } + }); + } + + /** + *

+ * Pause all of the {@link com.fr.third.org.quartz.Trigger}s in the + * given group. + *

+ * + * @see #resumeTriggerGroup(Connection, SchedulingContext, String) + */ + public Set getPausedTriggerGroups(Connection conn, SchedulingContext ctxt) + throws JobPersistenceException { + + try { + return getDelegate().selectPausedTriggerGroups(conn); + } catch (SQLException e) { + throw new JobPersistenceException( + "Couldn't determine paused trigger groups: " + e.getMessage(), e); + } + } + + /** + *

+ * Resume (un-pause) all of the {@link com.fr.third.org.quartz.Trigger}s + * in the given group. + *

+ * + *

+ * If any Trigger missed one or more fire-times, then the + * Trigger's misfire instruction will be applied. + *

+ * + * @see #pauseTriggerGroup(SchedulingContext, String) + */ + public void resumeTriggerGroup(final SchedulingContext ctxt, final String groupName) + throws JobPersistenceException { + executeInLock( + LOCK_TRIGGER_ACCESS, + new VoidTransactionCallback() { + public void execute(Connection conn) throws JobPersistenceException { + resumeTriggerGroup(conn, ctxt, groupName); + } + }); + } + + /** + *

+ * Resume (un-pause) all of the {@link com.fr.third.org.quartz.Trigger}s + * in the given group. + *

+ * + *

+ * If any Trigger missed one or more fire-times, then the + * Trigger's misfire instruction will be applied. + *

+ * + * @see #pauseTriggerGroup(Connection, SchedulingContext, String) + */ + public void resumeTriggerGroup(Connection conn, SchedulingContext ctxt, + String groupName) throws JobPersistenceException { + + try { + + getDelegate().deletePausedTriggerGroup(conn, groupName); + + String[] trigNames = getDelegate().selectTriggersInGroup(conn, + groupName); + + for (int i = 0; i < trigNames.length; i++) { + resumeTrigger(conn, ctxt, trigNames[i], groupName); + } + + // TODO: find an efficient way to resume triggers (better than the + // above)... logic below is broken because of + // findTriggersToBeBlocked() + /* + * int res = + * getDelegate().updateTriggerGroupStateFromOtherState(conn, + * groupName, STATE_WAITING, STATE_PAUSED); + * + * if(res > 0) { + * + * long misfireTime = System.currentTimeMillis(); + * if(getMisfireThreshold() > 0) misfireTime -= + * getMisfireThreshold(); + * + * Key[] misfires = + * getDelegate().selectMisfiredTriggersInGroupInState(conn, + * groupName, STATE_WAITING, misfireTime); + * + * List blockedTriggers = findTriggersToBeBlocked(conn, ctxt, + * groupName); + * + * Iterator itr = blockedTriggers.iterator(); while(itr.hasNext()) { + * Key key = (Key)itr.next(); + * getDelegate().updateTriggerState(conn, key.getName(), + * key.getGroup(), STATE_BLOCKED); } + * + * for(int i=0; i < misfires.length; i++) { String + * newState = STATE_WAITING; + * if(blockedTriggers.contains(misfires[i])) newState = + * STATE_BLOCKED; updateMisfiredTrigger(conn, ctxt, + * misfires[i].getName(), misfires[i].getGroup(), newState, true); } } + */ + + } catch (SQLException e) { + throw new JobPersistenceException("Couldn't pause trigger group '" + + groupName + "': " + e.getMessage(), e); + } + } + + /** + *

+ * Pause all triggers - equivalent of calling pauseTriggerGroup(group) + * on every group. + *

+ * + *

+ * When resumeAll() is called (to un-pause), trigger misfire + * instructions WILL be applied. + *

+ * + * @see #resumeAll(SchedulingContext) + * @see #pauseTriggerGroup(SchedulingContext, String) + */ + public void pauseAll(final SchedulingContext ctxt) throws JobPersistenceException { + executeInLock( + LOCK_TRIGGER_ACCESS, + new VoidTransactionCallback() { + public void execute(Connection conn) throws JobPersistenceException { + pauseAll(conn, ctxt); + } + }); + } + + /** + *

+ * Pause all triggers - equivalent of calling pauseTriggerGroup(group) + * on every group. + *

+ * + *

+ * When resumeAll() is called (to un-pause), trigger misfire + * instructions WILL be applied. + *

+ * + * @see #resumeAll(SchedulingContext) + * @see #pauseTriggerGroup(SchedulingContext, String) + */ + public void pauseAll(Connection conn, SchedulingContext ctxt) + throws JobPersistenceException { + + String[] names = getTriggerGroupNames(conn, ctxt); + + for (int i = 0; i < names.length; i++) { + pauseTriggerGroup(conn, ctxt, names[i]); + } + + try { + if (!getDelegate().isTriggerGroupPaused(conn, ALL_GROUPS_PAUSED)) { + getDelegate().insertPausedTriggerGroup(conn, ALL_GROUPS_PAUSED); + } + + } catch (SQLException e) { + throw new JobPersistenceException( + "Couldn't pause all trigger groups: " + e.getMessage(), e); + } + + } + + /** + *

+ * Resume (un-pause) all triggers - equivalent of calling resumeTriggerGroup(group) + * on every group. + *

+ * + *

+ * If any Trigger missed one or more fire-times, then the + * Trigger's misfire instruction will be applied. + *

+ * + * @see #pauseAll(SchedulingContext) + */ + public void resumeAll(final SchedulingContext ctxt) + throws JobPersistenceException { + executeInLock( + LOCK_TRIGGER_ACCESS, + new VoidTransactionCallback() { + public void execute(Connection conn) throws JobPersistenceException { + resumeAll(conn, ctxt); + } + }); + } + + /** + * protected + *

+ * Resume (un-pause) all triggers - equivalent of calling resumeTriggerGroup(group) + * on every group. + *

+ * + *

+ * If any Trigger missed one or more fire-times, then the + * Trigger's misfire instruction will be applied. + *

+ * + * @see #pauseAll(SchedulingContext) + */ + public void resumeAll(Connection conn, SchedulingContext ctxt) + throws JobPersistenceException { + + String[] names = getTriggerGroupNames(conn, ctxt); + + for (int i = 0; i < names.length; i++) { + resumeTriggerGroup(conn, ctxt, names[i]); + } + + try { + getDelegate().deletePausedTriggerGroup(conn, ALL_GROUPS_PAUSED); + } catch (SQLException e) { + throw new JobPersistenceException( + "Couldn't resume all trigger groups: " + e.getMessage(), e); + } + } + + private static long ftrCtr = System.currentTimeMillis(); + + protected synchronized String getFiredTriggerRecordId() { + return getInstanceId() + ftrCtr++; + } + + /** + *

+ * Get a handle to the next N triggers to be fired, and mark them as 'reserved' + * by the calling scheduler. + *

+ * + * @see #releaseAcquiredTrigger(SchedulingContext, Trigger) + */ + public Trigger acquireNextTrigger(final SchedulingContext ctxt, final long noLaterThan) + throws JobPersistenceException { + + if(isAcquireTriggersWithinLock()) { // behavior before Quartz 1.6.3 release + return (Trigger)executeInNonManagedTXLock( + LOCK_TRIGGER_ACCESS, + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + return acquireNextTrigger(conn, ctxt, noLaterThan); + } + }); + } + else { // default behavior since Quartz 1.6.3 release + return (Trigger)executeWithoutLock( + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + return acquireNextTrigger(conn, ctxt, noLaterThan); + } + }); + } + } + + // TODO: this really ought to return something like a FiredTriggerBundle, + // so that the fireInstanceId doesn't have to be on the trigger... + protected Trigger acquireNextTrigger(Connection conn, SchedulingContext ctxt, long noLaterThan) + throws JobPersistenceException { + do { + try { + Trigger nextTrigger = null; + + List keys = getDelegate().selectTriggerToAcquire(conn, noLaterThan, getMisfireTime()); + + // No trigger is ready to fire yet. + if (keys == null || keys.size() == 0) + return null; + + Iterator itr = keys.iterator(); + while(itr.hasNext()) { + Key triggerKey = (Key) itr.next(); + + int rowsUpdated = + getDelegate().updateTriggerStateFromOtherState( + conn, + triggerKey.getName(), triggerKey.getGroup(), + STATE_ACQUIRED, STATE_WAITING); + + // If our trigger was no longer in the expected state, try a new one. + if (rowsUpdated <= 0) { + continue; + } + + nextTrigger = + retrieveTrigger(conn, ctxt, triggerKey.getName(), triggerKey.getGroup()); + + // If our trigger is no longer available, try a new one. + if(nextTrigger == null) { + continue; + } + + break; + } + + // if we didn't end up with a trigger to fire from that first + // batch, try again for another batch + if(nextTrigger == null) { + continue; + } + + nextTrigger.setFireInstanceId(getFiredTriggerRecordId()); + getDelegate().insertFiredTrigger(conn, nextTrigger, STATE_ACQUIRED, null); + + return nextTrigger; + } catch (Exception e) { + throw new JobPersistenceException( + "Couldn't acquire next trigger: " + e.getMessage(), e); + } + } while (true); + } + + /** + *

+ * Inform the JobStore that the scheduler no longer plans to + * fire the given Trigger, that it had previously acquired + * (reserved). + *

+ */ + public void releaseAcquiredTrigger(final SchedulingContext ctxt, final Trigger trigger) + throws JobPersistenceException { + executeInNonManagedTXLock( + LOCK_TRIGGER_ACCESS, + new VoidTransactionCallback() { + public void execute(Connection conn) throws JobPersistenceException { + releaseAcquiredTrigger(conn, ctxt, trigger); + } + }); + } + + protected void releaseAcquiredTrigger(Connection conn, + SchedulingContext ctxt, Trigger trigger) + throws JobPersistenceException { + try { + getDelegate().updateTriggerStateFromOtherState(conn, + trigger.getName(), trigger.getGroup(), STATE_WAITING, + STATE_ACQUIRED); + getDelegate().deleteFiredTrigger(conn, trigger.getFireInstanceId()); + } catch (SQLException e) { + throw new JobPersistenceException( + "Couldn't release acquired trigger: " + e.getMessage(), e); + } + } + + /** + *

+ * Inform the JobStore that the scheduler is now firing the + * given Trigger (executing its associated Job), + * that it had previously acquired (reserved). + *

+ * + * @return null if the trigger or its job or calendar no longer exist, or + * if the trigger was not successfully put into the 'executing' + * state. + */ + public TriggerFiredBundle triggerFired( + final SchedulingContext ctxt, final Trigger trigger) throws JobPersistenceException { + return + (TriggerFiredBundle)executeInNonManagedTXLock( + LOCK_TRIGGER_ACCESS, + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + try { + return triggerFired(conn, ctxt, trigger); + } catch (JobPersistenceException jpe) { + // If job didn't exisit, we still want to commit our work and return null. + if (jpe.getErrorCode() == SchedulerException.ERR_PERSISTENCE_JOB_DOES_NOT_EXIST) { + return null; + } else { + throw jpe; + } + } + } + }); + } + + protected TriggerFiredBundle triggerFired(Connection conn, + SchedulingContext ctxt, Trigger trigger) + throws JobPersistenceException { + JobDetail job = null; + Calendar cal = null; + + // Make sure trigger wasn't deleted, paused, or completed... + try { // if trigger was deleted, state will be STATE_DELETED + String state = getDelegate().selectTriggerState(conn, + trigger.getName(), trigger.getGroup()); + if (!state.equals(STATE_ACQUIRED)) { + return null; + } + } catch (SQLException e) { + throw new JobPersistenceException("Couldn't select trigger state: " + + e.getMessage(), e); + } + + try { + job = retrieveJob(conn, ctxt, trigger.getJobName(), trigger + .getJobGroup()); + if (job == null) { return null; } + } catch (JobPersistenceException jpe) { + try { + getLog().error("Error retrieving job, setting trigger state to ERROR.", jpe); + getDelegate().updateTriggerState(conn, trigger.getName(), + trigger.getGroup(), STATE_ERROR); + } catch (SQLException sqle) { + getLog().error("Unable to set trigger state to ERROR.", sqle); + } + throw jpe; + } + + if (trigger.getCalendarName() != null) { + cal = retrieveCalendar(conn, ctxt, trigger.getCalendarName()); + if (cal == null) { return null; } + } + + try { + getDelegate().deleteFiredTrigger(conn, trigger.getFireInstanceId()); + getDelegate().insertFiredTrigger(conn, trigger, STATE_EXECUTING, + job); + } catch (SQLException e) { + throw new JobPersistenceException("Couldn't insert fired trigger: " + + e.getMessage(), e); + } + + Date prevFireTime = trigger.getPreviousFireTime(); + + // call triggered - to update the trigger's next-fire-time state... + trigger.triggered(cal); + + String state = STATE_WAITING; + boolean force = true; + + if (job.isStateful()) { + state = STATE_BLOCKED; + force = false; + try { + getDelegate().updateTriggerStatesForJobFromOtherState(conn, job.getName(), + job.getGroup(), STATE_BLOCKED, STATE_WAITING); + getDelegate().updateTriggerStatesForJobFromOtherState(conn, job.getName(), + job.getGroup(), STATE_BLOCKED, STATE_ACQUIRED); + getDelegate().updateTriggerStatesForJobFromOtherState(conn, job.getName(), + job.getGroup(), STATE_PAUSED_BLOCKED, STATE_PAUSED); + } catch (SQLException e) { + throw new JobPersistenceException( + "Couldn't update states of blocked triggers: " + + e.getMessage(), e); + } + } + + if (trigger.getNextFireTime() == null) { + state = STATE_COMPLETE; + force = true; + } + + storeTrigger(conn, ctxt, trigger, job, true, state, force, false); + + job.getJobDataMap().clearDirtyFlag(); + + return new TriggerFiredBundle(job, trigger, cal, trigger.getGroup() + .equals(Scheduler.DEFAULT_RECOVERY_GROUP), new Date(), trigger + .getPreviousFireTime(), prevFireTime, trigger.getNextFireTime()); + } + + /** + *

+ * Inform the JobStore that the scheduler has completed the + * firing of the given Trigger (and the execution its + * associated Job), and that the {@link com.fr.third.org.quartz.JobDataMap} + * in the given JobDetail should be updated if the Job + * is stateful. + *

+ */ + public void triggeredJobComplete(final SchedulingContext ctxt, final Trigger trigger, + final JobDetail jobDetail, final int triggerInstCode) + throws JobPersistenceException { + executeInNonManagedTXLock( + LOCK_TRIGGER_ACCESS, + new VoidTransactionCallback() { + public void execute(Connection conn) throws JobPersistenceException { + triggeredJobComplete(conn, ctxt, trigger, jobDetail,triggerInstCode); + } + }); + } + + protected void triggeredJobComplete(Connection conn, + SchedulingContext ctxt, Trigger trigger, JobDetail jobDetail, + int triggerInstCode) throws JobPersistenceException { + try { + if (triggerInstCode == Trigger.INSTRUCTION_DELETE_TRIGGER) { + if(trigger.getNextFireTime() == null) { + // double check for possible reschedule within job + // execution, which would cancel the need to delete... + TriggerStatus stat = getDelegate().selectTriggerStatus( + conn, trigger.getName(), trigger.getGroup()); + if(stat != null && stat.getNextFireTime() == null) { + removeTrigger(conn, ctxt, trigger.getName(), trigger.getGroup()); + } + } else{ + removeTrigger(conn, ctxt, trigger.getName(), trigger.getGroup()); + signaler.signalSchedulingChange(0L); + } + } else if (triggerInstCode == Trigger.INSTRUCTION_SET_TRIGGER_COMPLETE) { + getDelegate().updateTriggerState(conn, trigger.getName(), + trigger.getGroup(), STATE_COMPLETE); + signaler.signalSchedulingChange(0L); + } else if (triggerInstCode == Trigger.INSTRUCTION_SET_TRIGGER_ERROR) { + getLog().info("Trigger " + trigger.getFullName() + " set to ERROR state."); + getDelegate().updateTriggerState(conn, trigger.getName(), + trigger.getGroup(), STATE_ERROR); + signaler.signalSchedulingChange(0L); + } else if (triggerInstCode == Trigger.INSTRUCTION_SET_ALL_JOB_TRIGGERS_COMPLETE) { + getDelegate().updateTriggerStatesForJob(conn, + trigger.getJobName(), trigger.getJobGroup(), + STATE_COMPLETE); + signaler.signalSchedulingChange(0L); + } else if (triggerInstCode == Trigger.INSTRUCTION_SET_ALL_JOB_TRIGGERS_ERROR) { + getLog().info("All triggers of Job " + + trigger.getFullJobName() + " set to ERROR state."); + getDelegate().updateTriggerStatesForJob(conn, + trigger.getJobName(), trigger.getJobGroup(), + STATE_ERROR); + signaler.signalSchedulingChange(0L); + } + + if (jobDetail.isStateful()) { + getDelegate().updateTriggerStatesForJobFromOtherState(conn, + jobDetail.getName(), jobDetail.getGroup(), + STATE_WAITING, STATE_BLOCKED); + + getDelegate().updateTriggerStatesForJobFromOtherState(conn, + jobDetail.getName(), jobDetail.getGroup(), + STATE_PAUSED, STATE_PAUSED_BLOCKED); + + signaler.signalSchedulingChange(0L); + + try { + if (jobDetail.getJobDataMap().isDirty()) { + getDelegate().updateJobData(conn, jobDetail); + } + } catch (IOException e) { + throw new JobPersistenceException( + "Couldn't serialize job data: " + e.getMessage(), e); + } catch (SQLException e) { + throw new JobPersistenceException( + "Couldn't update job data: " + e.getMessage(), e); + } + } + } catch (SQLException e) { + throw new JobPersistenceException( + "Couldn't update trigger state(s): " + e.getMessage(), e); + } + + try { + getDelegate().deleteFiredTrigger(conn, trigger.getFireInstanceId()); + } catch (SQLException e) { + throw new JobPersistenceException("Couldn't delete fired trigger: " + + e.getMessage(), e); + } + } + + /** + *

+ * Get the driver delegate for DB operations. + *

+ */ + protected DriverDelegate getDelegate() throws NoSuchDelegateException { + if (null == delegate) { + try { + if(delegateClassName != null) { + delegateClass = + getClassLoadHelper().loadClass(delegateClassName); + } + + Constructor ctor = null; + Object[] ctorParams = null; + if (canUseProperties()) { + Class[] ctorParamTypes = new Class[]{ + Log.class, String.class, String.class, Boolean.class}; + ctor = delegateClass.getConstructor(ctorParamTypes); + ctorParams = new Object[]{ + getLog(), tablePrefix, + instanceId, new Boolean(canUseProperties())}; + } else { + Class[] ctorParamTypes = new Class[]{ + Log.class, String.class, String.class}; + ctor = delegateClass.getConstructor(ctorParamTypes); + ctorParams = new Object[]{getLog(), tablePrefix, instanceId}; + } + + delegate = (DriverDelegate) ctor.newInstance(ctorParams); + } catch (NoSuchMethodException e) { + throw new NoSuchDelegateException( + "Couldn't find delegate constructor: " + e.getMessage()); + } catch (InstantiationException e) { + throw new NoSuchDelegateException("Couldn't create delegate: " + + e.getMessage()); + } catch (IllegalAccessException e) { + throw new NoSuchDelegateException("Couldn't create delegate: " + + e.getMessage()); + } catch (InvocationTargetException e) { + throw new NoSuchDelegateException("Couldn't create delegate: " + + e.getMessage()); + } catch (ClassNotFoundException e) { + throw new NoSuchDelegateException("Couldn't load delegate class: " + + e.getMessage()); + } + } + + return delegate; + } + + protected Semaphore getLockHandler() { + return lockHandler; + } + + public void setLockHandler(Semaphore lockHandler) { + this.lockHandler = lockHandler; + } + + //--------------------------------------------------------------------------- + // Management methods + //--------------------------------------------------------------------------- + + protected RecoverMisfiredJobsResult doRecoverMisfires() throws JobPersistenceException { + boolean transOwner = false; + Connection conn = getNonManagedTXConnection(); + try { + RecoverMisfiredJobsResult result = RecoverMisfiredJobsResult.NO_OP; + + // Before we make the potentially expensive call to acquire the + // trigger lock, peek ahead to see if it is likely we would find + // misfired triggers requiring recovery. + int misfireCount = (getDoubleCheckLockMisfireHandler()) ? + getDelegate().countMisfiredTriggersInStates( + conn, STATE_MISFIRED, STATE_WAITING, getMisfireTime()) : + Integer.MAX_VALUE; + + if (misfireCount == 0) { + getLog().debug( + "Found 0 triggers that missed their scheduled fire-time."); + } else { + transOwner = getLockHandler().obtainLock(conn, LOCK_TRIGGER_ACCESS); + + result = recoverMisfiredJobs(conn, false); + } + + commitConnection(conn); + return result; + } catch (JobPersistenceException e) { + rollbackConnection(conn); + throw e; + } catch (SQLException e) { + rollbackConnection(conn); + throw new JobPersistenceException("Database error recovering from misfires.", e); + } catch (RuntimeException e) { + rollbackConnection(conn); + throw new JobPersistenceException("Unexpected runtime exception: " + + e.getMessage(), e); + } finally { + try { + releaseLock(conn, LOCK_TRIGGER_ACCESS, transOwner); + } finally { + cleanupConnection(conn); + } + } + } + + protected void signalSchedulingChange(long candidateNewNextFireTime) { + signaler.signalSchedulingChange(candidateNewNextFireTime); + } + + //--------------------------------------------------------------------------- + // Cluster management methods + //--------------------------------------------------------------------------- + + protected boolean firstCheckIn = true; + + protected long lastCheckin = System.currentTimeMillis(); + + protected boolean doCheckin() throws JobPersistenceException { + boolean transOwner = false; + boolean transStateOwner = false; + boolean recovered = false; + + Connection conn = getNonManagedTXConnection(); + try { + // Other than the first time, always checkin first to make sure there is + // work to be done before we aquire the lock (since that is expensive, + // and is almost never necessary). This must be done in a separate + // transaction to prevent a deadlock under recovery conditions. + List failedRecords = null; + if (firstCheckIn == false) { + boolean succeeded = false; + try { + failedRecords = clusterCheckIn(conn); + commitConnection(conn); + succeeded = true; + } catch (JobPersistenceException e) { + rollbackConnection(conn); + throw e; + } finally { + // Only cleanup the connection if we failed and are bailing + // as we will otherwise continue to use it. + if (succeeded == false) { + cleanupConnection(conn); + } + } + } + + if (firstCheckIn || (failedRecords.size() > 0)) { + getLockHandler().obtainLock(conn, LOCK_STATE_ACCESS); + transStateOwner = true; + + // Now that we own the lock, make sure we still have work to do. + // The first time through, we also need to make sure we update/create our state record + failedRecords = (firstCheckIn) ? clusterCheckIn(conn) : findFailedInstances(conn); + + if (failedRecords.size() > 0) { + getLockHandler().obtainLock(conn, LOCK_TRIGGER_ACCESS); + //getLockHandler().obtainLock(conn, LOCK_JOB_ACCESS); + transOwner = true; + + clusterRecover(conn, failedRecords); + recovered = true; + } + } + + commitConnection(conn); + } catch (JobPersistenceException e) { + rollbackConnection(conn); + throw e; + } finally { + try { + releaseLock(conn, LOCK_TRIGGER_ACCESS, transOwner); + } finally { + try { + releaseLock(conn, LOCK_STATE_ACCESS, transStateOwner); + } finally { + cleanupConnection(conn); + } + } + } + + firstCheckIn = false; + + return recovered; + } + + /** + * Get a list of all scheduler instances in the cluster that may have failed. + * This includes this scheduler if it is checking in for the first time. + */ + protected List findFailedInstances(Connection conn) + throws JobPersistenceException { + try { + List failedInstances = new LinkedList(); + boolean foundThisScheduler = false; + long timeNow = System.currentTimeMillis(); + + List states = getDelegate().selectSchedulerStateRecords(conn, null); + + for (Iterator itr = states.iterator(); itr.hasNext();) { + SchedulerStateRecord rec = (SchedulerStateRecord) itr.next(); + + // find own record... + if (rec.getSchedulerInstanceId().equals(getInstanceId())) { + foundThisScheduler = true; + if (firstCheckIn) { + failedInstances.add(rec); + } + } else { + // find failed instances... + if (calcFailedIfAfter(rec) < timeNow) { + failedInstances.add(rec); + } + } + } + + // The first time through, also check for orphaned fired triggers. + if (firstCheckIn) { + failedInstances.addAll(findOrphanedFailedInstances(conn, states)); + } + + // If not the first time but we didn't find our own instance, then + // Someone must have done recovery for us. + if ((foundThisScheduler == false) && (firstCheckIn == false)) { + // TODO: revisit when handle self-failed-out implied (see TODO in clusterCheckIn() below) + getLog().warn( + "This scheduler instance (" + getInstanceId() + ") is still " + + "active but was recovered by another instance in the cluster. " + + "This may cause inconsistent behavior."); + } + + return failedInstances; + } catch (Exception e) { + lastCheckin = System.currentTimeMillis(); + throw new JobPersistenceException("Failure identifying failed instances when checking-in: " + + e.getMessage(), e); + } + } + + /** + * Create dummy SchedulerStateRecord objects for fired triggers + * that have no scheduler state record. Checkin timestamp and interval are + * left as zero on these dummy SchedulerStateRecord objects. + * + * @param schedulerStateRecords List of all current SchedulerStateRecords + */ + private List findOrphanedFailedInstances( + Connection conn, + List schedulerStateRecords) + throws SQLException, NoSuchDelegateException { + List orphanedInstances = new ArrayList(); + + Set allFiredTriggerInstanceNames = getDelegate().selectFiredTriggerInstanceNames(conn); + if (allFiredTriggerInstanceNames.isEmpty() == false) { + for (Iterator schedulerStateIter = schedulerStateRecords.iterator(); + schedulerStateIter.hasNext();) { + SchedulerStateRecord rec = (SchedulerStateRecord)schedulerStateIter.next(); + + allFiredTriggerInstanceNames.remove(rec.getSchedulerInstanceId()); + } + + for (Iterator orphanIter = allFiredTriggerInstanceNames.iterator(); + orphanIter.hasNext();) { + + SchedulerStateRecord orphanedInstance = new SchedulerStateRecord(); + orphanedInstance.setSchedulerInstanceId((String)orphanIter.next()); + + orphanedInstances.add(orphanedInstance); + + getLog().warn( + "Found orphaned fired triggers for instance: " + orphanedInstance.getSchedulerInstanceId()); + } + } + + return orphanedInstances; + } + + protected long calcFailedIfAfter(SchedulerStateRecord rec) { + return rec.getCheckinTimestamp() + + Math.max(rec.getCheckinInterval(), + (System.currentTimeMillis() - lastCheckin)) + + 7500L; + } + + protected List clusterCheckIn(Connection conn) + throws JobPersistenceException { + + List failedInstances = findFailedInstances(conn); + + try { + // TODO: handle self-failed-out + + // check in... + lastCheckin = System.currentTimeMillis(); + if(getDelegate().updateSchedulerState(conn, getInstanceId(), lastCheckin) == 0) { + getDelegate().insertSchedulerState(conn, getInstanceId(), + lastCheckin, getClusterCheckinInterval()); + } + + } catch (Exception e) { + throw new JobPersistenceException("Failure updating scheduler state when checking-in: " + + e.getMessage(), e); + } + + return failedInstances; + } + + protected void clusterRecover(Connection conn, List failedInstances) + throws JobPersistenceException { + + if (failedInstances.size() > 0) { + + long recoverIds = System.currentTimeMillis(); + + logWarnIfNonZero(failedInstances.size(), + "ClusterManager: detected " + failedInstances.size() + + " failed or restarted instances."); + try { + Iterator itr = failedInstances.iterator(); + while (itr.hasNext()) { + SchedulerStateRecord rec = (SchedulerStateRecord) itr + .next(); + + getLog().info( + "ClusterManager: Scanning for instance \"" + + rec.getSchedulerInstanceId() + + "\"'s failed in-progress jobs."); + + List firedTriggerRecs = getDelegate() + .selectInstancesFiredTriggerRecords(conn, + rec.getSchedulerInstanceId()); + + int acquiredCount = 0; + int recoveredCount = 0; + int otherCount = 0; + + Set triggerKeys = new HashSet(); + + Iterator ftItr = firedTriggerRecs.iterator(); + while (ftItr.hasNext()) { + FiredTriggerRecord ftRec = (FiredTriggerRecord) ftItr + .next(); + + Key tKey = ftRec.getTriggerKey(); + Key jKey = ftRec.getJobKey(); + + triggerKeys.add(tKey); + + // release blocked triggers.. + if (ftRec.getFireInstanceState().equals(STATE_BLOCKED)) { + getDelegate() + .updateTriggerStatesForJobFromOtherState( + conn, jKey.getName(), + jKey.getGroup(), STATE_WAITING, + STATE_BLOCKED); + } else if (ftRec.getFireInstanceState().equals(STATE_PAUSED_BLOCKED)) { + getDelegate() + .updateTriggerStatesForJobFromOtherState( + conn, jKey.getName(), + jKey.getGroup(), STATE_PAUSED, + STATE_PAUSED_BLOCKED); + } + + // release acquired triggers.. + if (ftRec.getFireInstanceState().equals(STATE_ACQUIRED)) { + getDelegate().updateTriggerStateFromOtherState( + conn, tKey.getName(), tKey.getGroup(), + STATE_WAITING, STATE_ACQUIRED); + acquiredCount++; + } else if (ftRec.isJobRequestsRecovery()) { + // handle jobs marked for recovery that were not fully + // executed.. + if (jobExists(conn, jKey.getName(), jKey.getGroup())) { + SimpleTrigger rcvryTrig = new SimpleTrigger( + "recover_" + + rec.getSchedulerInstanceId() + + "_" + + String.valueOf(recoverIds++), + Scheduler.DEFAULT_RECOVERY_GROUP, + new Date(ftRec.getFireTimestamp())); + rcvryTrig.setVolatility(ftRec.isTriggerIsVolatile()); + rcvryTrig.setJobName(jKey.getName()); + rcvryTrig.setJobGroup(jKey.getGroup()); + rcvryTrig.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW); + rcvryTrig.setPriority(ftRec.getPriority()); + JobDataMap jd = getDelegate().selectTriggerJobDataMap(conn, tKey.getName(), tKey.getGroup()); + jd.put(Scheduler.FAILED_JOB_ORIGINAL_TRIGGER_NAME, tKey.getName()); + jd.put(Scheduler.FAILED_JOB_ORIGINAL_TRIGGER_GROUP, tKey.getGroup()); + jd.put(Scheduler.FAILED_JOB_ORIGINAL_TRIGGER_FIRETIME_IN_MILLISECONDS, String.valueOf(ftRec.getFireTimestamp())); + rcvryTrig.setJobDataMap(jd); + + rcvryTrig.computeFirstFireTime(null); + storeTrigger(conn, null, rcvryTrig, null, false, + STATE_WAITING, false, true); + recoveredCount++; + } else { + getLog() + .warn( + "ClusterManager: failed job '" + + jKey + + "' no longer exists, cannot schedule recovery."); + otherCount++; + } + } else { + otherCount++; + } + + // free up stateful job's triggers + if (ftRec.isJobIsStateful()) { + getDelegate() + .updateTriggerStatesForJobFromOtherState( + conn, jKey.getName(), + jKey.getGroup(), STATE_WAITING, + STATE_BLOCKED); + getDelegate() + .updateTriggerStatesForJobFromOtherState( + conn, jKey.getName(), + jKey.getGroup(), STATE_PAUSED, + STATE_PAUSED_BLOCKED); + } + } + + getDelegate().deleteFiredTriggers(conn, + rec.getSchedulerInstanceId()); + + // Check if any of the fired triggers we just deleted were the last fired trigger + // records of a COMPLETE trigger. + int completeCount = 0; + for (Iterator triggerKeyIter = triggerKeys.iterator(); triggerKeyIter.hasNext();) { + Key triggerKey = (Key)triggerKeyIter.next(); + + if (getDelegate().selectTriggerState(conn, triggerKey.getName(), triggerKey.getGroup()). + equals(STATE_COMPLETE)) { + List firedTriggers = + getDelegate().selectFiredTriggerRecords(conn, triggerKey.getName(), triggerKey.getGroup()); + if (firedTriggers.isEmpty()) { + SchedulingContext schedulingContext = new SchedulingContext(); + schedulingContext.setInstanceId(instanceId); + + if (removeTrigger(conn, schedulingContext, triggerKey.getName(), triggerKey.getGroup())) { + completeCount++; + } + } + } + } + + logWarnIfNonZero(acquiredCount, + "ClusterManager: ......Freed " + acquiredCount + + " acquired trigger(s)."); + logWarnIfNonZero(completeCount, + "ClusterManager: ......Deleted " + completeCount + + " complete triggers(s)."); + logWarnIfNonZero(recoveredCount, + "ClusterManager: ......Scheduled " + recoveredCount + + " recoverable job(s) for recovery."); + logWarnIfNonZero(otherCount, + "ClusterManager: ......Cleaned-up " + otherCount + + " other failed job(s)."); + + if (rec.getSchedulerInstanceId().equals(getInstanceId()) == false) { + getDelegate().deleteSchedulerState(conn, + rec.getSchedulerInstanceId()); + } + } + } catch (Exception e) { + throw new JobPersistenceException("Failure recovering jobs: " + + e.getMessage(), e); + } + } + } + + protected void logWarnIfNonZero(int val, String warning) { + if (val > 0) { + getLog().info(warning); + } else { + getLog().debug(warning); + } + } + + /** + *

+ * Cleanup the given database connection. This means restoring + * any modified auto commit or transaction isolation connection + * attributes, and then closing the underlying connection. + *

+ * + *

+ * This is separate from closeConnection() because the Spring + * integration relies on being able to overload closeConnection() and + * expects the same connection back that it originally returned + * from the datasource. + *

+ * + * @see #closeConnection(Connection) + */ + protected void cleanupConnection(Connection conn) { + if (conn != null) { + if (conn instanceof Proxy) { + Proxy connProxy = (Proxy)conn; + + InvocationHandler invocationHandler = + Proxy.getInvocationHandler(connProxy); + if (invocationHandler instanceof AttributeRestoringConnectionInvocationHandler) { + AttributeRestoringConnectionInvocationHandler connHandler = + (AttributeRestoringConnectionInvocationHandler)invocationHandler; + + connHandler.restoreOriginalAtributes(); + closeConnection(connHandler.getWrappedConnection()); + return; + } + } + + // Wan't a Proxy, or was a Proxy, but wasn't ours. + closeConnection(conn); + } + } + + + /** + * Closes the supplied Connection. + *

+ * Ignores a null Connection. + * Any exception thrown trying to close the Connection is + * logged and ignored. + *

+ * + * @param conn The Connection to close (Optional). + */ + protected void closeConnection(Connection conn) { + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + getLog().error("Failed to close Connection", e); + } catch (Throwable e) { + getLog().error( + "Unexpected exception closing Connection." + + " This is often due to a Connection being returned after or during shutdown.", e); + } + } + } + + /** + * Rollback the supplied connection. + * + *

+ * Logs any SQLException it gets trying to rollback, but will not propogate + * the exception lest it mask the exception that caused the caller to + * need to rollback in the first place. + *

+ * + * @param conn (Optional) + */ + protected void rollbackConnection(Connection conn) { + if (conn != null) { + try { + conn.rollback(); + } catch (SQLException e) { + getLog().error( + "Couldn't rollback jdbc connection. "+e.getMessage(), e); + } + } + } + + /** + * Commit the supplied connection + * + * @param conn (Optional) + * @throws JobPersistenceException thrown if a SQLException occurs when the + * connection is committed + */ + protected void commitConnection(Connection conn) + throws JobPersistenceException { + + if (conn != null) { + try { + conn.commit(); + } catch (SQLException e) { + throw new JobPersistenceException( + "Couldn't commit jdbc connection. "+e.getMessage(), e); + } + } + } + + /** + * Implement this interface to provide the code to execute within + * the a transaction template. If no return value is required, execute + * should just return null. + * + * @see JobStoreSupport#executeInNonManagedTXLock(String, TransactionCallback) + * @see JobStoreSupport#executeInLock(String, TransactionCallback) + * @see JobStoreSupport#executeWithoutLock(TransactionCallback) + */ + protected interface TransactionCallback { + Object execute(Connection conn) throws JobPersistenceException; + } + + /** + * Implement this interface to provide the code to execute within + * the a transaction template that has no return value. + * + * @see JobStoreSupport#executeInNonManagedTXLock(String, TransactionCallback) + */ + protected interface VoidTransactionCallback { + void execute(Connection conn) throws JobPersistenceException; + } + + /** + * Execute the given callback in a transaction. Depending on the JobStore, + * the surrounding transaction may be assumed to be already present + * (managed). + * + *

+ * This method just forwards to executeInLock() with a null lockName. + *

+ * + * @see #executeInLock(String, TransactionCallback) + */ + public Object executeWithoutLock( + TransactionCallback txCallback) throws JobPersistenceException { + return executeInLock(null, txCallback); + } + + /** + * Execute the given callback having aquired the given lock. + * Depending on the JobStore, the surrounding transaction may be + * assumed to be already present (managed). This version is just a + * handy wrapper around executeInLock that doesn't require a return + * value. + * + * @param lockName The name of the lock to aquire, for example + * "TRIGGER_ACCESS". If null, then no lock is aquired, but the + * lockCallback is still executed in a transaction. + * + * @see #executeInLock(String, TransactionCallback) + */ + protected void executeInLock( + final String lockName, + final VoidTransactionCallback txCallback) throws JobPersistenceException { + executeInLock( + lockName, + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + txCallback.execute(conn); + return null; + } + }); + } + + /** + * Execute the given callback having aquired the given lock. + * Depending on the JobStore, the surrounding transaction may be + * assumed to be already present (managed). + * + * @param lockName The name of the lock to aquire, for example + * "TRIGGER_ACCESS". If null, then no lock is aquired, but the + * lockCallback is still executed in a transaction. + */ + protected abstract Object executeInLock( + String lockName, + TransactionCallback txCallback) throws JobPersistenceException; + + /** + * Execute the given callback having optionally aquired the given lock. + * This uses the non-managed transaction connection. This version is just a + * handy wrapper around executeInNonManagedTXLock that doesn't require a return + * value. + * + * @param lockName The name of the lock to aquire, for example + * "TRIGGER_ACCESS". If null, then no lock is aquired, but the + * lockCallback is still executed in a non-managed transaction. + * + * @see #executeInNonManagedTXLock(String, TransactionCallback) + */ + protected void executeInNonManagedTXLock( + final String lockName, + final VoidTransactionCallback txCallback) throws JobPersistenceException { + executeInNonManagedTXLock( + lockName, + new TransactionCallback() { + public Object execute(Connection conn) throws JobPersistenceException { + txCallback.execute(conn); + return null; + } + }); + } + + /** + * Execute the given callback having optionally aquired the given lock. + * This uses the non-managed transaction connection. + * + * @param lockName The name of the lock to aquire, for example + * "TRIGGER_ACCESS". If null, then no lock is aquired, but the + * lockCallback is still executed in a non-managed transaction. + */ + protected Object executeInNonManagedTXLock( + String lockName, + TransactionCallback txCallback) throws JobPersistenceException { + boolean transOwner = false; + Connection conn = null; + try { + if (lockName != null) { + // If we aren't using db locks, then delay getting DB connection + // until after aquiring the lock since it isn't needed. + if (getLockHandler().requiresConnection()) { + conn = getNonManagedTXConnection(); + } + + transOwner = getLockHandler().obtainLock(conn, lockName); + } + + if (conn == null) { + conn = getNonManagedTXConnection(); + } + + Object result = txCallback.execute(conn); + commitConnection(conn); + return result; + } catch (JobPersistenceException e) { + rollbackConnection(conn); + throw e; + } catch (RuntimeException e) { + rollbackConnection(conn); + throw new JobPersistenceException("Unexpected runtime exception: " + + e.getMessage(), e); + } finally { + try { + releaseLock(conn, lockName, transOwner); + } finally { + cleanupConnection(conn); + } + } + } + + ///////////////////////////////////////////////////////////////////////////// + // + // ClusterManager Thread + // + ///////////////////////////////////////////////////////////////////////////// + + class ClusterManager extends Thread { + + private boolean shutdown = false; + + private int numFails = 0; + + ClusterManager() { + this.setPriority(Thread.NORM_PRIORITY + 2); + this.setName("QuartzScheduler_" + instanceName + "-" + instanceId + "_ClusterManager"); + this.setDaemon(getMakeThreadsDaemons()); + } + + public void initialize() { + this.manage(); + this.start(); + } + + public void shutdown() { + shutdown = true; + this.interrupt(); + } + + private boolean manage() { + boolean res = false; + try { + + res = doCheckin(); + + numFails = 0; + getLog().debug("ClusterManager: Check-in complete."); + } catch (Exception e) { + if(numFails % 4 == 0) { + getLog().error( + "ClusterManager: Error managing cluster: " + + e.getMessage(), e); + } + numFails++; + } + return res; + } + + public void run() { + while (!shutdown) { + + if (!shutdown) { + long timeToSleep = getClusterCheckinInterval(); + long transpiredTime = (System.currentTimeMillis() - lastCheckin); + timeToSleep = timeToSleep - transpiredTime; + if (timeToSleep <= 0) { + timeToSleep = 100L; + } + + if(numFails > 0) { + timeToSleep = Math.max(getDbRetryInterval(), timeToSleep); + } + + try { + Thread.sleep(timeToSleep); + } catch (Exception ignore) { + } + } + + if (!shutdown && this.manage()) { + signalSchedulingChange(0L); + } + + }//while !shutdown + } + } + + ///////////////////////////////////////////////////////////////////////////// + // + // MisfireHandler Thread + // + ///////////////////////////////////////////////////////////////////////////// + + class MisfireHandler extends Thread { + + private boolean shutdown = false; + + private int numFails = 0; + + + MisfireHandler() { + this.setName("QuartzScheduler_" + instanceName + "-" + instanceId + "_MisfireHandler"); + this.setDaemon(getMakeThreadsDaemons()); + } + + public void initialize() { + //this.manage(); + this.start(); + } + + public void shutdown() { + shutdown = true; + this.interrupt(); + } + + private RecoverMisfiredJobsResult manage() { + try { + getLog().debug("MisfireHandler: scanning for misfires..."); + + RecoverMisfiredJobsResult res = doRecoverMisfires(); + numFails = 0; + return res; + } catch (Exception e) { + if(numFails % 4 == 0) { + getLog().error( + "MisfireHandler: Error handling misfires: " + + e.getMessage(), e); + } + numFails++; + } + return RecoverMisfiredJobsResult.NO_OP; + } + + public void run() { + + while (!shutdown) { + + long sTime = System.currentTimeMillis(); + + RecoverMisfiredJobsResult recoverMisfiredJobsResult = manage(); + + if (recoverMisfiredJobsResult.getProcessedMisfiredTriggerCount() > 0) { + signalSchedulingChange(recoverMisfiredJobsResult.getEarliestNewTime()); + } + + if (!shutdown) { + long timeToSleep = 50l; // At least a short pause to help balance threads + if (!recoverMisfiredJobsResult.hasMoreMisfiredTriggers()) { + timeToSleep = getMisfireThreshold() - (System.currentTimeMillis() - sTime); + if (timeToSleep <= 0) { + timeToSleep = 50l; + } + + if(numFails > 0) { + timeToSleep = Math.max(getDbRetryInterval(), timeToSleep); + } + } + + try { + Thread.sleep(timeToSleep); + } catch (Exception ignore) { + } + }//while !shutdown + } + } + } +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/JobStoreTX.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/JobStoreTX.java new file mode 100644 index 000000000..faec53d41 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/JobStoreTX.java @@ -0,0 +1,96 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.sql.Connection; + +import com.fr.third.org.quartz.JobPersistenceException; +import com.fr.third.org.quartz.SchedulerConfigException; +import com.fr.third.org.quartz.spi.ClassLoadHelper; +import com.fr.third.org.quartz.spi.SchedulerSignaler; + +/** + *

+ * JobStoreTX is meant to be used in a standalone environment. + * Both commit and rollback will be handled by this class. + *

+ * + *

+ * If you need a {@link com.fr.third.org.quartz.spi.JobStore} class to use + * within an application-server environment, use {@link + * com.fr.third.org.quartz.impl.jdbcjobstore.JobStoreCMT} + * instead. + *

+ * + * @author Jeffrey Wescott + * @author James House + */ +public class JobStoreTX extends JobStoreSupport { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public void initialize(ClassLoadHelper loadHelper, + SchedulerSignaler signaler) throws SchedulerConfigException { + + super.initialize(loadHelper, signaler); + + getLog().info("JobStoreTX initialized."); + } + + /** + * For JobStoreTX, the non-managed TX connection is just + * the normal connection because it is not CMT. + * + * @see JobStoreSupport#getConnection() + */ + protected Connection getNonManagedTXConnection() + throws JobPersistenceException { + return getConnection(); + } + + /** + * Execute the given callback having optionally aquired the given lock. + * For JobStoreTX, because it manages its own transactions + * and only has the one datasource, this is the same behavior as + * executeInNonManagedTXLock(). + * + * @param lockName The name of the lock to aquire, for example + * "TRIGGER_ACCESS". If null, then no lock is aquired, but the + * lockCallback is still executed in a transaction. + * + * @see JobStoreSupport#executeInNonManagedTXLock(String, TransactionCallback) + * @see JobStoreCMT#executeInLock(String, TransactionCallback) + * @see JobStoreSupport#getNonManagedTXConnection() + * @see JobStoreSupport#getConnection() + */ + protected Object executeInLock( + String lockName, + TransactionCallback txCallback) throws JobPersistenceException { + return executeInNonManagedTXLock(lockName, txCallback); + } +} +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/LockException.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/LockException.java new file mode 100644 index 000000000..6c696831e --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/LockException.java @@ -0,0 +1,54 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import com.fr.third.org.quartz.JobPersistenceException; + +/** + *

+ * Exception class for when there is a failure obtaining or releasing a + * resource lock. + *

+ * + * @see Semaphore + * + * @author James House + */ +public class LockException extends JobPersistenceException { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public LockException(String msg) { + super(msg); + } + + public LockException(String msg, Throwable cause) { + super(msg, cause); + } +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/MSSQLDelegate.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/MSSQLDelegate.java new file mode 100644 index 000000000..fa2be5ee8 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/MSSQLDelegate.java @@ -0,0 +1,108 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.apache.commons.logging.Log; + +/** + *

+ * This is a driver delegate for the MSSQL JDBC driver. + *

+ * + * @author Jeffrey Wescott + */ +public class MSSQLDelegate extends StdJDBCDelegate { + /** + *

+ * Create new MSSQLDelegate instance. + *

+ * + * @param log + * the logger to use during execution + * @param tablePrefix + * the prefix of all table names + */ + public MSSQLDelegate(Log log, String tablePrefix, String instanceId) { + super(log, tablePrefix, instanceId); + } + + public MSSQLDelegate(Log log, String tablePrefix, String instanceId, Boolean useProperties) { + super(log, tablePrefix, instanceId, useProperties); + } + + //--------------------------------------------------------------------------- + // protected methods that can be overridden by subclasses + //--------------------------------------------------------------------------- + + /** + *

+ * This method should be overridden by any delegate subclasses that need + * special handling for BLOBs. The default implementation uses standard + * JDBC java.sql.Blob operations. + *

+ * + * @param rs + * the result set, already queued to the correct row + * @param colName + * the column name for the BLOB + * @return the deserialized Object from the ResultSet BLOB + * @throws ClassNotFoundException + * if a class found during deserialization cannot be found + * @throws IOException + * if deserialization causes an error + */ + protected Object getObjectFromBlob(ResultSet rs, String colName) + throws ClassNotFoundException, IOException, SQLException { + InputStream binaryInput = rs.getBinaryStream(colName); + + if(binaryInput == null || binaryInput.available() == 0) { + return null; + } + + Object obj = null; + + ObjectInputStream in = new ObjectInputStream(binaryInput); + try { + obj = in.readObject(); + } finally { + in.close(); + } + + return obj; + } + + protected Object getJobDetailFromBlob(ResultSet rs, String colName) + throws ClassNotFoundException, IOException, SQLException { + if (canUseProperties()) { + InputStream binaryInput = rs.getBinaryStream(colName); + return binaryInput; + } + return getObjectFromBlob(rs, colName); + } +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/NoSuchDelegateException.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/NoSuchDelegateException.java new file mode 100644 index 000000000..dfaaeaec0 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/NoSuchDelegateException.java @@ -0,0 +1,47 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import com.fr.third.org.quartz.JobPersistenceException; + +/** + *

+ * Exception class for when a driver delegate cannot be found for a given + * configuration, or lack thereof. + *

+ * + * @author Jeffrey Wescott + */ +public class NoSuchDelegateException extends JobPersistenceException { + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public NoSuchDelegateException(String msg) { + super(msg); + } +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/PointbaseDelegate.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/PointbaseDelegate.java new file mode 100644 index 000000000..c5d658833 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/PointbaseDelegate.java @@ -0,0 +1,507 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.apache.commons.logging.Log; +import com.fr.third.org.quartz.Calendar; +import com.fr.third.org.quartz.CronTrigger; +import com.fr.third.org.quartz.JobDetail; +import com.fr.third.org.quartz.SimpleTrigger; +import com.fr.third.org.quartz.Trigger; + +/** + *

+ * This is a driver delegate for the Pointbase JDBC driver. + *

+ * + * @author Gregg Freeman + */ +public class PointbaseDelegate extends StdJDBCDelegate { + + //private static Category log = + // Category.getInstance(PointbaseJDBCDelegate.class); + /** + *

+ * Create new PointbaseJDBCDelegate instance. + *

+ * + * @param logger + * the logger to use during execution + * @param tablePrefix + * the prefix of all table names + */ + public PointbaseDelegate(Log logger, String tablePrefix, String instanceId) { + super(logger, tablePrefix, instanceId); + } + + /** + *

+ * Create new PointbaseJDBCDelegate instance. + *

+ * + * @param logger + * the logger to use during execution + * @param tablePrefix + * the prefix of all table names + */ + public PointbaseDelegate(Log logger, String tablePrefix, String instanceId, + Boolean useProperties) { + super(logger, tablePrefix, instanceId, useProperties); + } + + //--------------------------------------------------------------------------- + // jobs + //--------------------------------------------------------------------------- + + /** + *

+ * Insert the job detail record. + *

+ * + * @param conn + * the DB Connection + * @param job + * the job to insert + * @return number of rows inserted + * @throws IOException + * if there were problems serializing the JobDataMap + */ + public int insertJobDetail(Connection conn, JobDetail job) + throws IOException, SQLException { + //log.debug( "Inserting JobDetail " + job ); + ByteArrayOutputStream baos = serializeJobData(job.getJobDataMap()); + int len = baos.toByteArray().length; + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + + PreparedStatement ps = null; + + int insertResult = 0; + + try { + ps = conn.prepareStatement(rtp(INSERT_JOB_DETAIL)); + ps.setString(1, job.getName()); + ps.setString(2, job.getGroup()); + ps.setString(3, job.getDescription()); + ps.setString(4, job.getJobClass().getName()); + setBoolean(ps, 5, job.isDurable()); + setBoolean(ps, 6, job.isVolatile()); + setBoolean(ps, 7, job.isStateful()); + setBoolean(ps, 8, job.requestsRecovery()); + ps.setBinaryStream(9, bais, len); + + insertResult = ps.executeUpdate(); + } finally { + closeStatement(ps); + } + + if (insertResult > 0) { + String[] jobListeners = job.getJobListenerNames(); + for (int i = 0; jobListeners != null && i < jobListeners.length; i++) { + insertJobListener(conn, job, jobListeners[i]); + } + } + + return insertResult; + } + + /** + *

+ * Update the job detail record. + *

+ * + * @param conn + * the DB Connection + * @param job + * the job to update + * @return number of rows updated + * @throws IOException + * if there were problems serializing the JobDataMap + */ + public int updateJobDetail(Connection conn, JobDetail job) + throws IOException, SQLException { + //log.debug( "Updating job detail " + job ); + ByteArrayOutputStream baos = serializeJobData(job.getJobDataMap()); + int len = baos.toByteArray().length; + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + + PreparedStatement ps = null; + + int insertResult = 0; + + try { + ps = conn.prepareStatement(rtp(UPDATE_JOB_DETAIL)); + ps.setString(1, job.getDescription()); + ps.setString(2, job.getJobClass().getName()); + setBoolean(ps, 3, job.isDurable()); + setBoolean(ps, 4, job.isVolatile()); + setBoolean(ps, 5, job.isStateful()); + setBoolean(ps, 6, job.requestsRecovery()); + ps.setBinaryStream(7, bais, len); + ps.setString(8, job.getName()); + ps.setString(9, job.getGroup()); + + insertResult = ps.executeUpdate(); + } finally { + closeStatement(ps); + } + + if (insertResult > 0) { + deleteJobListeners(conn, job.getName(), job.getGroup()); + + String[] jobListeners = job.getJobListenerNames(); + for (int i = 0; jobListeners != null && i < jobListeners.length; i++) { + insertJobListener(conn, job, jobListeners[i]); + } + } + + return insertResult; + } + + public int insertTrigger(Connection conn, Trigger trigger, String state, + JobDetail jobDetail) throws SQLException, IOException { + + ByteArrayOutputStream baos = serializeJobData(trigger.getJobDataMap()); + int len = baos.toByteArray().length; + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + + PreparedStatement ps = null; + + int insertResult = 0; + + try { + ps = conn.prepareStatement(rtp(INSERT_TRIGGER)); + ps.setString(1, trigger.getName()); + ps.setString(2, trigger.getGroup()); + ps.setString(3, trigger.getJobName()); + ps.setString(4, trigger.getJobGroup()); + setBoolean(ps, 5, trigger.isVolatile()); + ps.setString(6, trigger.getDescription()); + ps.setBigDecimal(7, new BigDecimal(String.valueOf(trigger + .getNextFireTime().getTime()))); + long prevFireTime = -1; + if (trigger.getPreviousFireTime() != null) { + prevFireTime = trigger.getPreviousFireTime().getTime(); + } + ps.setBigDecimal(8, new BigDecimal(String.valueOf(prevFireTime))); + ps.setString(9, state); + if (trigger instanceof SimpleTrigger && ((SimpleTrigger)trigger).hasAdditionalProperties() == false ) { + ps.setString(10, TTYPE_SIMPLE); + } else if (trigger instanceof CronTrigger && ((CronTrigger)trigger).hasAdditionalProperties() == false ) { + ps.setString(10, TTYPE_CRON); + } else { + ps.setString(10, TTYPE_BLOB); + } + ps.setBigDecimal(11, new BigDecimal(String.valueOf(trigger + .getStartTime().getTime()))); + long endTime = 0; + if (trigger.getEndTime() != null) { + endTime = trigger.getEndTime().getTime(); + } + ps.setBigDecimal(12, new BigDecimal(String.valueOf(endTime))); + ps.setString(13, trigger.getCalendarName()); + ps.setInt(14, trigger.getMisfireInstruction()); + ps.setBinaryStream(15, bais, len); + ps.setInt(16, trigger.getPriority()); + + insertResult = ps.executeUpdate(); + } finally { + closeStatement(ps); + } + + if (insertResult > 0) { + String[] trigListeners = trigger.getTriggerListenerNames(); + for (int i = 0; trigListeners != null && i < trigListeners.length; i++) { + insertTriggerListener(conn, trigger, trigListeners[i]); + } + } + + return insertResult; + } + + public int updateTrigger(Connection conn, Trigger trigger, String state, + JobDetail jobDetail) throws SQLException, IOException { + + ByteArrayOutputStream baos = serializeJobData(trigger.getJobDataMap()); + int len = baos.toByteArray().length; + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + + PreparedStatement ps = null; + + int insertResult = 0; + + + try { + ps = conn.prepareStatement(rtp(UPDATE_TRIGGER)); + + ps.setString(1, trigger.getJobName()); + ps.setString(2, trigger.getJobGroup()); + setBoolean(ps, 3, trigger.isVolatile()); + ps.setString(4, trigger.getDescription()); + long nextFireTime = -1; + if (trigger.getNextFireTime() != null) { + nextFireTime = trigger.getNextFireTime().getTime(); + } + ps.setBigDecimal(5, new BigDecimal(String.valueOf(nextFireTime))); + long prevFireTime = -1; + if (trigger.getPreviousFireTime() != null) { + prevFireTime = trigger.getPreviousFireTime().getTime(); + } + ps.setBigDecimal(6, new BigDecimal(String.valueOf(prevFireTime))); + ps.setString(7, state); + if (trigger instanceof SimpleTrigger && ((SimpleTrigger)trigger).hasAdditionalProperties() == false ) { + // updateSimpleTrigger(conn, (SimpleTrigger)trigger); + ps.setString(8, TTYPE_SIMPLE); + } else if (trigger instanceof CronTrigger && ((CronTrigger)trigger).hasAdditionalProperties() == false ) { + // updateCronTrigger(conn, (CronTrigger)trigger); + ps.setString(8, TTYPE_CRON); + } else { + // updateBlobTrigger(conn, trigger); + ps.setString(8, TTYPE_BLOB); + } + ps.setBigDecimal(9, new BigDecimal(String.valueOf(trigger + .getStartTime().getTime()))); + long endTime = 0; + if (trigger.getEndTime() != null) { + endTime = trigger.getEndTime().getTime(); + } + ps.setBigDecimal(10, new BigDecimal(String.valueOf(endTime))); + ps.setString(11, trigger.getCalendarName()); + ps.setInt(12, trigger.getMisfireInstruction()); + + ps.setInt(13, trigger.getPriority()); + ps.setBinaryStream(14, bais, len); + ps.setString(15, trigger.getName()); + ps.setString(16, trigger.getGroup()); + + insertResult = ps.executeUpdate(); + } finally { + closeStatement(ps); + } + + if (insertResult > 0) { + deleteTriggerListeners(conn, trigger.getName(), trigger.getGroup()); + + String[] trigListeners = trigger.getTriggerListenerNames(); + for (int i = 0; trigListeners != null && i < trigListeners.length; i++) { + insertTriggerListener(conn, trigger, trigListeners[i]); + } + } + + return insertResult; + } + + /** + *

+ * Update the job data map for the given job. + *

+ * + * @param conn + * the DB Connection + * @param job + * the job to update + * @return the number of rows updated + */ + public int updateJobData(Connection conn, JobDetail job) + throws IOException, SQLException { + //log.debug( "Updating Job Data for Job " + job ); + ByteArrayOutputStream baos = serializeJobData(job.getJobDataMap()); + int len = baos.toByteArray().length; + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(UPDATE_JOB_DATA)); + ps.setBinaryStream(1, bais, len); + ps.setString(2, job.getName()); + ps.setString(3, job.getGroup()); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + //--------------------------------------------------------------------------- + // triggers + //--------------------------------------------------------------------------- + + //--------------------------------------------------------------------------- + // calendars + //--------------------------------------------------------------------------- + + /** + *

+ * Insert a new calendar. + *

+ * + * @param conn + * the DB Connection + * @param calendarName + * the name for the new calendar + * @param calendar + * the calendar + * @return the number of rows inserted + * @throws IOException + * if there were problems serializing the calendar + */ + public int insertCalendar(Connection conn, String calendarName, + Calendar calendar) throws IOException, SQLException { + //log.debug( "Inserting Calendar " + calendarName + " : " + calendar + // ); + ByteArrayOutputStream baos = serializeObject(calendar); + byte buf[] = baos.toByteArray(); + ByteArrayInputStream bais = new ByteArrayInputStream(buf); + + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(INSERT_CALENDAR)); + ps.setString(1, calendarName); + ps.setBinaryStream(2, bais, buf.length); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Update a calendar. + *

+ * + * @param conn + * the DB Connection + * @param calendarName + * the name for the new calendar + * @param calendar + * the calendar + * @return the number of rows updated + * @throws IOException + * if there were problems serializing the calendar + */ + public int updateCalendar(Connection conn, String calendarName, + Calendar calendar) throws IOException, SQLException { + //log.debug( "Updating calendar " + calendarName + " : " + calendar ); + ByteArrayOutputStream baos = serializeObject(calendar); + byte buf[] = baos.toByteArray(); + ByteArrayInputStream bais = new ByteArrayInputStream(buf); + + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(UPDATE_CALENDAR)); + ps.setBinaryStream(1, bais, buf.length); + ps.setString(2, calendarName); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + //--------------------------------------------------------------------------- + // protected methods that can be overridden by subclasses + //--------------------------------------------------------------------------- + + /** + *

+ * This method should be overridden by any delegate subclasses that need + * special handling for BLOBs. The default implementation uses standard + * JDBC java.sql.Blob operations. + *

+ * + * @param rs + * the result set, already queued to the correct row + * @param colName + * the column name for the BLOB + * @return the deserialized Object from the ResultSet BLOB + * @throws ClassNotFoundException + * if a class found during deserialization cannot be found + * @throws IOException + * if deserialization causes an error + */ + protected Object getObjectFromBlob(ResultSet rs, String colName) + throws ClassNotFoundException, IOException, SQLException { + //log.debug( "Getting blob from column: " + colName ); + Object obj = null; + + byte binaryData[] = rs.getBytes(colName); + + InputStream binaryInput = new ByteArrayInputStream(binaryData); + + if (null != binaryInput && binaryInput.available() != 0) { + ObjectInputStream in = new ObjectInputStream(binaryInput); + try { + obj = in.readObject(); + } finally { + in.close(); + } + } + + return obj; + } + + /** + *

+ * This method should be overridden by any delegate subclasses that need + * special handling for BLOBs for job details. The default implementation + * uses standard JDBC java.sql.Blob operations. + *

+ * + * @param rs + * the result set, already queued to the correct row + * @param colName + * the column name for the BLOB + * @return the deserialized Object from the ResultSet BLOB + * @throws ClassNotFoundException + * if a class found during deserialization cannot be found + * @throws IOException + * if deserialization causes an error + */ + protected Object getJobDetailFromBlob(ResultSet rs, String colName) + throws ClassNotFoundException, IOException, SQLException { + //log.debug( "Getting Job details from blob in col " + colName ); + if (canUseProperties()) { + byte data[] = rs.getBytes(colName); + if(data == null) { + return null; + } + InputStream binaryInput = new ByteArrayInputStream(data); + return binaryInput; + } + + return getObjectFromBlob(rs, colName); + } +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/PostgreSQLDelegate.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/PostgreSQLDelegate.java new file mode 100644 index 000000000..455de6721 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/PostgreSQLDelegate.java @@ -0,0 +1,129 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.apache.commons.logging.Log; + +/** + *

+ * This is a driver delegate for the PostgreSQL JDBC driver. + *

+ * + * @author Jeffrey Wescott + */ +public class PostgreSQLDelegate extends StdJDBCDelegate { + /** + *

+ * Create new PostgreSQLDelegate instance. + *

+ * + * @param log + * the logger to use during execution + * @param tablePrefix + * the prefix of all table names + */ + public PostgreSQLDelegate(Log log, String tablePrefix, String instanceId) { + super(log, tablePrefix, instanceId); + } + + /** + *

+ * Create new PostgreSQLDelegate instance. + *

+ * + * @param log + * the logger to use during execution + * @param tablePrefix + * the prefix of all table names + * @param useProperties + * use java.util.Properties for storage + */ + public PostgreSQLDelegate(Log log, String tablePrefix, String instanceId, + Boolean useProperties) { + super(log, tablePrefix, instanceId, useProperties); + } + + //--------------------------------------------------------------------------- + // protected methods that can be overridden by subclasses + //--------------------------------------------------------------------------- + + /** + *

+ * This method should be overridden by any delegate subclasses that need + * special handling for BLOBs. The default implementation uses standard + * JDBC java.sql.Blob operations. + *

+ * + * @param rs + * the result set, already queued to the correct row + * @param colName + * the column name for the BLOB + * @return the deserialized Object from the ResultSet BLOB + * @throws ClassNotFoundException + * if a class found during deserialization cannot be found + * @throws IOException + * if deserialization causes an error + */ + protected Object getObjectFromBlob(ResultSet rs, String colName) + throws ClassNotFoundException, IOException, SQLException { + InputStream binaryInput = null; + byte[] bytes = rs.getBytes(colName); + + Object obj = null; + + if(bytes != null && bytes.length != 0) { + binaryInput = new ByteArrayInputStream(bytes); + + ObjectInputStream in = new ObjectInputStream(binaryInput); + try { + obj = in.readObject(); + } finally { + in.close(); + } + + } + + return obj; + } + + protected Object getJobDetailFromBlob(ResultSet rs, String colName) + throws ClassNotFoundException, IOException, SQLException { + if (canUseProperties()) { + InputStream binaryInput = null; + byte[] bytes = rs.getBytes(colName); + if(bytes == null || bytes.length == 0) { + return null; + } + binaryInput = new ByteArrayInputStream(bytes); + return binaryInput; + } + return getObjectFromBlob(rs, colName); + } +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/SchedulerStateRecord.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/SchedulerStateRecord.java new file mode 100644 index 000000000..575c012bc --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/SchedulerStateRecord.java @@ -0,0 +1,92 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +/** + *

+ * Conveys a scheduler-instance state record. + *

+ * + * @author James House + */ +public class SchedulerStateRecord implements java.io.Serializable { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private String schedulerInstanceId; + + private long checkinTimestamp; + + private long checkinInterval; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + */ + public long getCheckinInterval() { + return checkinInterval; + } + + /** + */ + public long getCheckinTimestamp() { + return checkinTimestamp; + } + + /** + */ + public String getSchedulerInstanceId() { + return schedulerInstanceId; + } + + /** + */ + public void setCheckinInterval(long l) { + checkinInterval = l; + } + + /** + */ + public void setCheckinTimestamp(long l) { + checkinTimestamp = l; + } + + /** + */ + public void setSchedulerInstanceId(String string) { + schedulerInstanceId = string; + } + +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/Semaphore.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/Semaphore.java new file mode 100644 index 000000000..081e5a9e0 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/Semaphore.java @@ -0,0 +1,79 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.sql.Connection; + +/** + * An interface for providing thread/resource locking in order to protect + * resources from being altered by multiple threads at the same time. + * + * @author jhouse + */ +public interface Semaphore { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Grants a lock on the identified resource to the calling thread (blocking + * until it is available). + * + * @param conn Database connection used to establish lock. Can be null if + * {@link #requiresConnection()} returns false. + * + * @return true if the lock was obtained. + */ + boolean obtainLock(Connection conn, String lockName) throws LockException; + + /** + * Release the lock on the identified resource if it is held by the calling + * thread. + + * @param conn Database connection used to establish lock. Can be null if + * {@link #requiresConnection()} returns false. + */ + void releaseLock(Connection conn, String lockName) throws LockException; + + /** + * Determine whether the calling thread owns a lock on the identified + * resource. + + * @param conn Database connection used to establish lock. Can be null if + * {@link #requiresConnection()} returns false. + */ + boolean isLockOwner(Connection conn, String lockName) throws LockException; + + /** + * Whether this Semaphore implementation requires a database connection for + * its lock management operations. + * + * @see #isLockOwner(Connection, String) + * @see #obtainLock(Connection, String) + * @see #releaseLock(Connection, String) + */ + boolean requiresConnection(); +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/SimpleSemaphore.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/SimpleSemaphore.java new file mode 100644 index 000000000..8c3c79183 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/SimpleSemaphore.java @@ -0,0 +1,170 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.sql.Connection; +import java.util.HashSet; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Internal in-memory lock handler for providing thread/resource locking in + * order to protect resources from being altered by multiple threads at the + * same time. + * + * @author jhouse + */ +public class SimpleSemaphore implements Semaphore { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + ThreadLocal lockOwners = new ThreadLocal(); + + HashSet locks = new HashSet(); + + private final Log log = LogFactory.getLog(getClass()); + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + protected Log getLog() { + return log; + } + + private HashSet getThreadLocks() { + HashSet threadLocks = (HashSet) lockOwners.get(); + if (threadLocks == null) { + threadLocks = new HashSet(); + lockOwners.set(threadLocks); + } + return threadLocks; + } + + /** + * Grants a lock on the identified resource to the calling thread (blocking + * until it is available). + * + * @return true if the lock was obtained. + */ + public synchronized boolean obtainLock(Connection conn, String lockName) { + + lockName = lockName.intern(); + + Log log = getLog(); + + if(log.isDebugEnabled()) { + log.debug( + "Lock '" + lockName + "' is desired by: " + + Thread.currentThread().getName()); + } + + if (!isLockOwner(conn, lockName)) { + if(log.isDebugEnabled()) { + log.debug( + "Lock '" + lockName + "' is being obtained: " + + Thread.currentThread().getName()); + } + while (locks.contains(lockName)) { + try { + this.wait(); + } catch (InterruptedException ie) { + if(log.isDebugEnabled()) { + log.debug( + "Lock '" + lockName + "' was not obtained by: " + + Thread.currentThread().getName()); + } + } + } + + if(log.isDebugEnabled()) { + log.debug( + "Lock '" + lockName + "' given to: " + + Thread.currentThread().getName()); + } + getThreadLocks().add(lockName); + locks.add(lockName); + } else if(log.isDebugEnabled()) { + log.debug( + "Lock '" + lockName + "' already owned by: " + + Thread.currentThread().getName() + + " -- but not owner!", + new Exception("stack-trace of wrongful returner")); + } + + return true; + } + + /** + * Release the lock on the identified resource if it is held by the calling + * thread. + */ + public synchronized void releaseLock(Connection conn, String lockName) { + + lockName = lockName.intern(); + + if (isLockOwner(conn, lockName)) { + if(getLog().isDebugEnabled()) { + getLog().debug( + "Lock '" + lockName + "' retuned by: " + + Thread.currentThread().getName()); + } + getThreadLocks().remove(lockName); + locks.remove(lockName); + this.notifyAll(); + } else if (getLog().isDebugEnabled()) { + getLog().debug( + "Lock '" + lockName + "' attempt to retun by: " + + Thread.currentThread().getName() + + " -- but not owner!", + new Exception("stack-trace of wrongful returner")); + } + } + + /** + * Determine whether the calling thread owns a lock on the identified + * resource. + */ + public synchronized boolean isLockOwner(Connection conn, String lockName) { + + lockName = lockName.intern(); + + return getThreadLocks().contains(lockName); + } + + /** + * This Semaphore implementation does not use the database. + */ + public boolean requiresConnection() { + return false; + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/StdJDBCConstants.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/StdJDBCConstants.java new file mode 100644 index 000000000..ceb6413e9 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/StdJDBCConstants.java @@ -0,0 +1,609 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ + +package com.fr.third.org.quartz.impl.jdbcjobstore; + +/** + *

+ * This interface extends {@link + * com.fr.third.org.quartz.impl.jdbcjobstore.Constants} + * to include the query string constants in use by the {@link + * com.fr.third.org.quartz.impl.jdbcjobstore.StdJDBCDelegate} + * class. + *

+ * + * @author Jeffrey Wescott + */ +public interface StdJDBCConstants extends Constants { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + // table prefix substitution string + String TABLE_PREFIX_SUBST = "{0}"; + + // QUERIES + String UPDATE_TRIGGER_STATES_FROM_OTHER_STATES = "UPDATE " + + TABLE_PREFIX_SUBST + + TABLE_TRIGGERS + + " SET " + + COL_TRIGGER_STATE + + " = ?" + + " WHERE " + + COL_TRIGGER_STATE + + " = ? OR " + + COL_TRIGGER_STATE + " = ?"; + + String UPDATE_TRIGGER_STATE_FROM_OTHER_STATES_BEFORE_TIME = "UPDATE " + + TABLE_PREFIX_SUBST + + TABLE_TRIGGERS + + " SET " + + COL_TRIGGER_STATE + + " = ?" + + " WHERE (" + + COL_TRIGGER_STATE + + " = ? OR " + + COL_TRIGGER_STATE + " = ?) AND " + COL_NEXT_FIRE_TIME + " < ?"; + + String SELECT_MISFIRED_TRIGGERS = "SELECT * FROM " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + + COL_NEXT_FIRE_TIME + " < ? ORDER BY START_TIME ASC"; + + String SELECT_TRIGGERS_IN_STATE = "SELECT " + + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + " FROM " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + + COL_TRIGGER_STATE + " = ?"; + + String SELECT_MISFIRED_TRIGGERS_IN_STATE = "SELECT " + + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + " FROM " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + + COL_NEXT_FIRE_TIME + " < ? AND " + COL_TRIGGER_STATE + " = ?"; + + String COUNT_MISFIRED_TRIGGERS_IN_STATES = "SELECT COUNT(" + + COL_TRIGGER_NAME + ") FROM " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + + COL_NEXT_FIRE_TIME + " < ? " + +"AND ((" + COL_TRIGGER_STATE + " = ?) OR (" + COL_TRIGGER_STATE + " = ?))"; + + String SELECT_MISFIRED_TRIGGERS_IN_STATES = "SELECT " + + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + " FROM " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + + COL_NEXT_FIRE_TIME + " < ? " + +"AND ((" + COL_TRIGGER_STATE + " = ?) OR (" + COL_TRIGGER_STATE + " = ?))"; + + String SELECT_MISFIRED_TRIGGERS_IN_GROUP_IN_STATE = "SELECT " + + COL_TRIGGER_NAME + + " FROM " + + TABLE_PREFIX_SUBST + + TABLE_TRIGGERS + + " WHERE " + + COL_NEXT_FIRE_TIME + + " < ? AND " + + COL_TRIGGER_GROUP + + " = ? AND " + COL_TRIGGER_STATE + " = ?"; + + String SELECT_VOLATILE_TRIGGERS = "SELECT " + + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + " FROM " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_IS_VOLATILE + + " = ?"; + + String DELETE_FIRED_TRIGGERS = "DELETE FROM " + + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS; + + String INSERT_JOB_DETAIL = "INSERT INTO " + + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " (" + COL_JOB_NAME + + ", " + COL_JOB_GROUP + ", " + COL_DESCRIPTION + ", " + + COL_JOB_CLASS + ", " + COL_IS_DURABLE + ", " + COL_IS_VOLATILE + + ", " + COL_IS_STATEFUL + ", " + COL_REQUESTS_RECOVERY + ", " + + COL_JOB_DATAMAP + ") " + " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + String UPDATE_JOB_DETAIL = "UPDATE " + + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " SET " + + COL_DESCRIPTION + " = ?, " + COL_JOB_CLASS + " = ?, " + + COL_IS_DURABLE + " = ?, " + COL_IS_VOLATILE + " = ?, " + + COL_IS_STATEFUL + " = ?, " + COL_REQUESTS_RECOVERY + " = ?, " + + COL_JOB_DATAMAP + " = ? " + " WHERE " + COL_JOB_NAME + + " = ? AND " + COL_JOB_GROUP + " = ?"; + + String SELECT_TRIGGERS_FOR_JOB = "SELECT " + + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + " FROM " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_JOB_NAME + + " = ? AND " + COL_JOB_GROUP + " = ?"; + + String SELECT_TRIGGERS_FOR_CALENDAR = "SELECT " + + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + " FROM " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_CALENDAR_NAME + + " = ?"; + + String SELECT_STATEFUL_JOBS_OF_TRIGGER_GROUP = "SELECT DISTINCT J." + + COL_JOB_NAME + + ", J." + + COL_JOB_GROUP + + " FROM " + + TABLE_PREFIX_SUBST + + TABLE_TRIGGERS + + " T, " + + TABLE_PREFIX_SUBST + + TABLE_JOB_DETAILS + + " J WHERE T." + + COL_TRIGGER_GROUP + + " = ? AND T." + + COL_JOB_NAME + + " = J." + + COL_JOB_NAME + + " AND T." + + COL_JOB_GROUP + + " = J." + + COL_JOB_GROUP + + " AND J." + + COL_IS_STATEFUL + " = ?"; + + String DELETE_JOB_LISTENERS = "DELETE FROM " + + TABLE_PREFIX_SUBST + TABLE_JOB_LISTENERS + " WHERE " + + COL_JOB_NAME + " = ? AND " + COL_JOB_GROUP + " = ?"; + + String DELETE_JOB_DETAIL = "DELETE FROM " + + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " WHERE " + COL_JOB_NAME + + " = ? AND " + COL_JOB_GROUP + " = ?"; + + String SELECT_JOB_STATEFUL = "SELECT " + + COL_IS_STATEFUL + " FROM " + TABLE_PREFIX_SUBST + + TABLE_JOB_DETAILS + " WHERE " + COL_JOB_NAME + " = ? AND " + + COL_JOB_GROUP + " = ?"; + + String SELECT_JOB_EXISTENCE = "SELECT " + COL_JOB_NAME + + " FROM " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " WHERE " + + COL_JOB_NAME + " = ? AND " + COL_JOB_GROUP + " = ?"; + + String UPDATE_JOB_DATA = "UPDATE " + TABLE_PREFIX_SUBST + + TABLE_JOB_DETAILS + " SET " + COL_JOB_DATAMAP + " = ? " + + " WHERE " + COL_JOB_NAME + " = ? AND " + COL_JOB_GROUP + " = ?"; + + String INSERT_JOB_LISTENER = "INSERT INTO " + + TABLE_PREFIX_SUBST + TABLE_JOB_LISTENERS + " (" + COL_JOB_NAME + + ", " + COL_JOB_GROUP + ", " + COL_JOB_LISTENER + + ") VALUES(?, ?, ?)"; + + String SELECT_JOB_LISTENERS = "SELECT " + + COL_JOB_LISTENER + " FROM " + TABLE_PREFIX_SUBST + + TABLE_JOB_LISTENERS + " WHERE " + COL_JOB_NAME + " = ? AND " + + COL_JOB_GROUP + " = ?"; + + String SELECT_JOB_DETAIL = "SELECT *" + " FROM " + + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " WHERE " + COL_JOB_NAME + + " = ? AND " + COL_JOB_GROUP + " = ?"; + + String SELECT_NUM_JOBS = "SELECT COUNT(" + COL_JOB_NAME + + ") " + " FROM " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS; + + String SELECT_JOB_GROUPS = "SELECT DISTINCT(" + + COL_JOB_GROUP + ") FROM " + TABLE_PREFIX_SUBST + + TABLE_JOB_DETAILS; + + String SELECT_JOBS_IN_GROUP = "SELECT " + COL_JOB_NAME + + " FROM " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + " WHERE " + + COL_JOB_GROUP + " = ?"; + + String SELECT_VOLATILE_JOBS = "SELECT " + COL_JOB_NAME + + ", " + COL_JOB_GROUP + " FROM " + TABLE_PREFIX_SUBST + + TABLE_JOB_DETAILS + " WHERE " + COL_IS_VOLATILE + " = ?"; + + String INSERT_TRIGGER = "INSERT INTO " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " (" + COL_TRIGGER_NAME + + ", " + COL_TRIGGER_GROUP + ", " + COL_JOB_NAME + ", " + + COL_JOB_GROUP + ", " + COL_IS_VOLATILE + ", " + COL_DESCRIPTION + + ", " + COL_NEXT_FIRE_TIME + ", " + COL_PREV_FIRE_TIME + ", " + + COL_TRIGGER_STATE + ", " + COL_TRIGGER_TYPE + ", " + + COL_START_TIME + ", " + COL_END_TIME + ", " + COL_CALENDAR_NAME + + ", " + COL_MISFIRE_INSTRUCTION + ", " + COL_JOB_DATAMAP + ", " + COL_PRIORITY + ") " + + " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + String INSERT_SIMPLE_TRIGGER = "INSERT INTO " + + TABLE_PREFIX_SUBST + TABLE_SIMPLE_TRIGGERS + " (" + + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + ", " + + COL_REPEAT_COUNT + ", " + COL_REPEAT_INTERVAL + ", " + + COL_TIMES_TRIGGERED + ") " + " VALUES(?, ?, ?, ?, ?)"; + + String INSERT_CRON_TRIGGER = "INSERT INTO " + + TABLE_PREFIX_SUBST + TABLE_CRON_TRIGGERS + " (" + + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + ", " + + COL_CRON_EXPRESSION + ", " + COL_TIME_ZONE_ID + ") " + + " VALUES(?, ?, ?, ?)"; + + String INSERT_BLOB_TRIGGER = "INSERT INTO " + + TABLE_PREFIX_SUBST + TABLE_BLOB_TRIGGERS + " (" + + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + ", " + COL_BLOB + + ") " + " VALUES(?, ?, ?)"; + + String UPDATE_TRIGGER_SKIP_DATA = "UPDATE " + TABLE_PREFIX_SUBST + + TABLE_TRIGGERS + " SET " + COL_JOB_NAME + " = ?, " + + COL_JOB_GROUP + " = ?, " + COL_IS_VOLATILE + " = ?, " + + COL_DESCRIPTION + " = ?, " + COL_NEXT_FIRE_TIME + " = ?, " + + COL_PREV_FIRE_TIME + " = ?, " + COL_TRIGGER_STATE + " = ?, " + + COL_TRIGGER_TYPE + " = ?, " + COL_START_TIME + " = ?, " + + COL_END_TIME + " = ?, " + COL_CALENDAR_NAME + " = ?, " + + COL_MISFIRE_INSTRUCTION + " = ?, " + COL_PRIORITY + + " = ? WHERE " + COL_TRIGGER_NAME + + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; + + String UPDATE_TRIGGER = "UPDATE " + TABLE_PREFIX_SUBST + + TABLE_TRIGGERS + " SET " + COL_JOB_NAME + " = ?, " + + COL_JOB_GROUP + " = ?, " + COL_IS_VOLATILE + " = ?, " + + COL_DESCRIPTION + " = ?, " + COL_NEXT_FIRE_TIME + " = ?, " + + COL_PREV_FIRE_TIME + " = ?, " + COL_TRIGGER_STATE + " = ?, " + + COL_TRIGGER_TYPE + " = ?, " + COL_START_TIME + " = ?, " + + COL_END_TIME + " = ?, " + COL_CALENDAR_NAME + " = ?, " + + COL_MISFIRE_INSTRUCTION + " = ?, " + COL_PRIORITY + " = ?, " + + COL_JOB_DATAMAP + " = ? WHERE " + + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; + + String UPDATE_SIMPLE_TRIGGER = "UPDATE " + + TABLE_PREFIX_SUBST + TABLE_SIMPLE_TRIGGERS + " SET " + + COL_REPEAT_COUNT + " = ?, " + COL_REPEAT_INTERVAL + " = ?, " + + COL_TIMES_TRIGGERED + " = ? WHERE " + COL_TRIGGER_NAME + + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; + + String UPDATE_CRON_TRIGGER = "UPDATE " + + TABLE_PREFIX_SUBST + TABLE_CRON_TRIGGERS + " SET " + + COL_CRON_EXPRESSION + " = ? WHERE " + COL_TRIGGER_NAME + + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; + + String UPDATE_BLOB_TRIGGER = "UPDATE " + + TABLE_PREFIX_SUBST + TABLE_BLOB_TRIGGERS + " SET " + COL_BLOB + + " = ? WHERE " + COL_TRIGGER_NAME + " = ? AND " + + COL_TRIGGER_GROUP + " = ?"; + + String SELECT_TRIGGER_EXISTENCE = "SELECT " + + COL_TRIGGER_NAME + " FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + + " WHERE " + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + + " = ?"; + + String UPDATE_TRIGGER_STATE = "UPDATE " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " SET " + COL_TRIGGER_STATE + + " = ?" + " WHERE " + COL_TRIGGER_NAME + " = ? AND " + + COL_TRIGGER_GROUP + " = ?"; + + String UPDATE_TRIGGER_STATE_FROM_STATE = "UPDATE " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " SET " + COL_TRIGGER_STATE + + " = ?" + " WHERE " + COL_TRIGGER_NAME + " = ? AND " + + COL_TRIGGER_GROUP + " = ? AND " + COL_TRIGGER_STATE + " = ?"; + + String UPDATE_TRIGGER_GROUP_STATE = "UPDATE " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " SET " + COL_TRIGGER_STATE + + " = ?"; + + String UPDATE_TRIGGER_GROUP_STATE_FROM_STATE = "UPDATE " + + TABLE_PREFIX_SUBST + + TABLE_TRIGGERS + + " SET " + + COL_TRIGGER_STATE + + " = ?" + + " WHERE " + + COL_TRIGGER_GROUP + + " = ? AND " + + COL_TRIGGER_STATE + " = ?"; + + String UPDATE_TRIGGER_STATE_FROM_STATES = "UPDATE " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " SET " + COL_TRIGGER_STATE + + " = ?" + " WHERE " + COL_TRIGGER_NAME + " = ? AND " + + COL_TRIGGER_GROUP + " = ? AND (" + COL_TRIGGER_STATE + " = ? OR " + + COL_TRIGGER_STATE + " = ? OR " + COL_TRIGGER_STATE + " = ?)"; + + String UPDATE_TRIGGER_GROUP_STATE_FROM_STATES = "UPDATE " + + TABLE_PREFIX_SUBST + + TABLE_TRIGGERS + + " SET " + + COL_TRIGGER_STATE + + " = ?" + + " WHERE " + + COL_TRIGGER_GROUP + + " = ? AND (" + + COL_TRIGGER_STATE + + " = ? OR " + + COL_TRIGGER_STATE + + " = ? OR " + + COL_TRIGGER_STATE + " = ?)"; + + String UPDATE_JOB_TRIGGER_STATES = "UPDATE " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " SET " + COL_TRIGGER_STATE + + " = ? WHERE " + COL_JOB_NAME + " = ? AND " + COL_JOB_GROUP + + " = ?"; + + String UPDATE_JOB_TRIGGER_STATES_FROM_OTHER_STATE = "UPDATE " + + TABLE_PREFIX_SUBST + + TABLE_TRIGGERS + + " SET " + + COL_TRIGGER_STATE + + " = ? WHERE " + + COL_JOB_NAME + + " = ? AND " + + COL_JOB_GROUP + + " = ? AND " + COL_TRIGGER_STATE + " = ?"; + + String DELETE_TRIGGER_LISTENERS = "DELETE FROM " + + TABLE_PREFIX_SUBST + TABLE_TRIGGER_LISTENERS + " WHERE " + + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; + + String INSERT_TRIGGER_LISTENER = "INSERT INTO " + + TABLE_PREFIX_SUBST + TABLE_TRIGGER_LISTENERS + " (" + + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + ", " + + COL_TRIGGER_LISTENER + ") VALUES(?, ?, ?)"; + + String SELECT_TRIGGER_LISTENERS = "SELECT " + + COL_TRIGGER_LISTENER + " FROM " + TABLE_PREFIX_SUBST + + TABLE_TRIGGER_LISTENERS + " WHERE " + COL_TRIGGER_NAME + + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; + + String DELETE_SIMPLE_TRIGGER = "DELETE FROM " + + TABLE_PREFIX_SUBST + TABLE_SIMPLE_TRIGGERS + " WHERE " + + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; + + String DELETE_CRON_TRIGGER = "DELETE FROM " + + TABLE_PREFIX_SUBST + TABLE_CRON_TRIGGERS + " WHERE " + + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; + + String DELETE_BLOB_TRIGGER = "DELETE FROM " + + TABLE_PREFIX_SUBST + TABLE_BLOB_TRIGGERS + " WHERE " + + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; + + String DELETE_TRIGGER = "DELETE FROM " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; + + String SELECT_NUM_TRIGGERS_FOR_JOB = "SELECT COUNT(" + + COL_TRIGGER_NAME + ") FROM " + TABLE_PREFIX_SUBST + + TABLE_TRIGGERS + " WHERE " + COL_JOB_NAME + " = ? AND " + + COL_JOB_GROUP + " = ?"; + + String SELECT_JOB_FOR_TRIGGER = "SELECT J." + + COL_JOB_NAME + ", J." + COL_JOB_GROUP + ", J." + COL_IS_DURABLE + + ", J." + COL_JOB_CLASS + ", J." + COL_REQUESTS_RECOVERY + " FROM " + TABLE_PREFIX_SUBST + + TABLE_TRIGGERS + " T, " + TABLE_PREFIX_SUBST + TABLE_JOB_DETAILS + + " J WHERE T." + COL_TRIGGER_NAME + " = ? AND T." + + COL_TRIGGER_GROUP + " = ? AND T." + COL_JOB_NAME + " = J." + + COL_JOB_NAME + " AND T." + COL_JOB_GROUP + " = J." + + COL_JOB_GROUP; + + String SELECT_TRIGGER = "SELECT *" + " FROM " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; + + String SELECT_TRIGGER_DATA = "SELECT " + + COL_JOB_DATAMAP + " FROM " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; + + String SELECT_TRIGGER_STATE = "SELECT " + + COL_TRIGGER_STATE + " FROM " + TABLE_PREFIX_SUBST + + TABLE_TRIGGERS + " WHERE " + COL_TRIGGER_NAME + " = ? AND " + + COL_TRIGGER_GROUP + " = ?"; + + String SELECT_TRIGGER_STATUS = "SELECT " + + COL_TRIGGER_STATE + ", " + COL_NEXT_FIRE_TIME + ", " + + COL_JOB_NAME + ", " + COL_JOB_GROUP + " FROM " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; + + String SELECT_SIMPLE_TRIGGER = "SELECT *" + " FROM " + + TABLE_PREFIX_SUBST + TABLE_SIMPLE_TRIGGERS + " WHERE " + + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; + + String SELECT_CRON_TRIGGER = "SELECT *" + " FROM " + + TABLE_PREFIX_SUBST + TABLE_CRON_TRIGGERS + " WHERE " + + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; + + String SELECT_BLOB_TRIGGER = "SELECT *" + " FROM " + + TABLE_PREFIX_SUBST + TABLE_BLOB_TRIGGERS + " WHERE " + + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; + + String SELECT_NUM_TRIGGERS = "SELECT COUNT(" + + COL_TRIGGER_NAME + ") " + " FROM " + TABLE_PREFIX_SUBST + + TABLE_TRIGGERS; + + String SELECT_NUM_TRIGGERS_IN_GROUP = "SELECT COUNT(" + + COL_TRIGGER_NAME + ") " + " FROM " + TABLE_PREFIX_SUBST + + TABLE_TRIGGERS + " WHERE " + COL_TRIGGER_GROUP + " = ?"; + + String SELECT_TRIGGER_GROUPS = "SELECT DISTINCT(" + + COL_TRIGGER_GROUP + ") FROM " + TABLE_PREFIX_SUBST + + TABLE_TRIGGERS; + + String SELECT_TRIGGERS_IN_GROUP = "SELECT " + + COL_TRIGGER_NAME + " FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + + " WHERE " + COL_TRIGGER_GROUP + " = ?"; + + String INSERT_CALENDAR = "INSERT INTO " + + TABLE_PREFIX_SUBST + TABLE_CALENDARS + " (" + COL_CALENDAR_NAME + + ", " + COL_CALENDAR + ") " + " VALUES(?, ?)"; + + String UPDATE_CALENDAR = "UPDATE " + TABLE_PREFIX_SUBST + + TABLE_CALENDARS + " SET " + COL_CALENDAR + " = ? " + " WHERE " + + COL_CALENDAR_NAME + " = ?"; + + String SELECT_CALENDAR_EXISTENCE = "SELECT " + + COL_CALENDAR_NAME + " FROM " + TABLE_PREFIX_SUBST + + TABLE_CALENDARS + " WHERE " + COL_CALENDAR_NAME + " = ?"; + + String SELECT_CALENDAR = "SELECT *" + " FROM " + + TABLE_PREFIX_SUBST + TABLE_CALENDARS + " WHERE " + + COL_CALENDAR_NAME + " = ?"; + + String SELECT_REFERENCED_CALENDAR = "SELECT " + + COL_CALENDAR_NAME + " FROM " + TABLE_PREFIX_SUBST + + TABLE_TRIGGERS + " WHERE " + COL_CALENDAR_NAME + " = ?"; + + String DELETE_CALENDAR = "DELETE FROM " + + TABLE_PREFIX_SUBST + TABLE_CALENDARS + " WHERE " + + COL_CALENDAR_NAME + " = ?"; + + String SELECT_NUM_CALENDARS = "SELECT COUNT(" + + COL_CALENDAR_NAME + ") " + " FROM " + TABLE_PREFIX_SUBST + + TABLE_CALENDARS; + + String SELECT_CALENDARS = "SELECT " + COL_CALENDAR_NAME + + " FROM " + TABLE_PREFIX_SUBST + TABLE_CALENDARS; + + String SELECT_NEXT_FIRE_TIME = "SELECT MIN(" + + COL_NEXT_FIRE_TIME + ") AS " + ALIAS_COL_NEXT_FIRE_TIME + + " FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + + COL_TRIGGER_STATE + " = ? AND " + COL_NEXT_FIRE_TIME + " >= 0"; + + String SELECT_TRIGGER_FOR_FIRE_TIME = "SELECT " + + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + " FROM " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + + COL_TRIGGER_STATE + " = ? AND " + COL_NEXT_FIRE_TIME + " = ?"; + + String SELECT_NEXT_TRIGGER_TO_ACQUIRE = "SELECT " + + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + ", " + + COL_NEXT_FIRE_TIME + ", " + COL_PRIORITY + " FROM " + + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + + COL_TRIGGER_STATE + " = ? AND " + COL_NEXT_FIRE_TIME + " < ? " + + "AND (" + COL_NEXT_FIRE_TIME + " >= ?) " + + "ORDER BY "+ COL_NEXT_FIRE_TIME + " ASC, " + COL_PRIORITY + " DESC"; + + + String INSERT_FIRED_TRIGGER = "INSERT INTO " + + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " (" + COL_ENTRY_ID + + ", " + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + ", " + + COL_IS_VOLATILE + ", " + COL_INSTANCE_NAME + ", " + + COL_FIRED_TIME + ", " + COL_ENTRY_STATE + ", " + COL_JOB_NAME + + ", " + COL_JOB_GROUP + ", " + COL_IS_STATEFUL + ", " + + COL_REQUESTS_RECOVERY + ", " + COL_PRIORITY + + ") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + String UPDATE_INSTANCES_FIRED_TRIGGER_STATE = "UPDATE " + + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " SET " + + COL_ENTRY_STATE + " = ? AND " + COL_FIRED_TIME + " = ? AND " + COL_PRIORITY+ " = ? WHERE " + + COL_INSTANCE_NAME + " = ?"; + + String SELECT_INSTANCES_FIRED_TRIGGERS = "SELECT * FROM " + + TABLE_PREFIX_SUBST + + TABLE_FIRED_TRIGGERS + + " WHERE " + + COL_INSTANCE_NAME + " = ?"; + + String SELECT_INSTANCES_RECOVERABLE_FIRED_TRIGGERS = "SELECT * FROM " + + TABLE_PREFIX_SUBST + + TABLE_FIRED_TRIGGERS + + " WHERE " + + COL_INSTANCE_NAME + " = ? AND " + COL_REQUESTS_RECOVERY + " = ?"; + + String SELECT_JOB_EXECUTION_COUNT = "SELECT COUNT(" + + COL_TRIGGER_NAME + ") FROM " + TABLE_PREFIX_SUBST + + TABLE_FIRED_TRIGGERS + " WHERE " + COL_JOB_NAME + " = ? AND " + + COL_JOB_GROUP + " = ?"; + + String SELECT_FIRED_TRIGGERS = "SELECT * FROM " + + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS; + + String SELECT_FIRED_TRIGGER = "SELECT * FROM " + + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " WHERE " + + COL_TRIGGER_NAME + " = ? AND " + COL_TRIGGER_GROUP + " = ?"; + + String SELECT_FIRED_TRIGGER_GROUP = "SELECT * FROM " + + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " WHERE " + + COL_TRIGGER_GROUP + " = ?"; + + String SELECT_FIRED_TRIGGERS_OF_JOB = "SELECT * FROM " + + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " WHERE " + + COL_JOB_NAME + " = ? AND " + COL_JOB_GROUP + " = ?"; + + String SELECT_FIRED_TRIGGERS_OF_JOB_GROUP = "SELECT * FROM " + + TABLE_PREFIX_SUBST + + TABLE_FIRED_TRIGGERS + + " WHERE " + + COL_JOB_GROUP + " = ?"; + + String DELETE_FIRED_TRIGGER = "DELETE FROM " + + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " WHERE " + + COL_ENTRY_ID + " = ?"; + + String DELETE_INSTANCES_FIRED_TRIGGERS = "DELETE FROM " + + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " WHERE " + + COL_INSTANCE_NAME + " = ?"; + + String DELETE_VOLATILE_FIRED_TRIGGERS = "DELETE FROM " + + TABLE_PREFIX_SUBST + TABLE_FIRED_TRIGGERS + " WHERE " + + COL_IS_VOLATILE + " = ?"; + + String DELETE_NO_RECOVERY_FIRED_TRIGGERS = "DELETE FROM " + + TABLE_PREFIX_SUBST + + TABLE_FIRED_TRIGGERS + + " WHERE " + + COL_INSTANCE_NAME + " = ?" + COL_REQUESTS_RECOVERY + " = ?"; + + String SELECT_FIRED_TRIGGER_INSTANCE_NAMES = + "SELECT DISTINCT " + COL_INSTANCE_NAME + " FROM " + + TABLE_PREFIX_SUBST + + TABLE_FIRED_TRIGGERS; + + String INSERT_SCHEDULER_STATE = "INSERT INTO " + + TABLE_PREFIX_SUBST + TABLE_SCHEDULER_STATE + " (" + + COL_INSTANCE_NAME + ", " + COL_LAST_CHECKIN_TIME + ", " + + COL_CHECKIN_INTERVAL + ") VALUES(?, ?, ?)"; + + String SELECT_SCHEDULER_STATE = "SELECT * FROM " + + TABLE_PREFIX_SUBST + TABLE_SCHEDULER_STATE + " WHERE " + + COL_INSTANCE_NAME + " = ?"; + + String SELECT_SCHEDULER_STATES = "SELECT * FROM " + + TABLE_PREFIX_SUBST + TABLE_SCHEDULER_STATE; + + String DELETE_SCHEDULER_STATE = "DELETE FROM " + + TABLE_PREFIX_SUBST + TABLE_SCHEDULER_STATE + " WHERE " + + COL_INSTANCE_NAME + " = ?"; + + String UPDATE_SCHEDULER_STATE = "UPDATE " + + TABLE_PREFIX_SUBST + TABLE_SCHEDULER_STATE + " SET " + + COL_LAST_CHECKIN_TIME + " = ? WHERE " + + COL_INSTANCE_NAME + " = ?"; + + String INSERT_PAUSED_TRIGGER_GROUP = "INSERT INTO " + + TABLE_PREFIX_SUBST + TABLE_PAUSED_TRIGGERS + " (" + + COL_TRIGGER_GROUP + ") VALUES(?)"; + + String SELECT_PAUSED_TRIGGER_GROUP = "SELECT " + + COL_TRIGGER_GROUP + " FROM " + TABLE_PREFIX_SUBST + + TABLE_PAUSED_TRIGGERS + " WHERE " + COL_TRIGGER_GROUP + " = ?"; + + String SELECT_PAUSED_TRIGGER_GROUPS = "SELECT " + + COL_TRIGGER_GROUP + " FROM " + TABLE_PREFIX_SUBST + + TABLE_PAUSED_TRIGGERS; + + String DELETE_PAUSED_TRIGGER_GROUP = "DELETE FROM " + + TABLE_PREFIX_SUBST + TABLE_PAUSED_TRIGGERS + " WHERE " + + COL_TRIGGER_GROUP + " = ?"; + + String DELETE_PAUSED_TRIGGER_GROUPS = "DELETE FROM " + + TABLE_PREFIX_SUBST + TABLE_PAUSED_TRIGGERS; + + // CREATE TABLE qrtz_scheduler_state(INSTANCE_NAME VARCHAR2(80) NOT NULL, + // LAST_CHECKIN_TIME NUMBER(13) NOT NULL, CHECKIN_INTERVAL NUMBER(13) NOT + // NULL, PRIMARY KEY (INSTANCE_NAME)); + +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/StdJDBCDelegate.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/StdJDBCDelegate.java new file mode 100644 index 000000000..984a32dc4 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/StdJDBCDelegate.java @@ -0,0 +1,3664 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.NotSerializableException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.math.BigDecimal; +import java.sql.Blob; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TimeZone; + +import org.apache.commons.logging.Log; +import com.fr.third.org.quartz.Calendar; +import com.fr.third.org.quartz.CronTrigger; +import com.fr.third.org.quartz.JobDataMap; +import com.fr.third.org.quartz.JobDetail; +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SimpleTrigger; +import com.fr.third.org.quartz.Trigger; +import com.fr.third.org.quartz.spi.ClassLoadHelper; +import com.fr.third.org.quartz.utils.Key; +import com.fr.third.org.quartz.utils.TriggerStatus; + +/** + *

+ * This is meant to be an abstract base class for most, if not all, {@link com.fr.third.org.quartz.impl.jdbcjobstore.DriverDelegate} + * implementations. Subclasses should override only those methods that need + * special handling for the DBMS driver in question. + *

+ * + * @author Jeffrey Wescott + * @author James House + * @author Eric Mueller + */ +public class StdJDBCDelegate implements DriverDelegate, StdJDBCConstants { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + protected Log logger = null; + + protected String tablePrefix = DEFAULT_TABLE_PREFIX; + + protected String instanceId; + + protected boolean useProperties; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Create new StdJDBCDelegate instance. + *

+ * + * @param logger + * the logger to use during execution + * @param tablePrefix + * the prefix of all table names + */ + public StdJDBCDelegate(Log logger, String tablePrefix, String instanceId) { + this.logger = logger; + this.tablePrefix = tablePrefix; + this.instanceId = instanceId; + } + + /** + *

+ * Create new StdJDBCDelegate instance. + *

+ * + * @param logger + * the logger to use during execution + * @param tablePrefix + * the prefix of all table names + */ + public StdJDBCDelegate(Log logger, String tablePrefix, String instanceId, + Boolean useProperties) { + this.logger = logger; + this.tablePrefix = tablePrefix; + this.instanceId = instanceId; + this.useProperties = useProperties.booleanValue(); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + protected boolean canUseProperties() { + return useProperties; + } + + //--------------------------------------------------------------------------- + // startup / recovery + //--------------------------------------------------------------------------- + + /** + *

+ * Insert the job detail record. + *

+ * + * @param conn + * the DB Connection + * @param newState + * the new state for the triggers + * @param oldState1 + * the first old state to update + * @param oldState2 + * the second old state to update + * @return number of rows updated + */ + public int updateTriggerStatesFromOtherStates(Connection conn, + String newState, String oldState1, String oldState2) + throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn + .prepareStatement(rtp(UPDATE_TRIGGER_STATES_FROM_OTHER_STATES)); + ps.setString(1, newState); + ps.setString(2, oldState1); + ps.setString(3, oldState2); + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Get the names of all of the triggers that have misfired. + *

+ * + * @param conn + * the DB Connection + * @return an array of {@link + * com.fr.third.org.quartz.utils.Key} objects + */ + public Key[] selectMisfiredTriggers(Connection conn, long ts) + throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_MISFIRED_TRIGGERS)); + ps.setBigDecimal(1, new BigDecimal(String.valueOf(ts))); + rs = ps.executeQuery(); + + ArrayList list = new ArrayList(); + while (rs.next()) { + String triggerName = rs.getString(COL_TRIGGER_NAME); + String groupName = rs.getString(COL_TRIGGER_GROUP); + list.add(new Key(triggerName, groupName)); + } + Object[] oArr = list.toArray(); + Key[] kArr = new Key[oArr.length]; + System.arraycopy(oArr, 0, kArr, 0, oArr.length); + return kArr; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Select all of the triggers in a given state. + *

+ * + * @param conn + * the DB Connection + * @param state + * the state the triggers must be in + * @return an array of trigger Key s + */ + public Key[] selectTriggersInState(Connection conn, String state) + throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_TRIGGERS_IN_STATE)); + ps.setString(1, state); + rs = ps.executeQuery(); + + ArrayList list = new ArrayList(); + while (rs.next()) { + list.add(new Key(rs.getString(1), rs.getString(2))); + } + + Key[] sArr = (Key[]) list.toArray(new Key[list.size()]); + return sArr; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + public Key[] selectMisfiredTriggersInState(Connection conn, String state, + long ts) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_MISFIRED_TRIGGERS_IN_STATE)); + ps.setBigDecimal(1, new BigDecimal(String.valueOf(ts))); + ps.setString(2, state); + rs = ps.executeQuery(); + + ArrayList list = new ArrayList(); + while (rs.next()) { + String triggerName = rs.getString(COL_TRIGGER_NAME); + String groupName = rs.getString(COL_TRIGGER_GROUP); + list.add(new Key(triggerName, groupName)); + } + Object[] oArr = list.toArray(); + Key[] kArr = new Key[oArr.length]; + System.arraycopy(oArr, 0, kArr, 0, oArr.length); + return kArr; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Get the names of all of the triggers in the given states that have + * misfired - according to the given timestamp. No more than count will + * be returned. + *

+ * + * @param conn The DB Connection + * @param count The most misfired triggers to return, negative for all + * @param resultList Output parameter. A List of + * {@link com.fr.third.org.quartz.utils.Key} objects. Must not be null. + * + * @return Whether there are more misfired triggers left to find beyond + * the given count. + */ + public boolean selectMisfiredTriggersInStates(Connection conn, String state1, String state2, + long ts, int count, List resultList) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_MISFIRED_TRIGGERS_IN_STATES)); + ps.setBigDecimal(1, new BigDecimal(String.valueOf(ts))); + ps.setString(2, state1); + ps.setString(3, state2); + rs = ps.executeQuery(); + + boolean hasReachedLimit = false; + while (rs.next() && (hasReachedLimit == false)) { + if (resultList.size() == count) { + hasReachedLimit = true; + } else { + String triggerName = rs.getString(COL_TRIGGER_NAME); + String groupName = rs.getString(COL_TRIGGER_GROUP); + resultList.add(new Key(triggerName, groupName)); + } + } + + return hasReachedLimit; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Get the number of triggers in the given states that have + * misfired - according to the given timestamp. + *

+ * + * @param conn the DB Connection + */ + public int countMisfiredTriggersInStates( + Connection conn, String state1, String state2, long ts) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(COUNT_MISFIRED_TRIGGERS_IN_STATES)); + ps.setBigDecimal(1, new BigDecimal(String.valueOf(ts))); + ps.setString(2, state1); + ps.setString(3, state2); + rs = ps.executeQuery(); + + if (rs.next()) { + return rs.getInt(1); + } + + throw new SQLException("No misfired trigger count returned."); + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Get the names of all of the triggers in the given group and state that + * have misfired. + *

+ * + * @param conn + * the DB Connection + * @return an array of {@link + * com.fr.third.org.quartz.utils.Key} objects + */ + public Key[] selectMisfiredTriggersInGroupInState(Connection conn, + String groupName, String state, long ts) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn + .prepareStatement(rtp(SELECT_MISFIRED_TRIGGERS_IN_GROUP_IN_STATE)); + ps.setBigDecimal(1, new BigDecimal(String.valueOf(ts))); + ps.setString(2, groupName); + ps.setString(3, state); + rs = ps.executeQuery(); + + ArrayList list = new ArrayList(); + while (rs.next()) { + String triggerName = rs.getString(COL_TRIGGER_NAME); + list.add(new Key(triggerName, groupName)); + } + Object[] oArr = list.toArray(); + Key[] kArr = new Key[oArr.length]; + System.arraycopy(oArr, 0, kArr, 0, oArr.length); + return kArr; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Select all of the triggers for jobs that are requesting recovery. The + * returned trigger objects will have unique "recoverXXX" trigger names and + * will be in the {@link + * com.fr.third.org.quartz.Scheduler}.DEFAULT_RECOVERY_GROUP + * trigger group. + *

+ * + *

+ * In order to preserve the ordering of the triggers, the fire time will be + * set from the COL_FIRED_TIME column in the TABLE_FIRED_TRIGGERS + * table. The caller is responsible for calling computeFirstFireTime + * on each returned trigger. It is also up to the caller to insert the + * returned triggers to ensure that they are fired. + *

+ * + * @param conn + * the DB Connection + * @return an array of {@link com.fr.third.org.quartz.Trigger} objects + */ + public Trigger[] selectTriggersForRecoveringJobs(Connection conn) + throws SQLException, IOException, ClassNotFoundException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn + .prepareStatement(rtp(SELECT_INSTANCES_RECOVERABLE_FIRED_TRIGGERS)); + ps.setString(1, instanceId); + setBoolean(ps, 2, true); + rs = ps.executeQuery(); + + long dumId = System.currentTimeMillis(); + ArrayList list = new ArrayList(); + while (rs.next()) { + String jobName = rs.getString(COL_JOB_NAME); + String jobGroup = rs.getString(COL_JOB_GROUP); + String trigName = rs.getString(COL_TRIGGER_NAME); + String trigGroup = rs.getString(COL_TRIGGER_GROUP); + long firedTime = rs.getLong(COL_FIRED_TIME); + int priority = rs.getInt(COL_PRIORITY); + SimpleTrigger rcvryTrig = new SimpleTrigger("recover_" + + instanceId + "_" + String.valueOf(dumId++), + Scheduler.DEFAULT_RECOVERY_GROUP, new Date(firedTime)); + rcvryTrig.setJobName(jobName); + rcvryTrig.setJobGroup(jobGroup); + rcvryTrig.setPriority(priority); + rcvryTrig + .setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW); + + JobDataMap jd = selectTriggerJobDataMap(conn, trigName, trigGroup); + jd.put(Scheduler.FAILED_JOB_ORIGINAL_TRIGGER_NAME, trigName); + jd.put(Scheduler.FAILED_JOB_ORIGINAL_TRIGGER_GROUP, trigGroup); + jd.put(Scheduler.FAILED_JOB_ORIGINAL_TRIGGER_FIRETIME_IN_MILLISECONDS, String.valueOf(firedTime)); + rcvryTrig.setJobDataMap(jd); + + list.add(rcvryTrig); + } + Object[] oArr = list.toArray(); + Trigger[] tArr = new Trigger[oArr.length]; + System.arraycopy(oArr, 0, tArr, 0, oArr.length); + return tArr; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Delete all fired triggers. + *

+ * + * @param conn + * the DB Connection + * @return the number of rows deleted + */ + public int deleteFiredTriggers(Connection conn) throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(DELETE_FIRED_TRIGGERS)); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + public int deleteFiredTriggers(Connection conn, String instanceId) + throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(DELETE_INSTANCES_FIRED_TRIGGERS)); + ps.setString(1, instanceId); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + //--------------------------------------------------------------------------- + // jobs + //--------------------------------------------------------------------------- + + /** + *

+ * Insert the job detail record. + *

+ * + * @param conn + * the DB Connection + * @param job + * the job to insert + * @return number of rows inserted + * @throws IOException + * if there were problems serializing the JobDataMap + */ + public int insertJobDetail(Connection conn, JobDetail job) + throws IOException, SQLException { + ByteArrayOutputStream baos = serializeJobData(job.getJobDataMap()); + + PreparedStatement ps = null; + + int insertResult = 0; + + try { + ps = conn.prepareStatement(rtp(INSERT_JOB_DETAIL)); + ps.setString(1, job.getName()); + ps.setString(2, job.getGroup()); + ps.setString(3, job.getDescription()); + ps.setString(4, job.getJobClass().getName()); + setBoolean(ps, 5, job.isDurable()); + setBoolean(ps, 6, job.isVolatile()); + setBoolean(ps, 7, job.isStateful()); + setBoolean(ps, 8, job.requestsRecovery()); + setBytes(ps, 9, baos); + + insertResult = ps.executeUpdate(); + } finally { + closeStatement(ps); + } + + if (insertResult > 0) { + String[] jobListeners = job.getJobListenerNames(); + for (int i = 0; jobListeners != null && i < jobListeners.length; i++) { + insertJobListener(conn, job, jobListeners[i]); + } + } + + return insertResult; + } + + /** + *

+ * Update the job detail record. + *

+ * + * @param conn + * the DB Connection + * @param job + * the job to update + * @return number of rows updated + * @throws IOException + * if there were problems serializing the JobDataMap + */ + public int updateJobDetail(Connection conn, JobDetail job) + throws IOException, SQLException { + ByteArrayOutputStream baos = serializeJobData(job.getJobDataMap()); + + PreparedStatement ps = null; + + int insertResult = 0; + + try { + ps = conn.prepareStatement(rtp(UPDATE_JOB_DETAIL)); + ps.setString(1, job.getDescription()); + ps.setString(2, job.getJobClass().getName()); + setBoolean(ps, 3, job.isDurable()); + setBoolean(ps, 4, job.isVolatile()); + setBoolean(ps, 5, job.isStateful()); + setBoolean(ps, 6, job.requestsRecovery()); + setBytes(ps, 7, baos); + ps.setString(8, job.getName()); + ps.setString(9, job.getGroup()); + + insertResult = ps.executeUpdate(); + } finally { + closeStatement(ps); + } + + if (insertResult > 0) { + deleteJobListeners(conn, job.getName(), job.getGroup()); + + String[] jobListeners = job.getJobListenerNames(); + for (int i = 0; jobListeners != null && i < jobListeners.length; i++) { + insertJobListener(conn, job, jobListeners[i]); + } + } + + return insertResult; + } + + /** + *

+ * Get the names of all of the triggers associated with the given job. + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the name of the job + * @param groupName + * the group containing the job + * @return an array of {@link + * com.fr.third.org.quartz.utils.Key} objects + */ + public Key[] selectTriggerNamesForJob(Connection conn, String jobName, + String groupName) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_TRIGGERS_FOR_JOB)); + ps.setString(1, jobName); + ps.setString(2, groupName); + rs = ps.executeQuery(); + + ArrayList list = new ArrayList(10); + while (rs.next()) { + String trigName = rs.getString(COL_TRIGGER_NAME); + String trigGroup = rs.getString(COL_TRIGGER_GROUP); + list.add(new Key(trigName, trigGroup)); + } + Object[] oArr = list.toArray(); + Key[] kArr = new Key[oArr.length]; + System.arraycopy(oArr, 0, kArr, 0, oArr.length); + return kArr; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Delete all job listeners for the given job. + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the name of the job + * @param groupName + * the group containing the job + * @return the number of rows deleted + */ + public int deleteJobListeners(Connection conn, String jobName, + String groupName) throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(DELETE_JOB_LISTENERS)); + ps.setString(1, jobName); + ps.setString(2, groupName); + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Delete the job detail record for the given job. + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the name of the job + * @param groupName + * the group containing the job + * @return the number of rows deleted + */ + public int deleteJobDetail(Connection conn, String jobName, String groupName) + throws SQLException { + PreparedStatement ps = null; + + try { + if (logger.isDebugEnabled()) { + logger.debug("Deleting job: " + groupName + "." + jobName); + } + ps = conn.prepareStatement(rtp(DELETE_JOB_DETAIL)); + ps.setString(1, jobName); + ps.setString(2, groupName); + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Check whether or not the given job is stateful. + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the name of the job + * @param groupName + * the group containing the job + * @return true if the job exists and is stateful, false otherwise + */ + public boolean isJobStateful(Connection conn, String jobName, + String groupName) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_JOB_STATEFUL)); + ps.setString(1, jobName); + ps.setString(2, groupName); + rs = ps.executeQuery(); + if (!rs.next()) { return false; } + return getBoolean(rs, COL_IS_STATEFUL); + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Check whether or not the given job exists. + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the name of the job + * @param groupName + * the group containing the job + * @return true if the job exists, false otherwise + */ + public boolean jobExists(Connection conn, String jobName, String groupName) + throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_JOB_EXISTENCE)); + ps.setString(1, jobName); + ps.setString(2, groupName); + rs = ps.executeQuery(); + if (rs.next()) { + return true; + } else { + return false; + } + } finally { + closeResultSet(rs); + closeStatement(ps); + } + + } + + /** + *

+ * Update the job data map for the given job. + *

+ * + * @param conn + * the DB Connection + * @param job + * the job to update + * @return the number of rows updated + */ + public int updateJobData(Connection conn, JobDetail job) + throws IOException, SQLException { + ByteArrayOutputStream baos = serializeJobData(job.getJobDataMap()); + + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(UPDATE_JOB_DATA)); + setBytes(ps, 1, baos); + ps.setString(2, job.getName()); + ps.setString(3, job.getGroup()); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Associate a listener with a job. + *

+ * + * @param conn + * the DB Connection + * @param job + * the job to associate with the listener + * @param listener + * the listener to insert + * @return the number of rows inserted + */ + public int insertJobListener(Connection conn, JobDetail job, String listener) + throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(INSERT_JOB_LISTENER)); + ps.setString(1, job.getName()); + ps.setString(2, job.getGroup()); + ps.setString(3, listener); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Get all of the listeners for a given job. + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the job name whose listeners are wanted + * @param groupName + * the group containing the job + * @return array of String listener names + */ + public String[] selectJobListeners(Connection conn, String jobName, + String groupName) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ArrayList list = new ArrayList(); + ps = conn.prepareStatement(rtp(SELECT_JOB_LISTENERS)); + ps.setString(1, jobName); + ps.setString(2, groupName); + rs = ps.executeQuery(); + + while (rs.next()) { + list.add(rs.getString(1)); + } + + Object[] oArr = list.toArray(); + String[] sArr = new String[oArr.length]; + System.arraycopy(oArr, 0, sArr, 0, oArr.length); + return sArr; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Select the JobDetail object for a given job name / group name. + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the job name whose listeners are wanted + * @param groupName + * the group containing the job + * @return the populated JobDetail object + * @throws ClassNotFoundException + * if a class found during deserialization cannot be found or if + * the job class could not be found + * @throws IOException + * if deserialization causes an error + */ + public JobDetail selectJobDetail(Connection conn, String jobName, + String groupName, ClassLoadHelper loadHelper) + throws ClassNotFoundException, IOException, SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_JOB_DETAIL)); + ps.setString(1, jobName); + ps.setString(2, groupName); + rs = ps.executeQuery(); + + JobDetail job = null; + + if (rs.next()) { + job = new JobDetail(); + + job.setName(rs.getString(COL_JOB_NAME)); + job.setGroup(rs.getString(COL_JOB_GROUP)); + job.setDescription(rs.getString(COL_DESCRIPTION)); + job.setJobClass(loadHelper.loadClass(rs + .getString(COL_JOB_CLASS))); + job.setDurability(getBoolean(rs, COL_IS_DURABLE)); + job.setVolatility(getBoolean(rs, COL_IS_VOLATILE)); + job.setRequestsRecovery(getBoolean(rs, COL_REQUESTS_RECOVERY)); + + Map map = null; + if (canUseProperties()) { + map = getMapFromProperties(rs); + } else { + map = (Map) getObjectFromBlob(rs, COL_JOB_DATAMAP); + } + + if (null != map) { + job.setJobDataMap(new JobDataMap(map)); + } + } + + return job; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + * build Map from java.util.Properties encoding. + */ + private Map getMapFromProperties(ResultSet rs) + throws ClassNotFoundException, IOException, SQLException { + Map map; + InputStream is = (InputStream) getJobDetailFromBlob(rs, COL_JOB_DATAMAP); + if(is == null) { + return null; + } + Properties properties = new Properties(); + if (is != null) { + try { + properties.load(is); + } finally { + is.close(); + } + } + map = convertFromProperty(properties); + return map; + } + + /** + *

+ * Select the total number of jobs stored. + *

+ * + * @param conn + * the DB Connection + * @return the total number of jobs stored + */ + public int selectNumJobs(Connection conn) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + int count = 0; + ps = conn.prepareStatement(rtp(SELECT_NUM_JOBS)); + rs = ps.executeQuery(); + + if (rs.next()) { + count = rs.getInt(1); + } + + return count; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Select all of the job group names that are stored. + *

+ * + * @param conn + * the DB Connection + * @return an array of String group names + */ + public String[] selectJobGroups(Connection conn) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_JOB_GROUPS)); + rs = ps.executeQuery(); + + ArrayList list = new ArrayList(); + while (rs.next()) { + list.add(rs.getString(1)); + } + + Object[] oArr = list.toArray(); + String[] sArr = new String[oArr.length]; + System.arraycopy(oArr, 0, sArr, 0, oArr.length); + return sArr; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Select all of the jobs contained in a given group. + *

+ * + * @param conn + * the DB Connection + * @param groupName + * the group containing the jobs + * @return an array of String job names + */ + public String[] selectJobsInGroup(Connection conn, String groupName) + throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_JOBS_IN_GROUP)); + ps.setString(1, groupName); + rs = ps.executeQuery(); + + ArrayList list = new ArrayList(); + while (rs.next()) { + list.add(rs.getString(1)); + } + + Object[] oArr = list.toArray(); + String[] sArr = new String[oArr.length]; + System.arraycopy(oArr, 0, sArr, 0, oArr.length); + return sArr; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + //--------------------------------------------------------------------------- + // triggers + //--------------------------------------------------------------------------- + + /** + *

+ * Insert the base trigger data. + *

+ * + * @param conn + * the DB Connection + * @param trigger + * the trigger to insert + * @param state + * the state that the trigger should be stored in + * @return the number of rows inserted + */ + public int insertTrigger(Connection conn, Trigger trigger, String state, + JobDetail jobDetail) throws SQLException, IOException { + + ByteArrayOutputStream baos = null; + if(trigger.getJobDataMap().size() > 0) { + baos = serializeJobData(trigger.getJobDataMap()); + } + + PreparedStatement ps = null; + + int insertResult = 0; + + try { + ps = conn.prepareStatement(rtp(INSERT_TRIGGER)); + ps.setString(1, trigger.getName()); + ps.setString(2, trigger.getGroup()); + ps.setString(3, trigger.getJobName()); + ps.setString(4, trigger.getJobGroup()); + setBoolean(ps, 5, trigger.isVolatile()); + ps.setString(6, trigger.getDescription()); + if(trigger.getNextFireTime() != null) + ps.setBigDecimal(7, new BigDecimal(String.valueOf(trigger + .getNextFireTime().getTime()))); + else + ps.setBigDecimal(7, null); + long prevFireTime = -1; + if (trigger.getPreviousFireTime() != null) { + prevFireTime = trigger.getPreviousFireTime().getTime(); + } + ps.setBigDecimal(8, new BigDecimal(String.valueOf(prevFireTime))); + ps.setString(9, state); + if (trigger instanceof SimpleTrigger && ((SimpleTrigger)trigger).hasAdditionalProperties() == false ) { + ps.setString(10, TTYPE_SIMPLE); + } else if (trigger instanceof CronTrigger && ((CronTrigger)trigger).hasAdditionalProperties() == false ) { + ps.setString(10, TTYPE_CRON); + } else { + ps.setString(10, TTYPE_BLOB); + } + ps.setBigDecimal(11, new BigDecimal(String.valueOf(trigger + .getStartTime().getTime()))); + long endTime = 0; + if (trigger.getEndTime() != null) { + endTime = trigger.getEndTime().getTime(); + } + ps.setBigDecimal(12, new BigDecimal(String.valueOf(endTime))); + ps.setString(13, trigger.getCalendarName()); + ps.setInt(14, trigger.getMisfireInstruction()); + setBytes(ps, 15, baos); + ps.setInt(16, trigger.getPriority()); + + insertResult = ps.executeUpdate(); + } finally { + closeStatement(ps); + } + + if (insertResult > 0) { + String[] trigListeners = trigger.getTriggerListenerNames(); + for (int i = 0; trigListeners != null && i < trigListeners.length; i++) { + insertTriggerListener(conn, trigger, trigListeners[i]); + } + } + + return insertResult; + } + + /** + *

+ * Insert the simple trigger data. + *

+ * + * @param conn + * the DB Connection + * @param trigger + * the trigger to insert + * @return the number of rows inserted + */ + public int insertSimpleTrigger(Connection conn, SimpleTrigger trigger) + throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(INSERT_SIMPLE_TRIGGER)); + ps.setString(1, trigger.getName()); + ps.setString(2, trigger.getGroup()); + ps.setInt(3, trigger.getRepeatCount()); + ps.setBigDecimal(4, new BigDecimal(String.valueOf(trigger + .getRepeatInterval()))); + ps.setInt(5, trigger.getTimesTriggered()); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Insert the cron trigger data. + *

+ * + * @param conn + * the DB Connection + * @param trigger + * the trigger to insert + * @return the number of rows inserted + */ + public int insertCronTrigger(Connection conn, CronTrigger trigger) + throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(INSERT_CRON_TRIGGER)); + ps.setString(1, trigger.getName()); + ps.setString(2, trigger.getGroup()); + ps.setString(3, trigger.getCronExpression()); + ps.setString(4, trigger.getTimeZone().getID()); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Insert the blob trigger data. + *

+ * + * @param conn + * the DB Connection + * @param trigger + * the trigger to insert + * @return the number of rows inserted + */ + public int insertBlobTrigger(Connection conn, Trigger trigger) + throws SQLException, IOException { + PreparedStatement ps = null; + ByteArrayOutputStream os = null; + + try { + // update the blob + os = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(os); + oos.writeObject(trigger); + oos.close(); + + byte[] buf = os.toByteArray(); + ByteArrayInputStream is = new ByteArrayInputStream(buf); + + ps = conn.prepareStatement(rtp(INSERT_BLOB_TRIGGER)); + ps.setString(1, trigger.getName()); + ps.setString(2, trigger.getGroup()); + ps.setBinaryStream(3, is, buf.length); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Update the base trigger data. + *

+ * + * @param conn + * the DB Connection + * @param trigger + * the trigger to insert + * @param state + * the state that the trigger should be stored in + * @return the number of rows updated + */ + public int updateTrigger(Connection conn, Trigger trigger, String state, + JobDetail jobDetail) throws SQLException, IOException { + + // save some clock cycles by unnecessarily writing job data blob ... + boolean updateJobData = trigger.getJobDataMap().isDirty(); + ByteArrayOutputStream baos = null; + if(updateJobData && trigger.getJobDataMap().size() > 0) { + baos = serializeJobData(trigger.getJobDataMap()); + } + + PreparedStatement ps = null; + + int insertResult = 0; + + + try { + if(updateJobData) { + ps = conn.prepareStatement(rtp(UPDATE_TRIGGER)); + } else { + ps = conn.prepareStatement(rtp(UPDATE_TRIGGER_SKIP_DATA)); + } + + ps.setString(1, trigger.getJobName()); + ps.setString(2, trigger.getJobGroup()); + setBoolean(ps, 3, trigger.isVolatile()); + ps.setString(4, trigger.getDescription()); + long nextFireTime = -1; + if (trigger.getNextFireTime() != null) { + nextFireTime = trigger.getNextFireTime().getTime(); + } + ps.setBigDecimal(5, new BigDecimal(String.valueOf(nextFireTime))); + long prevFireTime = -1; + if (trigger.getPreviousFireTime() != null) { + prevFireTime = trigger.getPreviousFireTime().getTime(); + } + ps.setBigDecimal(6, new BigDecimal(String.valueOf(prevFireTime))); + ps.setString(7, state); + if (trigger instanceof SimpleTrigger && ((SimpleTrigger)trigger).hasAdditionalProperties() == false ) { + // updateSimpleTrigger(conn, (SimpleTrigger)trigger); + ps.setString(8, TTYPE_SIMPLE); + } else if (trigger instanceof CronTrigger && ((CronTrigger)trigger).hasAdditionalProperties() == false ) { + // updateCronTrigger(conn, (CronTrigger)trigger); + ps.setString(8, TTYPE_CRON); + } else { + // updateBlobTrigger(conn, trigger); + ps.setString(8, TTYPE_BLOB); + } + ps.setBigDecimal(9, new BigDecimal(String.valueOf(trigger + .getStartTime().getTime()))); + long endTime = 0; + if (trigger.getEndTime() != null) { + endTime = trigger.getEndTime().getTime(); + } + ps.setBigDecimal(10, new BigDecimal(String.valueOf(endTime))); + ps.setString(11, trigger.getCalendarName()); + ps.setInt(12, trigger.getMisfireInstruction()); + ps.setInt(13, trigger.getPriority()); + + if(updateJobData) { + setBytes(ps, 14, baos); + ps.setString(15, trigger.getName()); + ps.setString(16, trigger.getGroup()); + } else { + ps.setString(14, trigger.getName()); + ps.setString(15, trigger.getGroup()); + } + + insertResult = ps.executeUpdate(); + } finally { + closeStatement(ps); + } + + if (insertResult > 0) { + deleteTriggerListeners(conn, trigger.getName(), trigger.getGroup()); + + String[] trigListeners = trigger.getTriggerListenerNames(); + for (int i = 0; trigListeners != null && i < trigListeners.length; i++) { + insertTriggerListener(conn, trigger, trigListeners[i]); + } + } + + return insertResult; + } + + /** + *

+ * Update the simple trigger data. + *

+ * + * @param conn + * the DB Connection + * @param trigger + * the trigger to insert + * @return the number of rows updated + */ + public int updateSimpleTrigger(Connection conn, SimpleTrigger trigger) + throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(UPDATE_SIMPLE_TRIGGER)); + + ps.setInt(1, trigger.getRepeatCount()); + ps.setBigDecimal(2, new BigDecimal(String.valueOf(trigger + .getRepeatInterval()))); + ps.setInt(3, trigger.getTimesTriggered()); + ps.setString(4, trigger.getName()); + ps.setString(5, trigger.getGroup()); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Update the cron trigger data. + *

+ * + * @param conn + * the DB Connection + * @param trigger + * the trigger to insert + * @return the number of rows updated + */ + public int updateCronTrigger(Connection conn, CronTrigger trigger) + throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(UPDATE_CRON_TRIGGER)); + ps.setString(1, trigger.getCronExpression()); + ps.setString(2, trigger.getName()); + ps.setString(3, trigger.getGroup()); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Update the blob trigger data. + *

+ * + * @param conn + * the DB Connection + * @param trigger + * the trigger to insert + * @return the number of rows updated + */ + public int updateBlobTrigger(Connection conn, Trigger trigger) + throws SQLException, IOException { + PreparedStatement ps = null; + ByteArrayOutputStream os = null; + + try { + // update the blob + os = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(os); + oos.writeObject(trigger); + oos.close(); + + byte[] buf = os.toByteArray(); + ByteArrayInputStream is = new ByteArrayInputStream(buf); + + ps = conn.prepareStatement(rtp(UPDATE_BLOB_TRIGGER)); + ps.setBinaryStream(1, is, buf.length); + ps.setString(2, trigger.getName()); + ps.setString(3, trigger.getGroup()); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + if (os != null) { + os.close(); + } + } + } + + /** + *

+ * Check whether or not a trigger exists. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return true if the trigger exists, false otherwise + */ + public boolean triggerExists(Connection conn, String triggerName, + String groupName) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_TRIGGER_EXISTENCE)); + ps.setString(1, triggerName); + ps.setString(2, groupName); + rs = ps.executeQuery(); + + if (rs.next()) { + return true; + } else { + return false; + } + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Update the state for a given trigger. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @param state + * the new state for the trigger + * @return the number of rows updated + */ + public int updateTriggerState(Connection conn, String triggerName, + String groupName, String state) throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(UPDATE_TRIGGER_STATE)); + ps.setString(1, state); + ps.setString(2, triggerName); + ps.setString(3, groupName); + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Update the given trigger to the given new state, if it is one of the + * given old states. + *

+ * + * @param conn + * the DB connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @param newState + * the new state for the trigger + * @param oldState1 + * one of the old state the trigger must be in + * @param oldState2 + * one of the old state the trigger must be in + * @param oldState3 + * one of the old state the trigger must be in + * @return int the number of rows updated + * @throws SQLException + */ + public int updateTriggerStateFromOtherStates(Connection conn, + String triggerName, String groupName, String newState, + String oldState1, String oldState2, String oldState3) + throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(UPDATE_TRIGGER_STATE_FROM_STATES)); + ps.setString(1, newState); + ps.setString(2, triggerName); + ps.setString(3, groupName); + ps.setString(4, oldState1); + ps.setString(5, oldState2); + ps.setString(6, oldState3); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + public int updateTriggerStateFromOtherStatesBeforeTime(Connection conn, + String newState, String oldState1, String oldState2, long time) + throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn + .prepareStatement(rtp(UPDATE_TRIGGER_STATE_FROM_OTHER_STATES_BEFORE_TIME)); + ps.setString(1, newState); + ps.setString(2, oldState1); + ps.setString(3, oldState2); + ps.setLong(4, time); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Update all triggers in the given group to the given new state, if they + * are in one of the given old states. + *

+ * + * @param conn + * the DB connection + * @param groupName + * the group containing the trigger + * @param newState + * the new state for the trigger + * @param oldState1 + * one of the old state the trigger must be in + * @param oldState2 + * one of the old state the trigger must be in + * @param oldState3 + * one of the old state the trigger must be in + * @return int the number of rows updated + * @throws SQLException + */ + public int updateTriggerGroupStateFromOtherStates(Connection conn, + String groupName, String newState, String oldState1, + String oldState2, String oldState3) throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn + .prepareStatement(rtp(UPDATE_TRIGGER_GROUP_STATE_FROM_STATES)); + ps.setString(1, newState); + ps.setString(2, groupName); + ps.setString(3, oldState1); + ps.setString(4, oldState2); + ps.setString(5, oldState3); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Update the given trigger to the given new state, if it is in the given + * old state. + *

+ * + * @param conn + * the DB connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @param newState + * the new state for the trigger + * @param oldState + * the old state the trigger must be in + * @return int the number of rows updated + * @throws SQLException + */ + public int updateTriggerStateFromOtherState(Connection conn, + String triggerName, String groupName, String newState, + String oldState) throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(UPDATE_TRIGGER_STATE_FROM_STATE)); + ps.setString(1, newState); + ps.setString(2, triggerName); + ps.setString(3, groupName); + ps.setString(4, oldState); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Update all of the triggers of the given group to the given new state, if + * they are in the given old state. + *

+ * + * @param conn + * the DB connection + * @param groupName + * the group containing the triggers + * @param newState + * the new state for the trigger group + * @param oldState + * the old state the triggers must be in + * @return int the number of rows updated + * @throws SQLException + */ + public int updateTriggerGroupStateFromOtherState(Connection conn, + String groupName, String newState, String oldState) + throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn + .prepareStatement(rtp(UPDATE_TRIGGER_GROUP_STATE_FROM_STATE)); + ps.setString(1, newState); + ps.setString(2, groupName); + ps.setString(3, oldState); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Update the states of all triggers associated with the given job. + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the name of the job + * @param groupName + * the group containing the job + * @param state + * the new state for the triggers + * @return the number of rows updated + */ + public int updateTriggerStatesForJob(Connection conn, String jobName, + String groupName, String state) throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(UPDATE_JOB_TRIGGER_STATES)); + ps.setString(1, state); + ps.setString(2, jobName); + ps.setString(3, groupName); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + public int updateTriggerStatesForJobFromOtherState(Connection conn, + String jobName, String groupName, String state, String oldState) + throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn + .prepareStatement(rtp(UPDATE_JOB_TRIGGER_STATES_FROM_OTHER_STATE)); + ps.setString(1, state); + ps.setString(2, jobName); + ps.setString(3, groupName); + ps.setString(4, oldState); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Delete all of the listeners associated with a given trigger. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger whose listeners will be deleted + * @param groupName + * the name of the group containing the trigger + * @return the number of rows deleted + */ + public int deleteTriggerListeners(Connection conn, String triggerName, + String groupName) throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(DELETE_TRIGGER_LISTENERS)); + ps.setString(1, triggerName); + ps.setString(2, groupName); + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Associate a listener with the given trigger. + *

+ * + * @param conn + * the DB Connection + * @param trigger + * the trigger + * @param listener + * the name of the listener to associate with the trigger + * @return the number of rows inserted + */ + public int insertTriggerListener(Connection conn, Trigger trigger, + String listener) throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(INSERT_TRIGGER_LISTENER)); + ps.setString(1, trigger.getName()); + ps.setString(2, trigger.getGroup()); + ps.setString(3, listener); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Select the listeners associated with a given trigger. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return array of String trigger listener names + */ + public String[] selectTriggerListeners(Connection conn, String triggerName, + String groupName) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_TRIGGER_LISTENERS)); + ps.setString(1, triggerName); + ps.setString(2, groupName); + rs = ps.executeQuery(); + + ArrayList list = new ArrayList(); + while (rs.next()) { + list.add(rs.getString(1)); + } + Object[] oArr = list.toArray(); + String[] sArr = new String[oArr.length]; + System.arraycopy(oArr, 0, sArr, 0, oArr.length); + return sArr; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Delete the simple trigger data for a trigger. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return the number of rows deleted + */ + public int deleteSimpleTrigger(Connection conn, String triggerName, + String groupName) throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(DELETE_SIMPLE_TRIGGER)); + ps.setString(1, triggerName); + ps.setString(2, groupName); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Delete the cron trigger data for a trigger. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return the number of rows deleted + */ + public int deleteCronTrigger(Connection conn, String triggerName, + String groupName) throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(DELETE_CRON_TRIGGER)); + ps.setString(1, triggerName); + ps.setString(2, groupName); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Delete the cron trigger data for a trigger. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return the number of rows deleted + */ + public int deleteBlobTrigger(Connection conn, String triggerName, + String groupName) throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(DELETE_BLOB_TRIGGER)); + ps.setString(1, triggerName); + ps.setString(2, groupName); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Delete the base trigger data for a trigger. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return the number of rows deleted + */ + public int deleteTrigger(Connection conn, String triggerName, + String groupName) throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(DELETE_TRIGGER)); + ps.setString(1, triggerName); + ps.setString(2, groupName); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Select the number of triggers associated with a given job. + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the name of the job + * @param groupName + * the group containing the job + * @return the number of triggers for the given job + */ + public int selectNumTriggersForJob(Connection conn, String jobName, + String groupName) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_NUM_TRIGGERS_FOR_JOB)); + ps.setString(1, jobName); + ps.setString(2, groupName); + rs = ps.executeQuery(); + + if (rs.next()) { + return rs.getInt(1); + } else { + return 0; + } + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Select the job to which the trigger is associated. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return the {@link com.fr.third.org.quartz.JobDetail} object + * associated with the given trigger + * @throws SQLException + * @throws ClassNotFoundException + */ + public JobDetail selectJobForTrigger(Connection conn, String triggerName, + String groupName, ClassLoadHelper loadHelper) throws ClassNotFoundException, SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_JOB_FOR_TRIGGER)); + ps.setString(1, triggerName); + ps.setString(2, groupName); + rs = ps.executeQuery(); + + if (rs.next()) { + JobDetail job = new JobDetail(); + job.setName(rs.getString(1)); + job.setGroup(rs.getString(2)); + job.setDurability(getBoolean(rs, 3)); + job.setJobClass(loadHelper.loadClass(rs + .getString(4))); + job.setRequestsRecovery(getBoolean(rs, 5)); + + return job; + } else { + if (logger.isDebugEnabled()) { + logger.debug("No job for trigger '" + groupName + "." + + triggerName + "'."); + } + return null; + } + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Select the triggers for a job + *

+ * + * @param conn + * the DB Connection + * @param jobName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return an array of (@link com.fr.third.org.quartz.Trigger) objects + * associated with a given job. + * @throws SQLException + */ + public Trigger[] selectTriggersForJob(Connection conn, String jobName, + String groupName) throws SQLException, ClassNotFoundException, + IOException { + + ArrayList trigList = new ArrayList(); + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_TRIGGERS_FOR_JOB)); + ps.setString(1, jobName); + ps.setString(2, groupName); + rs = ps.executeQuery(); + + while (rs.next()) { + Trigger t = selectTrigger(conn, + rs.getString(COL_TRIGGER_NAME), + rs.getString(COL_TRIGGER_GROUP)); + if(t != null) { + trigList.add(t); + } + } + } finally { + closeResultSet(rs); + closeStatement(ps); + } + + return (Trigger[]) trigList.toArray(new Trigger[trigList.size()]); + } + + public Trigger[] selectTriggersForCalendar(Connection conn, String calName) + throws SQLException, ClassNotFoundException, IOException { + + ArrayList trigList = new ArrayList(); + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_TRIGGERS_FOR_CALENDAR)); + ps.setString(1, calName); + rs = ps.executeQuery(); + + while (rs.next()) { + trigList.add(selectTrigger(conn, + rs.getString(COL_TRIGGER_NAME), rs + .getString(COL_TRIGGER_GROUP))); + } + } finally { + closeResultSet(rs); + closeStatement(ps); + } + + return (Trigger[]) trigList.toArray(new Trigger[trigList.size()]); + } + + public List selectStatefulJobsOfTriggerGroup(Connection conn, + String groupName) throws SQLException { + ArrayList jobList = new ArrayList(); + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn + .prepareStatement(rtp(SELECT_STATEFUL_JOBS_OF_TRIGGER_GROUP)); + ps.setString(1, groupName); + setBoolean(ps, 2, true); + rs = ps.executeQuery(); + + while (rs.next()) { + jobList.add(new Key(rs.getString(COL_JOB_NAME), rs + .getString(COL_JOB_GROUP))); + } + } finally { + closeResultSet(rs); + closeStatement(ps); + } + + return jobList; + } + + /** + *

+ * Select a trigger. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return the {@link com.fr.third.org.quartz.Trigger} object + */ + public Trigger selectTrigger(Connection conn, String triggerName, + String groupName) throws SQLException, ClassNotFoundException, + IOException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + Trigger trigger = null; + + ps = conn.prepareStatement(rtp(SELECT_TRIGGER)); + ps.setString(1, triggerName); + ps.setString(2, groupName); + rs = ps.executeQuery(); + + if (rs.next()) { + String jobName = rs.getString(COL_JOB_NAME); + String jobGroup = rs.getString(COL_JOB_GROUP); + boolean volatility = getBoolean(rs, COL_IS_VOLATILE); + String description = rs.getString(COL_DESCRIPTION); + long nextFireTime = rs.getLong(COL_NEXT_FIRE_TIME); + long prevFireTime = rs.getLong(COL_PREV_FIRE_TIME); + String triggerType = rs.getString(COL_TRIGGER_TYPE); + long startTime = rs.getLong(COL_START_TIME); + long endTime = rs.getLong(COL_END_TIME); + String calendarName = rs.getString(COL_CALENDAR_NAME); + int misFireInstr = rs.getInt(COL_MISFIRE_INSTRUCTION); + int priority = rs.getInt(COL_PRIORITY); + + Map map = null; + if (canUseProperties()) { + map = getMapFromProperties(rs); + } else { + map = (Map) getObjectFromBlob(rs, COL_JOB_DATAMAP); + } + + Date nft = null; + if (nextFireTime > 0) { + nft = new Date(nextFireTime); + } + + Date pft = null; + if (prevFireTime > 0) { + pft = new Date(prevFireTime); + } + Date startTimeD = new Date(startTime); + Date endTimeD = null; + if (endTime > 0) { + endTimeD = new Date(endTime); + } + + rs.close(); + ps.close(); + + if (triggerType.equals(TTYPE_SIMPLE)) { + ps = conn.prepareStatement(rtp(SELECT_SIMPLE_TRIGGER)); + ps.setString(1, triggerName); + ps.setString(2, groupName); + rs = ps.executeQuery(); + + if (rs.next()) { + int repeatCount = rs.getInt(COL_REPEAT_COUNT); + long repeatInterval = rs.getLong(COL_REPEAT_INTERVAL); + int timesTriggered = rs.getInt(COL_TIMES_TRIGGERED); + + SimpleTrigger st = new SimpleTrigger(triggerName, + groupName, jobName, jobGroup, startTimeD, + endTimeD, repeatCount, repeatInterval); + st.setCalendarName(calendarName); + st.setMisfireInstruction(misFireInstr); + st.setTimesTriggered(timesTriggered); + st.setVolatility(volatility); + st.setNextFireTime(nft); + st.setPreviousFireTime(pft); + st.setDescription(description); + st.setPriority(priority); + if (null != map) { + st.setJobDataMap(new JobDataMap(map)); + } + trigger = st; + } + } else if (triggerType.equals(TTYPE_CRON)) { + ps = conn.prepareStatement(rtp(SELECT_CRON_TRIGGER)); + ps.setString(1, triggerName); + ps.setString(2, groupName); + rs = ps.executeQuery(); + + if (rs.next()) { + String cronExpr = rs.getString(COL_CRON_EXPRESSION); + String timeZoneId = rs.getString(COL_TIME_ZONE_ID); + + CronTrigger ct = null; + try { + TimeZone timeZone = null; + if (timeZoneId != null) { + timeZone = TimeZone.getTimeZone(timeZoneId); + } + ct = new CronTrigger(triggerName, groupName, + jobName, jobGroup, startTimeD, endTimeD, + cronExpr, timeZone); + } catch (Exception neverHappens) { + // expr must be valid, or it never would have + // gotten to the store... + } + if (null != ct) { + ct.setCalendarName(calendarName); + ct.setMisfireInstruction(misFireInstr); + ct.setVolatility(volatility); + ct.setNextFireTime(nft); + ct.setPreviousFireTime(pft); + ct.setDescription(description); + ct.setPriority(priority); + if (null != map) { + ct.setJobDataMap(new JobDataMap(map)); + } + trigger = ct; + } + } + } else if (triggerType.equals(TTYPE_BLOB)) { + ps = conn.prepareStatement(rtp(SELECT_BLOB_TRIGGER)); + ps.setString(1, triggerName); + ps.setString(2, groupName); + rs = ps.executeQuery(); + + if (rs.next()) { + trigger = (Trigger) getObjectFromBlob(rs, COL_BLOB); + } + } else { + throw new ClassNotFoundException("class for trigger type '" + + triggerType + "' not found."); + } + } + + return trigger; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Select a trigger's JobDataMap. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return the {@link com.fr.third.org.quartz.JobDataMap} of the Trigger, + * never null, but possibly empty. + */ + public JobDataMap selectTriggerJobDataMap(Connection conn, String triggerName, + String groupName) throws SQLException, ClassNotFoundException, + IOException { + + PreparedStatement ps = null; + ResultSet rs = null; + + try { + Trigger trigger = null; + + ps = conn.prepareStatement(rtp(SELECT_TRIGGER_DATA)); + ps.setString(1, triggerName); + ps.setString(2, groupName); + rs = ps.executeQuery(); + + if (rs.next()) { + + Map map = null; + if (canUseProperties()) { + map = getMapFromProperties(rs); + } else { + map = (Map) getObjectFromBlob(rs, COL_JOB_DATAMAP); + } + + rs.close(); + ps.close(); + + if (null != map) { + return new JobDataMap(map); + } + } + } finally { + closeResultSet(rs); + closeStatement(ps); + } + + return new JobDataMap(); + } + + + /** + *

+ * Select a trigger' state value. + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return the {@link com.fr.third.org.quartz.Trigger} object + */ + public String selectTriggerState(Connection conn, String triggerName, + String groupName) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + String state = null; + + ps = conn.prepareStatement(rtp(SELECT_TRIGGER_STATE)); + ps.setString(1, triggerName); + ps.setString(2, groupName); + rs = ps.executeQuery(); + + if (rs.next()) { + state = rs.getString(COL_TRIGGER_STATE); + } else { + state = STATE_DELETED; + } + + return state.intern(); + } finally { + closeResultSet(rs); + closeStatement(ps); + } + + } + + /** + *

+ * Select a trigger' status (state & next fire time). + *

+ * + * @param conn + * the DB Connection + * @param triggerName + * the name of the trigger + * @param groupName + * the group containing the trigger + * @return a TriggerStatus object, or null + */ + public TriggerStatus selectTriggerStatus(Connection conn, + String triggerName, String groupName) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + TriggerStatus status = null; + + ps = conn.prepareStatement(rtp(SELECT_TRIGGER_STATUS)); + ps.setString(1, triggerName); + ps.setString(2, groupName); + rs = ps.executeQuery(); + + if (rs.next()) { + String state = rs.getString(COL_TRIGGER_STATE); + long nextFireTime = rs.getLong(COL_NEXT_FIRE_TIME); + String jobName = rs.getString(COL_JOB_NAME); + String jobGroup = rs.getString(COL_JOB_GROUP); + + Date nft = null; + if (nextFireTime > 0) { + nft = new Date(nextFireTime); + } + + status = new TriggerStatus(state, nft); + status.setKey(new Key(triggerName, groupName)); + status.setJobKey(new Key(jobName, jobGroup)); + } + + return status; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + + } + + /** + *

+ * Select the total number of triggers stored. + *

+ * + * @param conn + * the DB Connection + * @return the total number of triggers stored + */ + public int selectNumTriggers(Connection conn) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + int count = 0; + ps = conn.prepareStatement(rtp(SELECT_NUM_TRIGGERS)); + rs = ps.executeQuery(); + + if (rs.next()) { + count = rs.getInt(1); + } + + return count; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Select all of the trigger group names that are stored. + *

+ * + * @param conn + * the DB Connection + * @return an array of String group names + */ + public String[] selectTriggerGroups(Connection conn) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_TRIGGER_GROUPS)); + rs = ps.executeQuery(); + + ArrayList list = new ArrayList(); + while (rs.next()) { + list.add(rs.getString(1)); + } + + Object[] oArr = list.toArray(); + String[] sArr = new String[oArr.length]; + System.arraycopy(oArr, 0, sArr, 0, oArr.length); + return sArr; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Select all of the triggers contained in a given group. + *

+ * + * @param conn + * the DB Connection + * @param groupName + * the group containing the triggers + * @return an array of String trigger names + */ + public String[] selectTriggersInGroup(Connection conn, String groupName) + throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_TRIGGERS_IN_GROUP)); + ps.setString(1, groupName); + rs = ps.executeQuery(); + + ArrayList list = new ArrayList(); + while (rs.next()) { + list.add(rs.getString(1)); + } + + Object[] oArr = list.toArray(); + String[] sArr = new String[oArr.length]; + System.arraycopy(oArr, 0, sArr, 0, oArr.length); + return sArr; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + public int insertPausedTriggerGroup(Connection conn, String groupName) + throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(INSERT_PAUSED_TRIGGER_GROUP)); + ps.setString(1, groupName); + int rows = ps.executeUpdate(); + + return rows; + } finally { + closeStatement(ps); + } + } + + public int deletePausedTriggerGroup(Connection conn, String groupName) + throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(DELETE_PAUSED_TRIGGER_GROUP)); + ps.setString(1, groupName); + int rows = ps.executeUpdate(); + + return rows; + } finally { + closeStatement(ps); + } + } + + public int deleteAllPausedTriggerGroups(Connection conn) + throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(DELETE_PAUSED_TRIGGER_GROUPS)); + int rows = ps.executeUpdate(); + + return rows; + } finally { + closeStatement(ps); + } + } + + public boolean isTriggerGroupPaused(Connection conn, String groupName) + throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_PAUSED_TRIGGER_GROUP)); + ps.setString(1, groupName); + rs = ps.executeQuery(); + + return rs.next(); + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + public boolean isExistingTriggerGroup(Connection conn, String groupName) + throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_NUM_TRIGGERS_IN_GROUP)); + ps.setString(1, groupName); + rs = ps.executeQuery(); + + if (!rs.next()) { + return false; + } + + return (rs.getInt(1) > 0); + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + //--------------------------------------------------------------------------- + // calendars + //--------------------------------------------------------------------------- + + /** + *

+ * Insert a new calendar. + *

+ * + * @param conn + * the DB Connection + * @param calendarName + * the name for the new calendar + * @param calendar + * the calendar + * @return the number of rows inserted + * @throws IOException + * if there were problems serializing the calendar + */ + public int insertCalendar(Connection conn, String calendarName, + Calendar calendar) throws IOException, SQLException { + ByteArrayOutputStream baos = serializeObject(calendar); + + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(INSERT_CALENDAR)); + ps.setString(1, calendarName); + setBytes(ps, 2, baos); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Update a calendar. + *

+ * + * @param conn + * the DB Connection + * @param calendarName + * the name for the new calendar + * @param calendar + * the calendar + * @return the number of rows updated + * @throws IOException + * if there were problems serializing the calendar + */ + public int updateCalendar(Connection conn, String calendarName, + Calendar calendar) throws IOException, SQLException { + ByteArrayOutputStream baos = serializeObject(calendar); + + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(UPDATE_CALENDAR)); + setBytes(ps, 1, baos); + ps.setString(2, calendarName); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Check whether or not a calendar exists. + *

+ * + * @param conn + * the DB Connection + * @param calendarName + * the name of the calendar + * @return true if the trigger exists, false otherwise + */ + public boolean calendarExists(Connection conn, String calendarName) + throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_CALENDAR_EXISTENCE)); + ps.setString(1, calendarName); + rs = ps.executeQuery(); + + if (rs.next()) { + return true; + } else { + return false; + } + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Select a calendar. + *

+ * + * @param conn + * the DB Connection + * @param calendarName + * the name of the calendar + * @return the Calendar + * @throws ClassNotFoundException + * if a class found during deserialization cannot be found be + * found + * @throws IOException + * if there were problems deserializing the calendar + */ + public Calendar selectCalendar(Connection conn, String calendarName) + throws ClassNotFoundException, IOException, SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String selCal = rtp(SELECT_CALENDAR); + ps = conn.prepareStatement(selCal); + ps.setString(1, calendarName); + rs = ps.executeQuery(); + + Calendar cal = null; + if (rs.next()) { + cal = (Calendar) getObjectFromBlob(rs, COL_CALENDAR); + } + if (null == cal) { + logger.warn("Couldn't find calendar with name '" + calendarName + + "'."); + } + return cal; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Check whether or not a calendar is referenced by any triggers. + *

+ * + * @param conn + * the DB Connection + * @param calendarName + * the name of the calendar + * @return true if any triggers reference the calendar, false otherwise + */ + public boolean calendarIsReferenced(Connection conn, String calendarName) + throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + ps = conn.prepareStatement(rtp(SELECT_REFERENCED_CALENDAR)); + ps.setString(1, calendarName); + rs = ps.executeQuery(); + + if (rs.next()) { + return true; + } else { + return false; + } + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Delete a calendar. + *

+ * + * @param conn + * the DB Connection + * @param calendarName + * the name of the trigger + * @return the number of rows deleted + */ + public int deleteCalendar(Connection conn, String calendarName) + throws SQLException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(rtp(DELETE_CALENDAR)); + ps.setString(1, calendarName); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Select the total number of calendars stored. + *

+ * + * @param conn + * the DB Connection + * @return the total number of calendars stored + */ + public int selectNumCalendars(Connection conn) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + int count = 0; + ps = conn.prepareStatement(rtp(SELECT_NUM_CALENDARS)); + + rs = ps.executeQuery(); + + if (rs.next()) { + count = rs.getInt(1); + } + + return count; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Select all of the stored calendars. + *

+ * + * @param conn + * the DB Connection + * @return an array of String calendar names + */ + public String[] selectCalendars(Connection conn) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_CALENDARS)); + rs = ps.executeQuery(); + + ArrayList list = new ArrayList(); + while (rs.next()) { + list.add(rs.getString(1)); + } + + Object[] oArr = list.toArray(); + String[] sArr = new String[oArr.length]; + System.arraycopy(oArr, 0, sArr, 0, oArr.length); + return sArr; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + //--------------------------------------------------------------------------- + // trigger firing + //--------------------------------------------------------------------------- + + /** + *

+ * Select the next time that a trigger will be fired. + *

+ * + * @param conn + * the DB Connection + * @return the next fire time, or 0 if no trigger will be fired + * + * @deprecated Does not account for misfires. + */ + public long selectNextFireTime(Connection conn) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + ps = conn.prepareStatement(rtp(SELECT_NEXT_FIRE_TIME)); + ps.setString(1, STATE_WAITING); + rs = ps.executeQuery(); + + if (rs.next()) { + return rs.getLong(ALIAS_COL_NEXT_FIRE_TIME); + } else { + return 0l; + } + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Select the trigger that will be fired at the given fire time. + *

+ * + * @param conn + * the DB Connection + * @param fireTime + * the time that the trigger will be fired + * @return a {@link com.fr.third.org.quartz.utils.Key} representing the + * trigger that will be fired at the given fire time, or null if no + * trigger will be fired at that time + */ + public Key selectTriggerForFireTime(Connection conn, long fireTime) + throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + ps = conn.prepareStatement(rtp(SELECT_TRIGGER_FOR_FIRE_TIME)); + ps.setString(1, STATE_WAITING); + ps.setBigDecimal(2, new BigDecimal(String.valueOf(fireTime))); + rs = ps.executeQuery(); + + if (rs.next()) { + return new Key(rs.getString(COL_TRIGGER_NAME), rs + .getString(COL_TRIGGER_GROUP)); + } else { + return null; + } + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Select the next trigger which will fire to fire between the two given timestamps + * in ascending order of fire time, and then descending by priority. + *

+ * + * @param conn + * the DB Connection + * @param noLaterThan + * highest value of getNextFireTime() of the triggers (exclusive) + * @param noEarlierThan + * highest value of getNextFireTime() of the triggers (inclusive) + * + * @return A (never null, possibly empty) list of the identifiers (Key objects) of the next triggers to be fired. + */ + public List selectTriggerToAcquire(Connection conn, long noLaterThan, long noEarlierThan) + throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + List nextTriggers = new LinkedList(); + try { + ps = conn.prepareStatement(rtp(SELECT_NEXT_TRIGGER_TO_ACQUIRE)); + + // Try to give jdbc driver a hint to hopefully not pull over + // more than the few rows we actually need. + ps.setFetchSize(5); + ps.setMaxRows(5); + + ps.setString(1, STATE_WAITING); + ps.setBigDecimal(2, new BigDecimal(String.valueOf(noLaterThan))); + ps.setBigDecimal(3, new BigDecimal(String.valueOf(noEarlierThan))); + rs = ps.executeQuery(); + + while (rs.next() && nextTriggers.size() < 5) { + nextTriggers.add(new Key( + rs.getString(COL_TRIGGER_NAME), + rs.getString(COL_TRIGGER_GROUP))); + } + + return nextTriggers; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Insert a fired trigger. + *

+ * + * @param conn + * the DB Connection + * @param trigger + * the trigger + * @param state + * the state that the trigger should be stored in + * @return the number of rows inserted + */ + public int insertFiredTrigger(Connection conn, Trigger trigger, + String state, JobDetail job) throws SQLException { + PreparedStatement ps = null; + try { + ps = conn.prepareStatement(rtp(INSERT_FIRED_TRIGGER)); + ps.setString(1, trigger.getFireInstanceId()); + ps.setString(2, trigger.getName()); + ps.setString(3, trigger.getGroup()); + setBoolean(ps, 4, trigger.isVolatile()); + ps.setString(5, instanceId); + ps.setBigDecimal(6, new BigDecimal(String.valueOf(trigger + .getNextFireTime().getTime()))); + ps.setString(7, state); + if (job != null) { + ps.setString(8, trigger.getJobName()); + ps.setString(9, trigger.getJobGroup()); + setBoolean(ps, 10, job.isStateful()); + setBoolean(ps, 11, job.requestsRecovery()); + } else { + ps.setString(8, null); + ps.setString(9, null); + setBoolean(ps, 10, false); + setBoolean(ps, 11, false); + } + ps.setInt(12, trigger.getPriority()); + + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + /** + *

+ * Select the states of all fired-trigger records for a given trigger, or + * trigger group if trigger name is null. + *

+ * + * @return a List of FiredTriggerRecord objects. + */ + public List selectFiredTriggerRecords(Connection conn, String triggerName, + String groupName) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + List lst = new LinkedList(); + + if (triggerName != null) { + ps = conn.prepareStatement(rtp(SELECT_FIRED_TRIGGER)); + ps.setString(1, triggerName); + ps.setString(2, groupName); + } else { + ps = conn.prepareStatement(rtp(SELECT_FIRED_TRIGGER_GROUP)); + ps.setString(1, groupName); + } + rs = ps.executeQuery(); + + while (rs.next()) { + FiredTriggerRecord rec = new FiredTriggerRecord(); + + rec.setFireInstanceId(rs.getString(COL_ENTRY_ID)); + rec.setFireInstanceState(rs.getString(COL_ENTRY_STATE)); + rec.setFireTimestamp(rs.getLong(COL_FIRED_TIME)); + rec.setPriority(rs.getInt(COL_PRIORITY)); + rec.setSchedulerInstanceId(rs.getString(COL_INSTANCE_NAME)); + rec.setTriggerIsVolatile(getBoolean(rs, COL_IS_VOLATILE)); + rec.setTriggerKey(new Key(rs.getString(COL_TRIGGER_NAME), rs + .getString(COL_TRIGGER_GROUP))); + if (!rec.getFireInstanceState().equals(STATE_ACQUIRED)) { + rec.setJobIsStateful(getBoolean(rs, COL_IS_STATEFUL)); + rec.setJobRequestsRecovery(rs + .getBoolean(COL_REQUESTS_RECOVERY)); + rec.setJobKey(new Key(rs.getString(COL_JOB_NAME), rs + .getString(COL_JOB_GROUP))); + } + lst.add(rec); + } + + return lst; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Select the states of all fired-trigger records for a given job, or job + * group if job name is null. + *

+ * + * @return a List of FiredTriggerRecord objects. + */ + public List selectFiredTriggerRecordsByJob(Connection conn, String jobName, + String groupName) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + List lst = new LinkedList(); + + if (jobName != null) { + ps = conn.prepareStatement(rtp(SELECT_FIRED_TRIGGERS_OF_JOB)); + ps.setString(1, jobName); + ps.setString(2, groupName); + } else { + ps = conn + .prepareStatement(rtp(SELECT_FIRED_TRIGGERS_OF_JOB_GROUP)); + ps.setString(1, groupName); + } + rs = ps.executeQuery(); + + while (rs.next()) { + FiredTriggerRecord rec = new FiredTriggerRecord(); + + rec.setFireInstanceId(rs.getString(COL_ENTRY_ID)); + rec.setFireInstanceState(rs.getString(COL_ENTRY_STATE)); + rec.setFireTimestamp(rs.getLong(COL_FIRED_TIME)); + rec.setPriority(rs.getInt(COL_PRIORITY)); + rec.setSchedulerInstanceId(rs.getString(COL_INSTANCE_NAME)); + rec.setTriggerIsVolatile(getBoolean(rs, COL_IS_VOLATILE)); + rec.setTriggerKey(new Key(rs.getString(COL_TRIGGER_NAME), rs + .getString(COL_TRIGGER_GROUP))); + if (!rec.getFireInstanceState().equals(STATE_ACQUIRED)) { + rec.setJobIsStateful(getBoolean(rs, COL_IS_STATEFUL)); + rec.setJobRequestsRecovery(rs + .getBoolean(COL_REQUESTS_RECOVERY)); + rec.setJobKey(new Key(rs.getString(COL_JOB_NAME), rs + .getString(COL_JOB_GROUP))); + } + lst.add(rec); + } + + return lst; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + + } + + public List selectInstancesFiredTriggerRecords(Connection conn, + String instanceName) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + List lst = new LinkedList(); + + ps = conn.prepareStatement(rtp(SELECT_INSTANCES_FIRED_TRIGGERS)); + ps.setString(1, instanceName); + rs = ps.executeQuery(); + + while (rs.next()) { + FiredTriggerRecord rec = new FiredTriggerRecord(); + + rec.setFireInstanceId(rs.getString(COL_ENTRY_ID)); + rec.setFireInstanceState(rs.getString(COL_ENTRY_STATE)); + rec.setFireTimestamp(rs.getLong(COL_FIRED_TIME)); + rec.setSchedulerInstanceId(rs.getString(COL_INSTANCE_NAME)); + rec.setTriggerIsVolatile(getBoolean(rs, COL_IS_VOLATILE)); + rec.setTriggerKey(new Key(rs.getString(COL_TRIGGER_NAME), rs + .getString(COL_TRIGGER_GROUP))); + if (!rec.getFireInstanceState().equals(STATE_ACQUIRED)) { + rec.setJobIsStateful(getBoolean(rs, COL_IS_STATEFUL)); + rec.setJobRequestsRecovery(rs + .getBoolean(COL_REQUESTS_RECOVERY)); + rec.setJobKey(new Key(rs.getString(COL_JOB_NAME), rs + .getString(COL_JOB_GROUP))); + } + rec.setPriority(rs.getInt(COL_PRIORITY)); + lst.add(rec); + } + + return lst; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Select the distinct instance names of all fired-trigger records. + *

+ * + *

+ * This is useful when trying to identify orphaned fired triggers (a + * fired trigger without a scheduler state record.) + *

+ * + * @return a Set of String objects. + */ + public Set selectFiredTriggerInstanceNames(Connection conn) + throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + Set instanceNames = new HashSet(); + + ps = conn.prepareStatement(rtp(SELECT_FIRED_TRIGGER_INSTANCE_NAMES)); + rs = ps.executeQuery(); + + while (rs.next()) { + instanceNames.add(rs.getString(COL_INSTANCE_NAME)); + } + + return instanceNames; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * Delete a fired trigger. + *

+ * + * @param conn + * the DB Connection + * @param entryId + * the fired trigger entry to delete + * @return the number of rows deleted + */ + public int deleteFiredTrigger(Connection conn, String entryId) + throws SQLException { + PreparedStatement ps = null; + try { + ps = conn.prepareStatement(rtp(DELETE_FIRED_TRIGGER)); + ps.setString(1, entryId); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + public int selectJobExecutionCount(Connection conn, String jobName, + String jobGroup) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_JOB_EXECUTION_COUNT)); + ps.setString(1, jobName); + ps.setString(2, jobGroup); + + rs = ps.executeQuery(); + + return (rs.next()) ? rs.getInt(1) : 0; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + public int deleteVolatileFiredTriggers(Connection conn) throws SQLException { + PreparedStatement ps = null; + try { + ps = conn.prepareStatement(rtp(DELETE_VOLATILE_FIRED_TRIGGERS)); + setBoolean(ps, 1, true); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + public int insertSchedulerState(Connection conn, String instanceId, + long checkInTime, long interval) + throws SQLException { + PreparedStatement ps = null; + try { + ps = conn.prepareStatement(rtp(INSERT_SCHEDULER_STATE)); + ps.setString(1, instanceId); + ps.setLong(2, checkInTime); + ps.setLong(3, interval); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + public int deleteSchedulerState(Connection conn, String instanceId) + throws SQLException { + PreparedStatement ps = null; + try { + ps = conn.prepareStatement(rtp(DELETE_SCHEDULER_STATE)); + ps.setString(1, instanceId); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + public int updateSchedulerState(Connection conn, String instanceId, long checkInTime) + throws SQLException { + PreparedStatement ps = null; + try { + ps = conn.prepareStatement(rtp(UPDATE_SCHEDULER_STATE)); + ps.setLong(1, checkInTime); + ps.setString(2, instanceId); + + return ps.executeUpdate(); + } finally { + closeStatement(ps); + } + } + + public List selectSchedulerStateRecords(Connection conn, String instanceId) + throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + List lst = new LinkedList(); + + if (instanceId != null) { + ps = conn.prepareStatement(rtp(SELECT_SCHEDULER_STATE)); + ps.setString(1, instanceId); + } else { + ps = conn.prepareStatement(rtp(SELECT_SCHEDULER_STATES)); + } + rs = ps.executeQuery(); + + while (rs.next()) { + SchedulerStateRecord rec = new SchedulerStateRecord(); + + rec.setSchedulerInstanceId(rs.getString(COL_INSTANCE_NAME)); + rec.setCheckinTimestamp(rs.getLong(COL_LAST_CHECKIN_TIME)); + rec.setCheckinInterval(rs.getLong(COL_CHECKIN_INTERVAL)); + + lst.add(rec); + } + + return lst; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + + } + + //--------------------------------------------------------------------------- + // protected methods that can be overridden by subclasses + //--------------------------------------------------------------------------- + + /** + *

+ * Replace the table prefix in a query by replacing any occurrences of + * "{0}" with the table prefix. + *

+ * + * @param query + * the unsubstitued query + * @return the query, with proper table prefix substituted + */ + protected final String rtp(String query) { + return Util.rtp(query, tablePrefix); + } + + /** + *

+ * Create a serialized java.util.ByteArrayOutputStream + * version of an Object. + *

+ * + * @param obj + * the object to serialize + * @return the serialized ByteArrayOutputStream + * @throws IOException + * if serialization causes an error + */ + protected ByteArrayOutputStream serializeObject(Object obj) + throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + if (null != obj) { + ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(obj); + out.flush(); + } + return baos; + } + + /** + *

+ * Remove the transient data from and then create a serialized java.util.ByteArrayOutputStream + * version of a {@link com.fr.third.org.quartz.JobDataMap}. + *

+ * + * @param data + * the JobDataMap to serialize + * @return the serialized ByteArrayOutputStream + * @throws IOException + * if serialization causes an error + */ + protected ByteArrayOutputStream serializeJobData(JobDataMap data) + throws IOException { + if (canUseProperties()) { + return serializeProperties(data); + } + + try { + return serializeObject(data); + } catch (NotSerializableException e) { + throw new NotSerializableException( + "Unable to serialize JobDataMap for insertion into " + + "database because the value of property '" + + getKeyOfNonSerializableValue(data) + + "' is not serializable: " + e.getMessage()); + } + } + + /** + * Find the key of the first non-serializable value in the given Map. + * + * @return The key of the first non-serializable value in the given Map or + * null if all values are serializable. + */ + protected Object getKeyOfNonSerializableValue(Map data) { + for (Iterator entryIter = data.entrySet().iterator(); entryIter.hasNext();) { + Map.Entry entry = (Map.Entry)entryIter.next(); + + ByteArrayOutputStream baos = null; + try { + baos = serializeObject(entry.getValue()); + } catch (IOException e) { + return entry.getKey(); + } finally { + if (baos != null) { + try { baos.close(); } catch (IOException ignore) {} + } + } + } + + // As long as it is true that the Map was not serializable, we should + // not hit this case. + return null; + } + + /** + * serialize the java.util.Properties + */ + private ByteArrayOutputStream serializeProperties(JobDataMap data) + throws IOException { + ByteArrayOutputStream ba = new ByteArrayOutputStream(); + if (null != data) { + Properties properties = convertToProperty(data.getWrappedMap()); + properties.store(ba, ""); + } + + return ba; + } + + /** + * convert the JobDataMap into a list of properties + */ + protected Map convertFromProperty(Properties properties) throws IOException { + return new HashMap(properties); + } + + /** + * convert the JobDataMap into a list of properties + */ + protected Properties convertToProperty(Map data) throws IOException { + Properties properties = new Properties(); + + for (Iterator entryIter = data.entrySet().iterator(); entryIter.hasNext();) { + Map.Entry entry = (Map.Entry)entryIter.next(); + + Object key = entry.getKey(); + Object val = (entry.getValue() == null) ? "" : entry.getValue(); + + if(!(key instanceof String)) { + throw new IOException("JobDataMap keys/values must be Strings " + + "when the 'useProperties' property is set. " + + " offending Key: " + key); + } + + if(!(val instanceof String)) { + throw new IOException("JobDataMap values must be Strings " + + "when the 'useProperties' property is set. " + + " Key of offending value: " + key); + } + + properties.put(key, val); + } + + return properties; + } + + /** + *

+ * This method should be overridden by any delegate subclasses that need + * special handling for BLOBs. The default implementation uses standard + * JDBC java.sql.Blob operations. + *

+ * + * @param rs + * the result set, already queued to the correct row + * @param colName + * the column name for the BLOB + * @return the deserialized Object from the ResultSet BLOB + * @throws ClassNotFoundException + * if a class found during deserialization cannot be found + * @throws IOException + * if deserialization causes an error + */ + protected Object getObjectFromBlob(ResultSet rs, String colName) + throws ClassNotFoundException, IOException, SQLException { + Object obj = null; + + Blob blobLocator = rs.getBlob(colName); + if (blobLocator != null && blobLocator.length() != 0) { + InputStream binaryInput = blobLocator.getBinaryStream(); + + if (null != binaryInput) { + if (binaryInput instanceof ByteArrayInputStream + && ((ByteArrayInputStream) binaryInput).available() == 0 ) { + //do nothing + } else { + ObjectInputStream in = new ObjectInputStream(binaryInput); + try { + obj = in.readObject(); + } finally { + in.close(); + } + } + } + + } + return obj; + } + + public Key[] selectVolatileTriggers(Connection conn) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_VOLATILE_TRIGGERS)); + setBoolean(ps, 1, true); + rs = ps.executeQuery(); + + ArrayList list = new ArrayList(); + while (rs.next()) { + String triggerName = rs.getString(COL_TRIGGER_NAME); + String groupName = rs.getString(COL_TRIGGER_GROUP); + list.add(new Key(triggerName, groupName)); + } + Object[] oArr = list.toArray(); + Key[] kArr = new Key[oArr.length]; + System.arraycopy(oArr, 0, kArr, 0, oArr.length); + return kArr; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + public Key[] selectVolatileJobs(Connection conn) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + try { + ps = conn.prepareStatement(rtp(SELECT_VOLATILE_JOBS)); + setBoolean(ps, 1, true); + rs = ps.executeQuery(); + + ArrayList list = new ArrayList(); + while (rs.next()) { + String triggerName = rs.getString(COL_JOB_NAME); + String groupName = rs.getString(COL_JOB_GROUP); + list.add(new Key(triggerName, groupName)); + } + Object[] oArr = list.toArray(); + Key[] kArr = new Key[oArr.length]; + System.arraycopy(oArr, 0, kArr, 0, oArr.length); + return kArr; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + *

+ * This method should be overridden by any delegate subclasses that need + * special handling for BLOBs for job details. The default implementation + * uses standard JDBC java.sql.Blob operations. + *

+ * + * @param rs + * the result set, already queued to the correct row + * @param colName + * the column name for the BLOB + * @return the deserialized Object from the ResultSet BLOB + * @throws ClassNotFoundException + * if a class found during deserialization cannot be found + * @throws IOException + * if deserialization causes an error + */ + protected Object getJobDetailFromBlob(ResultSet rs, String colName) + throws ClassNotFoundException, IOException, SQLException { + if (canUseProperties()) { + Blob blobLocator = rs.getBlob(colName); + if (blobLocator != null) { + InputStream binaryInput = blobLocator.getBinaryStream(); + return binaryInput; + } else { + return null; + } + } + + return getObjectFromBlob(rs, colName); + } + + /** + * @see com.fr.third.org.quartz.impl.jdbcjobstore.DriverDelegate#selectPausedTriggerGroups(java.sql.Connection) + */ + public Set selectPausedTriggerGroups(Connection conn) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + + HashSet set = new HashSet(); + try { + ps = conn.prepareStatement(rtp(SELECT_PAUSED_TRIGGER_GROUPS)); + rs = ps.executeQuery(); + + while (rs.next()) { + String groupName = rs.getString(COL_TRIGGER_GROUP); + set.add(groupName); + } + return set; + } finally { + closeResultSet(rs); + closeStatement(ps); + } + } + + /** + * Cleanup helper method that closes the given ResultSet + * while ignoring any errors. + */ + protected void closeResultSet(ResultSet rs) { + if (null != rs) { + try { + rs.close(); + } catch (SQLException ignore) { + } + } + } + + /** + * Cleanup helper method that closes the given Statement + * while ignoring any errors. + */ + protected void closeStatement(Statement statement) { + if (null != statement) { + try { + statement.close(); + } catch (SQLException ignore) { + } + } + } + + + /** + * Sets the designated parameter to the given Java boolean value. + * This just wraps {@link PreparedStatement#setBoolean(int, boolean)} + * by default, but it can be overloaded by subclass delegates for databases that + * don't explicitly support the boolean type. + */ + protected void setBoolean(PreparedStatement ps, int index, boolean val) throws SQLException { + ps.setBoolean(index, val); + } + + /** + * Retrieves the value of the designated column in the current row as + * a boolean. + * This just wraps {@link ResultSet#getBoolean(java.lang.String)} + * by default, but it can be overloaded by subclass delegates for databases that + * don't explicitly support the boolean type. + */ + protected boolean getBoolean(ResultSet rs, String columnName) throws SQLException { + return rs.getBoolean(columnName); + } + + /** + * Retrieves the value of the designated column index in the current row as + * a boolean. + * This just wraps {@link ResultSet#getBoolean(java.lang.String)} + * by default, but it can be overloaded by subclass delegates for databases that + * don't explicitly support the boolean type. + */ + protected boolean getBoolean(ResultSet rs, int columnIndex) throws SQLException { + return rs.getBoolean(columnIndex); + } + + /** + * Sets the designated parameter to the byte array of the given + * ByteArrayOutputStream. Will set parameter value to null if the + * ByteArrayOutputStream is null. + * This just wraps {@link PreparedStatement#setBytes(int, byte[])} + * by default, but it can be overloaded by subclass delegates for databases that + * don't explicitly support storing bytes in this way. + */ + protected void setBytes(PreparedStatement ps, int index, ByteArrayOutputStream baos) throws SQLException { + ps.setBytes(index, (baos == null) ? new byte[0] : baos.toByteArray()); + } +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/StdRowLockSemaphore.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/StdRowLockSemaphore.java new file mode 100644 index 000000000..bb29f8b3d --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/StdRowLockSemaphore.java @@ -0,0 +1,137 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Internal database based lock handler for providing thread/resource locking + * in order to protect resources from being altered by multiple threads at the + * same time. + * + * @author jhouse + */ +public class StdRowLockSemaphore extends DBSemaphore { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public static final String SELECT_FOR_LOCK = "SELECT * FROM " + + TABLE_PREFIX_SUBST + TABLE_LOCKS + " WHERE " + COL_LOCK_NAME + + " = ? FOR UPDATE"; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * This constructor is for using the StdRowLockSemaphore as + * a bean. + */ + public StdRowLockSemaphore() { + super(DEFAULT_TABLE_PREFIX, null, SELECT_FOR_LOCK); + } + + public StdRowLockSemaphore(String tablePrefix, String selectWithLockSQL) { + super(tablePrefix, selectWithLockSQL, SELECT_FOR_LOCK); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Execute the SQL select for update that will lock the proper database row. + */ + protected void executeSQL(Connection conn, String lockName, String expandedSQL) throws LockException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + ps = conn.prepareStatement(expandedSQL); + ps.setString(1, lockName); + + if (getLog().isDebugEnabled()) { + getLog().debug( + "Lock '" + lockName + "' is being obtained: " + + Thread.currentThread().getName()); + } + rs = ps.executeQuery(); + if (!rs.next()) { + throw new SQLException(Util.rtp( + "No row exists in table " + TABLE_PREFIX_SUBST + + TABLE_LOCKS + " for lock named: " + lockName, getTablePrefix())); + } + } catch (SQLException sqle) { + //Exception src = + // (Exception)getThreadLocksObtainer().get(lockName); + //if(src != null) + // src.printStackTrace(); + //else + // System.err.println("--- ***************** NO OBTAINER!"); + + if (getLog().isDebugEnabled()) { + getLog().debug( + "Lock '" + lockName + "' was not obtained by: " + + Thread.currentThread().getName()); + } + + throw new LockException("Failure obtaining db row lock: " + + sqle.getMessage(), sqle); + } finally { + if (rs != null) { + try { + rs.close(); + } catch (Exception ignore) { + } + } + if (ps != null) { + try { + ps.close(); + } catch (Exception ignore) { + } + } + } + } + + protected String getSelectWithLockSQL() { + return getSQL(); + } + + public void setSelectWithLockSQL(String selectWithLockSQL) { + setSQL(selectWithLockSQL); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/TablePrefixAware.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/TablePrefixAware.java new file mode 100644 index 000000000..297c41a0b --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/TablePrefixAware.java @@ -0,0 +1,24 @@ +/* + * Copyright 2004-2006 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +/** + * Interface for Quartz objects that need to know what the table prefix of + * the tables used by a JDBC JobStore is. + */ +public interface TablePrefixAware { + void setTablePrefix(String tablePrefix); +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/UpdateLockRowSemaphore.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/UpdateLockRowSemaphore.java new file mode 100644 index 000000000..5f7406d09 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/UpdateLockRowSemaphore.java @@ -0,0 +1,126 @@ +/* + * Copyright 2004-2006 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * Provide thread/resource locking in order to protect + * resources from being altered by multiple threads at the same time using + * a db row update. + * + *

+ * Note: This Semaphore implementation is useful for databases that do + * not support row locking via "SELECT FOR UPDATE" type syntax, for example + * Microsoft SQLServer (MSSQL). + *

+ */ +public class UpdateLockRowSemaphore extends DBSemaphore { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public static final String UPDATE_FOR_LOCK = + "UPDATE " + TABLE_PREFIX_SUBST + TABLE_LOCKS + + " SET " + COL_LOCK_NAME + " = " + COL_LOCK_NAME + + " WHERE " + COL_LOCK_NAME + " = ? "; + + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public UpdateLockRowSemaphore() { + super(DEFAULT_TABLE_PREFIX, null, UPDATE_FOR_LOCK); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Execute the SQL select for update that will lock the proper database row. + */ + protected void executeSQL(Connection conn, String lockName, String expandedSQL) throws LockException { + PreparedStatement ps = null; + + try { + ps = conn.prepareStatement(expandedSQL); + ps.setString(1, lockName); + + if (getLog().isDebugEnabled()) { + getLog().debug( + "Lock '" + lockName + "' is being obtained: " + + Thread.currentThread().getName()); + } + + int numUpdate = ps.executeUpdate(); + + if (numUpdate < 1) { + throw new SQLException(Util.rtp( + "No row exists in table " + TABLE_PREFIX_SUBST + TABLE_LOCKS + + " for lock named: " + lockName, getTablePrefix())); + } + } catch (SQLException sqle) { + //Exception src = + // (Exception)getThreadLocksObtainer().get(lockName); + //if(src != null) + // src.printStackTrace(); + //else + // System.err.println("--- ***************** NO OBTAINER!"); + + if(getLog().isDebugEnabled()) { + getLog().debug( + "Lock '" + lockName + "' was not obtained by: " + + Thread.currentThread().getName()); + } + + throw new LockException( + "Failure obtaining db row lock: " + sqle.getMessage(), sqle); + } finally { + if (ps != null) { + try { + ps.close(); + } catch (Exception ignore) { + } + } + } + } + + protected String getUpdateLockRowSQL() { + return getSQL(); + } + + public void setUpdateLockRowSQL(String updateLockRowSQL) { + setSQL(updateLockRowSQL); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/Util.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/Util.java new file mode 100644 index 000000000..8891f0b64 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/jdbcjobstore/Util.java @@ -0,0 +1,95 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.impl.jdbcjobstore; + +import java.text.MessageFormat; + +/** + *

+ * This class contains utility functions for use in all delegate classes. + *

+ * + * @author Jeffrey Wescott + */ +public final class Util { + + /** + * Private constructor because this is a pure utility class. + */ + private Util() { + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Replace the table prefix in a query by replacing any occurrences of + * "{0}" with the table prefix. + *

+ * + * @param query + * the unsubstitued query + * @param tablePrefix + * the table prefix + * @return the query, with proper table prefix substituted + */ + public static String rtp(String query, String tablePrefix) { + return MessageFormat.format(query, new Object[]{tablePrefix}); + } + + /** + *

+ * Obtain a unique key for a given job. + *

+ * + * @param jobName + * the job name + * @param groupName + * the group containing the job + * @return a unique String key + */ + static String getJobNameKey(String jobName, String groupName) { + return (groupName + "_$x$x$_" + jobName).intern(); + } + + /** + *

+ * Obtain a unique key for a given trigger. + *

+ * + * @param triggerName + * the trigger name + * @param groupName + * the group containing the trigger + * @return a unique String key + */ + static String getTriggerNameKey(String triggerName, String groupName) { + return (groupName + "_$x$x$_" + triggerName).intern(); + } +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/package.html b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/package.html new file mode 100644 index 000000000..adb720946 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/impl/package.html @@ -0,0 +1,18 @@ + + +Package com.fr.third.org.quartz.impl + + +

Contains implementations of the SchedulerFactory, JobStore, ThreadPool, and +other interfaces required by the com.fr.third.org.quartz.core.QuartzScheduler.

+ +

Classes in this package may have dependencies on third-party packages.

+ +
+
+
+See the Quartz project + at Open Symphony for more information. + + + diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/FileScanJob.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/FileScanJob.java new file mode 100644 index 000000000..d4b3e8c08 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/FileScanJob.java @@ -0,0 +1,136 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.jobs; + +import java.io.File; +import java.net.URL; +import java.net.URLDecoder; + +import com.fr.third.org.quartz.JobDataMap; +import com.fr.third.org.quartz.JobExecutionContext; +import com.fr.third.org.quartz.JobExecutionException; +import com.fr.third.org.quartz.SchedulerContext; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.StatefulJob; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Inspects a file and compares whether it's "last modified date" has changed + * since the last time it was inspected. If the file has been updated, the + * job invokes a "call-back" method on an identified + * FileScanListener that can be found in the + * SchedulerContext. + * + * @author jhouse + * @author pl47ypus + * @see com.fr.third.org.quartz.jobs.FileScanListener + */ +public class FileScanJob implements StatefulJob { + + public static final String FILE_NAME = "FILE_NAME"; + public static final String FILE_SCAN_LISTENER_NAME = "FILE_SCAN_LISTENER_NAME"; + private static final String LAST_MODIFIED_TIME = "LAST_MODIFIED_TIME"; + + private final Log log = LogFactory.getLog(getClass()); + + public FileScanJob() { + } + + /** + * @see com.fr.third.org.quartz.Job#execute(com.fr.third.org.quartz.JobExecutionContext) + */ + public void execute(JobExecutionContext context) throws JobExecutionException { + JobDataMap mergedJobDataMap = context.getMergedJobDataMap(); + SchedulerContext schedCtxt = null; + try { + schedCtxt = context.getScheduler().getContext(); + } catch (SchedulerException e) { + throw new JobExecutionException("Error obtaining scheduler context.", e, false); + } + + String fileName = mergedJobDataMap.getString(FILE_NAME); + String listenerName = mergedJobDataMap.getString(FILE_SCAN_LISTENER_NAME); + + if(fileName == null) { + throw new JobExecutionException("Required parameter '" + + FILE_NAME + "' not found in merged JobDataMap"); + } + if(listenerName == null) { + throw new JobExecutionException("Required parameter '" + + FILE_SCAN_LISTENER_NAME + "' not found in merged JobDataMap"); + } + + FileScanListener listener = (FileScanListener)schedCtxt.get(listenerName); + + if(listener == null) { + throw new JobExecutionException("FileScanListener named '" + + listenerName + "' not found in SchedulerContext"); + } + + long lastDate = -1; + if(mergedJobDataMap.containsKey(LAST_MODIFIED_TIME)) { + lastDate = mergedJobDataMap.getLong(LAST_MODIFIED_TIME); + } + + long newDate = getLastModifiedDate(fileName); + + if(newDate < 0) { + log.warn("File '"+fileName+"' does not exist."); + return; + } + + if(lastDate > 0 && (newDate != lastDate)) { + // notify call back... + log.info("File '"+fileName+"' updated, notifying listener."); + listener.fileUpdated(fileName); + } else if (log.isDebugEnabled()) { + log.debug("File '"+fileName+"' unchanged."); + } + + // It is the JobDataMap on the JobDetail which is actually stateful + context.getJobDetail().getJobDataMap().put(LAST_MODIFIED_TIME, newDate); + } + + protected long getLastModifiedDate(String fileName) { + URL resource = Thread.currentThread().getContextClassLoader().getResource(fileName); + + // Get the absolute path. + String filePath = (resource == null) ? fileName : URLDecoder.decode(resource.getFile()); ; + + // If the jobs file is inside a jar point to the jar file (to get it modification date). + // Otherwise continue as usual. + int jarIndicator = filePath.indexOf('!'); + + if (jarIndicator > 0) { + filePath = filePath.substring(5, filePath.indexOf('!')); + } + + File file = new File(filePath); + + if(!file.exists()) { + return -1; + } else { + return file.lastModified(); + } + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/FileScanJob.java.bak b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/FileScanJob.java.bak new file mode 100644 index 000000000..260f737c9 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/FileScanJob.java.bak @@ -0,0 +1,121 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package org.quartz.jobs; + +import java.io.File; + +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.quartz.SchedulerContext; +import org.quartz.SchedulerException; +import org.quartz.StatefulJob; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Inspects a file and compares whether it's "last modified date" has changed + * since the last time it was inspected. If the file has been updated, the + * job invokes a "call-back" method on an identified + * FileScanListener that can be found in the + * SchedulerContext. + * + * @author jhouse + * @see org.quartz.jobs.FileScanListener + */ +public class FileScanJob implements StatefulJob { + + public static String FILE_NAME = "FILE_NAME"; + public static String FILE_SCAN_LISTENER_NAME = "FILE_SCAN_LISTENER_NAME"; + private static String LAST_MODIFIED_TIME = "LAST_MODIFIED_TIME"; + + private final Log log = LogFactory.getLog(getClass()); + + public FileScanJob() { + } + + /** + * @see org.quartz.Job#execute(org.quartz.JobExecutionContext) + */ + public void execute(JobExecutionContext context) throws JobExecutionException { + JobDataMap mergedJobDataMap = context.getMergedJobDataMap(); + SchedulerContext schedCtxt = null; + try { + schedCtxt = context.getScheduler().getContext(); + } catch (SchedulerException e) { + throw new JobExecutionException("Error obtaining scheduler context.", e, false); + } + + String fileName = mergedJobDataMap.getString(FILE_NAME); + String listenerName = mergedJobDataMap.getString(FILE_SCAN_LISTENER_NAME); + + if(fileName == null) { + throw new JobExecutionException("Required parameter '" + + FILE_NAME + "' not found in merged JobDataMap"); + } + if(listenerName == null) { + throw new JobExecutionException("Required parameter '" + + FILE_SCAN_LISTENER_NAME + "' not found in merged JobDataMap"); + } + + FileScanListener listener = (FileScanListener)schedCtxt.get(listenerName); + + if(listener == null) { + throw new JobExecutionException("FileScanListener named '" + + listenerName + "' not found in SchedulerContext"); + } + + long lastDate = -1; + if(mergedJobDataMap.containsKey(LAST_MODIFIED_TIME)) { + lastDate = mergedJobDataMap.getLong(LAST_MODIFIED_TIME); + } + + long newDate = getLastModifiedDate(fileName); + + if(newDate < 0) { + log.warn("File '"+fileName+"' does not exist."); + return; + } + + if(lastDate > 0 && (newDate != lastDate)) { + // notify call back... + log.info("File '"+fileName+"' updated, notifying listener."); + listener.fileUpdated(fileName); + } else if (log.isDebugEnabled()) { + log.debug("File '"+fileName+"' unchanged."); + } + + // It is the JobDataMap on the JobDetail which is actually stateful + context.getJobDetail().getJobDataMap().put(LAST_MODIFIED_TIME, newDate); + } + + protected long getLastModifiedDate(String fileName) { + + File file = new File(fileName); + + if(!file.exists()) { + return -1; + } else { + return file.lastModified(); + } + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/FileScanListener.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/FileScanListener.java new file mode 100644 index 000000000..e2c1674b2 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/FileScanListener.java @@ -0,0 +1,33 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.jobs; + +/** + * Interface for objects wishing to receive a 'call-back' from a + * FileScanJob. + * + * @author jhouse + * @see com.fr.third.org.quartz.jobs.FileScanJob + */ +public interface FileScanListener { + + void fileUpdated(String fileName); +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/NativeJob.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/NativeJob.java new file mode 100644 index 000000000..ee42fe012 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/NativeJob.java @@ -0,0 +1,280 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ + +package com.fr.third.org.quartz.jobs; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.Job; +import com.fr.third.org.quartz.JobDataMap; +import com.fr.third.org.quartz.JobExecutionContext; +import com.fr.third.org.quartz.JobExecutionException; + +/* + *

Built in job for executing native executables in a separate process.

+ * + * If PROP_WAIT_FOR_PROCESS is true, then the Integer exit value of the process + * will be saved as the job execution result in the JobExecutionContext. + * + * @see #PROP_COMMAND + * @see #PROP_PARAMETERS + * @see #PROP_WAIT_FOR_PROCESS + * @see #PROP_CONSUME_STREAMS + * + * @author Matthew Payne + * @author James House + * @author Steinar Overbeck Cook + * @date Sep 17, 2003 @Time: 11:27:13 AM + */ +public class NativeJob implements Job { + + private final Log log = LogFactory.getLog(getClass()); + + /* + *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Required parameter that specifies the name of the command (executable) + * to be ran. + */ + public static final String PROP_COMMAND = "command"; + + /** + * Optional parameter that specifies the parameters to be passed to the + * executed command. + */ + public static final String PROP_PARAMETERS = "parameters"; + + + /** + * Optional parameter (value should be 'true' or 'false') that specifies + * whether the job should wait for the execution of the native process to + * complete before it completes. + * + *

Defaults to true.

+ */ + public static final String PROP_WAIT_FOR_PROCESS = "waitForProcess"; + + /** + * Optional parameter (value should be 'true' or 'false') that specifies + * whether the spawned process's stdout and stderr streams should be + * consumed. If the process creates output, it is possible that it might + * 'hang' if the streams are not consumed. + * + *

Defaults to false.

+ */ + public static final String PROP_CONSUME_STREAMS = "consumeStreams"; + + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public void execute(JobExecutionContext context) + throws JobExecutionException { + + JobDataMap data = context.getMergedJobDataMap(); + + String command = data.getString(PROP_COMMAND); + + String parameters = data.getString(PROP_PARAMETERS); + + if (parameters == null) { + parameters = ""; + } + + boolean wait = true; + if(data.containsKey(PROP_WAIT_FOR_PROCESS)) { + wait = data.getBooleanValue(PROP_WAIT_FOR_PROCESS); + } + boolean consumeStreams = false; + if(data.containsKey(PROP_CONSUME_STREAMS)) { + consumeStreams = data.getBooleanValue(PROP_CONSUME_STREAMS); + } + + Integer exitCode = this.runNativeCommand(command, parameters, wait, consumeStreams); + context.setResult(exitCode); + + } + + protected Log getLog() { + return log; + } + + private Integer runNativeCommand(String command, String parameters, boolean wait, boolean consumeStreams) throws JobExecutionException { + + String[] cmd = null; + String[] args = new String[2]; + Integer result = null; + args[0] = command; + args[1] = parameters; + + + try { + //with this variable will be done the swithcing + String osName = System.getProperty("os.name"); + + //only will work with Windows NT + if (osName.equals("Windows NT")) { + if (cmd == null) { + cmd = new String[args.length + 2]; + } + cmd[0] = "cmd.exe"; + cmd[1] = "/C"; + for (int i = 0; i < args.length; i++) { + cmd[i + 2] = args[i]; + } + } else if (osName.equals("Windows 95")) { //only will work with Windows 95 + if (cmd == null) { + cmd = new String[args.length + 2]; + } + cmd[0] = "command.com"; + cmd[1] = "/C"; + for (int i = 0; i < args.length; i++) { + cmd[i + 2] = args[i]; + } + } else if (osName.equals("Windows 2003")) { //only will work with Windows 2003 + if (cmd == null) { + cmd = new String[args.length + 2]; + } + cmd[0] = "cmd.exe"; + cmd[1] = "/C"; + + for (int i = 0; i < args.length; i++) { + cmd[i + 2] = args[i]; + } + } else if (osName.equals("Windows 2000")) { //only will work with Windows 2000 + if (cmd == null) { + cmd = new String[args.length + 2]; + } + cmd[0] = "cmd.exe"; + cmd[1] = "/C"; + + for (int i = 0; i < args.length; i++) { + cmd[i + 2] = args[i]; + } + } else if (osName.equals("Windows XP")) { //only will work with Windows XP + if (cmd == null) { + cmd = new String[args.length + 2]; + } + cmd[0] = "cmd.exe"; + cmd[1] = "/C"; + + for (int i = 0; i < args.length; i++) { + cmd[i + 2] = args[i]; + } + } else if (osName.equals("Linux")) { + if (cmd == null) { + cmd = new String[3]; + } + cmd[0] = "/bin/sh"; + cmd[1] = "-c"; + cmd[2] = args[0] + " " + args[1]; + } else { // try this... + cmd = args; + } + + Runtime rt = Runtime.getRuntime(); + // Executes the command + getLog().info("About to run" + cmd[0] + " " + cmd[1] + " " + (cmd.length>2 ? cmd[2] : "") + " ..."); + Process proc = rt.exec(cmd); + // Consumes the stdout from the process + StreamConsumer stdoutConsumer = new StreamConsumer(proc.getInputStream(), "stdout"); + + // Consumes the stderr from the process + if(consumeStreams) { + StreamConsumer stderrConsumer = new StreamConsumer(proc.getErrorStream(), "stderr"); + stdoutConsumer.start(); + stderrConsumer.start(); + } + + if(wait) { + result = new Integer(proc.waitFor()); + } + // any error message? + + } catch (Exception x) { + throw new JobExecutionException("Error launching native command: ", x, false); + } + + return result; + } + + /** + * Consumes data from the given input stream until EOF and prints the data to stdout + * + * @author cooste + * @author jhouse + */ + class StreamConsumer extends Thread { + InputStream is; + String type; + + /** + * + */ + public StreamConsumer(InputStream inputStream, String type) { + this.is = inputStream; + this.type = type; + } + + /** + * Runs this object as a separate thread, printing the contents of the InputStream + * supplied during instantiation, to either stdout or stderr + */ + public void run() { + BufferedReader br = null; + try { + br = new BufferedReader(new InputStreamReader(is)); + String line = null; + + while ((line = br.readLine()) != null) { + if(type.equalsIgnoreCase("stderr")) { + getLog().warn(type + ">" + line); + } else { + getLog().info(type + ">" + line); + } + } + } catch (IOException ioe) { + getLog().error("Error consuming " + type + " stream of spawned process.", ioe); + } finally { + if(br != null) { + try { br.close(); } catch(Exception ignore) {} + } + } + } + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/NoOpJob.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/NoOpJob.java new file mode 100644 index 000000000..da2482286 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/NoOpJob.java @@ -0,0 +1,68 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.jobs; + +import com.fr.third.org.quartz.Job; +import com.fr.third.org.quartz.JobExecutionContext; +import com.fr.third.org.quartz.JobExecutionException; + +/** + *

+ * An implementation of Job, that does absolutely nothing - useful for system + * which only wish to use {@link com.fr.third.org.quartz.TriggerListener}s + * and {@link com.fr.third.org.quartz.JobListener}s, rather than writing + * Jobs that perform work. + *

+ * + * @author James House + */ +public class NoOpJob implements Job { + + + /* + *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public NoOpJob() { + } + + /* + *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Do nothing. + *

+ */ + public void execute(JobExecutionContext context) + throws JobExecutionException { + } + +} \ No newline at end of file diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/ee/jmx/JMXInvokerJob.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/ee/jmx/JMXInvokerJob.java new file mode 100644 index 000000000..c21f1d066 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/jobs/ee/jmx/JMXInvokerJob.java @@ -0,0 +1,190 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.jobs.ee.jmx; + + +import java.util.LinkedList; +import java.util.StringTokenizer; + +import javax.management.MBeanServer; +import javax.management.MBeanServerFactory; +import javax.management.ObjectName; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.Job; +import com.fr.third.org.quartz.JobDataMap; +import com.fr.third.org.quartz.JobExecutionContext; +import com.fr.third.org.quartz.JobExecutionException; + + +/** + * Generic JMX invoker Job. It supports any number or type of parameters + * to the JMX bean.

+ * + * The required parameters are as follows (case doesn't matter):

+ *

+ *
JMX_OBJECTNAME + *
This is the fully qualifed name of the object (ie in JBoss to lookup + * the log4j jmx bean you would specify "jboss.system:type=Log4jService,service=Logging" + *
JMX_METHOD + *
This is the method to invoke on the specified JMX Bean. (ie in JBoss to + * change the log level you would specify "setLoggerLevel" + *
JMX_PARAMDEFS + *
This is a definition of the parameters to be passed to the specified method + * and their corresponding java types. Each parameter definition is comma seperated + * and has the following parts: :. Type is the java type for the parameter. + * The following types are supported:

+ * i - is for int

+ * l - is for long

+ * f - is for float

+ * d - is for double

+ * s - is for String

+ * b - is for boolean

+ * For ilfdb use lower for native type and upper for object wrapper. The name portion + * of the definition is the name of the parameter holding the string value. (ie + * s:fname,s:lname would require 2 parameters of the name fname and lname and + * would be passed in that order to the method. + * + * @author James Nelson (jmn@provident-solutions.com) -- Provident Solutions LLC + * + */ +public class JMXInvokerJob implements Job { + + private final Log log = LogFactory.getLog(getClass()); + + public void execute(JobExecutionContext context) throws JobExecutionException { + try { + Object[] params=null; + String[] types=null; + String objName = null; + String objMethod = null; + + JobDataMap jobDataMap = context.getMergedJobDataMap(); + + String[] keys = jobDataMap.getKeys(); + for (int i = 0; i < keys.length; i++) { + String value = jobDataMap.getString(keys[i]); + if ("JMX_OBJECTNAME".equalsIgnoreCase(keys[i])) { + objName = value; + } else if ("JMX_METHOD".equalsIgnoreCase(keys[i])) { + objMethod = value; + } else if("JMX_PARAMDEFS".equalsIgnoreCase(keys[i])) { + String[] paramdefs=split(value, ","); + params=new Object[paramdefs.length]; + types=new String[paramdefs.length]; + for(int k=0;k + * A Job which sends an e-mail with the configured content to the configured + * recipient. + *

+ * + * @author James House + */ +public class SendMailJob implements Job { + + private final Log log = LogFactory.getLog(getClass()); + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * The host name of the smtp server. REQUIRED. + */ + public static final String PROP_SMTP_HOST = "smtp_host"; + + /** + * The e-mail address to send the mail to. REQUIRED. + */ + public static final String PROP_RECIPIENT = "recipient"; + + /** + * The e-mail address to cc the mail to. Optional. + */ + public static final String PROP_CC_RECIPIENT = "cc_recipient"; + + /** + * The e-mail address to claim the mail is from. REQUIRED. + */ + public static final String PROP_SENDER = "sender"; + + /** + * The e-mail address the message should say to reply to. Optional. + */ + public static final String PROP_REPLY_TO = "reply_to"; + + /** + * The subject to place on the e-mail. REQUIRED. + */ + public static final String PROP_SUBJECT = "subject"; + + /** + * The e-mail message body. REQUIRED. + */ + public static final String PROP_MESSAGE = "message"; + + /** + * The message content type. For example, "text/html". Optional. + */ + public static final String PROP_CONTENT_TYPE = "content_type"; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * @see com.fr.third.org.quartz.Job#execute(com.fr.third.org.quartz.JobExecutionContext) + */ + public void execute(JobExecutionContext context) + throws JobExecutionException { + + JobDataMap data = context.getMergedJobDataMap(); + + MailInfo mailInfo = populateMailInfo(data, createMailInfo()); + + getLog().info("Sending message " + mailInfo); + + try { + MimeMessage mimeMessage = prepareMimeMessage(mailInfo); + + Transport.send(mimeMessage); + } catch (MessagingException e) { + throw new JobExecutionException("Unable to send mail: " + mailInfo, + e, false); + } + + } + + protected Log getLog() { + return log; + } + + protected MimeMessage prepareMimeMessage(MailInfo mailInfo) + throws MessagingException { + Session session = getMailSession(mailInfo); + + MimeMessage mimeMessage = new MimeMessage(session); + + Address[] toAddresses = InternetAddress.parse(mailInfo.getTo()); + mimeMessage.setRecipients(Message.RecipientType.TO, toAddresses); + + if (mailInfo.getCc() != null) { + Address[] ccAddresses = InternetAddress.parse(mailInfo.getCc()); + mimeMessage.setRecipients(Message.RecipientType.CC, ccAddresses); + } + + mimeMessage.setFrom(new InternetAddress(mailInfo.getFrom())); + + if (mailInfo.getReplyTo() != null) { + mimeMessage.setReplyTo(new InternetAddress[]{new InternetAddress(mailInfo.getReplyTo())}); + } + + mimeMessage.setSubject(mailInfo.getSubject()); + + mimeMessage.setSentDate(new Date()); + + setMimeMessageContent(mimeMessage, mailInfo); + + return mimeMessage; + } + + protected void setMimeMessageContent(MimeMessage mimeMessage, MailInfo mailInfo) + throws MessagingException { + if (mailInfo.getContentType() == null) { + mimeMessage.setText(mailInfo.getMessage()); + } else { + mimeMessage.setContent(mailInfo.getMessage(), mailInfo.getContentType()); + } + } + + protected Session getMailSession(MailInfo mailInfo) throws MessagingException { + Properties properties = new Properties(); + properties.put("mail.smtp.host", mailInfo.getSmtpHost()); + + return Session.getDefaultInstance(properties, null); + } + + protected MailInfo createMailInfo() { + return new MailInfo(); + } + + protected MailInfo populateMailInfo(JobDataMap data, MailInfo mailInfo) { + // Required parameters + mailInfo.setSmtpHost(getRequiredParm(data, PROP_SMTP_HOST, "PROP_SMTP_HOST")); + mailInfo.setTo(getRequiredParm(data, PROP_RECIPIENT, "PROP_RECIPIENT")); + mailInfo.setFrom(getRequiredParm(data, PROP_SENDER, "PROP_SENDER")); + mailInfo.setSubject(getRequiredParm(data, PROP_SUBJECT, "PROP_SUBJECT")); + mailInfo.setMessage(getRequiredParm(data, PROP_MESSAGE, "PROP_MESSAGE")); + + // Optional parameters + mailInfo.setReplyTo(getOptionalParm(data, PROP_REPLY_TO)); + mailInfo.setCc(getOptionalParm(data, PROP_CC_RECIPIENT)); + mailInfo.setContentType(getOptionalParm(data, PROP_CONTENT_TYPE)); + + return mailInfo; + } + + + protected String getRequiredParm(JobDataMap data, String property, String constantName) { + String value = getOptionalParm(data, property); + + if (value == null) { + throw new IllegalArgumentException(constantName + " not specified."); + } + + return value; + } + + protected String getOptionalParm(JobDataMap data, String property) { + String value = data.getString(property); + + if ((value != null) && (value.trim().length() == 0)) { + return null; + } + + return value; + } + + protected static class MailInfo { + private String smtpHost; + private String to; + private String from; + private String subject; + private String message; + private String replyTo; + private String cc; + private String contentType; + + public String toString() { + return "'" + getSubject() + "' to: " + getTo(); + } + + public String getCc() { + return cc; + } + + public void setCc(String cc) { + this.cc = cc; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public String getFrom() { + return from; + } + + public void setFrom(String from) { + this.from = from; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getReplyTo() { + return replyTo; + } + + public void setReplyTo(String replyTo) { + this.replyTo = replyTo; + } + + public String getSmtpHost() { + return smtpHost; + } + + public void setSmtpHost(String smtpHost) { + this.smtpHost = smtpHost; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getTo() { + return to; + } + + public void setTo(String to) { + this.to = to; + } + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/BroadcastSchedulerListener.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/BroadcastSchedulerListener.java new file mode 100644 index 000000000..7caef7944 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/BroadcastSchedulerListener.java @@ -0,0 +1,126 @@ +package com.fr.third.org.quartz.listeners; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.SchedulerListener; +import com.fr.third.org.quartz.Trigger; + +/** + * Holds a List of references to SchedulerListener instances and broadcasts all + * events to them (in order). + * + *

This may be more convenient than registering all of the listeners + * directly with the Scheduler, and provides the flexibility of easily changing + * which listeners get notified.

+ * + * @see #addListener(com.fr.third.org.quartz.SchedulerListener) + * @see #removeListener(com.fr.third.org.quartz.SchedulerListener) + * + * @author James House (jhouse AT revolition DOT net) + */ +public class BroadcastSchedulerListener implements SchedulerListener { + + private List listeners; + + public BroadcastSchedulerListener() { + listeners = new LinkedList(); + } + + /** + * Construct an instance with the given List of listeners. + * + * @param listeners the initial List of SchedulerListeners to broadcast to. + */ + public BroadcastSchedulerListener(List listeners) { + this(); + this.listeners.addAll(listeners); + } + + + public void addListener(SchedulerListener listener) { + listeners.add(listener); + } + + public boolean removeListener(SchedulerListener listener) { + return listeners.remove(listener); + } + + public List getListeners() { + return java.util.Collections.unmodifiableList(listeners); + } + + public void jobScheduled(Trigger trigger) { + Iterator itr = listeners.iterator(); + while(itr.hasNext()) { + SchedulerListener l = (SchedulerListener) itr.next(); + l.jobScheduled(trigger); + } + } + + public void jobUnscheduled(String triggerName, String triggerGroup) { + Iterator itr = listeners.iterator(); + while(itr.hasNext()) { + SchedulerListener l = (SchedulerListener) itr.next(); + l.jobUnscheduled(triggerName, triggerGroup); + } + } + + public void triggerFinalized(Trigger trigger) { + Iterator itr = listeners.iterator(); + while(itr.hasNext()) { + SchedulerListener l = (SchedulerListener) itr.next(); + l.triggerFinalized(trigger); + } + } + + public void triggersPaused(String triggerName, String triggerGroup) { + Iterator itr = listeners.iterator(); + while(itr.hasNext()) { + SchedulerListener l = (SchedulerListener) itr.next(); + l.triggersPaused(triggerName, triggerGroup); + } + } + + public void triggersResumed(String triggerName, String triggerGroup) { + Iterator itr = listeners.iterator(); + while(itr.hasNext()) { + SchedulerListener l = (SchedulerListener) itr.next(); + l.triggersResumed(triggerName, triggerGroup); + } + } + + public void jobsPaused(String jobName, String jobGroup) { + Iterator itr = listeners.iterator(); + while(itr.hasNext()) { + SchedulerListener l = (SchedulerListener) itr.next(); + l.jobsPaused(jobName, jobGroup); + } + } + + public void jobsResumed(String jobName, String jobGroup) { + Iterator itr = listeners.iterator(); + while(itr.hasNext()) { + SchedulerListener l = (SchedulerListener) itr.next(); + l.jobsResumed(jobName, jobGroup); + } + } + + public void schedulerError(String msg, SchedulerException cause) { + Iterator itr = listeners.iterator(); + while(itr.hasNext()) { + SchedulerListener l = (SchedulerListener) itr.next(); + l.schedulerError(msg, cause); + } + } + + public void schedulerShutdown() { + Iterator itr = listeners.iterator(); + while(itr.hasNext()) { + SchedulerListener l = (SchedulerListener) itr.next(); + l.schedulerShutdown(); + } + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/FilterAndBroadcastJobListener.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/FilterAndBroadcastJobListener.java new file mode 100644 index 000000000..1bb396883 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/FilterAndBroadcastJobListener.java @@ -0,0 +1,214 @@ +/* + * Copyright 2004-2006 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.fr.third.org.quartz.listeners; + +import java.util.LinkedList; +import java.util.List; +import java.util.Iterator; + +import com.fr.third.org.quartz.JobListener; +import com.fr.third.org.quartz.JobExecutionContext; +import com.fr.third.org.quartz.JobExecutionException; +import com.fr.third.org.quartz.JobDetail; + +/** + * Holds a List of references to JobListener instances and broadcasts all + * events to them (in order) - if the event is not excluded via filtering + * (read on). + * + *

The broadcasting behavior of this listener to delegate listeners may be + * more convenient than registering all of the listeners directly with the + * Trigger, and provides the flexibility of easily changing which listeners + * get notified.

+ * + *

You may also register a number of Regular Expression patterns to match + * the events against. If one or more patterns are registered, the broadcast + * will only take place if the event applies to a job who's name/group + * matches one or more of the patterns.

+ * + * @see #addListener(com.fr.third.org.quartz.JobListener) + * @see #removeListener(com.fr.third.org.quartz.JobListener) + * @see #removeListener(String) + * @see #addJobNamePattern(String) + * @see #addJobGroupPattern(String) + * + * @author James House (jhouse AT revolition DOT net) + */ +public class FilterAndBroadcastJobListener implements JobListener { + + private String name; + private List listeners; + private List namePatterns = new LinkedList(); + private List groupPatterns = new LinkedList(); + + /** + * Construct an instance with the given name. + * + * (Remember to add some delegate listeners!) + * + * @param name the name of this instance + */ + public FilterAndBroadcastJobListener(String name) { + if(name == null) { + throw new IllegalArgumentException("Listener name cannot be null!"); + } + this.name = name; + listeners = new LinkedList(); + } + + /** + * Construct an instance with the given name, and List of listeners. + * + * @param name the name of this instance + * @param listeners the initial List of JobListeners to broadcast to. + */ + public FilterAndBroadcastJobListener(String name, List listeners) { + this(name); + this.listeners.addAll(listeners); + } + + public String getName() { + return name; + } + + public void addListener(JobListener listener) { + listeners.add(listener); + } + + public boolean removeListener(JobListener listener) { + return listeners.remove(listener); + } + + public boolean removeListener(String listenerName) { + Iterator itr = listeners.iterator(); + while(itr.hasNext()) { + JobListener jl = (JobListener) itr.next(); + if(jl.getName().equals(listenerName)) { + itr.remove(); + return true; + } + } + return false; + } + + public List getListeners() { + return java.util.Collections.unmodifiableList(listeners); + } + + /** + * If one or more name patterns are specified, only events relating to + * jobs who's name matches the given regular expression pattern + * will be dispatched to the delegate listeners. + * + * @param regularExpression + */ + public void addJobNamePattern(String regularExpression) { + if(regularExpression == null) { + throw new IllegalArgumentException("Expression cannot be null!"); + } + + namePatterns.add(regularExpression); + } + + public List getJobNamePatterns() { + return namePatterns; + } + + /** + * If one or more group patterns are specified, only events relating to + * jobs who's group matches the given regular expression pattern + * will be dispatched to the delegate listeners. + * + * @param regularExpression + */ + public void addJobGroupPattern(String regularExpression) { + if(regularExpression == null) { + throw new IllegalArgumentException("Expression cannot be null!"); + } + + groupPatterns.add(regularExpression); + } + + public List getJobGroupPatterns() { + return namePatterns; + } + + protected boolean shouldDispatch(JobExecutionContext context) { + JobDetail job = context.getJobDetail(); + + if(namePatterns.size() == 0 && groupPatterns.size() == 0) { + return true; + } + + Iterator itr = groupPatterns.iterator(); + while(itr.hasNext()) { + String pat = (String) itr.next(); + if(job.getGroup().matches(pat)) { + return true; + } + } + + itr = namePatterns.iterator(); + while(itr.hasNext()) { + String pat = (String) itr.next(); + if(job.getName().matches(pat)) { + return true; + } + } + + return false; + } + + public void jobToBeExecuted(JobExecutionContext context) { + + if(!shouldDispatch(context)) { + return; + } + + Iterator itr = listeners.iterator(); + while(itr.hasNext()) { + JobListener jl = (JobListener) itr.next(); + jl.jobToBeExecuted(context); + } + } + + public void jobExecutionVetoed(JobExecutionContext context) { + + if(!shouldDispatch(context)) { + return; + } + + Iterator itr = listeners.iterator(); + while(itr.hasNext()) { + JobListener jl = (JobListener) itr.next(); + jl.jobExecutionVetoed(context); + } + } + + public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { + + if(!shouldDispatch(context)) { + return; + } + + Iterator itr = listeners.iterator(); + while(itr.hasNext()) { + JobListener jl = (JobListener) itr.next(); + jl.jobWasExecuted(context, jobException); + } + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/FilterAndBroadcastTriggerListener.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/FilterAndBroadcastTriggerListener.java new file mode 100644 index 000000000..c07cde032 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/FilterAndBroadcastTriggerListener.java @@ -0,0 +1,228 @@ +/* + * Copyright 2004-2006 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.fr.third.org.quartz.listeners; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import com.fr.third.org.quartz.JobExecutionContext; +import com.fr.third.org.quartz.Trigger; +import com.fr.third.org.quartz.TriggerListener; + +/** + * Holds a List of references to TriggerListener instances and broadcasts all + * events to them (in order) - if the event is not excluded via filtering + * (read on). + * + *

The broadcasting behavior of this listener to delegate listeners may be + * more convenient than registering all of the listeners directly with the + * Trigger, and provides the flexibility of easily changing which listeners + * get notified.

+ * + *

You may also register a number of Regular Expression patterns to match + * the events against. If one or more patterns are registered, the broadcast + * will only take place if the event applies to a trigger who's name/group + * matches one or more of the patterns.

+ * + * @see #addListener(com.fr.third.org.quartz.TriggerListener) + * @see #removeListener(com.fr.third.org.quartz.TriggerListener) + * @see #removeListener(String) + * @see #addTriggerNamePattern(String) + * @see #addTriggerGroupPattern(String) + * + * @author James House (jhouse AT revolition DOT net) + */ +public class FilterAndBroadcastTriggerListener implements TriggerListener { + + private String name; + private List listeners; + private List namePatterns = new LinkedList(); + private List groupPatterns = new LinkedList(); + + /** + * Construct an instance with the given name. + * + * (Remember to add some delegate listeners!) + * + * @param name the name of this instance + */ + public FilterAndBroadcastTriggerListener(String name) { + if(name == null) { + throw new IllegalArgumentException("Listener name cannot be null!"); + } + this.name = name; + listeners = new LinkedList(); + } + + /** + * Construct an instance with the given name, and List of listeners. + * + * @param name the name of this instance + * @param listeners the initial List of TriggerListeners to broadcast to. + */ + public FilterAndBroadcastTriggerListener(String name, List listeners) { + this(name); + this.listeners.addAll(listeners); + } + + public String getName() { + return name; + } + + public void addListener(TriggerListener listener) { + listeners.add(listener); + } + + public boolean removeListener(TriggerListener listener) { + return listeners.remove(listener); + } + + public boolean removeListener(String listenerName) { + Iterator itr = listeners.iterator(); + while(itr.hasNext()) { + TriggerListener l = (TriggerListener) itr.next(); + if(l.getName().equals(listenerName)) { + itr.remove(); + return true; + } + } + return false; + } + + public List getListeners() { + return java.util.Collections.unmodifiableList(listeners); + } + + /** + * If one or more name patterns are specified, only events relating to + * triggers who's name matches the given regular expression pattern + * will be dispatched to the delegate listeners. + * + * @param regularExpression + */ + public void addTriggerNamePattern(String regularExpression) { + if(regularExpression == null) { + throw new IllegalArgumentException("Expression cannot be null!"); + } + + namePatterns.add(regularExpression); + } + + public List getTriggerNamePatterns() { + return namePatterns; + } + + /** + * If one or more group patterns are specified, only events relating to + * triggers who's group matches the given regular expression pattern + * will be dispatched to the delegate listeners. + * + * @param regularExpression + */ + public void addTriggerGroupPattern(String regularExpression) { + if(regularExpression == null) { + throw new IllegalArgumentException("Expression cannot be null!"); + } + + groupPatterns.add(regularExpression); + } + + public List getTriggerGroupPatterns() { + return namePatterns; + } + + protected boolean shouldDispatch(Trigger trigger) { + + if(namePatterns.size() == 0 && groupPatterns.size() == 0) { + return true; + } + + Iterator itr = groupPatterns.iterator(); + while(itr.hasNext()) { + String pat = (String) itr.next(); + if(trigger.getGroup().matches(pat)) { + return true; + } + } + + itr = namePatterns.iterator(); + while(itr.hasNext()) { + String pat = (String) itr.next(); + if(trigger.getName().matches(pat)) { + return true; + } + } + + return false; + } + + public void triggerFired(Trigger trigger, JobExecutionContext context) { + + if(!shouldDispatch(trigger)) { + return; + } + + Iterator itr = listeners.iterator(); + while(itr.hasNext()) { + TriggerListener l = (TriggerListener) itr.next(); + l.triggerFired(trigger, context); + } + } + + public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) { + + if(!shouldDispatch(trigger)) { + return false; + } + + Iterator itr = listeners.iterator(); + while(itr.hasNext()) { + TriggerListener l = (TriggerListener) itr.next(); + if(l.vetoJobExecution(trigger, context)) { + return true; + } + } + return false; + } + + public void triggerMisfired(Trigger trigger) { + + if(!shouldDispatch(trigger)) { + return; + } + + Iterator itr = listeners.iterator(); + while(itr.hasNext()) { + TriggerListener l = (TriggerListener) itr.next(); + l.triggerMisfired(trigger); + } + } + + public void triggerComplete(Trigger trigger, JobExecutionContext context, int triggerInstructionCode) { + + if(!shouldDispatch(trigger)) { + return; + } + + Iterator itr = listeners.iterator(); + while(itr.hasNext()) { + TriggerListener l = (TriggerListener) itr.next(); + l.triggerComplete(trigger, context, triggerInstructionCode); + } + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/JobChainingJobListener.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/JobChainingJobListener.java new file mode 100644 index 000000000..8eb6c3105 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/JobChainingJobListener.java @@ -0,0 +1,108 @@ +/* + * Copyright 2004-2006 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.fr.third.org.quartz.listeners; + +import java.util.HashMap; +import java.util.Map; + +import com.fr.third.org.quartz.utils.Key; +import com.fr.third.org.quartz.JobExecutionContext; +import com.fr.third.org.quartz.JobExecutionException; +import com.fr.third.org.quartz.SchedulerException; + +/** + * Keeps a collection of mappings of which Job to trigger after the completion + * of a given job. If this listener is notified of a job completing that has a + * mapping, then it will then attempt to trigger the follow-up job. This + * achieves "job chaining", or a "poor man's workflow". + * + *

Generally an instance of this listener would be registered as a global + * job listener, rather than being registered directly to a given job.

+ * + *

If for some reason there is a failure creating the trigger for the + * follow-up job (which would generally only be caused by a rare serious + * failure in the system, or the non-existence of the follow-up job), an error + * messsage is logged, but no other action is taken. If you need more rigorous + * handling of the error, consider scheduling the triggering of the flow-up + * job within your job itself.

+ * + * @author James House (jhouse AT revolition DOT net) + */ +public class JobChainingJobListener extends JobListenerSupport { + + private String name; + private Map chainLinks; + + + /** + * Construct an instance with the given name. + * + * @param name the name of this instance + */ + public JobChainingJobListener(String name) { + if(name == null) { + throw new IllegalArgumentException("Listener name cannot be null!"); + } + this.name = name; + chainLinks = new HashMap(); + } + + public String getName() { + return name; + } + + /** + * Add a chain mapping - when the Job identified by the first key completes + * the job identified by the second key will be triggered. + * + * @param firstJob a Key with the name and group of the first job + * @param secondJob a Key with the name and group of the follow-up job + */ + public void addJobChainLink(Key firstJob, Key secondJob) { + + if(firstJob == null || secondJob == null) { + throw new IllegalArgumentException("Key cannot be null!"); + } + + if(firstJob.getName() == null || secondJob.getName() == null) { + throw new IllegalArgumentException("Key cannot have a null name!"); + } + + chainLinks.put(firstJob, secondJob); + } + + public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { + + Key sj = (Key) chainLinks.get(context.getJobDetail().getKey()); + + if(sj == null) { + return; + } + + getLog().info("Job '" + context.getJobDetail().getFullName() + "' will now chain to Job '" + sj + "'"); + + try { + if(context.getJobDetail().isVolatile() || context.getTrigger().isVolatile()) { + context.getScheduler().triggerJobWithVolatileTrigger(sj.getName(), sj.getGroup()); + } else { + context.getScheduler().triggerJob(sj.getName(), sj.getGroup()); + } + } catch(SchedulerException se) { + getLog().error("Error encountered during chaining to Job '" + sj + "'", se); + } + } +} + diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/JobListenerSupport.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/JobListenerSupport.java new file mode 100644 index 000000000..36cabd55c --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/JobListenerSupport.java @@ -0,0 +1,60 @@ +/* + * Copyright 2004-2006 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.fr.third.org.quartz.listeners; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.JobListener; +import com.fr.third.org.quartz.JobExecutionContext; +import com.fr.third.org.quartz.JobExecutionException; + +/** + * A helpful abstract base class for implementors of + * {@link com.fr.third.org.quartz.JobListener}. + * + *

+ * The methods in this class are empty so you only need to override the + * subset for the {@link com.fr.third.org.quartz.JobListener} events + * you care about. + *

+ * + *

+ * You are required to implement {@link com.fr.third.org.quartz.JobListener#getName()} + * to return the unique name of your JobListener. + *

+ * + * @see com.fr.third.org.quartz.JobListener + */ +public abstract class JobListenerSupport implements JobListener { + private final Log log = LogFactory.getLog(getClass()); + + /** + * Get the {@link org.apache.commons.logging.Log} for this + * class's category. This should be used by subclasses for logging. + */ + protected Log getLog() { + return log; + } + + public void jobToBeExecuted(JobExecutionContext context) { + } + + public void jobExecutionVetoed(JobExecutionContext context) { + } + + public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/SchedulerListenerSupport.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/SchedulerListenerSupport.java new file mode 100644 index 000000000..f18565736 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/SchedulerListenerSupport.java @@ -0,0 +1,73 @@ +/* + * Copyright 2004-2006 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.fr.third.org.quartz.listeners; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.SchedulerListener; +import com.fr.third.org.quartz.Trigger; +import com.fr.third.org.quartz.SchedulerException; + +/** + * A helpful abstract base class for implementors of + * {@link com.fr.third.org.quartz.SchedulerListener}. + * + *

+ * The methods in this class are empty so you only need to override the + * subset for the {@link com.fr.third.org.quartz.SchedulerListener} events + * you care about. + *

+ * + * @see com.fr.third.org.quartz.SchedulerListener + */ +public abstract class SchedulerListenerSupport implements SchedulerListener { + private final Log log = LogFactory.getLog(getClass()); + + /** + * Get the {@link org.apache.commons.logging.Log} for this + * class's category. This should be used by subclasses for logging. + */ + protected Log getLog() { + return log; + } + + public void jobScheduled(Trigger trigger) { + } + + public void jobUnscheduled(String triggerName, String triggerGroup) { + } + + public void triggerFinalized(Trigger trigger) { + } + + public void triggersPaused(String triggerName, String triggerGroup) { + } + + public void triggersResumed(String triggerName, String triggerGroup) { + } + + public void jobsPaused(String jobName, String jobGroup) { + } + + public void jobsResumed(String jobName, String jobGroup) { + } + + public void schedulerError(String msg, SchedulerException cause) { + } + + public void schedulerShutdown() { + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/TriggerListenerSupport.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/TriggerListenerSupport.java new file mode 100644 index 000000000..b8c895557 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/listeners/TriggerListenerSupport.java @@ -0,0 +1,67 @@ +/* + * Copyright 2004-2006 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.fr.third.org.quartz.listeners; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.TriggerListener; +import com.fr.third.org.quartz.Trigger; +import com.fr.third.org.quartz.JobExecutionContext; + +/** + * A helpful abstract base class for implementors of + * {@link com.fr.third.org.quartz.TriggerListener}. + * + *

+ * The methods in this class are empty so you only need to override the + * subset for the {@link com.fr.third.org.quartz.TriggerListener} events + * you care about. + *

+ * + *

+ * You are required to implement {@link com.fr.third.org.quartz.TriggerListener#getName()} + * to return the unique name of your TriggerListener. + *

+ * + * @see com.fr.third.org.quartz.TriggerListener + */ +public abstract class TriggerListenerSupport implements TriggerListener { + private final Log log = LogFactory.getLog(getClass()); + + /** + * Get the {@link org.apache.commons.logging.Log} for this + * class's category. This should be used by subclasses for logging. + */ + protected Log getLog() { + return log; + } + + public void triggerFired(Trigger trigger, JobExecutionContext context) { + } + + public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) { + return false; + } + + public void triggerMisfired(Trigger trigger) { + } + + public void triggerComplete( + Trigger trigger, + JobExecutionContext context, + int triggerInstructionCode) { + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/package.html b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/package.html new file mode 100644 index 000000000..73e9635f9 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/package.html @@ -0,0 +1,15 @@ + + +Package com.fr.third.org.quartz + + +

The main package of Quartz, containing the client-side interfaces.

+ +
+
+
+See the Quartz project + at Open Symphony for more information. + + + \ No newline at end of file diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/plugins/SchedulerPluginWithUserTransactionSupport.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/plugins/SchedulerPluginWithUserTransactionSupport.java new file mode 100644 index 000000000..4cf1c35af --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/plugins/SchedulerPluginWithUserTransactionSupport.java @@ -0,0 +1,205 @@ +/* + * Copyright 2004-2006 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.fr.third.org.quartz.plugins; + +import javax.transaction.Status; +import javax.transaction.UserTransaction; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.ee.jta.UserTransactionHelper; +import com.fr.third.org.quartz.spi.SchedulerPlugin; + +/** + * Base class for plugins that wish to support having their start and + * shutdown methods run within a UserTransaction. This is + * often necessary if using the JobStoreCMT and the plugin interacts with + * jobs/triggers. + * + *

+ * The subclass should implement start(UserTransaction) and + * shutdown(UserTransaction). The UserTransaction will be + * non-null if property wrapInUserTransaction is set to true. + *

+ *

+ * For convenience, this base class also provides an initialize() implementation + * which saves the scheduler and plugin name, as well as getLog() for logging. + *

+ */ +public abstract class SchedulerPluginWithUserTransactionSupport implements + SchedulerPlugin { + + private String name; + private Scheduler scheduler; + private final Log log = LogFactory.getLog(getClass()); + + // Properties + + private boolean wrapInUserTransaction = false; + + /** + *

+ * Called when the associated Scheduler is started, in order + * to let the plug-in know it can now make calls into the scheduler if it + * needs to. + *

+ * + *

+ * If UserTransaction is not null, the plugin can call setRollbackOnly() + * on it to signal that the wrapped transaction should rollback. + *

+ * + * @param userTransaction The UserTranaction object used to provide a + * transaction around the start() operation. It will be null if + * wrapInUserTransaction is false or if the transaction failed + * to be started. + */ + protected void start(UserTransaction userTransaction) { + } + + /** + *

+ * Called in order to inform the SchedulerPlugin that it + * should free up all of it's resources because the scheduler is shutting + * down. + *

+ * + *

+ * If UserTransaction is not null, the plugin can call setRollbackOnly() + * on it to signal that the wrapped transaction should rollback. + *

+ * + * @param userTransaction The UserTranaction object used to provide a + * transaction around the shutdown() operation. It will be null if + * wrapInUserTransaction is false or if the transaction failed + * to be started. + */ + protected void shutdown(UserTransaction userTransaction) { + } + + /** + * Get the commons Log for this class. + */ + protected Log getLog() { + return log; + } + + /** + * Get the name of this plugin. Set as part of initialize(). + */ + protected String getName() { + return name; + } + + /** + * Get this plugin's Scheduler. Set as part of initialize(). + */ + protected Scheduler getScheduler() { + return scheduler; + } + + public void initialize(String name, Scheduler scheduler) throws SchedulerException { + this.name = name; + this.scheduler = scheduler; + } + + /** + * Wrap the start() and shutdown() methods in a UserTransaction. This is necessary + * for some plugins if using the JobStoreCMT. + */ + public boolean getWrapInUserTransaction() { + return wrapInUserTransaction; + } + + /** + * Wrap the start() and shutdown() methods in a UserTransaction. This is necessary + * for some plugins if using the JobStoreCMT. + */ + public void setWrapInUserTransaction(boolean wrapInUserTransaction) { + this.wrapInUserTransaction = wrapInUserTransaction; + } + + /** + * Based on the value of wrapInUserTransaction, wraps the + * call to start(UserTransaction) in a UserTransaction. + */ + public void start() { + UserTransaction userTransaction = startUserTransaction(); + try { + start(userTransaction); + } finally { + resolveUserTransaction(userTransaction); + } + } + + /** + * Based on the value of wrapInUserTransaction, wraps the + * call to shutdown(UserTransaction) in a UserTransaction. + */ + public void shutdown() { + UserTransaction userTransaction = startUserTransaction(); + try { + shutdown(userTransaction); + } finally { + resolveUserTransaction(userTransaction); + } + } + + /** + * If wrapInUserTransaction is true, starts a new UserTransaction + * and returns it. Otherwise, or if establishing the transaction fail, it + * will return null. + */ + private UserTransaction startUserTransaction() { + if (wrapInUserTransaction == false) { + return null; + } + + UserTransaction userTransaction = null; + try { + userTransaction = UserTransactionHelper.lookupUserTransaction(); + userTransaction.begin(); + } catch (Throwable t) { + UserTransactionHelper.returnUserTransaction(userTransaction); + userTransaction = null; + getLog().error("Failed to start UserTransaction for plugin: " + getName(), t); + } + + return userTransaction; + } + + /** + * If the given UserTransaction is not null, it is committed/rolledback, + * and then returned to the UserTransactionHelper. + */ + private void resolveUserTransaction(UserTransaction userTransaction) { + if (userTransaction != null) { + try { + if (userTransaction.getStatus() == Status.STATUS_MARKED_ROLLBACK) { + userTransaction.rollback(); + } else { + userTransaction.commit(); + } + } catch (Throwable t) { + getLog().error("Failed to resolve UserTransaction for plugin: " + getName(), t); + } finally { + UserTransactionHelper.returnUserTransaction(userTransaction); + } + } + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/plugins/history/LoggingJobHistoryPlugin.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/plugins/history/LoggingJobHistoryPlugin.java new file mode 100644 index 000000000..7ec7e7fc1 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/plugins/history/LoggingJobHistoryPlugin.java @@ -0,0 +1,542 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.plugins.history; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.JobExecutionContext; +import com.fr.third.org.quartz.JobExecutionException; +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.Trigger; +import com.fr.third.org.quartz.JobListener; +import com.fr.third.org.quartz.spi.SchedulerPlugin; + +import java.text.MessageFormat; + +/** + * Logs a history of all job executions (and execution vetos) via the + * Jakarta Commons-Logging framework. + * + *

+ * The logged message is customizable by setting one of the following message + * properties to a String that conforms to the syntax of java.util.MessageFormat. + *

+ * + *

+ * JobToBeFiredMessage - available message data are: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ElementData TypeDescription
0StringThe Job's Name.
1StringThe Job's Group.
2DateThe current time.
3StringThe Trigger's name.
4StringThe Triggers's group.
5DateThe scheduled fire time.
6DateThe next scheduled fire time.
7IntegerThe re-fire count from the JobExecutionContext.
+ * + * The default message text is "Job {1}.{0} fired (by trigger {4}.{3}) at: + * {2, date, HH:mm:ss MM/dd/yyyy}" + *

+ * + * + *

+ * JobSuccessMessage - available message data are: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ElementData TypeDescription
0StringThe Job's Name.
1StringThe Job's Group.
2DateThe current time.
3StringThe Trigger's name.
4StringThe Triggers's group.
5DateThe scheduled fire time.
6DateThe next scheduled fire time.
7IntegerThe re-fire count from the JobExecutionContext.
8ObjectThe string value (toString() having been called) of the result (if any) + * that the Job set on the JobExecutionContext, with on it. "NULL" if no + * result was set.
+ * + * The default message text is "Job {1}.{0} execution complete at {2, date, + * HH:mm:ss MM/dd/yyyy} and reports: {8}" + *

+ * + *

+ * JobFailedMessage - available message data are: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ElementData TypeDescription
0StringThe Job's Name.
1StringThe Job's Group.
2DateThe current time.
3StringThe Trigger's name.
4StringThe Triggers's group.
5DateThe scheduled fire time.
6DateThe next scheduled fire time.
7IntegerThe re-fire count from the JobExecutionContext.
8StringThe message from the thrown JobExecution Exception. + *
+ * + * The default message text is "Job {1}.{0} execution failed at {2, date, + * HH:mm:ss MM/dd/yyyy} and reports: {8}" + *

+ * + * + *

+ * JobWasVetoedMessage - available message data are: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ElementData TypeDescription
0StringThe Job's Name.
1StringThe Job's Group.
2DateThe current time.
3StringThe Trigger's name.
4StringThe Triggers's group.
5DateThe scheduled fire time.
6DateThe next scheduled fire time.
7IntegerThe re-fire count from the JobExecutionContext.
+ * + * The default message text is "Job {1}.{0} was vetoed. It was to be fired + * (by trigger {4}.{3}) at: {2, date, HH:mm:ss MM/dd/yyyy}" + *

+ * + * + * @author James House + */ +public class LoggingJobHistoryPlugin implements SchedulerPlugin, JobListener { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private String name; + + private String jobToBeFiredMessage = "Job {1}.{0} fired (by trigger {4}.{3}) at: {2, date, HH:mm:ss MM/dd/yyyy}"; + + private String jobSuccessMessage = "Job {1}.{0} execution complete at {2, date, HH:mm:ss MM/dd/yyyy} and reports: {8}"; + + private String jobFailedMessage = "Job {1}.{0} execution failed at {2, date, HH:mm:ss MM/dd/yyyy} and reports: {8}"; + + private String jobWasVetoedMessage = "Job {1}.{0} was vetoed. It was to be fired (by trigger {4}.{3}) at: {2, date, HH:mm:ss MM/dd/yyyy}"; + + private final Log log = LogFactory.getLog(getClass()); + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public LoggingJobHistoryPlugin() { + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + protected Log getLog() { + return log; + } + + /** + * Get the message that is logged when a Job successfully completes its + * execution. + */ + public String getJobSuccessMessage() { + return jobSuccessMessage; + } + + /** + * Get the message that is logged when a Job fails its + * execution. + */ + public String getJobFailedMessage() { + return jobFailedMessage; + } + + /** + * Get the message that is logged when a Job is about to execute. + */ + public String getJobToBeFiredMessage() { + return jobToBeFiredMessage; + } + + /** + * Set the message that is logged when a Job successfully completes its + * execution. + * + * @param jobSuccessMessage + * String in java.text.MessageFormat syntax. + */ + public void setJobSuccessMessage(String jobSuccessMessage) { + this.jobSuccessMessage = jobSuccessMessage; + } + + /** + * Set the message that is logged when a Job fails its + * execution. + * + * @param jobFailedMessage + * String in java.text.MessageFormat syntax. + */ + public void setJobFailedMessage(String jobFailedMessage) { + this.jobFailedMessage = jobFailedMessage; + } + + /** + * Set the message that is logged when a Job is about to execute. + * + * @param jobToBeFiredMessage + * String in java.text.MessageFormat syntax. + */ + public void setJobToBeFiredMessage(String jobToBeFiredMessage) { + this.jobToBeFiredMessage = jobToBeFiredMessage; + } + + /** + * Get the message that is logged when a Job execution is vetoed by a + * trigger listener. + */ + public String getJobWasVetoedMessage() { + return jobWasVetoedMessage; + } + + /** + * Set the message that is logged when a Job execution is vetoed by a + * trigger listener. + * + * @param jobWasVetoedMessage + * String in java.text.MessageFormat syntax. + */ + public void setJobWasVetoedMessage(String jobWasVetoedMessage) { + this.jobWasVetoedMessage = jobWasVetoedMessage; + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * SchedulerPlugin Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Called during creation of the Scheduler in order to give + * the SchedulerPlugin a chance to initialize. + *

+ * + * @throws SchedulerConfigException + * if there is an error initializing. + */ + public void initialize(String name, Scheduler scheduler) + throws SchedulerException { + this.name = name; + scheduler.addGlobalJobListener(this); + } + + public void start() { + // do nothing... + } + + /** + *

+ * Called in order to inform the SchedulerPlugin that it + * should free up all of it's resources because the scheduler is shutting + * down. + *

+ */ + public void shutdown() { + // nothing to do... + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * JobListener Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /* + * Object[] arguments = { new Integer(7), new + * Date(System.currentTimeMillis()), "a disturbance in the Force" }; + * + * String result = MessageFormat.format( "At {1,time} on {1,date}, there + * was {2} on planet {0,number,integer}.", arguments); + */ + + public String getName() { + return name; + } + + /** + * @see com.fr.third.org.quartz.JobListener#jobToBeExecuted(JobExecutionContext) + */ + public void jobToBeExecuted(JobExecutionContext context) { + if (!getLog().isInfoEnabled()) { + return; + } + + Trigger trigger = context.getTrigger(); + + Object[] args = { + context.getJobDetail().getName(), + context.getJobDetail().getGroup(), new java.util.Date(), + trigger.getName(), trigger.getGroup(), + trigger.getPreviousFireTime(), trigger.getNextFireTime(), + new Integer(context.getRefireCount()) + }; + + getLog().info(MessageFormat.format(getJobToBeFiredMessage(), args)); + } + + /** + * @see com.fr.third.org.quartz.JobListener#jobWasExecuted(JobExecutionContext, JobExecutionException) + */ + public void jobWasExecuted(JobExecutionContext context, + JobExecutionException jobException) { + + Trigger trigger = context.getTrigger(); + + Object[] args = null; + + if (jobException != null) { + if (!getLog().isWarnEnabled()) { + return; + } + + String errMsg = jobException.getMessage(); + args = + new Object[] { + context.getJobDetail().getName(), + context.getJobDetail().getGroup(), new java.util.Date(), + trigger.getName(), trigger.getGroup(), + trigger.getPreviousFireTime(), trigger.getNextFireTime(), + new Integer(context.getRefireCount()), errMsg + }; + + getLog().warn(MessageFormat.format(getJobFailedMessage(), args), jobException); + } else { + if (!getLog().isInfoEnabled()) { + return; + } + + String result = String.valueOf(context.getResult()); + args = + new Object[] { + context.getJobDetail().getName(), + context.getJobDetail().getGroup(), new java.util.Date(), + trigger.getName(), trigger.getGroup(), + trigger.getPreviousFireTime(), trigger.getNextFireTime(), + new Integer(context.getRefireCount()), result + }; + + getLog().info(MessageFormat.format(getJobSuccessMessage(), args)); + } + } + + /** + * @see com.fr.third.org.quartz.JobListener#jobExecutionVetoed(com.fr.third.org.quartz.JobExecutionContext) + */ + public void jobExecutionVetoed(JobExecutionContext context) { + + if (!getLog().isInfoEnabled()) { + return; + } + + Trigger trigger = context.getTrigger(); + + Object[] args = { + context.getJobDetail().getName(), + context.getJobDetail().getGroup(), new java.util.Date(), + trigger.getName(), trigger.getGroup(), + trigger.getPreviousFireTime(), trigger.getNextFireTime(), + new Integer(context.getRefireCount()) + }; + + getLog().info(MessageFormat.format(getJobWasVetoedMessage(), args)); + } + +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/plugins/history/LoggingTriggerHistoryPlugin.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/plugins/history/LoggingTriggerHistoryPlugin.java new file mode 100644 index 000000000..cafc60a9c --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/plugins/history/LoggingTriggerHistoryPlugin.java @@ -0,0 +1,440 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.plugins.history; + +import java.text.MessageFormat; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.JobExecutionContext; +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.Trigger; +import com.fr.third.org.quartz.TriggerListener; +import com.fr.third.org.quartz.spi.SchedulerPlugin; + +/** + * Logs a history of all trigger firings via the Jakarta Commons-Logging + * framework. + * + *

+ * The logged message is customizable by setting one of the following message + * properties to a String that conforms to the syntax of java.util.MessageFormat. + *

+ * + *

+ * TriggerFiredMessage - available message data are: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ElementData TypeDescription
0StringThe Trigger's Name.
1StringThe Trigger's Group.
2DateThe scheduled fire time.
3DateThe next scheduled fire time.
4DateThe actual fire time.
5StringThe Job's name.
6StringThe Job's group.
7IntegerThe re-fire count from the JobExecutionContext.
+ * + * The default message text is "Trigger {1}.{0} fired job {6}.{5} at: {4, + * date, HH:mm:ss MM/dd/yyyy}" + *

+ * + *

+ * TriggerMisfiredMessage - available message data are: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ElementData TypeDescription
0StringThe Trigger's Name.
1StringThe Trigger's Group.
2DateThe scheduled fire time.
3DateThe next scheduled fire time.
4DateThe actual fire time. (the time the misfire was detected/handled)
5StringThe Job's name.
6StringThe Job's group.
+ * + * The default message text is "Trigger {1}.{0} misfired job {6}.{5} at: + * {4, date, HH:mm:ss MM/dd/yyyy}. Should have fired at: {3, date, HH:mm:ss + * MM/dd/yyyy}" + *

+ * + *

+ * TriggerCompleteMessage - available message data are: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ElementData TypeDescription
0StringThe Trigger's Name.
1StringThe Trigger's Group.
2DateThe scheduled fire time.
3DateThe next scheduled fire time.
4DateThe job completion time.
5StringThe Job's name.
6StringThe Job's group.
7IntegerThe re-fire count from the JobExecutionContext.
8IntegerThe trigger's resulting instruction code.
9StringA human-readable translation of the trigger's resulting instruction + * code.
+ * + * The default message text is "Trigger {1}.{0} completed firing job + * {6}.{5} at {4, date, HH:mm:ss MM/dd/yyyy} with resulting trigger instruction + * code: {9}" + *

+ * + * @author James House + */ +public class LoggingTriggerHistoryPlugin implements SchedulerPlugin, + TriggerListener { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private String name; + + private String triggerFiredMessage = "Trigger {1}.{0} fired job {6}.{5} at: {4, date, HH:mm:ss MM/dd/yyyy}"; + + private String triggerMisfiredMessage = "Trigger {1}.{0} misfired job {6}.{5} at: {4, date, HH:mm:ss MM/dd/yyyy}. Should have fired at: {3, date, HH:mm:ss MM/dd/yyyy}"; + + private String triggerCompleteMessage = "Trigger {1}.{0} completed firing job {6}.{5} at {4, date, HH:mm:ss MM/dd/yyyy} with resulting trigger instruction code: {9}"; + + private final Log log = LogFactory.getLog(getClass()); + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public LoggingTriggerHistoryPlugin() { + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + protected Log getLog() { + return log; + } + + /** + * Get the message that is printed upon the completion of a trigger's + * firing. + * + * @return String + */ + public String getTriggerCompleteMessage() { + return triggerCompleteMessage; + } + + /** + * Get the message that is printed upon a trigger's firing. + * + * @return String + */ + public String getTriggerFiredMessage() { + return triggerFiredMessage; + } + + /** + * Get the message that is printed upon a trigger's mis-firing. + * + * @return String + */ + public String getTriggerMisfiredMessage() { + return triggerMisfiredMessage; + } + + /** + * Set the message that is printed upon the completion of a trigger's + * firing. + * + * @param triggerCompleteMessage + * String in java.text.MessageFormat syntax. + */ + public void setTriggerCompleteMessage(String triggerCompleteMessage) { + this.triggerCompleteMessage = triggerCompleteMessage; + } + + /** + * Set the message that is printed upon a trigger's firing. + * + * @param triggerFiredMessage + * String in java.text.MessageFormat syntax. + */ + public void setTriggerFiredMessage(String triggerFiredMessage) { + this.triggerFiredMessage = triggerFiredMessage; + } + + /** + * Set the message that is printed upon a trigger's firing. + * + * @param triggerMisfiredMessage + * String in java.text.MessageFormat syntax. + */ + public void setTriggerMisfiredMessage(String triggerMisfiredMessage) { + this.triggerMisfiredMessage = triggerMisfiredMessage; + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * SchedulerPlugin Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Called during creation of the Scheduler in order to give + * the SchedulerPlugin a chance to initialize. + *

+ * + * @throws SchedulerConfigException + * if there is an error initializing. + */ + public void initialize(String name, Scheduler scheduler) + throws SchedulerException { + this.name = name; + + scheduler.addGlobalTriggerListener(this); + } + + public void start() { + // do nothing... + } + + /** + *

+ * Called in order to inform the SchedulerPlugin that it + * should free up all of it's resources because the scheduler is shutting + * down. + *

+ */ + public void shutdown() { + // nothing to do... + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * TriggerListener Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /* + * Object[] arguments = { new Integer(7), new + * Date(System.currentTimeMillis()), "a disturbance in the Force" }; + * + * String result = MessageFormat.format( "At {1,time} on {1,date}, there + * was {2} on planet {0,number,integer}.", arguments); + */ + + public String getName() { + return name; + } + + public void triggerFired(Trigger trigger, JobExecutionContext context) { + if (!getLog().isInfoEnabled()) { + return; + } + + Object[] args = { + trigger.getName(), trigger.getGroup(), + trigger.getPreviousFireTime(), trigger.getNextFireTime(), + new java.util.Date(), context.getJobDetail().getName(), + context.getJobDetail().getGroup(), + new Integer(context.getRefireCount()) + }; + + getLog().info(MessageFormat.format(getTriggerFiredMessage(), args)); + } + + public void triggerMisfired(Trigger trigger) { + if (!getLog().isInfoEnabled()) { + return; + } + + Object[] args = { + trigger.getName(), trigger.getGroup(), + trigger.getPreviousFireTime(), trigger.getNextFireTime(), + new java.util.Date(), trigger.getJobGroup(), + trigger.getJobGroup() + }; + + getLog().info(MessageFormat.format(getTriggerMisfiredMessage(), args)); + } + + public void triggerComplete(Trigger trigger, JobExecutionContext context, + int triggerInstructionCode) { + if (!getLog().isInfoEnabled()) { + return; + } + + String instrCode = "UNKNOWN"; + if (triggerInstructionCode == Trigger.INSTRUCTION_DELETE_TRIGGER) { + instrCode = "DELETE TRIGGER"; + } else if (triggerInstructionCode == Trigger.INSTRUCTION_NOOP) { + instrCode = "DO NOTHING"; + } else if (triggerInstructionCode == Trigger.INSTRUCTION_RE_EXECUTE_JOB) { + instrCode = "RE-EXECUTE JOB"; + } else if (triggerInstructionCode == Trigger.INSTRUCTION_SET_ALL_JOB_TRIGGERS_COMPLETE) { + instrCode = "SET ALL OF JOB'S TRIGGERS COMPLETE"; + } else if (triggerInstructionCode == Trigger.INSTRUCTION_SET_TRIGGER_COMPLETE) { + instrCode = "SET THIS TRIGGER COMPLETE"; + } + + Object[] args = { + trigger.getName(), trigger.getGroup(), + trigger.getPreviousFireTime(), trigger.getNextFireTime(), + new java.util.Date(), context.getJobDetail().getName(), + context.getJobDetail().getGroup(), + new Integer(context.getRefireCount()), + new Integer(triggerInstructionCode), instrCode + }; + + getLog().info(MessageFormat.format(getTriggerCompleteMessage(), args)); + } + + public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) { + return false; + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/plugins/management/ShutdownHookPlugin.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/plugins/management/ShutdownHookPlugin.java new file mode 100644 index 000000000..f84ac9a19 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/plugins/management/ShutdownHookPlugin.java @@ -0,0 +1,164 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.plugins.management; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.spi.SchedulerPlugin; + +/** + * This plugin catches the event of the JVM terminating (such as upon a CRTL-C) + * and tells the scheuler to shutdown. + * + * @see com.fr.third.org.quartz.Scheduler#shutdown(boolean) + * + * @author James House + */ +public class ShutdownHookPlugin implements SchedulerPlugin { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private String name; + + private Scheduler scheduler; + + private boolean cleanShutdown = true; + + private final Log log = LogFactory.getLog(getClass()); + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public ShutdownHookPlugin() { + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Determine whether or not the plug-in is configured to cause a clean + * shutdown of the scheduler. + * + *

+ * The default value is true. + *

+ * + * @see com.fr.third.org.quartz.Scheduler#shutdown(boolean) + */ + public boolean isCleanShutdown() { + return cleanShutdown; + } + + /** + * Set whether or not the plug-in is configured to cause a clean shutdown + * of the scheduler. + * + *

+ * The default value is true. + *

+ * + * @see com.fr.third.org.quartz.Scheduler#shutdown(boolean) + */ + public void setCleanShutdown(boolean b) { + cleanShutdown = b; + } + + protected Log getLog() { + return log; + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * SchedulerPlugin Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Called during creation of the Scheduler in order to give + * the SchedulerPlugin a chance to initialize. + *

+ * + * @throws SchedulerConfigException + * if there is an error initializing. + */ + public void initialize(String name, final Scheduler scheduler) + throws SchedulerException { + this.name = name; + this.scheduler = scheduler; + + getLog().info("Registering Quartz shutdown hook."); + + Thread t = new Thread("Quartz Shutdown-Hook " + + scheduler.getSchedulerName()) { + public void run() { + getLog().info("Shutting down Quartz..."); + try { + scheduler.shutdown(isCleanShutdown()); + } catch (SchedulerException e) { + getLog().info( + "Error shutting down Quartz: " + e.getMessage(), e); + } + } + }; + + Runtime.getRuntime().addShutdownHook(t); + } + + public void start() { + // do nothing. + } + + /** + *

+ * Called in order to inform the SchedulerPlugin that it + * should free up all of it's resources because the scheduler is shutting + * down. + *

+ */ + public void shutdown() { + // nothing to do in this case (since the scheduler is already shutting + // down) + } + +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/quartz.properties b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/quartz.properties new file mode 100644 index 000000000..b136b40ee --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/quartz.properties @@ -0,0 +1,19 @@ +# Default Properties file for use by StdSchedulerFactory +# to create a Quartz Scheduler Instance, if a different +# properties file is not explicitly specified. +# + +com.fr.third.org.quartz.scheduler.instanceName = DefaultQuartzScheduler +com.fr.third.org.quartz.scheduler.rmi.export = false +com.fr.third.org.quartz.scheduler.rmi.proxy = false +com.fr.third.org.quartz.scheduler.wrapJobExecutionInUserTransaction = false + +com.fr.third.org.quartz.threadPool.class = com.fr.third.org.quartz.simpl.SimpleThreadPool +com.fr.third.org.quartz.threadPool.threadCount = 10 +com.fr.third.org.quartz.threadPool.threadPriority = 5 +com.fr.third.org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true + +com.fr.third.org.quartz.jobStore.misfireThreshold = 60000 + +com.fr.third.org.quartz.jobStore.class = com.fr.third.org.quartz.simpl.RAMJobStore + diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/CascadingClassLoadHelper.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/CascadingClassLoadHelper.java new file mode 100644 index 000000000..ba8bf0914 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/CascadingClassLoadHelper.java @@ -0,0 +1,214 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.simpl; + +import java.util.Iterator; +import java.util.LinkedList; +import java.net.URL; +import java.io.InputStream; + +import com.fr.third.org.quartz.spi.ClassLoadHelper; + +/** + * A ClassLoadHelper uses all of the ClassLoadHelper + * types that are found in this package in its attempts to load a class, when + * one scheme is found to work, it is promoted to the scheme that will be used + * first the next time a class is loaded (in order to improve performance). + * + *

+ * This approach is used because of the wide variance in class loader behavior + * between the various environments in which Quartz runs (e.g. disparate + * application servers, stand-alone, mobile devices, etc.). Because of this + * disparity, Quartz ran into difficulty with a one class-load style fits-all + * design. Thus, this class loader finds the approach that works, then + * 'remembers' it. + *

+ * + * @see com.fr.third.org.quartz.spi.ClassLoadHelper + * @see com.fr.third.org.quartz.simpl.LoadingLoaderClassLoadHelper + * @see com.fr.third.org.quartz.simpl.SimpleClassLoadHelper + * @see com.fr.third.org.quartz.simpl.ThreadContextClassLoadHelper + * @see com.fr.third.org.quartz.simpl.InitThreadContextClassLoadHelper + * + * @author jhouse + * @author pl47ypus + */ +public class CascadingClassLoadHelper implements ClassLoadHelper { + + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private LinkedList loadHelpers; + + private ClassLoadHelper bestCandidate; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Called to give the ClassLoadHelper a chance to initialize itself, + * including the opportunity to "steal" the class loader off of the calling + * thread, which is the thread that is initializing Quartz. + */ + public void initialize() { + loadHelpers = new LinkedList(); + + loadHelpers.add(new LoadingLoaderClassLoadHelper()); + loadHelpers.add(new SimpleClassLoadHelper()); + loadHelpers.add(new ThreadContextClassLoadHelper()); + loadHelpers.add(new InitThreadContextClassLoadHelper()); + + Iterator iter = loadHelpers.iterator(); + while (iter.hasNext()) { + ClassLoadHelper loadHelper = (ClassLoadHelper) iter.next(); + loadHelper.initialize(); + } + } + + /** + * Return the class with the given name. + */ + public Class loadClass(String name) throws ClassNotFoundException { + + if (bestCandidate != null) { + try { + return bestCandidate.loadClass(name); + } catch (Exception e) { + bestCandidate = null; + } + } + + ClassNotFoundException cnfe = null; + Class clazz = null; + ClassLoadHelper loadHelper = null; + + Iterator iter = loadHelpers.iterator(); + while (iter.hasNext()) { + loadHelper = (ClassLoadHelper) iter.next(); + + try { + clazz = loadHelper.loadClass(name); + break; + } catch (ClassNotFoundException e) { + cnfe = e; + } + } + + if (clazz == null) { + throw cnfe; + } + + bestCandidate = loadHelper; + + return clazz; + } + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.net.URL object + */ + public URL getResource(String name) { + + if (bestCandidate != null) { + try { + return bestCandidate.getResource(name); + } catch (Exception e) { + bestCandidate = null; + } + } + + URL result = null; + ClassLoadHelper loadHelper = null; + + Iterator iter = loadHelpers.iterator(); + while (iter.hasNext()) { + loadHelper = (ClassLoadHelper) iter.next(); + + result = loadHelper.getResource(name); + if (result != null) { + break; + } + } + + bestCandidate = loadHelper; + return result; + + } + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.io.InputStream object + */ + public InputStream getResourceAsStream(String name) { + + if (bestCandidate != null) { + try { + return bestCandidate.getResourceAsStream(name); + } catch (Exception e) { + bestCandidate = null; + } + } + + InputStream result = null; + ClassLoadHelper loadHelper = null; + + Iterator iter = loadHelpers.iterator(); + while (iter.hasNext()) { + loadHelper = (ClassLoadHelper) iter.next(); + + result = loadHelper.getResourceAsStream(name); + if (result != null) { + break; + } + } + + bestCandidate = loadHelper; + return result; + + } + + /** + * Enable sharing of the class-loader with 3rd party (e.g. digester). + * + * @return the class-loader user be the helper. + */ + public ClassLoader getClassLoader() { + return (this.bestCandidate == null) ? + Thread.currentThread().getContextClassLoader() : + this.bestCandidate.getClassLoader(); + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/CascadingClassLoadHelper.java.bak b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/CascadingClassLoadHelper.java.bak new file mode 100644 index 000000000..4ed568acf --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/CascadingClassLoadHelper.java.bak @@ -0,0 +1,202 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package org.quartz.simpl; + +import java.util.Iterator; +import java.util.LinkedList; +import java.net.URL; +import java.io.InputStream; + +import org.quartz.spi.ClassLoadHelper; + +/** + * A ClassLoadHelper uses all of the ClassLoadHelper + * types that are found in this package in its attempts to load a class, when + * one scheme is found to work, it is promoted to the scheme that will be used + * first the next time a class is loaded (in order to improve perfomance). + * + *

+ * This approach is used because of the wide variance in class loader behavior + * between the various environments in which Quartz runs (e.g. disparate + * application servers, stand-alone, mobile devices, etc.). Because of this + * disparity, Quartz ran into difficulty with a one class-load style fits-all + * design. Thus, this class loader finds the approach that works, then + * 'remembers' it. + *

+ * + * @see org.quartz.spi.ClassLoadHelper + * @see org.quartz.simpl.LoadingLoaderClassLoadHelper + * @see org.quartz.simpl.SimpleClassLoadHelper + * @see org.quartz.simpl.ThreadContextClassLoadHelper + * @see org.quartz.simpl.InitThreadContextClassLoadHelper + * + * @author jhouse + */ +public class CascadingClassLoadHelper implements ClassLoadHelper { + + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private LinkedList loadHelpers; + + private ClassLoadHelper bestCandidate; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Called to give the ClassLoadHelper a chance to initialize itself, + * including the oportunity to "steal" the class loader off of the calling + * thread, which is the thread that is initializing Quartz. + */ + public void initialize() { + loadHelpers = new LinkedList(); + + loadHelpers.add(new LoadingLoaderClassLoadHelper()); + loadHelpers.add(new SimpleClassLoadHelper()); + loadHelpers.add(new ThreadContextClassLoadHelper()); + loadHelpers.add(new InitThreadContextClassLoadHelper()); + + Iterator iter = loadHelpers.iterator(); + while (iter.hasNext()) { + ClassLoadHelper loadHelper = (ClassLoadHelper) iter.next(); + loadHelper.initialize(); + } + } + + /** + * Return the class with the given name. + */ + public Class loadClass(String name) throws ClassNotFoundException { + + if (bestCandidate != null) { + try { + return bestCandidate.loadClass(name); + } catch (Exception e) { + bestCandidate = null; + } + } + + ClassNotFoundException cnfe = null; + Class clazz = null; + ClassLoadHelper loadHelper = null; + + Iterator iter = loadHelpers.iterator(); + while (iter.hasNext()) { + loadHelper = (ClassLoadHelper) iter.next(); + + try { + clazz = loadHelper.loadClass(name); + break; + } catch (ClassNotFoundException e) { + cnfe = e; + } + } + + if (clazz == null) { + throw cnfe; + } + + bestCandidate = loadHelper; + + return clazz; + } + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.net.URL object + */ + public URL getResource(String name) { + + if (bestCandidate != null) { + try { + return bestCandidate.getResource(name); + } catch (Exception e) { + bestCandidate = null; + } + } + + URL result = null; + ClassLoadHelper loadHelper = null; + + Iterator iter = loadHelpers.iterator(); + while (iter.hasNext()) { + loadHelper = (ClassLoadHelper) iter.next(); + + result = loadHelper.getResource(name); + if (result != null) { + break; + } + } + + bestCandidate = loadHelper; + return result; + + } + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.io.InputStream object + */ + public InputStream getResourceAsStream(String name) { + + if (bestCandidate != null) { + try { + return bestCandidate.getResourceAsStream(name); + } catch (Exception e) { + bestCandidate = null; + } + } + + InputStream result = null; + ClassLoadHelper loadHelper = null; + + Iterator iter = loadHelpers.iterator(); + while (iter.hasNext()) { + loadHelper = (ClassLoadHelper) iter.next(); + + result = loadHelper.getResourceAsStream(name); + if (result != null) { + break; + } + } + + bestCandidate = loadHelper; + return result; + + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/HostnameInstanceIdGenerator.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/HostnameInstanceIdGenerator.java new file mode 100644 index 000000000..3abd798c0 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/HostnameInstanceIdGenerator.java @@ -0,0 +1,48 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ +package com.fr.third.org.quartz.simpl; + +import java.net.InetAddress; + +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.spi.InstanceIdGenerator; + +/** + *

+ * InstanceIdGenerator that names the scheduler instance using + * just the machine hostname. + *

+ * + *

+ * This class is useful when you know that your scheduler instance will be the + * only one running on a particular machine. Each time the scheduler is + * restarted, it will get the same instance id as long as the machine is not + * renamed. + *

+ * + * @see InstanceIdGenerator + * @see SimpleInstanceIdGenerator + */ +public class HostnameInstanceIdGenerator implements InstanceIdGenerator { + public String generateInstanceId() throws SchedulerException { + try { + return InetAddress.getLocalHost().getHostName(); + } catch (Exception e) { + throw new SchedulerException("Couldn't get host name!", e); + } + } +} \ No newline at end of file diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/InitThreadContextClassLoadHelper.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/InitThreadContextClassLoadHelper.java new file mode 100644 index 000000000..a5906b729 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/InitThreadContextClassLoadHelper.java @@ -0,0 +1,106 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.simpl; + +import com.fr.third.org.quartz.spi.ClassLoadHelper; + +import java.net.URL; +import java.io.InputStream; + +/** + * A ClassLoadHelper that uses either the context class loader + * of the thread that initialized Quartz. + * + * @see com.fr.third.org.quartz.spi.ClassLoadHelper + * @see com.fr.third.org.quartz.simpl.ThreadContextClassLoadHelper + * @see com.fr.third.org.quartz.simpl.SimpleClassLoadHelper + * @see com.fr.third.org.quartz.simpl.CascadingClassLoadHelper + * @see com.fr.third.org.quartz.simpl.LoadingLoaderClassLoadHelper + * + * @author jhouse + * @author pl47ypus + */ +public class InitThreadContextClassLoadHelper implements ClassLoadHelper { + + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private ClassLoader initClassLoader; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Called to give the ClassLoadHelper a chance to initialize itself, + * including the opportunity to "steal" the class loader off of the calling + * thread, which is the thread that is initializing Quartz. + */ + public void initialize() { + initClassLoader = Thread.currentThread().getContextClassLoader(); + } + + /** + * Return the class with the given name. + */ + public Class loadClass(String name) throws ClassNotFoundException { + return initClassLoader.loadClass(name); + } + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.net.URL object + */ + public URL getResource(String name) { + return initClassLoader.getResource(name); + } + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.io.InputStream object + */ + public InputStream getResourceAsStream(String name) { + return initClassLoader.getResourceAsStream(name); + } + + /** + * Enable sharing of the class-loader with 3rd party (e.g. digester). + * + * @return the class-loader user be the helper. + */ + public ClassLoader getClassLoader() { + return this.initClassLoader; + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/InitThreadContextClassLoadHelper.java.bak b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/InitThreadContextClassLoadHelper.java.bak new file mode 100644 index 000000000..9c6f84d1e --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/InitThreadContextClassLoadHelper.java.bak @@ -0,0 +1,96 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package org.quartz.simpl; + +import org.quartz.spi.ClassLoadHelper; + +import java.net.URL; +import java.io.InputStream; + +/** + * A ClassLoadHelper that uses either the context class loader + * of the thread that initialized Quartz. + * + * @see org.quartz.spi.ClassLoadHelper + * @see org.quartz.simpl.ThreadContextClassLoadHelper + * @see org.quartz.simpl.SimpleClassLoadHelper + * @see org.quartz.simpl.CascadingClassLoadHelper + * @see org.quartz.simpl.LoadingLoaderClassLoadHelper + * + * @author jhouse + */ +public class InitThreadContextClassLoadHelper implements ClassLoadHelper { + + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private ClassLoader initClassLoader; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Called to give the ClassLoadHelper a chance to initialize itself, + * including the oportunity to "steal" the class loader off of the calling + * thread, which is the thread that is initializing Quartz. + */ + public void initialize() { + initClassLoader = Thread.currentThread().getContextClassLoader(); + } + + /** + * Return the class with the given name. + */ + public Class loadClass(String name) throws ClassNotFoundException { + return initClassLoader.loadClass(name); + } + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.net.URL object + */ + public URL getResource(String name) { + return initClassLoader.getResource(name); + } + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.io.InputStream object + */ + public InputStream getResourceAsStream(String name) { + return initClassLoader.getResourceAsStream(name); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/LoadingLoaderClassLoadHelper.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/LoadingLoaderClassLoadHelper.java new file mode 100644 index 000000000..b61c2af89 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/LoadingLoaderClassLoadHelper.java @@ -0,0 +1,93 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.simpl; + +import com.fr.third.org.quartz.spi.ClassLoadHelper; + +import java.net.URL; +import java.io.InputStream; + +/** + * A ClassLoadHelper that uses either the loader of it's own + * class (this.getClass().getClassLoader().loadClass( .. )). + * + * @see com.fr.third.org.quartz.spi.ClassLoadHelper + * @see com.fr.third.org.quartz.simpl.InitThreadContextClassLoadHelper + * @see com.fr.third.org.quartz.simpl.SimpleClassLoadHelper + * @see com.fr.third.org.quartz.simpl.CascadingClassLoadHelper + * + * @author jhouse + * @author pl47ypus + */ +public class LoadingLoaderClassLoadHelper implements ClassLoadHelper { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Called to give the ClassLoadHelper a chance to initialize itself, + * including the opportunity to "steal" the class loader off of the calling + * thread, which is the thread that is initializing Quartz. + */ + public void initialize() { + } + + /** + * Return the class with the given name. + */ + public Class loadClass(String name) throws ClassNotFoundException { + return getClassLoader().loadClass(name); + } + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.net.URL object + */ + public URL getResource(String name) { + return getClassLoader().getResource(name); + } + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.io.InputStream object + */ + public InputStream getResourceAsStream(String name) { + return getClassLoader().getResourceAsStream(name); + } + + /** + * Enable sharing of the class-loader with 3rd party (e.g. digester). + * + * @return the class-loader user be the helper. + */ + public ClassLoader getClassLoader() { + return this.getClass().getClassLoader(); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/LoadingLoaderClassLoadHelper.java.bak b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/LoadingLoaderClassLoadHelper.java.bak new file mode 100644 index 000000000..8121baa7c --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/LoadingLoaderClassLoadHelper.java.bak @@ -0,0 +1,87 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package org.quartz.simpl; + +import org.quartz.spi.ClassLoadHelper; + +import java.net.URL; +import java.io.InputStream; + +/** + * A ClassLoadHelper that uses either the loader of it's own + * class (this.getClass().getClassLoader().loadClass( .. )). + * + * @see org.quartz.spi.ClassLoadHelper + * @see org.quartz.simpl.InitThreadContextClassLoadHelper + * @see org.quartz.simpl.SimpleClassLoadHelper + * @see org.quartz.simpl.CascadingClassLoadHelper + * + * @author jhouse + */ +public class LoadingLoaderClassLoadHelper implements ClassLoadHelper { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Called to give the ClassLoadHelper a chance to initialize itself, + * including the oportunity to "steal" the class loader off of the calling + * thread, which is the thread that is initializing Quartz. + */ + public void initialize() { + } + + /** + * Return the class with the given name. + */ + public Class loadClass(String name) throws ClassNotFoundException { + return getClassLoader().loadClass(name); + } + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.net.URL object + */ + public URL getResource(String name) { + return getClassLoader().getResource(name); + } + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.io.InputStream object + */ + public InputStream getResourceAsStream(String name) { + return getClassLoader().getResourceAsStream(name); + } + + private ClassLoader getClassLoader() { + return this.getClass().getClassLoader(); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/PropertySettingJobFactory.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/PropertySettingJobFactory.java new file mode 100644 index 000000000..fe6c29e90 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/PropertySettingJobFactory.java @@ -0,0 +1,288 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ +package com.fr.third.org.quartz.simpl; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; + +import com.fr.third.org.quartz.Job; +import com.fr.third.org.quartz.JobDataMap; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.spi.TriggerFiredBundle; + + + +/** + * A JobFactory that instantiates the Job instance (using the default no-arg + * constructor, or more specifically: class.newInstance()), and + * then attempts to set all values in the JobExecutionContext's + * JobDataMap onto bean properties of the Job. + * + * @see com.fr.third.org.quartz.spi.JobFactory + * @see SimpleJobFactory + * @see com.fr.third.org.quartz.JobExecutionContext#getMergedJobDataMap() + * @see #setWarnIfPropertyNotFound(boolean) + * @see #setThrowIfPropertyNotFound(boolean) + * + * @author jhouse + */ +public class PropertySettingJobFactory extends SimpleJobFactory { + private boolean warnIfNotFound = true; + private boolean throwIfNotFound = false; + + public Job newJob(TriggerFiredBundle bundle) throws SchedulerException { + + Job job = super.newJob(bundle); + + JobDataMap jobDataMap = new JobDataMap(); + jobDataMap.putAll(bundle.getJobDetail().getJobDataMap()); + jobDataMap.putAll(bundle.getTrigger().getJobDataMap()); + + setBeanProps(job, jobDataMap); + + return job; + } + + protected void setBeanProps(Object obj, JobDataMap data) throws SchedulerException { + + BeanInfo bi = null; + try { + bi = Introspector.getBeanInfo(obj.getClass()); + } catch (IntrospectionException e) { + handleError("Unable to introspect Job class.", e); + } + + PropertyDescriptor[] propDescs = bi.getPropertyDescriptors(); + + // Get the wrapped entry set so don't have to incur overhead of wrapping for + // dirty flag checking since this is read only access + for (Iterator entryIter = data.getWrappedMap().entrySet().iterator(); entryIter.hasNext();) { + Map.Entry entry = (Map.Entry)entryIter.next(); + + String name = (String)entry.getKey(); + String c = name.substring(0, 1).toUpperCase(Locale.US); + String methName = "set" + c + name.substring(1); + + java.lang.reflect.Method setMeth = getSetMethod(methName, propDescs); + + Class paramType = null; + Object o = null; + + try { + if (setMeth == null) { + handleError( + "No setter on Job class " + obj.getClass().getName() + + " for property '" + name + "'"); + continue; + } + + paramType = setMeth.getParameterTypes()[0]; + o = entry.getValue(); + + Object parm = null; + if (paramType.isPrimitive()) { + if (o == null) { + handleError( + "Cannot set primitive property '" + name + + "' on Job class " + obj.getClass().getName() + + " to null."); + continue; + } + + if (paramType.equals(int.class)) { + if (o instanceof String) { + parm = new Integer((String)o); + } else if (o instanceof Integer) { + parm = o; + } + } else if (paramType.equals(long.class)) { + if (o instanceof String) { + parm = new Long((String)o); + } else if (o instanceof Long) { + parm = o; + } + } else if (paramType.equals(float.class)) { + if (o instanceof String) { + parm = new Float((String)o); + } else if (o instanceof Float) { + parm = o; + } + } else if (paramType.equals(double.class)) { + if (o instanceof String) { + parm = new Double((String)o); + } else if (o instanceof Double) { + parm = o; + } + } else if (paramType.equals(boolean.class)) { + if (o instanceof String) { + parm = new Boolean((String)o); + } else if (o instanceof Boolean) { + parm = o; + } + } else if (paramType.equals(byte.class)) { + if (o instanceof String) { + parm = new Byte((String)o); + } else if (o instanceof Byte) { + parm = o; + } + } else if (paramType.equals(short.class)) { + if (o instanceof String) { + parm = new Short((String)o); + } else if (o instanceof Short) { + parm = o; + } + } else if (paramType.equals(char.class)) { + if (o instanceof String) { + String str = (String)o; + if (str.length() == 1) { + parm = new Character(str.charAt(0)); + } + } else if (o instanceof Character) { + parm = o; + } + } + } else if ((o != null) && (paramType.isAssignableFrom(o.getClass()))) { + parm = o; + } + + // If the parameter wasn't originally null, but we didn't find a + // matching parameter, then we are stuck. + if ((o != null) && (parm == null)) { + handleError( + "The setter on Job class " + obj.getClass().getName() + + " for property '" + name + + "' expects a " + paramType + + " but was given " + o.getClass().getName()); + continue; + } + + setMeth.invoke(obj, new Object[]{ parm }); + } catch (NumberFormatException nfe) { + handleError( + "The setter on Job class " + obj.getClass().getName() + + " for property '" + name + + "' expects a " + paramType + + " but was given " + o.getClass().getName(), nfe); + } catch (IllegalArgumentException e) { + handleError( + "The setter on Job class " + obj.getClass().getName() + + " for property '" + name + + "' expects a " + paramType + + " but was given " + o.getClass().getName(), e); + } catch (IllegalAccessException e) { + handleError( + "The setter on Job class " + obj.getClass().getName() + + " for property '" + name + + "' could not be accessed.", e); + } catch (InvocationTargetException e) { + handleError( + "The setter on Job class " + obj.getClass().getName() + + " for property '" + name + + "' could not be invoked.", e); + } + } + } + + private void handleError(String message) throws SchedulerException { + handleError(message, null); + } + + private void handleError(String message, Exception e) throws SchedulerException { + if (isThrowIfPropertyNotFound()) { + throw new SchedulerException(message, e); + } + + if (isWarnIfPropertyNotFound()) { + if (e == null) { + getLog().warn(message); + } else { + getLog().warn(message, e); + } + } + } + + private java.lang.reflect.Method getSetMethod(String name, + PropertyDescriptor[] props) { + for (int i = 0; i < props.length; i++) { + java.lang.reflect.Method wMeth = props[i].getWriteMethod(); + + if(wMeth == null) { + continue; + } + + if(wMeth.getParameterTypes().length != 1) { + continue; + } + + if (wMeth.getName().equals(name)) { + return wMeth; + } + } + + return null; + } + + /** + * Whether the JobInstantiation should fail and throw and exception if + * a key (name) and value (type) found in the JobDataMap does not + * correspond to a proptery setter on the Job class. + * + * @return Returns the throwIfNotFound. + */ + public boolean isThrowIfPropertyNotFound() { + return throwIfNotFound; + } + + /** + * Whether the JobInstantiation should fail and throw and exception if + * a key (name) and value (type) found in the JobDataMap does not + * correspond to a proptery setter on the Job class. + * + * @param throwIfNotFound defaults to false. + */ + public void setThrowIfPropertyNotFound(boolean throwIfNotFound) { + this.throwIfNotFound = throwIfNotFound; + } + + /** + * Whether a warning should be logged if + * a key (name) and value (type) found in the JobDataMap does not + * correspond to a proptery setter on the Job class. + * + * @return Returns the warnIfNotFound. + */ + public boolean isWarnIfPropertyNotFound() { + return warnIfNotFound; + } + + /** + * Whether a warning should be logged if + * a key (name) and value (type) found in the JobDataMap does not + * correspond to a proptery setter on the Job class. + * + * @param warnIfNotFound defaults to true. + */ + public void setWarnIfPropertyNotFound(boolean warnIfNotFound) { + this.warnIfNotFound = warnIfNotFound; + } +} \ No newline at end of file diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/RAMJobStore.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/RAMJobStore.java new file mode 100644 index 000000000..116927ee6 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/RAMJobStore.java @@ -0,0 +1,1615 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.simpl; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.Calendar; +import com.fr.third.org.quartz.JobDataMap; +import com.fr.third.org.quartz.JobDetail; +import com.fr.third.org.quartz.JobPersistenceException; +import com.fr.third.org.quartz.ObjectAlreadyExistsException; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.Trigger; +import com.fr.third.org.quartz.core.SchedulingContext; +import com.fr.third.org.quartz.spi.ClassLoadHelper; +import com.fr.third.org.quartz.spi.JobStore; +import com.fr.third.org.quartz.spi.SchedulerSignaler; +import com.fr.third.org.quartz.spi.TriggerFiredBundle; + +/** + *

+ * This class implements a {@link com.fr.third.org.quartz.spi.JobStore} that + * utilizes RAM as its storage device. + *

+ * + *

+ * As you should know, the ramification of this is that access is extrememly + * fast, but the data is completely volatile - therefore this JobStore + * should not be used if true persistence between program shutdowns is + * required. + *

+ * + * @author James House + * @author Sharada Jambula + * @author Eric Mueller + */ +public class RAMJobStore implements JobStore { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + protected HashMap jobsByFQN = new HashMap(1000); + + protected HashMap triggersByFQN = new HashMap(1000); + + protected HashMap jobsByGroup = new HashMap(25); + + protected HashMap triggersByGroup = new HashMap(25); + + protected TreeSet timeTriggers = new TreeSet(new TriggerComparator()); + + protected HashMap calendarsByName = new HashMap(25); + + protected ArrayList triggers = new ArrayList(1000); + + protected final Object triggerLock = new Object(); + + protected HashSet pausedTriggerGroups = new HashSet(); + + protected HashSet pausedJobGroups = new HashSet(); + + protected HashSet blockedJobs = new HashSet(); + + protected long misfireThreshold = 5000l; + + protected SchedulerSignaler signaler; + + private final Log log = LogFactory.getLog(getClass()); + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Create a new RAMJobStore. + *

+ */ + public RAMJobStore() { + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + protected Log getLog() { + return log; + } + + /** + *

+ * Called by the QuartzScheduler before the JobStore is + * used, in order to give the it a chance to initialize. + *

+ */ + public void initialize(ClassLoadHelper loadHelper, + SchedulerSignaler signaler) { + + this.signaler = signaler; + + getLog().info("RAMJobStore initialized."); + } + + public void schedulerStarted() throws SchedulerException { + // nothing to do + } + + public long getMisfireThreshold() { + return misfireThreshold; + } + + /** + * The number of milliseconds by which a trigger must have missed its + * next-fire-time, in order for it to be considered "misfired" and thus + * have its misfire instruction applied. + * + * @param misfireThreshold + */ + public void setMisfireThreshold(long misfireThreshold) { + if (misfireThreshold < 1) { + throw new IllegalArgumentException("Misfirethreashold must be larger than 0"); + } + this.misfireThreshold = misfireThreshold; + } + + /** + *

+ * Called by the QuartzScheduler to inform the JobStore that + * it should free up all of it's resources because the scheduler is + * shutting down. + *

+ */ + public void shutdown() { + } + + public boolean supportsPersistence() { + return false; + } + + /** + *

+ * Store the given {@link com.fr.third.org.quartz.JobDetail} and {@link com.fr.third.org.quartz.Trigger}. + *

+ * + * @param newJob + * The JobDetail to be stored. + * @param newTrigger + * The Trigger to be stored. + * @throws ObjectAlreadyExistsException + * if a Job with the same name/group already + * exists. + */ + public void storeJobAndTrigger(SchedulingContext ctxt, JobDetail newJob, + Trigger newTrigger) throws JobPersistenceException { + storeJob(ctxt, newJob, false); + storeTrigger(ctxt, newTrigger, false); + } + + /** + *

+ * Store the given {@link com.fr.third.org.quartz.Job}. + *

+ * + * @param newJob + * The Job to be stored. + * @param replaceExisting + * If true, any Job existing in the + * JobStore with the same name & group should be + * over-written. + * @throws ObjectAlreadyExistsException + * if a Job with the same name/group already + * exists, and replaceExisting is set to false. + */ + public void storeJob(SchedulingContext ctxt, JobDetail newJob, + boolean replaceExisting) throws ObjectAlreadyExistsException { + JobWrapper jw = new JobWrapper((JobDetail)newJob.clone()); + + boolean repl = false; + + if (jobsByFQN.get(jw.key) != null) { + if (!replaceExisting) { + throw new ObjectAlreadyExistsException(newJob); + } + repl = true; + } + + synchronized (triggerLock) { + if (!repl) { + // get job group + HashMap grpMap = (HashMap) jobsByGroup.get(newJob.getGroup()); + if (grpMap == null) { + grpMap = new HashMap(100); + jobsByGroup.put(newJob.getGroup(), grpMap); + } + // add to jobs by group + grpMap.put(newJob.getName(), jw); + // add to jobs by FQN map + jobsByFQN.put(jw.key, jw); + } else { + // update job detail + JobWrapper orig = (JobWrapper) jobsByFQN.get(jw.key); + orig.jobDetail = jw.jobDetail; // already cloned + } + } + } + + /** + *

+ * Remove (delete) the {@link com.fr.third.org.quartz.Job} with the given + * name, and any {@link com.fr.third.org.quartz.Trigger} s that reference + * it. + *

+ * + * @param jobName + * The name of the Job to be removed. + * @param groupName + * The group name of the Job to be removed. + * @return true if a Job with the given name & + * group was found and removed from the store. + */ + public boolean removeJob(SchedulingContext ctxt, String jobName, + String groupName) { + String key = JobWrapper.getJobNameKey(jobName, groupName); + + boolean found = false; + + Trigger[] trigger = getTriggersForJob(ctxt, jobName, + groupName); + for (int i = 0; i < trigger.length; i++) { + Trigger trig = trigger[i]; + this.removeTrigger(ctxt, trig.getName(), trig.getGroup()); + found = true; + } + synchronized (triggerLock) { + found = (jobsByFQN.remove(key) != null) | found; + if (found) { + + HashMap grpMap = (HashMap) jobsByGroup.get(groupName); + if (grpMap != null) { + grpMap.remove(jobName); + if (grpMap.size() == 0) { + jobsByGroup.remove(groupName); + } + } + } + } + + return found; + } + + /** + *

+ * Store the given {@link com.fr.third.org.quartz.Trigger}. + *

+ * + * @param newTrigger + * The Trigger to be stored. + * @param replaceExisting + * If true, any Trigger existing in + * the JobStore with the same name & group should + * be over-written. + * @throws ObjectAlreadyExistsException + * if a Trigger with the same name/group already + * exists, and replaceExisting is set to false. + * + * @see #pauseTriggerGroup(SchedulingContext, String) + */ + public void storeTrigger(SchedulingContext ctxt, Trigger newTrigger, + boolean replaceExisting) throws JobPersistenceException { + TriggerWrapper tw = new TriggerWrapper((Trigger)newTrigger.clone()); + + if (triggersByFQN.get(tw.key) != null) { + if (!replaceExisting) { + throw new ObjectAlreadyExistsException(newTrigger); + } + + removeTrigger(ctxt, newTrigger.getName(), newTrigger.getGroup(), false); + } + + if (retrieveJob(ctxt, newTrigger.getJobName(), newTrigger.getJobGroup()) == null) { + throw new JobPersistenceException("The job (" + + newTrigger.getFullJobName() + + ") referenced by the trigger does not exist."); + } + + synchronized (triggerLock) { + // add to triggers array + triggers.add(tw); + // add to triggers by group + HashMap grpMap = (HashMap) triggersByGroup.get(newTrigger + .getGroup()); + if (grpMap == null) { + grpMap = new HashMap(100); + triggersByGroup.put(newTrigger.getGroup(), grpMap); + } + grpMap.put(newTrigger.getName(), tw); + // add to triggers by FQN map + triggersByFQN.put(tw.key, tw); + + if (pausedTriggerGroups.contains(newTrigger.getGroup()) + || pausedJobGroups.contains(newTrigger.getJobGroup())) { + tw.state = TriggerWrapper.STATE_PAUSED; + if (blockedJobs.contains(tw.jobKey)) { + tw.state = TriggerWrapper.STATE_PAUSED_BLOCKED; + } + } else if (blockedJobs.contains(tw.jobKey)) { + tw.state = TriggerWrapper.STATE_BLOCKED; + } else { + timeTriggers.add(tw); + } + } + } + + /** + *

+ * Remove (delete) the {@link com.fr.third.org.quartz.Trigger} with the + * given name. + *

+ * + * @param triggerName + * The name of the Trigger to be removed. + * @param groupName + * The group name of the Trigger to be removed. + * @return true if a Trigger with the given + * name & group was found and removed from the store. + */ + public boolean removeTrigger(SchedulingContext ctxt, String triggerName, + String groupName) { + return removeTrigger(ctxt, triggerName, groupName, true); + } + private boolean removeTrigger(SchedulingContext ctxt, String triggerName, + String groupName, boolean removeOrphanedJob) { + String key = TriggerWrapper.getTriggerNameKey(triggerName, groupName); + + boolean found = false; + + synchronized (triggerLock) { + // remove from triggers by FQN map + found = (triggersByFQN.remove(key) == null) ? false : true; + if (found) { + TriggerWrapper tw = null; + // remove from triggers by group + HashMap grpMap = (HashMap) triggersByGroup.get(groupName); + if (grpMap != null) { + grpMap.remove(triggerName); + if (grpMap.size() == 0) { + triggersByGroup.remove(groupName); + } + } + // remove from triggers array + Iterator tgs = triggers.iterator(); + while (tgs.hasNext()) { + tw = (TriggerWrapper) tgs.next(); + if (key.equals(tw.key)) { + tgs.remove(); + break; + } + } + timeTriggers.remove(tw); + + if (removeOrphanedJob) { + JobWrapper jw = (JobWrapper) jobsByFQN.get(JobWrapper + .getJobNameKey(tw.trigger.getJobName(), tw.trigger + .getJobGroup())); + Trigger[] trigs = getTriggersForJob(ctxt, tw.trigger + .getJobName(), tw.trigger.getJobGroup()); + if ((trigs == null || trigs.length == 0) && !jw.jobDetail.isDurable()) { + removeJob(ctxt, tw.trigger.getJobName(), tw.trigger + .getJobGroup()); + } + } + } + } + + return found; + } + + + /** + * @see com.fr.third.org.quartz.spi.JobStore#replaceTrigger(com.fr.third.org.quartz.core.SchedulingContext, java.lang.String, java.lang.String, com.fr.third.org.quartz.Trigger) + */ + public boolean replaceTrigger(SchedulingContext ctxt, String triggerName, String groupName, Trigger newTrigger) throws JobPersistenceException { + String key = TriggerWrapper.getTriggerNameKey(triggerName, groupName); + + boolean found = false; + + synchronized (triggerLock) { + // remove from triggers by FQN map + TriggerWrapper tw = (TriggerWrapper) triggersByFQN.remove(key); + found = ( tw == null) ? false : true; + + if (found) { + + if (!tw.getTrigger().getJobName().equals(newTrigger.getJobName()) || + !tw.getTrigger().getJobGroup().equals(newTrigger.getJobGroup())) { + throw new JobPersistenceException("New trigger is not related to the same job as the old trigger."); + } + + tw = null; + // remove from triggers by group + HashMap grpMap = (HashMap) triggersByGroup.get(groupName); + if (grpMap != null) { + grpMap.remove(triggerName); + if (grpMap.size() == 0) { + triggersByGroup.remove(groupName); + } + } + // remove from triggers array + Iterator tgs = triggers.iterator(); + while (tgs.hasNext()) { + tw = (TriggerWrapper) tgs.next(); + if (key.equals(tw.key)) { + tgs.remove(); + break; + } + } + timeTriggers.remove(tw); + + try { + storeTrigger(ctxt, newTrigger, false); + } catch(JobPersistenceException jpe) { + storeTrigger(ctxt, tw.getTrigger(), false); // put previous trigger back... + throw jpe; + } + } + } + + return found; + } + + /** + *

+ * Retrieve the {@link com.fr.third.org.quartz.JobDetail} for the given + * {@link com.fr.third.org.quartz.Job}. + *

+ * + * @param jobName + * The name of the Job to be retrieved. + * @param groupName + * The group name of the Job to be retrieved. + * @return The desired Job, or null if there is no match. + */ + public JobDetail retrieveJob(SchedulingContext ctxt, String jobName, + String groupName) { + JobWrapper jw = (JobWrapper) jobsByFQN.get(JobWrapper.getJobNameKey( + jobName, groupName)); + + return (jw != null) ? (JobDetail)jw.jobDetail.clone() : null; + } + + /** + *

+ * Retrieve the given {@link com.fr.third.org.quartz.Trigger}. + *

+ * + * @param triggerName + * The name of the Trigger to be retrieved. + * @param groupName + * The group name of the Trigger to be retrieved. + * @return The desired Trigger, or null if there is no + * match. + */ + public Trigger retrieveTrigger(SchedulingContext ctxt, String triggerName, + String groupName) { + TriggerWrapper tw = (TriggerWrapper) triggersByFQN.get(TriggerWrapper + .getTriggerNameKey(triggerName, groupName)); + + return (tw != null) ? (Trigger)tw.getTrigger().clone() : null; + } + + /** + *

+ * Get the current state of the identified {@link Trigger}. + *

+ * + * @see Trigger#STATE_NORMAL + * @see Trigger#STATE_PAUSED + * @see Trigger#STATE_COMPLETE + * @see Trigger#STATE_ERROR + * @see Trigger#STATE_BLOCKED + * @see Trigger#STATE_NONE + */ + public int getTriggerState(SchedulingContext ctxt, String triggerName, + String groupName) throws JobPersistenceException { + TriggerWrapper tw = (TriggerWrapper) triggersByFQN.get(TriggerWrapper + .getTriggerNameKey(triggerName, groupName)); + if (tw == null) { + return Trigger.STATE_NONE; + } + + if (tw.state == TriggerWrapper.STATE_COMPLETE) { + return Trigger.STATE_COMPLETE; + } + + if (tw.state == TriggerWrapper.STATE_PAUSED) { + return Trigger.STATE_PAUSED; + } + + if (tw.state == TriggerWrapper.STATE_PAUSED_BLOCKED) { + return Trigger.STATE_PAUSED; + } + + if (tw.state == TriggerWrapper.STATE_BLOCKED) { + return Trigger.STATE_BLOCKED; + } + + if (tw.state == TriggerWrapper.STATE_ERROR) { + return Trigger.STATE_ERROR; + } + + return Trigger.STATE_NORMAL; + } + + /** + *

+ * Store the given {@link com.fr.third.org.quartz.Calendar}. + *

+ * + * @param calendar + * The Calendar to be stored. + * @param replaceExisting + * If true, any Calendar existing + * in the JobStore with the same name & group + * should be over-written. + * @param updateTriggers + * If true, any Triggers existing + * in the JobStore that reference an existing + * Calendar with the same name with have their next fire time + * re-computed with the new Calendar. + * @throws ObjectAlreadyExistsException + * if a Calendar with the same name already + * exists, and replaceExisting is set to false. + */ + public void storeCalendar(SchedulingContext ctxt, String name, + Calendar calendar, boolean replaceExisting, boolean updateTriggers) + throws ObjectAlreadyExistsException { + Object obj = calendarsByName.get(name); + + if (obj != null && replaceExisting == false) { + throw new ObjectAlreadyExistsException( + "Calendar with name '" + name + "' already exists."); + } else if (obj != null) { + calendarsByName.remove(name); + } + + calendarsByName.put(name, calendar); + + if(obj != null && updateTriggers) { + synchronized (triggerLock) { + Iterator trigs = getTriggerWrappersForCalendar(name).iterator(); + while (trigs.hasNext()) { + TriggerWrapper tw = (TriggerWrapper) trigs.next(); + Trigger trig = tw.getTrigger(); + boolean removed = timeTriggers.remove(tw); + + trig.updateWithNewCalendar(calendar, getMisfireThreshold()); + + if(removed) { + timeTriggers.add(tw); + } + } + } + } + } + + /** + *

+ * Remove (delete) the {@link com.fr.third.org.quartz.Calendar} with the + * given name. + *

+ * + *

+ * If removal of the Calendar would result in + * s pointing to non-existent calendars, then a + * JobPersistenceException will be thrown.

+ * * + * @param calName The name of the Calendar to be removed. + * @return true if a Calendar with the given name + * was found and removed from the store. + */ + public boolean removeCalendar(SchedulingContext ctxt, String calName) + throws JobPersistenceException { + int numRefs = 0; + + synchronized (triggerLock) { + Iterator itr = triggers.iterator(); + while (itr.hasNext()) { + Trigger trigg = ((TriggerWrapper) itr.next()).trigger; + if (trigg.getCalendarName() != null + && trigg.getCalendarName().equals(calName)) { + numRefs++; + } + } + } + + if (numRefs > 0) { + throw new JobPersistenceException( + "Calender cannot be removed if it referenced by a Trigger!"); + } + + return (calendarsByName.remove(calName) != null); + } + + /** + *

+ * Retrieve the given {@link com.fr.third.org.quartz.Trigger}. + *

+ * + * @param calName + * The name of the Calendar to be retrieved. + * @return The desired Calendar, or null if there is no + * match. + */ + public Calendar retrieveCalendar(SchedulingContext ctxt, String calName) { + return (Calendar) calendarsByName.get(calName); + } + + /** + *

+ * Get the number of {@link com.fr.third.org.quartz.JobDetail} s that are + * stored in the JobsStore. + *

+ */ + public int getNumberOfJobs(SchedulingContext ctxt) { + return jobsByFQN.size(); + } + + /** + *

+ * Get the number of {@link com.fr.third.org.quartz.Trigger} s that are + * stored in the JobsStore. + *

+ */ + public int getNumberOfTriggers(SchedulingContext ctxt) { + return triggers.size(); + } + + /** + *

+ * Get the number of {@link com.fr.third.org.quartz.Calendar} s that are + * stored in the JobsStore. + *

+ */ + public int getNumberOfCalendars(SchedulingContext ctxt) { + return calendarsByName.size(); + } + + /** + *

+ * Get the names of all of the {@link com.fr.third.org.quartz.Job} s that + * have the given group name. + *

+ */ + public String[] getJobNames(SchedulingContext ctxt, String groupName) { + String[] outList = null; + HashMap grpMap = (HashMap) jobsByGroup.get(groupName); + if (grpMap != null) { + synchronized (triggerLock) { + outList = new String[grpMap.size()]; + int outListPos = 0; + + for (Iterator valueIter = grpMap.values().iterator(); valueIter.hasNext();) { + JobWrapper jw = (JobWrapper)valueIter.next(); + + if (jw != null) { + outList[outListPos++] = jw.jobDetail.getName(); + } + } + } + } else { + outList = new String[0]; + } + + return outList; + } + + /** + *

+ * Get the names of all of the {@link com.fr.third.org.quartz.Calendar} s + * in the JobStore. + *

+ * + *

+ * If there are no Calendars in the given group name, the result should be + * a zero-length array (not null). + *

+ */ + public String[] getCalendarNames(SchedulingContext ctxt) { + Set names = calendarsByName.keySet(); + return (String[]) names.toArray(new String[names.size()]); + } + + /** + *

+ * Get the names of all of the {@link com.fr.third.org.quartz.Trigger} s + * that have the given group name. + *

+ */ + public String[] getTriggerNames(SchedulingContext ctxt, String groupName) { + String[] outList = null; + HashMap grpMap = (HashMap) triggersByGroup.get(groupName); + if (grpMap != null) { + synchronized (triggerLock) { + outList = new String[grpMap.size()]; + int outListPos = 0; + + for (Iterator valueIter = grpMap.values().iterator(); valueIter.hasNext();) { + TriggerWrapper tw = (TriggerWrapper) valueIter.next(); + + if (tw != null) { + outList[outListPos++] = tw.trigger.getName(); + } + } + } + } else { + outList = new String[0]; + } + + return outList; + } + + /** + *

+ * Get the names of all of the {@link com.fr.third.org.quartz.Job} + * groups. + *

+ */ + public String[] getJobGroupNames(SchedulingContext ctxt) { + String[] outList = null; + + synchronized (triggerLock) { + outList = new String[jobsByGroup.size()]; + int outListPos = 0; + Iterator keys = jobsByGroup.keySet().iterator(); + while (keys.hasNext()) { + outList[outListPos++] = (String) keys.next(); + } + } + + return outList; + } + + /** + *

+ * Get the names of all of the {@link com.fr.third.org.quartz.Trigger} + * groups. + *

+ */ + public String[] getTriggerGroupNames(SchedulingContext ctxt) { + String[] outList = null; + + synchronized (triggerLock) { + outList = new String[triggersByGroup.size()]; + int outListPos = 0; + Iterator keys = triggersByGroup.keySet().iterator(); + while (keys.hasNext()) { + outList[outListPos++] = (String) keys.next(); + } + } + + return outList; + } + + /** + *

+ * Get all of the Triggers that are associated to the given Job. + *

+ * + *

+ * If there are no matches, a zero-length array should be returned. + *

+ */ + public Trigger[] getTriggersForJob(SchedulingContext ctxt, String jobName, + String groupName) { + ArrayList trigList = new ArrayList(); + + String jobKey = JobWrapper.getJobNameKey(jobName, groupName); + synchronized (triggerLock) { + for (int i = 0; i < triggers.size(); i++) { + TriggerWrapper tw = (TriggerWrapper) triggers.get(i); + if (tw.jobKey.equals(jobKey)) { + trigList.add(tw.trigger.clone()); + } + } + } + + return (Trigger[]) trigList.toArray(new Trigger[trigList.size()]); + } + + protected ArrayList getTriggerWrappersForJob(String jobName, String groupName) { + ArrayList trigList = new ArrayList(); + + String jobKey = JobWrapper.getJobNameKey(jobName, groupName); + synchronized (triggerLock) { + for (int i = 0; i < triggers.size(); i++) { + TriggerWrapper tw = (TriggerWrapper) triggers.get(i); + if (tw.jobKey.equals(jobKey)) { + trigList.add(tw); + } + } + } + + return trigList; + } + + protected ArrayList getTriggerWrappersForCalendar(String calName) { + ArrayList trigList = new ArrayList(); + + synchronized (triggerLock) { + for (int i = 0; i < triggers.size(); i++) { + TriggerWrapper tw = (TriggerWrapper) triggers.get(i); + String tcalName = tw.getTrigger().getCalendarName(); + if (tcalName != null && tcalName.equals(calName)) { + trigList.add(tw); + } + } + } + + return trigList; + } + + /** + *

+ * Pause the {@link Trigger} with the given name. + *

+ * + */ + public void pauseTrigger(SchedulingContext ctxt, String triggerName, + String groupName) { + + TriggerWrapper tw = (TriggerWrapper) triggersByFQN.get(TriggerWrapper + .getTriggerNameKey(triggerName, groupName)); + + // does the trigger exist? + if (tw == null || tw.trigger == null) { + return; + } + + // if the trigger is "complete" pausing it does not make sense... + if (tw.state == TriggerWrapper.STATE_COMPLETE) { + return; + } + + synchronized (triggerLock) { + if(tw.state == TriggerWrapper.STATE_BLOCKED) { + tw.state = TriggerWrapper.STATE_PAUSED_BLOCKED; + } else { + tw.state = TriggerWrapper.STATE_PAUSED; + } + + timeTriggers.remove(tw); + } + } + + /** + *

+ * Pause all of the {@link Trigger}s in the given group. + *

+ * + *

+ * The JobStore should "remember" that the group is paused, and impose the + * pause on any new triggers that are added to the group while the group is + * paused. + *

+ * + */ + public void pauseTriggerGroup(SchedulingContext ctxt, String groupName) { + + synchronized (triggerLock) { + if (pausedTriggerGroups.contains(groupName)) { + return; + } + + pausedTriggerGroups.add(groupName); + String[] names = getTriggerNames(ctxt, groupName); + + for (int i = 0; i < names.length; i++) { + pauseTrigger(ctxt, names[i], groupName); + } + } + } + + /** + *

+ * Pause the {@link com.fr.third.org.quartz.JobDetail} with the given + * name - by pausing all of its current Triggers. + *

+ * + */ + public void pauseJob(SchedulingContext ctxt, String jobName, + String groupName) { + synchronized (triggerLock) { + Trigger[] triggers = getTriggersForJob(ctxt, jobName, groupName); + for (int j = 0; j < triggers.length; j++) { + pauseTrigger(ctxt, triggers[j].getName(), triggers[j].getGroup()); + } + } + } + + /** + *

+ * Pause all of the {@link com.fr.third.org.quartz.JobDetail}s in the + * given group - by pausing all of their Triggers. + *

+ * + * + *

+ * The JobStore should "remember" that the group is paused, and impose the + * pause on any new jobs that are added to the group while the group is + * paused. + *

+ */ + public void pauseJobGroup(SchedulingContext ctxt, String groupName) { + synchronized (triggerLock) { + if (!pausedJobGroups.contains(groupName)) { + pausedJobGroups.add(groupName); + } + + String[] jobNames = getJobNames(ctxt, groupName); + + for (int i = 0; i < jobNames.length; i++) { + Trigger[] triggers = getTriggersForJob(ctxt, jobNames[i], + groupName); + for (int j = 0; j < triggers.length; j++) { + pauseTrigger(ctxt, triggers[j].getName(), + triggers[j].getGroup()); + } + } + } + } + + /** + *

+ * Resume (un-pause) the {@link Trigger} with the given + * name. + *

+ * + *

+ * If the Trigger missed one or more fire-times, then the + * Trigger's misfire instruction will be applied. + *

+ * + */ + public void resumeTrigger(SchedulingContext ctxt, String triggerName, + String groupName) { + + TriggerWrapper tw = (TriggerWrapper) triggersByFQN.get(TriggerWrapper + .getTriggerNameKey(triggerName, groupName)); + + // does the trigger exist? + if (tw == null || tw.trigger == null) { + return; + } + + Trigger trig = tw.getTrigger(); + + // if the trigger is not paused resuming it does not make sense... + if (tw.state != TriggerWrapper.STATE_PAUSED && + tw.state != TriggerWrapper.STATE_PAUSED_BLOCKED) { + return; + } + + synchronized (triggerLock) { + if(blockedJobs.contains( JobWrapper.getJobNameKey(trig.getJobName(), trig.getJobGroup()) )) { + tw.state = TriggerWrapper.STATE_BLOCKED; + } else { + tw.state = TriggerWrapper.STATE_WAITING; + } + + applyMisfire(tw); + + if (tw.state == TriggerWrapper.STATE_WAITING) { + timeTriggers.add(tw); + } + } + } + + /** + *

+ * Resume (un-pause) all of the {@link Trigger}s in the + * given group. + *

+ * + *

+ * If any Trigger missed one or more fire-times, then the + * Trigger's misfire instruction will be applied. + *

+ * + */ + public void resumeTriggerGroup(SchedulingContext ctxt, String groupName) { + + synchronized (triggerLock) { + String[] names = getTriggerNames(ctxt, groupName); + + for (int i = 0; i < names.length; i++) { + String key = TriggerWrapper.getTriggerNameKey(names[i], groupName); + if(triggersByFQN.get(key) != null) { + String jobGroup = ((TriggerWrapper) triggersByFQN.get(key)).getTrigger().getJobGroup(); + if(pausedJobGroups.contains(jobGroup)) { + continue; + } + } + resumeTrigger(ctxt, names[i], groupName); + } + pausedTriggerGroups.remove(groupName); + } + } + + /** + *

+ * Resume (un-pause) the {@link com.fr.third.org.quartz.JobDetail} with + * the given name. + *

+ * + *

+ * If any of the Job'sTrigger s missed one + * or more fire-times, then the Trigger's misfire + * instruction will be applied. + *

+ * + */ + public void resumeJob(SchedulingContext ctxt, String jobName, + String groupName) { + + synchronized (triggerLock) { + Trigger[] triggers = getTriggersForJob(ctxt, jobName, groupName); + for (int j = 0; j < triggers.length; j++) { + resumeTrigger(ctxt, triggers[j].getName(), triggers[j].getGroup()); + } + } + } + + /** + *

+ * Resume (un-pause) all of the {@link com.fr.third.org.quartz.JobDetail}s + * in the given group. + *

+ * + *

+ * If any of the Job s had Trigger s that + * missed one or more fire-times, then the Trigger's + * misfire instruction will be applied. + *

+ * + */ + public void resumeJobGroup(SchedulingContext ctxt, String groupName) { + synchronized (triggerLock) { + String[] jobNames = getJobNames(ctxt, groupName); + + if(pausedJobGroups.contains(groupName)) { + pausedJobGroups.remove(groupName); + } + + for (int i = 0; i < jobNames.length; i++) { + Trigger[] triggers = getTriggersForJob(ctxt, jobNames[i], + groupName); + for (int j = 0; j < triggers.length; j++) { + resumeTrigger(ctxt, triggers[j].getName(), + triggers[j].getGroup()); + } + } + } + } + + /** + *

+ * Pause all triggers - equivalent of calling pauseTriggerGroup(group) + * on every group. + *

+ * + *

+ * When resumeAll() is called (to un-pause), trigger misfire + * instructions WILL be applied. + *

+ * + * @see #resumeAll(SchedulingContext) + * @see #pauseTriggerGroup(SchedulingContext, String) + */ + public void pauseAll(SchedulingContext ctxt) { + + synchronized (triggerLock) { + String[] names = getTriggerGroupNames(ctxt); + + for (int i = 0; i < names.length; i++) { + pauseTriggerGroup(ctxt, names[i]); + } + } + } + + /** + *

+ * Resume (un-pause) all triggers - equivalent of calling resumeTriggerGroup(group) + * on every group. + *

+ * + *

+ * If any Trigger missed one or more fire-times, then the + * Trigger's misfire instruction will be applied. + *

+ * + * @see #pauseAll(SchedulingContext) + */ + public void resumeAll(SchedulingContext ctxt) { + + synchronized (triggerLock) { + pausedJobGroups.clear(); + String[] names = getTriggerGroupNames(ctxt); + + for (int i = 0; i < names.length; i++) { + resumeTriggerGroup(ctxt, names[i]); + } + } + } + + protected boolean applyMisfire(TriggerWrapper tw) { + + long misfireTime = System.currentTimeMillis(); + if (getMisfireThreshold() > 0) { + misfireTime -= getMisfireThreshold(); + } + + Date tnft = tw.trigger.getNextFireTime(); + if (tnft == null || tnft.getTime() > misfireTime) { + return false; + } + + Calendar cal = null; + if (tw.trigger.getCalendarName() != null) { + cal = retrieveCalendar(null, tw.trigger.getCalendarName()); + } + + signaler.notifyTriggerListenersMisfired((Trigger)tw.trigger.clone()); + + tw.trigger.updateAfterMisfire(cal); + + if (tw.trigger.getNextFireTime() == null) { + tw.state = TriggerWrapper.STATE_COMPLETE; + signaler.notifySchedulerListenersFinalized(tw.trigger); + synchronized (triggerLock) { + timeTriggers.remove(tw); + } + } else if (tnft.equals(tw.trigger.getNextFireTime())) { + return false; + } + + return true; + } + + private static long ftrCtr = System.currentTimeMillis(); + + protected synchronized String getFiredTriggerRecordId() { + return String.valueOf(ftrCtr++); + } + + /** + *

+ * Get a handle to the next trigger to be fired, and mark it as 'reserved' + * by the calling scheduler. + *

+ * + * @see #releaseAcquiredTrigger(SchedulingContext, Trigger) + */ + public Trigger acquireNextTrigger(SchedulingContext ctxt, long noLaterThan) { + TriggerWrapper tw = null; + + synchronized (triggerLock) { + + while (tw == null) { + try { + tw = (TriggerWrapper) timeTriggers.first(); + } catch (java.util.NoSuchElementException nsee) { + return null; + } + + if (tw == null) { + return null; + } + + if (tw.trigger.getNextFireTime() == null) { + timeTriggers.remove(tw); + tw = null; + continue; + } + + timeTriggers.remove(tw); + + if (applyMisfire(tw)) { + if (tw.trigger.getNextFireTime() != null) { + timeTriggers.add(tw); + } + tw = null; + continue; + } + + if(tw.trigger.getNextFireTime().getTime() > noLaterThan) { + timeTriggers.add(tw); + return null; + } + + tw.state = TriggerWrapper.STATE_ACQUIRED; + + tw.trigger.setFireInstanceId(getFiredTriggerRecordId()); + Trigger trig = (Trigger) tw.trigger.clone(); + return trig; + } + } + + return null; + } + + /** + *

+ * Inform the JobStore that the scheduler no longer plans to + * fire the given Trigger, that it had previously acquired + * (reserved). + *

+ */ + public void releaseAcquiredTrigger(SchedulingContext ctxt, Trigger trigger) { + synchronized (triggerLock) { + TriggerWrapper tw = (TriggerWrapper) triggersByFQN.get(TriggerWrapper + .getTriggerNameKey(trigger)); + if (tw != null && tw.state == TriggerWrapper.STATE_ACQUIRED) { + tw.state = TriggerWrapper.STATE_WAITING; + timeTriggers.add(tw); + } + } + } + + /** + *

+ * Inform the JobStore that the scheduler is now firing the + * given Trigger (executing its associated Job), + * that it had previously acquired (reserved). + *

+ */ + public TriggerFiredBundle triggerFired(SchedulingContext ctxt, + Trigger trigger) { + + synchronized (triggerLock) { + TriggerWrapper tw = (TriggerWrapper) triggersByFQN.get(TriggerWrapper + .getTriggerNameKey(trigger)); + // was the trigger deleted since being acquired? + if (tw == null || tw.trigger == null) { + return null; + } + // was the trigger completed, paused, blocked, etc. since being acquired? + if (tw.state != TriggerWrapper.STATE_ACQUIRED) { + return null; + } + + Calendar cal = null; + if (tw.trigger.getCalendarName() != null) { + cal = retrieveCalendar(ctxt, tw.trigger.getCalendarName()); + if(cal == null) + return null; + } + Date prevFireTime = trigger.getPreviousFireTime(); + // in case trigger was replaced between acquiring and firering + timeTriggers.remove(tw); + // call triggered on our copy, and the scheduler's copy + tw.trigger.triggered(cal); + trigger.triggered(cal); + //tw.state = TriggerWrapper.STATE_EXECUTING; + tw.state = TriggerWrapper.STATE_WAITING; + + TriggerFiredBundle bndle = new TriggerFiredBundle(retrieveJob(ctxt, + trigger.getJobName(), trigger.getJobGroup()), trigger, cal, + false, new Date(), trigger.getPreviousFireTime(), prevFireTime, + trigger.getNextFireTime()); + + JobDetail job = bndle.getJobDetail(); + + if (job.isStateful()) { + ArrayList trigs = getTriggerWrappersForJob(job.getName(), job + .getGroup()); + Iterator itr = trigs.iterator(); + while (itr.hasNext()) { + TriggerWrapper ttw = (TriggerWrapper) itr.next(); + if(ttw.state == TriggerWrapper.STATE_WAITING) { + ttw.state = TriggerWrapper.STATE_BLOCKED; + } + if(ttw.state == TriggerWrapper.STATE_PAUSED) { + ttw.state = TriggerWrapper.STATE_PAUSED_BLOCKED; + } + timeTriggers.remove(ttw); + } + blockedJobs.add(JobWrapper.getJobNameKey(job)); + } else if (tw.trigger.getNextFireTime() != null) { + synchronized (triggerLock) { + timeTriggers.add(tw); + } + } + + return bndle; + } + } + + /** + *

+ * Inform the JobStore that the scheduler has completed the + * firing of the given Trigger (and the execution its + * associated Job), and that the {@link com.fr.third.org.quartz.JobDataMap} + * in the given JobDetail should be updated if the Job + * is stateful. + *

+ */ + public void triggeredJobComplete(SchedulingContext ctxt, Trigger trigger, + JobDetail jobDetail, int triggerInstCode) { + + synchronized (triggerLock) { + + String jobKey = JobWrapper.getJobNameKey(jobDetail.getName(), jobDetail + .getGroup()); + JobWrapper jw = (JobWrapper) jobsByFQN.get(jobKey); + TriggerWrapper tw = (TriggerWrapper) triggersByFQN.get(TriggerWrapper + .getTriggerNameKey(trigger)); + + // It's possible that the job is null if: + // 1- it was deleted during execution + // 2- RAMJobStore is being used only for volatile jobs / triggers + // from the JDBC job store + if (jw != null) { + JobDetail jd = jw.jobDetail; + + if (jd.isStateful()) { + JobDataMap newData = jobDetail.getJobDataMap(); + if (newData != null) { + newData = (JobDataMap)newData.clone(); + newData.clearDirtyFlag(); + } + jd.setJobDataMap(newData); + blockedJobs.remove(JobWrapper.getJobNameKey(jd)); + ArrayList trigs = getTriggerWrappersForJob(jd.getName(), jd + .getGroup()); + Iterator itr = trigs.iterator(); + while (itr.hasNext()) { + TriggerWrapper ttw = (TriggerWrapper) itr.next(); + if (ttw.state == TriggerWrapper.STATE_BLOCKED) { + ttw.state = TriggerWrapper.STATE_WAITING; + timeTriggers.add(ttw); + } + if (ttw.state == TriggerWrapper.STATE_PAUSED_BLOCKED) { + ttw.state = TriggerWrapper.STATE_PAUSED; + } + } + signaler.signalSchedulingChange(0L); + } + } else { // even if it was deleted, there may be cleanup to do + blockedJobs.remove(JobWrapper.getJobNameKey(jobDetail)); + } + + // check for trigger deleted during execution... + if (tw != null) { + if (triggerInstCode == Trigger.INSTRUCTION_DELETE_TRIGGER) { + + if(trigger.getNextFireTime() == null) { + // double check for possible reschedule within job + // execution, which would cancel the need to delete... + if(tw.getTrigger().getNextFireTime() == null) { + removeTrigger(ctxt, trigger.getName(), trigger.getGroup()); + } + } else { + removeTrigger(ctxt, trigger.getName(), trigger.getGroup()); + signaler.signalSchedulingChange(0L); + } + } else if (triggerInstCode == Trigger.INSTRUCTION_SET_TRIGGER_COMPLETE) { + tw.state = TriggerWrapper.STATE_COMPLETE; + timeTriggers.remove(tw); + signaler.signalSchedulingChange(0L); + } else if(triggerInstCode == Trigger.INSTRUCTION_SET_TRIGGER_ERROR) { + getLog().info("Trigger " + trigger.getFullName() + " set to ERROR state."); + tw.state = TriggerWrapper.STATE_ERROR; + signaler.signalSchedulingChange(0L); + } else if (triggerInstCode == Trigger.INSTRUCTION_SET_ALL_JOB_TRIGGERS_ERROR) { + getLog().info("All triggers of Job " + + trigger.getFullJobName() + " set to ERROR state."); + setAllTriggersOfJobToState( + trigger.getJobName(), + trigger.getJobGroup(), + TriggerWrapper.STATE_ERROR); + signaler.signalSchedulingChange(0L); + } else if (triggerInstCode == Trigger.INSTRUCTION_SET_ALL_JOB_TRIGGERS_COMPLETE) { + setAllTriggersOfJobToState( + trigger.getJobName(), + trigger.getJobGroup(), + TriggerWrapper.STATE_COMPLETE); + signaler.signalSchedulingChange(0L); + } + } + } + } + + protected void setAllTriggersOfJobToState(String jobName, String jobGroup, int state) { + ArrayList tws = getTriggerWrappersForJob(jobName, jobGroup); + Iterator itr = tws.iterator(); + while (itr.hasNext()) { + TriggerWrapper tw = (TriggerWrapper) itr.next(); + tw.state = state; + if(state != TriggerWrapper.STATE_WAITING) { + timeTriggers.remove(tw); + } + } + } + + protected String peekTriggers() { + + StringBuffer str = new StringBuffer(); + TriggerWrapper tw = null; + synchronized (triggerLock) { + for (Iterator valueIter = triggersByFQN.values().iterator(); valueIter.hasNext();) { + tw = (TriggerWrapper)valueIter.next(); + str.append(tw.trigger.getName()); + str.append("/"); + } + } + str.append(" | "); + + synchronized (triggerLock) { + Iterator itr = timeTriggers.iterator(); + while (itr.hasNext()) { + tw = (TriggerWrapper) itr.next(); + str.append(tw.trigger.getName()); + str.append("->"); + } + } + + return str.toString(); + } + + /** + * @see com.fr.third.org.quartz.spi.JobStore#getPausedTriggerGroups(com.fr.third.org.quartz.core.SchedulingContext) + */ + public Set getPausedTriggerGroups(SchedulingContext ctxt) throws JobPersistenceException { + HashSet set = new HashSet(); + + set.addAll(pausedTriggerGroups); + + return set; + } + +} + +/******************************************************************************* + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * + * Helper Classes. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + */ + +class TriggerComparator implements Comparator { + + public int compare(Object obj1, Object obj2) { + TriggerWrapper trig1 = (TriggerWrapper) obj1; + TriggerWrapper trig2 = (TriggerWrapper) obj2; + + int comp = trig1.trigger.compareTo(trig2.trigger); + if (comp != 0) { + return comp; + } + + comp = trig2.trigger.getPriority() - trig1.trigger.getPriority(); + if (comp != 0) { + return comp; + } + + return trig1.trigger.getFullName().compareTo(trig2.trigger.getFullName()); + } + + public boolean equals(Object obj) { + return (obj instanceof TriggerComparator); + } +} + +class JobWrapper { + + public String key; + + public JobDetail jobDetail; + + JobWrapper(JobDetail jobDetail) { + this.jobDetail = jobDetail; + key = getJobNameKey(jobDetail); + } + + JobWrapper(JobDetail jobDetail, String key) { + this.jobDetail = jobDetail; + this.key = key; + } + + static String getJobNameKey(JobDetail jobDetail) { + return jobDetail.getGroup() + "_$x$x$_" + jobDetail.getName(); + } + + static String getJobNameKey(String jobName, String groupName) { + return groupName + "_$x$x$_" + jobName; + } + + public boolean equals(Object obj) { + if (obj instanceof JobWrapper) { + JobWrapper jw = (JobWrapper) obj; + if (jw.key.equals(this.key)) { + return true; + } + } + + return false; + } + + public int hashCode() { + return key.hashCode(); + } + + +} + +class TriggerWrapper { + + public String key; + + public String jobKey; + + public Trigger trigger; + + public int state = STATE_WAITING; + + public static final int STATE_WAITING = 0; + + public static final int STATE_ACQUIRED = 1; + + public static final int STATE_EXECUTING = 2; + + public static final int STATE_COMPLETE = 3; + + public static final int STATE_PAUSED = 4; + + public static final int STATE_BLOCKED = 5; + + public static final int STATE_PAUSED_BLOCKED = 6; + + public static final int STATE_ERROR = 7; + + TriggerWrapper(Trigger trigger) { + this.trigger = trigger; + key = getTriggerNameKey(trigger); + this.jobKey = JobWrapper.getJobNameKey(trigger.getJobName(), trigger + .getJobGroup()); + } + + static String getTriggerNameKey(Trigger trigger) { + return trigger.getGroup() + "_$x$x$_" + trigger.getName(); + } + + static String getTriggerNameKey(String triggerName, String groupName) { + return groupName + "_$x$x$_" + triggerName; + } + + public boolean equals(Object obj) { + if (obj instanceof TriggerWrapper) { + TriggerWrapper tw = (TriggerWrapper) obj; + if (tw.key.equals(this.key)) { + return true; + } + } + + return false; + } + + public int hashCode() { + return key.hashCode(); + } + + + public Trigger getTrigger() { + return this.trigger; + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleClassLoadHelper.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleClassLoadHelper.java new file mode 100644 index 000000000..ce32740c8 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleClassLoadHelper.java @@ -0,0 +1,112 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.simpl; + +import com.fr.third.org.quartz.spi.ClassLoadHelper; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Method; +import java.net.URL; +import java.io.InputStream; + +/** + * A ClassLoadHelper that simply calls Class.forName(..). + * + * @see com.fr.third.org.quartz.spi.ClassLoadHelper + * @see com.fr.third.org.quartz.simpl.ThreadContextClassLoadHelper + * @see com.fr.third.org.quartz.simpl.CascadingClassLoadHelper + * @see com.fr.third.org.quartz.simpl.LoadingLoaderClassLoadHelper + * + * @author jhouse + * @author pl47ypus + */ +public class SimpleClassLoadHelper implements ClassLoadHelper { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Called to give the ClassLoadHelper a chance to initialize itself, + * including the opportunity to "steal" the class loader off of the calling + * thread, which is the thread that is initializing Quartz. + */ + public void initialize() { + } + + /** + * Return the class with the given name. + */ + public Class loadClass(String name) throws ClassNotFoundException { + return Class.forName(name); + } + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.net.URL object + */ + public URL getResource(String name) { + return getClassLoader().getResource(name); + } + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.io.InputStream object + */ + public InputStream getResourceAsStream(String name) { + return getClassLoader().getResourceAsStream(name); + } + + /** + * Enable sharing of the class-loader with 3rd party (e.g. digester). + * + * @return the class-loader user be the helper. + */ + public ClassLoader getClassLoader() { + // To follow the same behavior of Class.forName(...) I had to play + // dirty (Supported by Sun, IBM & BEA JVMs) + // ToDo - Test it more. + try { + // Get a reference to this class' class-loader + ClassLoader cl = this.getClass().getClassLoader(); + // Create a method instance representing the protected + // getCallerClassLoader method of class ClassLoader + Method mthd = ClassLoader.class.getDeclaredMethod( + "getCallerClassLoader", new Class[0]); + // Make the method accessible. + AccessibleObject.setAccessible(new AccessibleObject[] {mthd}, true); + // Try to get the caller's class-loader + return (ClassLoader)mthd.invoke(cl, new Object[0]); + } catch (Exception all) { + // Use this class' class-loader + return this.getClass().getClassLoader(); + } + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleClassLoadHelper.java.bak b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleClassLoadHelper.java.bak new file mode 100644 index 000000000..309a0ff0b --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleClassLoadHelper.java.bak @@ -0,0 +1,106 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package org.quartz.simpl; + +import org.quartz.spi.ClassLoadHelper; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Method; +import java.net.URL; +import java.io.InputStream; + +/** + * A ClassLoadHelper that simply calls Class.forName(..). + * + * @see org.quartz.spi.ClassLoadHelper + * @see org.quartz.simpl.ThreadContextClassLoadHelper + * @see org.quartz.simpl.CascadingClassLoadHelper + * @see org.quartz.simpl.LoadingLoaderClassLoadHelper + * + * @author jhouse + */ +public class SimpleClassLoadHelper implements ClassLoadHelper { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Called to give the ClassLoadHelper a chance to initialize itself, + * including the oportunity to "steal" the class loader off of the calling + * thread, which is the thread that is initializing Quartz. + */ + public void initialize() { + } + + /** + * Return the class with the given name. + */ + public Class loadClass(String name) throws ClassNotFoundException { + return Class.forName(name); + } + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.net.URL object + */ + public URL getResource(String name) { + return getClassLoader().getResource(name); + } + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.io.InputStream object + */ + public InputStream getResourceAsStream(String name) { + return getClassLoader().getResourceAsStream(name); + } + + private ClassLoader getClassLoader() { + // To follow the same behavior of Class.forName(...) I had to play + // dirty (Supported by Sun, IBM & BEA JVMs) + // ToDo - Test it more. + try { + // Get a reference to this class' class-loader + ClassLoader cl = this.getClass().getClassLoader(); + // Create a method instance represnting the protected + // getCallerClassLoader method of class ClassLoader + Method mthd = ClassLoader.class.getDeclaredMethod( + "getCallerClassLoader", new Class[0]); + // Make the method accessible. + AccessibleObject.setAccessible(new AccessibleObject[] {mthd}, true); + // Try to get the caller's class-loader + return (ClassLoader)mthd.invoke(cl, new Object[0]); + } catch (Exception all) { + // Use this class' class-loader + return this.getClass().getClassLoader(); + } + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleInstanceIdGenerator.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleInstanceIdGenerator.java new file mode 100644 index 000000000..a64e924a0 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleInstanceIdGenerator.java @@ -0,0 +1,39 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ +package com.fr.third.org.quartz.simpl; + +import java.net.InetAddress; + +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.spi.InstanceIdGenerator; + +/** + * The default InstanceIdGenerator used by Quartz when instance id is to be + * automatically generated. Instance id is of the form HOSTNAME + CURRENT_TIME. + * + * @see InstanceIdGenerator + * @see HostnameInstanceIdGenerator + */ +public class SimpleInstanceIdGenerator implements InstanceIdGenerator { + public String generateInstanceId() throws SchedulerException { + try { + return InetAddress.getLocalHost().getHostName() + System.currentTimeMillis(); + } catch (Exception e) { + throw new SchedulerException("Couldn't get host name!", e); + } + } +} \ No newline at end of file diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleJobFactory.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleJobFactory.java new file mode 100644 index 000000000..f3c81ff8e --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleJobFactory.java @@ -0,0 +1,63 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ +package com.fr.third.org.quartz.simpl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.Job; +import com.fr.third.org.quartz.JobDetail; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.spi.JobFactory; +import com.fr.third.org.quartz.spi.TriggerFiredBundle; + +/** + * The default JobFactory used by Quartz - simply calls + * newInstance() on the job class. + * + * @see JobFactory + * @see PropertySettingJobFactory + * + * @author jhouse + */ +public class SimpleJobFactory implements JobFactory { + + private final Log log = LogFactory.getLog(getClass()); + + protected Log getLog() { + return log; + } + + public Job newJob(TriggerFiredBundle bundle) throws SchedulerException { + + JobDetail jobDetail = bundle.getJobDetail(); + Class jobClass = jobDetail.getJobClass(); + try { + if(log.isDebugEnabled()) { + log.debug( + "Producing instance of Job '" + jobDetail.getFullName() + + "', class=" + jobClass.getName()); + } + + return (Job) jobClass.newInstance(); + } catch (Exception e) { + SchedulerException se = new SchedulerException( + "Problem instantiating class '" + + jobDetail.getJobClass().getName() + "'", e); + throw se; + } + } +} \ No newline at end of file diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleThreadPool.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleThreadPool.java new file mode 100644 index 000000000..d7f0befff --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleThreadPool.java @@ -0,0 +1,576 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.simpl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.SchedulerConfigException; +import com.fr.third.org.quartz.spi.ThreadPool; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** + *

+ * This is class is a simple implementation of a thread pool, based on the + * {@link com.fr.third.org.quartz.spi.ThreadPool} interface. + *

+ * + *

+ * Runnable objects are sent to the pool with the {@link #runInThread(Runnable)} + * method, which blocks until a Thread becomes available. + *

+ * + *

+ * The pool has a fixed number of Threads, and does not grow or + * shrink based on demand. + *

+ * + * @author James House + * @author Juergen Donnerstag + */ +public class SimpleThreadPool implements ThreadPool { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private int count = -1; + + private int prio = Thread.NORM_PRIORITY; + + private boolean isShutdown = false; + private boolean handoffPending = false; + + private boolean inheritLoader = false; + + private boolean inheritGroup = true; + + private boolean makeThreadsDaemons = false; + + private ThreadGroup threadGroup; + + private final Object nextRunnableLock = new Object(); + + private List workers; + private LinkedList availWorkers = new LinkedList(); + private LinkedList busyWorkers = new LinkedList(); + + private String threadNamePrefix = "SimpleThreadPoolWorker"; + + private final Log log = LogFactory.getLog(getClass()); + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Create a new (unconfigured) SimpleThreadPool. + *

+ * + * @see #setThreadCount(int) + * @see #setThreadPriority(int) + */ + public SimpleThreadPool() { + } + + /** + *

+ * Create a new SimpleThreadPool with the specified number + * of Thread s that have the given priority. + *

+ * + * @param threadCount + * the number of worker Threads in the pool, must + * be > 0. + * @param threadPriority + * the thread priority for the worker threads. + * + * @see java.lang.Thread + */ + public SimpleThreadPool(int threadCount, int threadPriority) { + setThreadCount(threadCount); + setThreadPriority(threadPriority); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public Log getLog() { + return log; + } + + public int getPoolSize() { + return getThreadCount(); + } + + /** + *

+ * Set the number of worker threads in the pool - has no effect after + * initialize() has been called. + *

+ */ + public void setThreadCount(int count) { + this.count = count; + } + + /** + *

+ * Get the number of worker threads in the pool. + *

+ */ + public int getThreadCount() { + return count; + } + + /** + *

+ * Set the thread priority of worker threads in the pool - has no effect + * after initialize() has been called. + *

+ */ + public void setThreadPriority(int prio) { + this.prio = prio; + } + + /** + *

+ * Get the thread priority of worker threads in the pool. + *

+ */ + public int getThreadPriority() { + return prio; + } + + public void setThreadNamePrefix(String prfx) { + this.threadNamePrefix = prfx; + } + + public String getThreadNamePrefix() { + return threadNamePrefix; + } + + /** + * @return Returns the + * threadsInheritContextClassLoaderOfInitializingThread. + */ + public boolean isThreadsInheritContextClassLoaderOfInitializingThread() { + return inheritLoader; + } + + /** + * @param inheritLoader + * The threadsInheritContextClassLoaderOfInitializingThread to + * set. + */ + public void setThreadsInheritContextClassLoaderOfInitializingThread( + boolean inheritLoader) { + this.inheritLoader = inheritLoader; + } + + public boolean isThreadsInheritGroupOfInitializingThread() { + return inheritGroup; + } + + public void setThreadsInheritGroupOfInitializingThread( + boolean inheritGroup) { + this.inheritGroup = inheritGroup; + } + + + /** + * @return Returns the value of makeThreadsDaemons. + */ + public boolean isMakeThreadsDaemons() { + return makeThreadsDaemons; + } + + /** + * @param makeThreadsDaemons + * The value of makeThreadsDaemons to set. + */ + public void setMakeThreadsDaemons(boolean makeThreadsDaemons) { + this.makeThreadsDaemons = makeThreadsDaemons; + } + + public void initialize() throws SchedulerConfigException { + + if (count <= 0) { + throw new SchedulerConfigException( + "Thread count must be > 0"); + } + if (prio <= 0 || prio > 9) { + throw new SchedulerConfigException( + "Thread priority must be > 0 and <= 9"); + } + + if(isThreadsInheritGroupOfInitializingThread()) { + threadGroup = Thread.currentThread().getThreadGroup(); + } else { + // follow the threadGroup tree to the root thread group. + threadGroup = Thread.currentThread().getThreadGroup(); + ThreadGroup parent = threadGroup; + while ( !parent.getName().equals("main") ) { + threadGroup = parent; + parent = threadGroup.getParent(); + } + threadGroup = new ThreadGroup(parent, "SimpleThreadPool"); + if (isMakeThreadsDaemons()) { + threadGroup.setDaemon(true); + } + } + + + if (isThreadsInheritContextClassLoaderOfInitializingThread()) { + getLog().info( + "Job execution threads will use class loader of thread: " + + Thread.currentThread().getName()); + } + + // create the worker threads and start them + Iterator workerThreads = createWorkerThreads(count).iterator(); + while(workerThreads.hasNext()) { + WorkerThread wt = (WorkerThread) workerThreads.next(); + wt.start(); + availWorkers.add(wt); + } + } + + protected List createWorkerThreads(int count) { + workers = new LinkedList(); + for (int i = 1; i<= count; ++i) { + WorkerThread wt = new WorkerThread(this, threadGroup, + getThreadNamePrefix() + "-" + i, + getThreadPriority(), + isMakeThreadsDaemons()); + if (isThreadsInheritContextClassLoaderOfInitializingThread()) { + wt.setContextClassLoader(Thread.currentThread() + .getContextClassLoader()); + } + workers.add(wt); + } + + return workers; + } + + /** + *

+ * Terminate any worker threads in this thread group. + *

+ * + *

+ * Jobs currently in progress will complete. + *

+ */ + public void shutdown() { + shutdown(true); + } + + /** + *

+ * Terminate any worker threads in this thread group. + *

+ * + *

+ * Jobs currently in progress will complete. + *

+ */ + public void shutdown(boolean waitForJobsToComplete) { + + synchronized (nextRunnableLock) { + isShutdown = true; + + // signal each worker thread to shut down + Iterator workerThreads = workers.iterator(); + while(workerThreads.hasNext()) { + WorkerThread wt = (WorkerThread) workerThreads.next(); + wt.shutdown(); + availWorkers.remove(wt); + } + + // Give waiting (wait(1000)) worker threads a chance to shut down. + // Active worker threads will shut down after finishing their + // current job. + nextRunnableLock.notifyAll(); + + if (waitForJobsToComplete == true) { + + // wait for hand-off in runInThread to complete... + while(handoffPending) { + try { nextRunnableLock.wait(100); } catch(Throwable t) {} + } + + // Wait until all worker threads are shut down + while (busyWorkers.size() > 0) { + WorkerThread wt = (WorkerThread) busyWorkers.getFirst(); + try { + getLog().debug( + "Waiting for thread " + wt.getName() + + " to shut down"); + + // note: with waiting infinite time the + // application may appear to 'hang'. + nextRunnableLock.wait(2000); + } catch (InterruptedException ex) { + } + } + + getLog().debug("shutdown complete"); + } + } + } + + /** + *

+ * Run the given Runnable object in the next available + * Thread. If while waiting the thread pool is asked to + * shut down, the Runnable is executed immediately within a new additional + * thread. + *

+ * + * @param runnable + * the Runnable to be added. + */ + public boolean runInThread(Runnable runnable) { + if (runnable == null) { + return false; + } + + synchronized (nextRunnableLock) { + + handoffPending = true; + + // Wait until a worker thread is available + while ((availWorkers.size() < 1) && !isShutdown) { + try { + nextRunnableLock.wait(500); + } catch (InterruptedException ignore) { + } + } + + if (!isShutdown) { + WorkerThread wt = (WorkerThread)availWorkers.removeFirst(); + busyWorkers.add(wt); + wt.run(runnable); + } else { + // If the thread pool is going down, execute the Runnable + // within a new additional worker thread (no thread from the pool). + WorkerThread wt = new WorkerThread(this, threadGroup, + "WorkerThread-LastJob", prio, isMakeThreadsDaemons(), runnable); + busyWorkers.add(wt); + workers.add(wt); + wt.start(); + } + nextRunnableLock.notifyAll(); + handoffPending = false; + } + + return true; + } + + public int blockForAvailableThreads() { + synchronized(nextRunnableLock) { + + while((availWorkers.size() < 1 || handoffPending) && !isShutdown) { + try { + nextRunnableLock.wait(500); + } catch (InterruptedException ignore) { + } + } + + return availWorkers.size(); + } + } + + protected void makeAvailable(WorkerThread wt) { + synchronized(nextRunnableLock) { + if(!isShutdown) { + availWorkers.add(wt); + } + busyWorkers.remove(wt); + nextRunnableLock.notifyAll(); + } + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * WorkerThread Class. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * A Worker loops, waiting to execute tasks. + *

+ */ + class WorkerThread extends Thread { + + // A flag that signals the WorkerThread to terminate. + private boolean run = true; + + private SimpleThreadPool tp; + + private Runnable runnable = null; + + /** + *

+ * Create a worker thread and start it. Waiting for the next Runnable, + * executing it, and waiting for the next Runnable, until the shutdown + * flag is set. + *

+ */ + WorkerThread(SimpleThreadPool tp, ThreadGroup threadGroup, String name, + int prio, boolean isDaemon) { + + this(tp, threadGroup, name, prio, isDaemon, null); + } + + /** + *

+ * Create a worker thread, start it, execute the runnable and terminate + * the thread (one time execution). + *

+ */ + WorkerThread(SimpleThreadPool tp, ThreadGroup threadGroup, String name, + int prio, boolean isDaemon, Runnable runnable) { + + super(threadGroup, name); + this.tp = tp; + this.runnable = runnable; + setPriority(prio); + setDaemon(isDaemon); + } + + /** + *

+ * Signal the thread that it should terminate. + *

+ */ + void shutdown() { + synchronized (this) { + run = false; + } + } + + public void run(Runnable newRunnable) { + synchronized(this) { + if(runnable != null) { + throw new IllegalStateException("Already running a Runnable!"); + } + + runnable = newRunnable; + this.notifyAll(); + } + } + + /** + *

+ * Loop, executing targets as they are received. + *

+ */ + public void run() { + boolean ran = false; + boolean runOnce = false; + boolean shouldRun = false; + synchronized(this) { + runOnce = (runnable != null); + shouldRun = run; + } + + while (shouldRun) { + try { + synchronized(this) { + while (runnable == null && run) { + this.wait(500); + } + } + + if (runnable != null) { + ran = true; + runnable.run(); + } + } catch (InterruptedException unblock) { + // do nothing (loop will terminate if shutdown() was called + try { + getLog().error("worker threat got 'interrupt'ed.", unblock); + } catch(Exception e) { + // ignore to help with a tomcat glitch + } + } catch (Exception exceptionInRunnable) { + try { + getLog().error("Error while executing the Runnable: ", + exceptionInRunnable); + } catch(Exception e) { + // ignore to help with a tomcat glitch + } + } finally { + synchronized(this) { + runnable = null; + } + // repair the thread in case the runnable mucked it up... + if(getPriority() != tp.getThreadPriority()) { + setPriority(tp.getThreadPriority()); + } + + if (runOnce) { + synchronized(this) { + run = false; + } + } else if(ran) { + ran = false; + makeAvailable(this); + } + + } + + // read value of run within synchronized block to be + // sure of its value + synchronized(this) { + shouldRun = run; + } + } + + //if (log.isDebugEnabled()) + try { + getLog().debug("WorkerThread is shutting down"); + } catch(Exception e) { + // ignore to help with a tomcat glitch + } + } + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleTimeBroker.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleTimeBroker.java new file mode 100644 index 000000000..a9e811387 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/SimpleTimeBroker.java @@ -0,0 +1,76 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.simpl; + +import java.util.Date; + +import com.fr.third.org.quartz.SchedulerConfigException; +import com.fr.third.org.quartz.spi.TimeBroker; + +/** + *

+ * The interface to be implemented by classes that want to provide a mechanism + * by which the {@link com.fr.third.org.quartz.core.QuartzScheduler} can + * reliably determine the current time. + *

+ * + *

+ * In general, the default implementation of this interface ({@link com.fr.third.org.quartz.simpl.SimpleTimeBroker}- + * which simply uses System.getCurrentTimeMillis() )is + * sufficient. However situations may exist where this default scheme is + * lacking in its robustsness - especially when Quartz is used in a clustered + * configuration. For example, if one or more of the machines in the cluster + * has a system time that varies by more than a few seconds from the clocks on + * the other systems in the cluster, scheduling confusion will result. + *

+ * + * @see com.fr.third.org.quartz.core.QuartzScheduler + * + * @author James House + */ +public class SimpleTimeBroker implements TimeBroker { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Get the current time, simply using new Date(). + *

+ */ + public Date getCurrentTime() { + return new Date(); + } + + public void initialize() throws SchedulerConfigException { + // do nothing... + } + + public void shutdown() { + // do nothing... + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/ThreadContextClassLoadHelper.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/ThreadContextClassLoadHelper.java new file mode 100644 index 000000000..159889467 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/ThreadContextClassLoadHelper.java @@ -0,0 +1,94 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.simpl; + +import com.fr.third.org.quartz.spi.ClassLoadHelper; + +import java.net.URL; +import java.io.InputStream; + +/** + * A ClassLoadHelper that uses either the current thread's + * context class loader (Thread.currentThread().getContextClassLoader().loadClass( .. )). + * + * @see com.fr.third.org.quartz.spi.ClassLoadHelper + * @see com.fr.third.org.quartz.simpl.InitThreadContextClassLoadHelper + * @see com.fr.third.org.quartz.simpl.SimpleClassLoadHelper + * @see com.fr.third.org.quartz.simpl.CascadingClassLoadHelper + * @see com.fr.third.org.quartz.simpl.LoadingLoaderClassLoadHelper + * + * @author jhouse + * @author pl47ypus + */ +public class ThreadContextClassLoadHelper implements ClassLoadHelper { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Called to give the ClassLoadHelper a chance to initialize itself, + * including the opportunity to "steal" the class loader off of the calling + * thread, which is the thread that is initializing Quartz. + */ + public void initialize() { + } + + /** + * Return the class with the given name. + */ + public Class loadClass(String name) throws ClassNotFoundException { + return getClassLoader().loadClass(name); + } + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.net.URL object + */ + public URL getResource(String name) { + return getClassLoader().getResource(name); + } + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.io.InputStream object + */ + public InputStream getResourceAsStream(String name) { + return getClassLoader().getResourceAsStream(name); + } + + /** + * Enable sharing of the class-loader with 3rd party (e.g. digester). + * + * @return the class-loader user be the helper. + */ + public ClassLoader getClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/ThreadContextClassLoadHelper.java.bak b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/ThreadContextClassLoadHelper.java.bak new file mode 100644 index 000000000..80a9a3f7d --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/ThreadContextClassLoadHelper.java.bak @@ -0,0 +1,89 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package org.quartz.simpl; + +import org.quartz.spi.ClassLoadHelper; + +import java.net.URL; +import java.io.InputStream; + +/** + * A ClassLoadHelper that uses either the current thread's + * context class loader (Thread.currentThread().getContextClassLoader().loadClass( .. )). + * + * @see org.quartz.spi.ClassLoadHelper + * @see org.quartz.simpl.InitThreadContextClassLoadHelper + * @see org.quartz.simpl.SimpleClassLoadHelper + * @see org.quartz.simpl.CascadingClassLoadHelper + * @see org.quartz.simpl.LoadingLoaderClassLoadHelper + * + * @author jhouse + */ +public class ThreadContextClassLoadHelper implements ClassLoadHelper { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Called to give the ClassLoadHelper a chance to initialize itself, + * including the oportunity to "steal" the class loader off of the calling + * thread, which is the thread that is initializing Quartz. + */ + public void initialize() { + } + + /** + * Return the class with the given name. + */ + public Class loadClass(String name) throws ClassNotFoundException { + return getClassLoader().loadClass(name); + } + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.net.URL object + */ + public URL getResource(String name) { + return getClassLoader().getResource(name); + } + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.io.InputStream object + */ + public InputStream getResourceAsStream(String name) { + return getClassLoader().getResourceAsStream(name); + } + + + private ClassLoader getClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/ZeroSizeThreadPool.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/ZeroSizeThreadPool.java new file mode 100644 index 000000000..ed11a20fa --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/ZeroSizeThreadPool.java @@ -0,0 +1,111 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.simpl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.fr.third.org.quartz.SchedulerConfigException; +import com.fr.third.org.quartz.spi.ThreadPool; + +/** + *

+ * This is class is a simple implementation of a zero size thread pool, based on the + * {@link com.fr.third.org.quartz.spi.ThreadPool} interface. + *

+ * + *

+ * The pool has zero Threads and does not grow or shrink based on demand. + * Which means it is obviously not useful for most scenarios. When it may be useful + * is to prevent creating any worker threads at all - which may be desirable for + * the sole purpose of preserving system resources in the case where the scheduler + * instance only exists in order to schedule jobs, but which will never execute + * jobs (e.g. will never have start() called on it). + *

+ * + *

+ *

+ * + * @author Wayne Fay + */ +public class ZeroSizeThreadPool implements ThreadPool { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private final Log log = LogFactory.getLog(getClass()); + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Create a new ZeroSizeThreadPool. + *

+ */ + public ZeroSizeThreadPool() { + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public Log getLog() { + return log; + } + + public int getPoolSize() { + return 0; + } + + public void initialize() throws SchedulerConfigException { + } + + public void shutdown() { + shutdown(true); + } + + public void shutdown(boolean waitForJobsToComplete) { + getLog().debug("shutdown complete"); + } + + public boolean runInThread(Runnable runnable) { + throw new UnsupportedOperationException("This ThreadPool should not be used on Scheduler instances that are start()ed."); + } + + public int blockForAvailableThreads() { + throw new UnsupportedOperationException("This ThreadPool should not be used on Scheduler instances that are start()ed."); + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/package.html b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/package.html new file mode 100644 index 000000000..6d42d7293 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/simpl/package.html @@ -0,0 +1,17 @@ + + +Package com.fr.third.org.quartz.simpl + + +

Contains simple / light-weight implementations (with no dependencies on +external libraries) of interfaces required by the +com.fr.third.org.quartz.core.QuartzScheduler.

+ +
+
+
+See the Quartz project + at Open Symphony for more information. + + + diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/ClassLoadHelper.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/ClassLoadHelper.java new file mode 100644 index 000000000..b228cf003 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/ClassLoadHelper.java @@ -0,0 +1,75 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.spi; + +import java.net.URL; +import java.io.InputStream; + +/** + * An interface for classes wishing to provide the service of loading classes + * and resources within the scheduler... + * + * @author jhouse + * @author pl47ypus + */ +public interface ClassLoadHelper { + + /** + * Called to give the ClassLoadHelper a chance to initialize itself, + * including the opportunity to "steal" the class loader off of the calling + * thread, which is the thread that is initializing Quartz. + */ + void initialize(); + + /** + * Return the class with the given name. + * + * @param name the fqcn of the class to load. + * @return the requested class. + * @throws ClassNotFoundException if the class can be found in the classpath. + */ + Class loadClass(String name) throws ClassNotFoundException; + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * + * @param name name of the desired resource + * @return a java.net.URL object + */ + URL getResource(String name); + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * + * @param name name of the desired resource + * @return a java.io.InputStream object + */ + InputStream getResourceAsStream(String name); + + /** + * Enable sharing of the class-loader with 3rd party (e.g. digester). + * + * @return the class-loader user be the helper. + */ + ClassLoader getClassLoader(); +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/ClassLoadHelper.java.bak b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/ClassLoadHelper.java.bak new file mode 100644 index 000000000..63a5930f3 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/ClassLoadHelper.java.bak @@ -0,0 +1,69 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package org.quartz.spi; + +import java.net.URL; +import java.io.InputStream; + +/** + * An interface for classes wishing to provide the service of loading classes + * and resources within the scheduler... + * + * @author jhouse + */ +public interface ClassLoadHelper { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Called to give the ClassLoadHelper a chance to initialize itself, + * including the oportunity to "steal" the class loader off of the calling + * thread, which is the thread that is initializing Quartz. + */ + void initialize(); + + /** + * Return the class with the given name. + */ + Class loadClass(String name) throws ClassNotFoundException; + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.net.URL object + */ + URL getResource(String name); + + /** + * Finds a resource with a given name. This method returns null if no + * resource with this name is found. + * @param name name of the desired resource + * @return a java.io.InputStream object + */ + InputStream getResourceAsStream(String name); +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/InstanceIdGenerator.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/InstanceIdGenerator.java new file mode 100644 index 000000000..6b8775b49 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/InstanceIdGenerator.java @@ -0,0 +1,42 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ +package com.fr.third.org.quartz.spi; + +import com.fr.third.org.quartz.SchedulerException; + +/** + *

+ * An InstanceIdGenerator is responsible for generating the clusterwide unique + * instance id for a Scheduler node. + *

+ * + *

+ * This interface may be of use to those wishing to have specific control over + * the mechanism by which the Scheduler instances in their + * application are named. + *

+ * + * @see com.fr.third.org.quartz.simpl.SimpleInstanceIdGenerator + */ +public interface InstanceIdGenerator { + /** + * Generate the instance id for a Scheduler + * + * @return The clusterwide unique instance id. + */ + String generateInstanceId() throws SchedulerException; +} \ No newline at end of file diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/JobFactory.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/JobFactory.java new file mode 100644 index 000000000..97dd71501 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/JobFactory.java @@ -0,0 +1,64 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ +package com.fr.third.org.quartz.spi; + +import com.fr.third.org.quartz.Job; +import com.fr.third.org.quartz.SchedulerException; + +/** + *

+ * A JobFactory is responsible for producing instances of Job + * classes. + *

+ * + *

+ * This interface may be of use to those wishing to have their application + * produce Job instances via some special mechanism, such as to + * give the opertunity for dependency injection. + *

+ * + * @see com.fr.third.org.quartz.Scheduler#setJobFactory(JobFactory) + * @see com.fr.third.org.quartz.simpl.SimpleJobFactory + * @see com.fr.third.org.quartz.simpl.PropertySettingJobFactory + * + * @author James House + */ +public interface JobFactory { + + /** + * Called by the scheduler at the time of the trigger firing, in order to + * produce a Job instance on which to call execute. + * + *

+ * It should be extremely rare for this method to throw an exception - + * basically only the the case where there is no way at all to instantiate + * and prepare the Job for execution. When the exception is thrown, the + * Scheduler will move all triggers associated with the Job into the + * Trigger.STATE_ERROR state, which will require human + * intervention (e.g. an application restart after fixing whatever + * configuration problem led to the issue wih instantiating the Job. + *

+ * + * @param bundle + * The TriggerFiredBundle from which the JobDetail + * and other info relating to the trigger firing can be obtained. + * @throws SchedulerException if there is a problem instantiating the Job. + * @return the newly instantiated Job + */ + Job newJob(TriggerFiredBundle bundle) throws SchedulerException; + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/JobStore.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/JobStore.java new file mode 100644 index 000000000..a33de56f3 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/JobStore.java @@ -0,0 +1,664 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.spi; + +import java.util.Set; + +import com.fr.third.org.quartz.Calendar; +import com.fr.third.org.quartz.JobDetail; +import com.fr.third.org.quartz.JobPersistenceException; +import com.fr.third.org.quartz.ObjectAlreadyExistsException; +import com.fr.third.org.quartz.SchedulerConfigException; +import com.fr.third.org.quartz.SchedulerException; +import com.fr.third.org.quartz.Trigger; +import com.fr.third.org.quartz.core.SchedulingContext; + +/** + *

+ * The interface to be implemented by classes that want to provide a {@link com.fr.third.org.quartz.Job} + * and {@link com.fr.third.org.quartz.Trigger} storage mechanism for the + * {@link com.fr.third.org.quartz.core.QuartzScheduler}'s use. + *

+ * + *

+ * Storage of Job s and Trigger s should be keyed + * on the combination of their name and group for uniqueness. + *

+ * + * @see com.fr.third.org.quartz.core.QuartzScheduler + * @see com.fr.third.org.quartz.Trigger + * @see com.fr.third.org.quartz.Job + * @see com.fr.third.org.quartz.JobDetail + * @see com.fr.third.org.quartz.JobDataMap + * @see com.fr.third.org.quartz.Calendar + * + * @author James House + * @author Eric Mueller + */ +public interface JobStore { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Called by the QuartzScheduler before the JobStore is + * used, in order to give the it a chance to initialize. + *

+ */ + void initialize(ClassLoadHelper loadHelper, + SchedulerSignaler signaler) throws SchedulerConfigException; + + /** + *

+ * Called by the QuartzScheduler to inform the JobStore that + * the scheduler has started. + *

+ */ + void schedulerStarted() throws SchedulerException ; + + /** + *

+ * Called by the QuartzScheduler to inform the JobStore that + * it should free up all of it's resources because the scheduler is + * shutting down. + *

+ */ + void shutdown(); + + boolean supportsPersistence(); + + ///////////////////////////////////////////////////////////////////////////// + // + // Job & Trigger Storage methods + // + ///////////////////////////////////////////////////////////////////////////// + + /** + *

+ * Store the given {@link com.fr.third.org.quartz.JobDetail} and {@link com.fr.third.org.quartz.Trigger}. + *

+ * + * @param newJob + * The JobDetail to be stored. + * @param newTrigger + * The Trigger to be stored. + * @throws ObjectAlreadyExistsException + * if a Job with the same name/group already + * exists. + */ + void storeJobAndTrigger(SchedulingContext ctxt, JobDetail newJob, + Trigger newTrigger) throws ObjectAlreadyExistsException, + JobPersistenceException; + + /** + *

+ * Store the given {@link com.fr.third.org.quartz.JobDetail}. + *

+ * + * @param newJob + * The JobDetail to be stored. + * @param replaceExisting + * If true, any Job existing in the + * JobStore with the same name & group should be + * over-written. + * @throws ObjectAlreadyExistsException + * if a Job with the same name/group already + * exists, and replaceExisting is set to false. + */ + void storeJob(SchedulingContext ctxt, JobDetail newJob, + boolean replaceExisting) throws ObjectAlreadyExistsException, + JobPersistenceException; + + /** + *

+ * Remove (delete) the {@link com.fr.third.org.quartz.Job} with the given + * name, and any {@link com.fr.third.org.quartz.Trigger} s that reference + * it. + *

+ * + *

+ * If removal of the Job results in an empty group, the + * group should be removed from the JobStore's list of + * known group names. + *

+ * + * @param jobName + * The name of the Job to be removed. + * @param groupName + * The group name of the Job to be removed. + * @return true if a Job with the given name & + * group was found and removed from the store. + */ + boolean removeJob(SchedulingContext ctxt, String jobName, + String groupName) throws JobPersistenceException; + + /** + *

+ * Retrieve the {@link com.fr.third.org.quartz.JobDetail} for the given + * {@link com.fr.third.org.quartz.Job}. + *

+ * + * @param jobName + * The name of the Job to be retrieved. + * @param groupName + * The group name of the Job to be retrieved. + * @return The desired Job, or null if there is no match. + */ + JobDetail retrieveJob(SchedulingContext ctxt, String jobName, + String groupName) throws JobPersistenceException; + + /** + *

+ * Store the given {@link com.fr.third.org.quartz.Trigger}. + *

+ * + * @param newTrigger + * The Trigger to be stored. + * @param replaceExisting + * If true, any Trigger existing in + * the JobStore with the same name & group should + * be over-written. + * @throws ObjectAlreadyExistsException + * if a Trigger with the same name/group already + * exists, and replaceExisting is set to false. + * + * @see #pauseTriggerGroup(SchedulingContext, String) + */ + void storeTrigger(SchedulingContext ctxt, Trigger newTrigger, + boolean replaceExisting) throws ObjectAlreadyExistsException, + JobPersistenceException; + + /** + *

+ * Remove (delete) the {@link com.fr.third.org.quartz.Trigger} with the + * given name. + *

+ * + *

+ * If removal of the Trigger results in an empty group, the + * group should be removed from the JobStore's list of + * known group names. + *

+ * + *

+ * If removal of the Trigger results in an 'orphaned' Job + * that is not 'durable', then the Job should be deleted + * also. + *

+ * + * @param triggerName + * The name of the Trigger to be removed. + * @param groupName + * The group name of the Trigger to be removed. + * @return true if a Trigger with the given + * name & group was found and removed from the store. + */ + boolean removeTrigger(SchedulingContext ctxt, String triggerName, + String groupName) throws JobPersistenceException; + + /** + *

+ * Remove (delete) the {@link com.fr.third.org.quartz.Trigger} with the + * given name, and store the new given one - which must be associated + * with the same job. + *

+ * + * @param triggerName + * The name of the Trigger to be removed. + * @param groupName + * The group name of the Trigger to be removed. + * @param newTrigger + * The new Trigger to be stored. + * @return true if a Trigger with the given + * name & group was found and removed from the store. + */ + boolean replaceTrigger(SchedulingContext ctxt, String triggerName, + String groupName, Trigger newTrigger) throws JobPersistenceException; + + + /** + *

+ * Retrieve the given {@link com.fr.third.org.quartz.Trigger}. + *

+ * + * @param triggerName + * The name of the Trigger to be retrieved. + * @param groupName + * The group name of the Trigger to be retrieved. + * @return The desired Trigger, or null if there is no + * match. + */ + Trigger retrieveTrigger(SchedulingContext ctxt, String triggerName, + String groupName) throws JobPersistenceException; + + /** + *

+ * Store the given {@link com.fr.third.org.quartz.Calendar}. + *

+ * + * @param calendar + * The Calendar to be stored. + * @param replaceExisting + * If true, any Calendar existing + * in the JobStore with the same name & group + * should be over-written. + * @param updateTriggers + * If true, any Triggers existing + * in the JobStore that reference an existing + * Calendar with the same name with have their next fire time + * re-computed with the new Calendar. + * @throws ObjectAlreadyExistsException + * if a Calendar with the same name already + * exists, and replaceExisting is set to false. + */ + void storeCalendar(SchedulingContext ctxt, String name, + Calendar calendar, boolean replaceExisting, boolean updateTriggers) + throws ObjectAlreadyExistsException, JobPersistenceException; + + /** + *

+ * Remove (delete) the {@link com.fr.third.org.quartz.Calendar} with the + * given name. + *

+ * + *

+ * If removal of the Calendar would result in + * s pointing to non-existent calendars, then a + * JobPersistenceException will be thrown.

+ * * + * @param calName The name of the Calendar to be removed. + * @return true if a Calendar with the given name + * was found and removed from the store. + */ + boolean removeCalendar(SchedulingContext ctxt, String calName) + throws JobPersistenceException; + + /** + *

+ * Retrieve the given {@link com.fr.third.org.quartz.Trigger}. + *

+ * + * @param calName + * The name of the Calendar to be retrieved. + * @return The desired Calendar, or null if there is no + * match. + */ + Calendar retrieveCalendar(SchedulingContext ctxt, String calName) + throws JobPersistenceException; + + ///////////////////////////////////////////////////////////////////////////// + // + // Informational methods + // + ///////////////////////////////////////////////////////////////////////////// + + /** + *

+ * Get the number of {@link com.fr.third.org.quartz.Job} s that are + * stored in the JobsStore. + *

+ */ + int getNumberOfJobs(SchedulingContext ctxt) + throws JobPersistenceException; + + /** + *

+ * Get the number of {@link com.fr.third.org.quartz.Trigger} s that are + * stored in the JobsStore. + *

+ */ + int getNumberOfTriggers(SchedulingContext ctxt) + throws JobPersistenceException; + + /** + *

+ * Get the number of {@link com.fr.third.org.quartz.Calendar} s that are + * stored in the JobsStore. + *

+ */ + int getNumberOfCalendars(SchedulingContext ctxt) + throws JobPersistenceException; + + /** + *

+ * Get the names of all of the {@link com.fr.third.org.quartz.Job} s that + * have the given group name. + *

+ * + *

+ * If there are no jobs in the given group name, the result should be a + * zero-length array (not null). + *

+ */ + String[] getJobNames(SchedulingContext ctxt, String groupName) + throws JobPersistenceException; + + /** + *

+ * Get the names of all of the {@link com.fr.third.org.quartz.Trigger} s + * that have the given group name. + *

+ * + *

+ * If there are no triggers in the given group name, the result should be a + * zero-length array (not null). + *

+ */ + String[] getTriggerNames(SchedulingContext ctxt, String groupName) + throws JobPersistenceException; + + /** + *

+ * Get the names of all of the {@link com.fr.third.org.quartz.Job} + * groups. + *

+ * + *

+ * If there are no known group names, the result should be a zero-length + * array (not null). + *

+ */ + String[] getJobGroupNames(SchedulingContext ctxt) + throws JobPersistenceException; + + /** + *

+ * Get the names of all of the {@link com.fr.third.org.quartz.Trigger} + * groups. + *

+ * + *

+ * If there are no known group names, the result should be a zero-length + * array (not null). + *

+ */ + String[] getTriggerGroupNames(SchedulingContext ctxt) + throws JobPersistenceException; + + /** + *

+ * Get the names of all of the {@link com.fr.third.org.quartz.Calendar} s + * in the JobStore. + *

+ * + *

+ * If there are no Calendars in the given group name, the result should be + * a zero-length array (not null). + *

+ */ + String[] getCalendarNames(SchedulingContext ctxt) + throws JobPersistenceException; + + /** + *

+ * Get all of the Triggers that are associated to the given Job. + *

+ * + *

+ * If there are no matches, a zero-length array should be returned. + *

+ */ + Trigger[] getTriggersForJob(SchedulingContext ctxt, String jobName, + String groupName) throws JobPersistenceException; + + /** + *

+ * Get the current state of the identified {@link Trigger}. + *

+ * + * @see Trigger#STATE_NORMAL + * @see Trigger#STATE_PAUSED + * @see Trigger#STATE_COMPLETE + * @see Trigger#STATE_ERROR + * @see Trigger#STATE_NONE + */ + int getTriggerState(SchedulingContext ctxt, String triggerName, + String triggerGroup) throws JobPersistenceException; + + ///////////////////////////////////////////////////////////////////////////// + // + // Trigger State manipulation methods + // + ///////////////////////////////////////////////////////////////////////////// + + /** + *

+ * Pause the {@link com.fr.third.org.quartz.Trigger} with the given name. + *

+ * + * @see #resumeTrigger(SchedulingContext, String, String) + */ + void pauseTrigger(SchedulingContext ctxt, String triggerName, + String groupName) throws JobPersistenceException; + + /** + *

+ * Pause all of the {@link com.fr.third.org.quartz.Trigger}s in the + * given group. + *

+ * + * + *

+ * The JobStore should "remember" that the group is paused, and impose the + * pause on any new triggers that are added to the group while the group is + * paused. + *

+ * + * @see #resumeTriggerGroup(SchedulingContext, String) + */ + void pauseTriggerGroup(SchedulingContext ctxt, String groupName) + throws JobPersistenceException; + + /** + *

+ * Pause the {@link com.fr.third.org.quartz.Job} with the given name - by + * pausing all of its current Triggers. + *

+ * + * @see #resumeJob(SchedulingContext, String, String) + */ + void pauseJob(SchedulingContext ctxt, String jobName, + String groupName) throws JobPersistenceException; + + /** + *

+ * Pause all of the {@link com.fr.third.org.quartz.Job}s in the given + * group - by pausing all of their Triggers. + *

+ * + *

+ * The JobStore should "remember" that the group is paused, and impose the + * pause on any new jobs that are added to the group while the group is + * paused. + *

+ * + * @see #resumeJobGroup(SchedulingContext, String) + */ + void pauseJobGroup(SchedulingContext ctxt, String groupName) + throws JobPersistenceException; + + /** + *

+ * Resume (un-pause) the {@link com.fr.third.org.quartz.Trigger} with the + * given name. + *

+ * + *

+ * If the Trigger missed one or more fire-times, then the + * Trigger's misfire instruction will be applied. + *

+ * + * @see #pauseTrigger(SchedulingContext, String, String) + */ + void resumeTrigger(SchedulingContext ctxt, String triggerName, + String groupName) throws JobPersistenceException; + + /** + *

+ * Resume (un-pause) all of the {@link com.fr.third.org.quartz.Trigger}s + * in the given group. + *

+ * + *

+ * If any Trigger missed one or more fire-times, then the + * Trigger's misfire instruction will be applied. + *

+ * + * @see #pauseTriggerGroup(SchedulingContext, String) + */ + void resumeTriggerGroup(SchedulingContext ctxt, String groupName) + throws JobPersistenceException; + + Set getPausedTriggerGroups(SchedulingContext ctxt) + throws JobPersistenceException; + + + /** + *

+ * Resume (un-pause) the {@link com.fr.third.org.quartz.Job} with the + * given name. + *

+ * + *

+ * If any of the Job'sTrigger s missed one + * or more fire-times, then the Trigger's misfire + * instruction will be applied. + *

+ * + * @see #pauseJob(SchedulingContext, String, String) + */ + void resumeJob(SchedulingContext ctxt, String jobName, + String groupName) throws JobPersistenceException; + + /** + *

+ * Resume (un-pause) all of the {@link com.fr.third.org.quartz.Job}s in + * the given group. + *

+ * + *

+ * If any of the Job s had Trigger s that + * missed one or more fire-times, then the Trigger's + * misfire instruction will be applied. + *

+ * + * @see #pauseJobGroup(SchedulingContext, String) + */ + void resumeJobGroup(SchedulingContext ctxt, String groupName) + throws JobPersistenceException; + + /** + *

+ * Pause all triggers - equivalent of calling pauseTriggerGroup(group) + * on every group. + *

+ * + *

+ * When resumeAll() is called (to un-pause), trigger misfire + * instructions WILL be applied. + *

+ * + * @see #resumeAll(SchedulingContext) + * @see #pauseTriggerGroup(SchedulingContext, String) + */ + void pauseAll(SchedulingContext ctxt) throws JobPersistenceException; + + /** + *

+ * Resume (un-pause) all triggers - equivalent of calling resumeTriggerGroup(group) + * on every group. + *

+ * + *

+ * If any Trigger missed one or more fire-times, then the + * Trigger's misfire instruction will be applied. + *

+ * + * @see #pauseAll(SchedulingContext) + */ + void resumeAll(SchedulingContext ctxt) + throws JobPersistenceException; + + ///////////////////////////////////////////////////////////////////////////// + // + // Trigger-Firing methods + // + ///////////////////////////////////////////////////////////////////////////// + + /** + *

+ * Get a handle to the next trigger to be fired, and mark it as 'reserved' + * by the calling scheduler. + *

+ * + * @param noLaterThan If > 0, the JobStore should only return a Trigger + * that will fire no later than the time represented in this value as + * milliseconds. + * @see #releaseAcquiredTrigger(SchedulingContext, Trigger) + */ + Trigger acquireNextTrigger(SchedulingContext ctxt, long noLaterThan) + throws JobPersistenceException; + + /** + *

+ * Inform the JobStore that the scheduler no longer plans to + * fire the given Trigger, that it had previously acquired + * (reserved). + *

+ */ + void releaseAcquiredTrigger(SchedulingContext ctxt, Trigger trigger) + throws JobPersistenceException; + + /** + *

+ * Inform the JobStore that the scheduler is now firing the + * given Trigger (executing its associated Job), + * that it had previously acquired (reserved). + *

+ * + * @return null if the trigger or it's job or calendar no longer exist, or + * if the trigger was not successfully put into the 'executing' + * state. + */ + TriggerFiredBundle triggerFired(SchedulingContext ctxt, + Trigger trigger) throws JobPersistenceException; + + /** + *

+ * Inform the JobStore that the scheduler has completed the + * firing of the given Trigger (and the execution of its + * associated Job completed, threw an exception, or was vetoed), + * and that the {@link com.fr.third.org.quartz.JobDataMap} + * in the given JobDetail should be updated if the Job + * is stateful. + *

+ */ + void triggeredJobComplete(SchedulingContext ctxt, Trigger trigger, + JobDetail jobDetail, int triggerInstCode) + throws JobPersistenceException; + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/SchedulerPlugin.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/SchedulerPlugin.java new file mode 100644 index 000000000..67749a8c8 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/SchedulerPlugin.java @@ -0,0 +1,110 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.spi; + +import com.fr.third.org.quartz.Scheduler; +import com.fr.third.org.quartz.SchedulerException; + +/** + *

+ * Provides an interface for a class to become a "plugin" to Quartz. + *

+ * + *

+ * Plugins can do virtually anything you wish, though the most interesting ones + * will obviously interact with the scheduler in some way - either actively: by + * invoking actions on the scheduler, or passively: by being a JobListener, + * TriggerListener, and/or SchedulerListener. + *

+ * + *

+ * If you use {@link com.fr.third.org.quartz.impl.StdSchedulerFactory} to + * initialize your Scheduler, it can also create and initialize your plugins - + * look at the configuration docs for details. + *

+ * + *

+ * If you need direct access your plugin, you can have it explicitly put a + * reference to itself in the Scheduler's + * SchedulerContext as part of its + * {@link #initialize(String, Scheduler)} method. + *

+ * + * @author James House + */ +public interface SchedulerPlugin { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Called during creation of the Scheduler in order to give + * the SchedulerPlugin a chance to initialize. + *

+ * + *

+ * At this point, the Scheduler's JobStore is not yet + * initialized. + *

+ * + *

+ * If you need direct access your plugin, for example during Job + * execution, you can have this method explicitly put a + * reference to this plugin in the Scheduler's + * SchedulerContext. + *

+ * + * @param name + * The name by which the plugin is identified. + * @param scheduler + * The scheduler to which the plugin is registered. + * + * @throws com.fr.third.org.quartz.SchedulerConfigException + * if there is an error initializing. + */ + void initialize(String name, Scheduler scheduler) + throws SchedulerException; + + /** + *

+ * Called when the associated Scheduler is started, in order + * to let the plug-in know it can now make calls into the scheduler if it + * needs to. + *

+ */ + void start(); + + /** + *

+ * Called in order to inform the SchedulerPlugin that it + * should free up all of it's resources because the scheduler is shutting + * down. + *

+ */ + void shutdown(); + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/SchedulerSignaler.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/SchedulerSignaler.java new file mode 100644 index 000000000..ee8f58dd9 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/SchedulerSignaler.java @@ -0,0 +1,47 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.spi; + +import com.fr.third.org.quartz.Trigger; + +/** + * An interface to be used by JobStore instances in order to + * communicate signals back to the QuartzScheduler. + * + * @author jhouse + */ +public interface SchedulerSignaler { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + void notifyTriggerListenersMisfired(Trigger trigger); + + void notifySchedulerListenersFinalized(Trigger trigger); + + void signalSchedulingChange(long candidateNewNextFireTime); + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/ThreadPool.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/ThreadPool.java new file mode 100644 index 000000000..29718df8d --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/ThreadPool.java @@ -0,0 +1,105 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.spi; + +import com.fr.third.org.quartz.SchedulerConfigException; + +/** + *

+ * The interface to be implemented by classes that want to provide a thread + * pool for the {@link com.fr.third.org.quartz.core.QuartzScheduler}'s use. + *

+ * + *

+ * ThreadPool implementation instances should ideally be made + * for the sole use of Quartz. Most importantly, when the method + * blockForAvailableThreads() returns a value of 1 or greater, + * there must still be at least one available thread in the pool when the + * method runInThread(Runnable) is called a few moments (or + * many moments) later. If this assumption does not hold true, it may + * result in extra JobStore queries and updates, and if clustering features + * are being used, it may result in greater imballance of load. + *

+ * + * @see com.fr.third.org.quartz.core.QuartzScheduler + * + * @author James House + */ +public interface ThreadPool { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Execute the given {@link java.lang.Runnable} in the next + * available Thread. + *

+ * + *

+ * The implementation of this interface should not throw exceptions unless + * there is a serious problem (i.e. a serious misconfiguration). If there + * are no immediately available threads false should be returned. + *

+ * + * @return true, if the runnable was assigned to run on a Thread. + */ + boolean runInThread(Runnable runnable); + + /** + *

+ * Determines the number of threads that are currently available in in + * the pool. Useful for determining the number of times + * runInThread(Runnable) can be called before returning + * false. + *

+ * + *

The implementation of this method should block until there is at + * least one available thread.

+ * + * @return the number of currently available threads + */ + int blockForAvailableThreads(); + + /** + *

+ * Called by the QuartzScheduler before the ThreadPool is + * used, in order to give the it a chance to initialize. + *

+ */ + void initialize() throws SchedulerConfigException; + + /** + *

+ * Called by the QuartzScheduler to inform the ThreadPool + * that it should free up all of it's resources because the scheduler is + * shutting down. + *

+ */ + void shutdown(boolean waitForJobsToComplete); + + int getPoolSize(); +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/TimeBroker.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/TimeBroker.java new file mode 100644 index 000000000..2899ed537 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/TimeBroker.java @@ -0,0 +1,89 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.spi; + +import java.util.Date; + +import com.fr.third.org.quartz.SchedulerConfigException; +import com.fr.third.org.quartz.SchedulerException; + +/** + *

NOTE: TimeBroker is not currently used in the Quartz code base.

+ * + *

+ * The interface to be implemented by classes that want to provide a mechanism + * by which the {@link com.fr.third.org.quartz.core.QuartzScheduler} can + * reliably determine the current time. + *

+ * + *

+ * In general, the default implementation of this interface ({@link com.fr.third.org.quartz.simpl.SimpleTimeBroker}- + * which simply uses System.getCurrentTimeMillis() )is + * sufficient. However situations may exist where this default scheme is + * lacking in its robustness - especially when Quartz is used in a clustered + * configuration. For example, if one or more of the machines in the cluster + * has a system time that varies by more than a few seconds from the clocks on + * the other systems in the cluster, scheduling confusion will result. + *

+ * + * @see com.fr.third.org.quartz.core.QuartzScheduler + * @deprecated TimeBroker is not currently used in the Quartz code base. + * @author James House + */ +public interface TimeBroker { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Get the current time, as known by the TimeBroker. + *

+ * + * @throws SchedulerException + * with the error code set to + * SchedulerException.ERR_TIME_BROKER_FAILURE + */ + Date getCurrentTime() throws SchedulerException; + + /** + *

+ * Called by the QuartzScheduler before the TimeBroker is + * used, in order to give the it a chance to initialize. + *

+ */ + void initialize() throws SchedulerConfigException; + + /** + *

+ * Called by the QuartzScheduler to inform the TimeBroker + * that it should free up all of it's resources because the scheduler is + * shutting down. + *

+ */ + void shutdown(); + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/TriggerFiredBundle.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/TriggerFiredBundle.java new file mode 100644 index 000000000..e8beb8d3c --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/TriggerFiredBundle.java @@ -0,0 +1,138 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.spi; + +import java.util.Date; + +import com.fr.third.org.quartz.Calendar; +import com.fr.third.org.quartz.JobDetail; +import com.fr.third.org.quartz.Trigger; + +/** + *

+ * A simple class (structure) used for returning execution-time data from the + * JobStore to the QuartzSchedulerThread. + *

+ * + * @see com.fr.third.org.quartz.core.QuartzScheduler + * + * @author James House + */ +public class TriggerFiredBundle implements java.io.Serializable { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private JobDetail job; + + private Trigger trigger; + + private Calendar cal; + + private boolean jobIsRecovering; + + private Date fireTime; + + private Date scheduledFireTime; + + private Date prevFireTime; + + private Date nextFireTime; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public TriggerFiredBundle(JobDetail job, Trigger trigger, Calendar cal, + boolean jobIsRecovering, Date fireTime, Date scheduledFireTime, + Date prevFireTime, Date nextFireTime) { + this.job = job; + this.trigger = trigger; + this.cal = cal; + this.jobIsRecovering = jobIsRecovering; + this.fireTime = fireTime; + this.scheduledFireTime = scheduledFireTime; + this.prevFireTime = prevFireTime; + this.nextFireTime = nextFireTime; + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public JobDetail getJobDetail() { + return job; + } + + public Trigger getTrigger() { + return trigger; + } + + public Calendar getCalendar() { + return cal; + } + + public boolean isRecovering() { + return jobIsRecovering; + } + + /** + * @return Returns the fireTime. + */ + public Date getFireTime() { + return fireTime; + } + + /** + * @return Returns the nextFireTime. + */ + public Date getNextFireTime() { + return nextFireTime; + } + + /** + * @return Returns the prevFireTime. + */ + public Date getPrevFireTime() { + return prevFireTime; + } + + /** + * @return Returns the scheduledFireTime. + */ + public Date getScheduledFireTime() { + return scheduledFireTime; + } + +} \ No newline at end of file diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/package.html b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/package.html new file mode 100644 index 000000000..32ed01464 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/spi/package.html @@ -0,0 +1,17 @@ + + +Package com.fr.third.org.quartz.spi + + +

Contains Service Provider Interfaces that can be implemented by those +wishing to create and use custom versions of Quartz back-end/behind-the-scenes +services.

+ +
+
+
+See the Quartz project + at Open Symphony for more information. + + + diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/ConnectionProvider.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/ConnectionProvider.java new file mode 100644 index 000000000..f45a34474 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/ConnectionProvider.java @@ -0,0 +1,54 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.utils; + +import java.sql.Connection; +import java.sql.SQLException; + +/** + * Implementations of this interface used by DBConnectionManager + * to provide connections from various sources. + * + * @see DBConnectionManager + * @see PoolingConnectionProvider + * @see JNDIConnectionProvider + * @see com.fr.third.org.quartz.utils.weblogic.WeblogicConnectionProvider + * + * @author Mohammad Rezaei + */ +public interface ConnectionProvider { + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * @return connection managed by this provider + * @throws SQLException + */ + Connection getConnection() throws SQLException; + + + void shutdown() throws SQLException; +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/DBConnectionManager.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/DBConnectionManager.java new file mode 100644 index 000000000..98d42d17d --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/DBConnectionManager.java @@ -0,0 +1,146 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.utils; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.HashMap; + +/** + *

+ * Manages a collection of ConnectionProviders, and provides transparent access + * to their connections. + *

+ * + * @see ConnectionProvider + * @see PoolingConnectionProvider + * @see JNDIConnectionProvider + * @see com.fr.third.org.quartz.utils.weblogic.WeblogicConnectionProvider + * + * @author James House + * @author Sharada Jambula + * @author Mohammad Rezaei + */ +public class DBConnectionManager { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public static final String DB_PROPS_PREFIX = "com.fr.third.org.quartz.db."; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private static DBConnectionManager instance = new DBConnectionManager(); + + private HashMap providers = new HashMap(); + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Private constructor + *

+ * + */ + private DBConnectionManager() { + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public void addConnectionProvider(String dataSourceName, + ConnectionProvider provider) { + this.providers.put(dataSourceName, provider); + } + + /** + * Get a database connection from the DataSource with the given name. + * + * @return a database connection + * @exception SQLException + * if an error occurs, or there is no DataSource with the + * given name. + */ + public Connection getConnection(String dsName) throws SQLException { + ConnectionProvider provider = (ConnectionProvider) providers + .get(dsName); + if (provider == null) { + throw new SQLException("There is no DataSource named '" + + dsName + "'"); + } + + return provider.getConnection(); + } + + /** + * Get the class instance. + * + * @return an instance of this class + */ + public static DBConnectionManager getInstance() { + // since the instance variable is initialized at class loading time, + // it's not necessary to synchronize this method */ + return instance; + } + + /** + * Shuts down database connections from the DataSource with the given name, + * if applicable for the underlying provider. + * + * @exception SQLException + * if an error occurs, or there is no DataSource with the + * given name. + */ + public void shutdown(String dsName) throws SQLException { + + ConnectionProvider provider = (ConnectionProvider) providers + .get(dsName); + if (provider == null) { + throw new SQLException("There is no DataSource named '" + + dsName + "'"); + } + + provider.shutdown(); + + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/DirtyFlagMap.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/DirtyFlagMap.java new file mode 100644 index 000000000..85775013b --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/DirtyFlagMap.java @@ -0,0 +1,394 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.utils; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + *

+ * An implementation of Map that wraps another Map + * and flags itself 'dirty' when it is modified. + *

+ * + * @author James House + */ +public class DirtyFlagMap implements Map, Cloneable, java.io.Serializable { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + private static final long serialVersionUID = 1433884852607126222L; + + private boolean dirty = false; + private Map map; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Create a DirtyFlagMap that 'wraps' a HashMap. + *

+ * + * @see java.util.HashMap + */ + public DirtyFlagMap() { + map = new HashMap(); + } + + /** + *

+ * Create a DirtyFlagMap that 'wraps' a HashMap that has the + * given initial capacity. + *

+ * + * @see java.util.HashMap + */ + public DirtyFlagMap(int initialCapacity) { + map = new HashMap(initialCapacity); + } + + /** + *

+ * Create a DirtyFlagMap that 'wraps' a HashMap that has the + * given initial capacity and load factor. + *

+ * + * @see java.util.HashMap + */ + public DirtyFlagMap(int initialCapacity, float loadFactor) { + map = new HashMap(initialCapacity, loadFactor); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Clear the 'dirty' flag (set dirty flag to false). + *

+ */ + public void clearDirtyFlag() { + dirty = false; + } + + /** + *

+ * Determine whether the Map is flagged dirty. + *

+ */ + public boolean isDirty() { + return dirty; + } + + /** + *

+ * Get a direct handle to the underlying Map. + *

+ */ + public Map getWrappedMap() { + return map; + } + + public void clear() { + if (map.isEmpty() == false) { + dirty = true; + } + + map.clear(); + } + + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + public boolean containsValue(Object val) { + return map.containsValue(val); + } + + public Set entrySet() { + return new DirtyFlagMapEntrySet(map.entrySet()); + } + + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof DirtyFlagMap)) { + return false; + } + + return map.equals(((DirtyFlagMap) obj).getWrappedMap()); + } + + public int hashCode() + { + return map.hashCode(); + } + + public Object get(Object key) { + return map.get(key); + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + public Set keySet() { + return new DirtyFlagSet(map.keySet()); + } + + public Object put(Object key, Object val) { + dirty = true; + + return map.put(key, val); + } + + public void putAll(Map t) { + if (!t.isEmpty()) { + dirty = true; + } + + map.putAll(t); + } + + public Object remove(Object key) { + Object obj = map.remove(key); + + if (obj != null) { + dirty = true; + } + + return obj; + } + + public int size() { + return map.size(); + } + + public Collection values() { + return new DirtyFlagCollection(map.values()); + } + + public Object clone() { + DirtyFlagMap copy; + try { + copy = (DirtyFlagMap) super.clone(); + if (map instanceof HashMap) { + copy.map = (Map)((HashMap)map).clone(); + } + } catch (CloneNotSupportedException ex) { + throw new IncompatibleClassChangeError("Not Cloneable."); + } + + return copy; + } + + /** + * Wrap a Collection so we can mark the DirtyFlagMap as dirty if + * the underlying Collection is modified. + */ + private class DirtyFlagCollection implements Collection { + private Collection collection; + + public DirtyFlagCollection(Collection c) { + collection = c; + } + + protected Collection getWrappedCollection() { + return collection; + } + + public Iterator iterator() { + return new DirtyFlagIterator(collection.iterator()); + } + + public boolean remove(Object o) { + boolean removed = collection.remove(o); + if (removed) { + dirty = true; + } + return removed; + } + + public boolean removeAll(Collection c) { + boolean changed = collection.removeAll(c); + if (changed) { + dirty = true; + } + return changed; + } + + public boolean retainAll(Collection c) { + boolean changed = collection.retainAll(c); + if (changed) { + dirty = true; + } + return changed; + } + + public void clear() { + if (collection.isEmpty() == false) { + dirty = true; + } + collection.clear(); + } + + // Pure wrapper methods + public int size() { return collection.size(); } + public boolean isEmpty() { return collection.isEmpty(); } + public boolean contains(Object o) { return collection.contains(o); } + public boolean add(Object o) { return collection.add(o); } // Not supported + public boolean addAll(Collection c) { return collection.addAll(c); } // Not supported + public boolean containsAll(Collection c) { return collection.containsAll(c); } + public Object[] toArray() { return collection.toArray(); } + public Object[] toArray(Object[] array) { return collection.toArray(array); } + } + + /** + * Wrap a Set so we can mark the DirtyFlagMap as dirty if + * the underlying Collection is modified. + */ + private class DirtyFlagSet extends DirtyFlagCollection implements Set { + public DirtyFlagSet(Set set) { + super(set); + } + + protected Set getWrappedSet() { + return (Set)getWrappedCollection(); + } + } + + /** + * Wrap an Iterator so that we can mark the DirtyFlagMap as dirty if an + * element is removed. + */ + private class DirtyFlagIterator implements Iterator { + private Iterator iterator; + + public DirtyFlagIterator(Iterator iterator) { + this.iterator = iterator; + } + + public void remove() { + dirty = true; + iterator.remove(); + } + + // Pure wrapper methods + public boolean hasNext() { return iterator.hasNext(); } + public Object next() { return iterator.next(); } + } + + /** + * Wrap a Map.Entry Set so we can mark the Map as dirty if + * the Set is modified, and return Map.Entry objects + * wrapped in the DirtyFlagMapEntry class. + */ + private class DirtyFlagMapEntrySet extends DirtyFlagSet { + + public DirtyFlagMapEntrySet(Set set) { + super(set); + } + + public Iterator iterator() { + return new DirtyFlagMapEntryIterator(getWrappedSet().iterator()); + } + + public Object[] toArray() { + return toArray(new Object[super.size()]); + } + + public Object[] toArray(Object[] array) { + if (array.getClass().getComponentType().isAssignableFrom(Map.Entry.class) == false) { + throw new IllegalArgumentException("Array must be of type assignable from Map.Entry"); + } + + int size = super.size(); + + Object[] result = + (array.length < size) ? + (Object[])Array.newInstance(array.getClass().getComponentType(), size) : array; + + Iterator entryIter = iterator(); // Will return DirtyFlagMapEntry objects + for (int i = 0; i < size; i++) { + result[i] = entryIter.next(); + } + + if (result.length > size) { + result[size] = null; + } + + return result; + } + } + + /** + * Wrap an Iterator over Map.Entry objects so that we can + * mark the Map as dirty if an element is removed or modified. + */ + private class DirtyFlagMapEntryIterator extends DirtyFlagIterator { + public DirtyFlagMapEntryIterator(Iterator iterator) { + super(iterator); + } + + public Object next() { + return new DirtyFlagMapEntry((Map.Entry)super.next()); + } + } + + /** + * Wrap a Map.Entry so we can mark the Map as dirty if + * a value is set. + */ + private class DirtyFlagMapEntry implements Map.Entry { + private Map.Entry entry; + + public DirtyFlagMapEntry(Map.Entry entry) { + this.entry = entry; + } + + public Object setValue(Object o) { + dirty = true; + return entry.setValue(o); + } + + // Pure wrapper methods + public Object getKey() { return entry.getKey(); } + public Object getValue() { return entry.getValue(); } + } +} \ No newline at end of file diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/ExceptionHelper.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/ExceptionHelper.java new file mode 100644 index 000000000..eb072a139 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/ExceptionHelper.java @@ -0,0 +1,110 @@ +/* + * Copyright 2004-2006 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.fr.third.org.quartz.utils; + +import java.lang.reflect.Method; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + *

+ * Helper class for handling exception nesting, which is only supported in JDKs + * which are version 1.4 or later. + *

+ * + * Sample usage: + *
+ * try {
+ *     // Validate arguments
+ * } catch (Exception e) {
+ *     Exception myException = new IllegalArgumentException("Doh!");
+ *     ExceptionHelper.setCause(myException, e);
+ *     throw myException;
+ * }
+ * 
+ */ +public class ExceptionHelper { + private static Boolean supportsNestedThrowable = null; + + private ExceptionHelper() { + } + + /** + * Set the given Throwable's cause if this JDK supports + * the Throwable#initCause(Throwable) method. + */ + public static Throwable setCause(Throwable exception, Throwable cause) { + if (exception != null) { + if (supportsNestedThrowable()) { + try { + Method initCauseMethod = + exception.getClass().getMethod("initCause", new Class[] {Throwable.class}); + initCauseMethod.invoke(exception, new Object[] {cause}); + } catch (Exception e) { + getLog().warn( + "Unable to invoke initCause() method on class: " + + exception.getClass().getName(), e); + } + } + } + return exception; + } + + /** + * Get the underlying cause Throwable of the given exception + * if this JDK supports the Throwable#getCause() method. + */ + public static Throwable getCause(Throwable exception) { + if (supportsNestedThrowable()) { + try { + Method getCauseMethod = + exception.getClass().getMethod("getCause", (Class[])null); + return (Throwable)getCauseMethod.invoke(exception, (Object[])null); + } catch (Exception e) { + getLog().warn( + "Unable to invoke getCause() method on class: " + + exception.getClass().getName(), e); + } + } + + return null; + } + + /** + * Get whether the Throwable hierarchy for this JDK supports + * initCause()/getCause(). + */ + public static synchronized boolean supportsNestedThrowable() { + if (supportsNestedThrowable == null) { + try { + Throwable.class.getMethod("initCause", new Class[] {Throwable.class}); + Throwable.class.getMethod("getCause", (Class[])null); + supportsNestedThrowable = Boolean.TRUE; + getLog().debug("Detected JDK support for nested exceptions."); + } catch (NoSuchMethodException e) { + supportsNestedThrowable = Boolean.FALSE; + getLog().debug("Nested exceptions are not supported by this JDK."); + } + } + + return supportsNestedThrowable.booleanValue(); + } + + private static Log getLog() { + return LogFactory.getLog(ExceptionHelper.class); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/JNDIConnectionProvider.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/JNDIConnectionProvider.java new file mode 100644 index 000000000..35c3f9f2f --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/JNDIConnectionProvider.java @@ -0,0 +1,191 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.utils; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Properties; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.sql.DataSource; +import javax.sql.XADataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + *

+ * A ConnectionProvider that provides connections from a DataSource + * that is managed by an application server, and made available via JNDI. + *

+ * + * @see DBConnectionManager + * @see ConnectionProvider + * @see PoolingConnectionProvider + * + * @author James House + * @author Sharada Jambula + * @author Mohammad Rezaei + * @author Patrick Lightbody + * @author Srinivas Venkatarangaiah + */ +public class JNDIConnectionProvider implements ConnectionProvider { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private String url; + + private Properties props; + + private Object datasource; + + private boolean alwaysLookup = false; + + private final Log log = LogFactory.getLog(getClass()); + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Constructor + * + * @param jndiUrl + * The url for the datasource + */ + public JNDIConnectionProvider(String jndiUrl, boolean alwaysLookup) { + this.url = jndiUrl; + this.alwaysLookup = alwaysLookup; + init(); + } + + /** + * Constructor + * + * @param jndiUrl + * The URL for the DataSource + * @param jndiProps + * The JNDI properties to use when establishing the InitialContext + * for the lookup of the given URL. + */ + public JNDIConnectionProvider(String jndiUrl, Properties jndiProps, + boolean alwaysLookup) { + this.url = jndiUrl; + this.props = jndiProps; + this.alwaysLookup = alwaysLookup; + init(); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + protected Log getLog() { + return log; + } + + private void init() { + + if (!isAlwaysLookup()) { + Context ctx = null; + try { + ctx = (props != null) ? new InitialContext(props) : new InitialContext(); + + datasource = (DataSource) ctx.lookup(url); + } catch (Exception e) { + getLog().error( + "Error looking up datasource: " + e.getMessage(), e); + } finally { + if (ctx != null) { + try { ctx.close(); } catch(Exception ignore) {} + } + } + } + } + + public Connection getConnection() throws SQLException { + Context ctx = null; + try { + Object ds = this.datasource; + + if (ds == null || isAlwaysLookup()) { + ctx = (props != null) ? new InitialContext(props): new InitialContext(); + + ds = ctx.lookup(url); + if (!isAlwaysLookup()) { + this.datasource = ds; + } + } + + if (ds == null) { + throw new SQLException( "There is no object at the JNDI URL '" + url + "'"); + } + + if (ds instanceof XADataSource) { + return (((XADataSource) ds).getXAConnection().getConnection()); + } else if (ds instanceof DataSource) { + return ((DataSource) ds).getConnection(); + } else { + throw new SQLException("Object at JNDI URL '" + url + "' is not a DataSource."); + } + } catch (Exception e) { + this.datasource = null; + throw new SQLException( + "Could not retrieve datasource via JNDI url '" + url + "' " + + e.getClass().getName() + ": " + e.getMessage()); + } finally { + if (ctx != null) { + try { ctx.close(); } catch(Exception ignore) {} + } + } + } + + public boolean isAlwaysLookup() { + return alwaysLookup; + } + + public void setAlwaysLookup(boolean b) { + alwaysLookup = b; + } + + /* + * @see com.fr.third.org.quartz.utils.ConnectionProvider#shutdown() + */ + public void shutdown() throws SQLException { + // do nothing + } + +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/Key.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/Key.java new file mode 100644 index 000000000..b8f8dfb21 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/Key.java @@ -0,0 +1,97 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.utils; + +/** + *

+ * Object representing a job or trigger key. + *

+ * + * @author Jeffrey Wescott + */ +public class Key extends Pair { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Construct a new key with the given name and group. + * + * @param name + * the name + * @param group + * the group + */ + public Key(String name, String group) { + super(); + super.setFirst(name); + super.setSecond(group); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Get the name portion of the key. + *

+ * + * @return the name + */ + public String getName() { + return (String) getFirst(); + } + + /** + *

+ * Get the group portion of the key. + *

+ * + * @return the group + */ + public String getGroup() { + return (String) getSecond(); + } + + /** + *

+ * Return the string representation of the key. The format will be: + * <group>.<name>. + *

+ * + * @return the string representation of the key + */ + public String toString() { + return getGroup() + '.' + getName(); + } +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/Pair.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/Pair.java new file mode 100644 index 000000000..f997764eb --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/Pair.java @@ -0,0 +1,151 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ + +package com.fr.third.org.quartz.utils; + +/** + *

+ * Utility class for storing two pieces of information together. + *

+ * + * @author Jeffrey Wescott + */ +public class Pair { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private Object first; + + private Object second; + + + public Pair() + {} + + public Pair(Object first, Object second) + { + setFirst(first); + setSecond(second); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + *

+ * Get the first object in the pair. + *

+ * + * @return the first object + */ + public final Object getFirst() { + return first; + } + + /** + *

+ * Set the value of the first object in the pair. + *

+ * + * @param first + * the first object + */ + public final void setFirst(Object first) { + this.first = first; + } + + /** + *

+ * Get the second object in the pair. + *

+ * + * @return the second object + */ + public final Object getSecond() { + return second; + } + + /** + *

+ * Set the second object in the pair. + *

+ * + * @param second + * the second object + */ + public final void setSecond(Object second) { + this.second = second; + } + + /** + *

+ * Test equality of this object with that. + *

+ * + * @param that + * object to compare + * @return true if objects are equal, false otherwise + */ + public boolean equals(Object that) { + if (this == that) { + return true; + } else { + try { + Pair other = (Pair) that; + if(first == null && second == null) + return (other.first == null && other.second == null); + else if(first == null) + return this.second.equals(other.second); + else if(second == null) + return this.first.equals(other.first); + else + return (this.first.equals(other.first) && this.second + .equals(other.second)); + + } catch (ClassCastException e) { + return false; + } + } + } + + public int hashCode() { + if(first != null && second != null) + return (first.hashCode() ^ second.hashCode()); + if(first != null) + return (17 ^ first.hashCode()); + if(second != null) + return (17 ^ second.hashCode()); + return super.hashCode(); + } +} + +// EOF diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/PoolingConnectionProvider.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/PoolingConnectionProvider.java new file mode 100644 index 000000000..d2f8892bf --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/PoolingConnectionProvider.java @@ -0,0 +1,203 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.utils; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Properties; + +import com.fr.third.alibaba.druid.pool.DruidDataSource; + +/** + *

+ * A ConnectionProvider implementation that creates its own + * pool of connections. + *

+ * + *

+ * This class uses DBCP, + * an Apache-Jakarta-Commons product. + *

+ * + * @see DBConnectionManager + * @see ConnectionProvider + * + * @author Sharada Jambula + * @author James House + * @author Mohammad Rezaei + */ +public class PoolingConnectionProvider implements ConnectionProvider { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constants. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** The JDBC database driver. */ + public static final String DB_DRIVER = "driver"; + + /** The JDBC database URL. */ + public static final String DB_URL = "URL"; + + /** The database user name. */ + public static final String DB_USER = "user"; + + /** The database user password. */ + public static final String DB_PASSWORD = "password"; + + /** The maximum number of database connections to have in the pool. */ + public static final String DB_MAX_CONNECTIONS = "maxConnections"; + + /** + * The database sql query to execute everytime a connection is retrieved + * from the pool to ensure that it is still valid. + */ + public static final String DB_VALIDATION_QUERY = "validationQuery"; + + /** Default maximum number of database connections in the pool. */ + public static final int DEFAULT_DB_MAX_CONNECTIONS = 10; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private DruidDataSource datasource; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public PoolingConnectionProvider(String dbDriver, String dbURL, + String dbUser, String dbPassword, int maxConnections, + String dbValidationQuery) throws SQLException { + initialize( + dbDriver, dbURL, dbUser, dbPassword, + maxConnections, dbValidationQuery); + } + + /** + * Create a connection pool using the given properties. + * + *

+ * The properties passed should contain: + *

    + *
  • {@link #DB_DRIVER}- The database driver class name + *
  • {@link #DB_URL}- The database URL + *
  • {@link #DB_USER}- The database user + *
  • {@link #DB_PASSWORD}- The database password + *
  • {@link #DB_MAX_CONNECTIONS}- The maximum # connections in the pool, + * optional + *
  • {@link #DB_VALIDATION_QUERY}- The sql validation query, optional + *
+ *

+ * + * @param config + * configuration properties + */ + public PoolingConnectionProvider(Properties config) throws SQLException { + PropertiesParser cfg = new PropertiesParser(config); + initialize( + cfg.getStringProperty(DB_DRIVER), + cfg.getStringProperty(DB_URL), + cfg.getStringProperty(DB_USER, ""), + cfg.getStringProperty(DB_PASSWORD, ""), + cfg.getIntProperty(DB_MAX_CONNECTIONS, DEFAULT_DB_MAX_CONNECTIONS), + cfg.getStringProperty(DB_VALIDATION_QUERY)); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Create the underlying DBCP BasicDataSource with the + * default supported properties. + */ + private void initialize( + String dbDriver, + String dbURL, + String dbUser, + String dbPassword, + int maxConnections, + String dbValidationQuery) throws SQLException { + if (dbURL == null) { + throw new SQLException( + "DBPool could not be created: DB URL cannot be null"); + } + + if (dbDriver == null) { + throw new SQLException( + "DBPool '" + dbURL + "' could not be created: " + + "DB driver class name cannot be null!"); + } + + if (maxConnections < 0) { + throw new SQLException( + "DBPool '" + dbURL + "' could not be created: " + + "Max connections must be greater than zero!"); + } + + datasource = new DruidDataSource(); + datasource.setDriverClassName(dbDriver); + datasource.setUrl(dbURL); + datasource.setUsername(dbUser); + datasource.setPassword(dbPassword); + datasource.setMaxActive(maxConnections); + if (dbValidationQuery != null) { + datasource.setValidationQuery(dbValidationQuery); + } + } + + /** + * Get the DBCP BasicDataSource created during initialization. + * + *

+ * This can be used to set additional data source properties in a + * subclass's constructor. + *

+ */ + protected DruidDataSource getDataSource() { + return datasource; + } + + public Connection getConnection() throws SQLException { + return datasource.getConnection(); + } + + public void shutdown() throws SQLException { + datasource.close(); + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/PropertiesParser.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/PropertiesParser.java new file mode 100644 index 000000000..69e879f43 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/PropertiesParser.java @@ -0,0 +1,409 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ +package com.fr.third.org.quartz.utils; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Properties; +import java.util.StringTokenizer; + +/** + *

+ * This is an utility calss used to parse the properties. + *

+ * + * @author James House + */ +public class PropertiesParser { + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + Properties props = null; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public PropertiesParser(Properties props) { + this.props = props; + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public Properties getUnderlyingProperties() { + return props; + } + + /** + * Get the trimmed String value of the property with the given + * name. If the value the empty String (after + * trimming), then it returns null. + */ + public String getStringProperty(String name) { + return getStringProperty(name, null); + } + + /** + * Get the trimmed String value of the property with the given + * name or the given default value if the value is + * null or empty after trimming. + */ + public String getStringProperty(String name, String def) { + String val = props.getProperty(name, def); + if (val == null) { + return def; + } + + val = val.trim(); + + return (val.length() == 0) ? def : val; + } + + public String[] getStringArrayProperty(String name) { + return getStringArrayProperty(name, null); + } + + public String[] getStringArrayProperty(String name, String[] def) { + String vals = getStringProperty(name); + if (vals == null) { + return def; + } + + StringTokenizer stok = new StringTokenizer(vals, ","); + ArrayList strs = new ArrayList(); + try { + while (stok.hasMoreTokens()) { + strs.add(stok.nextToken().trim()); + } + + return (String[])strs.toArray(new String[strs.size()]); + } catch (Exception e) { + return def; + } + } + + public boolean getBooleanProperty(String name) { + return getBooleanProperty(name, false); + } + + public boolean getBooleanProperty(String name, boolean def) { + String val = getStringProperty(name); + + return (val == null) ? def : Boolean.valueOf(val).booleanValue(); + } + + public byte getByteProperty(String name) throws NumberFormatException { + String val = getStringProperty(name); + if (val == null) { + throw new NumberFormatException(" null string"); + } + + try { + return Byte.parseByte(val); + } catch (NumberFormatException nfe) { + throw new NumberFormatException(" '" + val + "'"); + } + } + + public byte getByteProperty(String name, byte def) + throws NumberFormatException { + String val = getStringProperty(name); + if (val == null) { + return def; + } + + try { + return Byte.parseByte(val); + } catch (NumberFormatException nfe) { + throw new NumberFormatException(" '" + val + "'"); + } + } + + public char getCharProperty(String name) { + return getCharProperty(name, '\0'); + } + + public char getCharProperty(String name, char def) { + String param = getStringProperty(name); + return (param == null) ? def : param.charAt(0); + } + + public double getDoubleProperty(String name) throws NumberFormatException { + String val = getStringProperty(name); + if (val == null) { + throw new NumberFormatException(" null string"); + } + + try { + return Double.parseDouble(val); + } catch (NumberFormatException nfe) { + throw new NumberFormatException(" '" + val + "'"); + } + } + + public double getDoubleProperty(String name, double def) + throws NumberFormatException { + String val = getStringProperty(name); + if (val == null) { + return def; + } + + try { + return Double.parseDouble(val); + } catch (NumberFormatException nfe) { + throw new NumberFormatException(" '" + val + "'"); + } + } + + public float getFloatProperty(String name) throws NumberFormatException { + String val = getStringProperty(name); + if (val == null) { + throw new NumberFormatException(" null string"); + } + + try { + return Float.parseFloat(val); + } catch (NumberFormatException nfe) { + throw new NumberFormatException(" '" + val + "'"); + } + } + + public float getFloatProperty(String name, float def) + throws NumberFormatException { + String val = getStringProperty(name); + if (val == null) { + return def; + } + + try { + return Float.parseFloat(val); + } catch (NumberFormatException nfe) { + throw new NumberFormatException(" '" + val + "'"); + } + } + + public int getIntProperty(String name) throws NumberFormatException { + String val = getStringProperty(name); + if (val == null) { + throw new NumberFormatException(" null string"); + } + + try { + return Integer.parseInt(val); + } catch (NumberFormatException nfe) { + throw new NumberFormatException(" '" + val + "'"); + } + } + + public int getIntProperty(String name, int def) + throws NumberFormatException { + String val = getStringProperty(name); + if (val == null) { + return def; + } + + try { + return Integer.parseInt(val); + } catch (NumberFormatException nfe) { + throw new NumberFormatException(" '" + val + "'"); + } + } + + public int[] getIntArrayProperty(String name) throws NumberFormatException { + return getIntArrayProperty(name, null); + } + + public int[] getIntArrayProperty(String name, int[] def) + throws NumberFormatException { + String vals = getStringProperty(name); + if (vals == null) { + return def; + } + + StringTokenizer stok = new StringTokenizer(vals, ","); + ArrayList ints = new ArrayList(); + try { + while (stok.hasMoreTokens()) { + try { + ints.add(new Integer(stok.nextToken().trim())); + } catch (NumberFormatException nfe) { + throw new NumberFormatException(" '" + vals + "'"); + } + } + + int[] outInts = new int[ints.size()]; + for (int i = 0; i < ints.size(); i++) { + outInts[i] = ((Integer)ints.get(i)).intValue(); + } + return outInts; + } catch (Exception e) { + return def; + } + } + + public long getLongProperty(String name) throws NumberFormatException { + String val = getStringProperty(name); + if (val == null) { + throw new NumberFormatException(" null string"); + } + + try { + return Long.parseLong(val); + } catch (NumberFormatException nfe) { + throw new NumberFormatException(" '" + val + "'"); + } + } + + public long getLongProperty(String name, long def) + throws NumberFormatException { + String val = getStringProperty(name); + if (val == null) { + return def; + } + + try { + return Long.parseLong(val); + } catch (NumberFormatException nfe) { + throw new NumberFormatException(" '" + val + "'"); + } + } + + public short getShortProperty(String name) throws NumberFormatException { + String val = getStringProperty(name); + if (val == null) { + throw new NumberFormatException(" null string"); + } + + try { + return Short.parseShort(val); + } catch (NumberFormatException nfe) { + throw new NumberFormatException(" '" + val + "'"); + } + } + + public short getShortProperty(String name, short def) + throws NumberFormatException { + String val = getStringProperty(name); + if (val == null) { + return def; + } + + try { + return Short.parseShort(val); + } catch (NumberFormatException nfe) { + throw new NumberFormatException(" '" + val + "'"); + } + } + + public String[] getPropertyGroups(String prefix) { + Enumeration keys = props.propertyNames(); + HashSet groups = new HashSet(10); + + if (!prefix.endsWith(".")) { + prefix += "."; + } + + while (keys.hasMoreElements()) { + String key = (String) keys.nextElement(); + if (key.startsWith(prefix)) { + String groupName = key.substring(prefix.length(), key.indexOf( + '.', prefix.length())); + groups.add(groupName); + } + } + + return (String[]) groups.toArray(new String[groups.size()]); + } + + public Properties getPropertyGroup(String prefix) { + return getPropertyGroup(prefix, false, null); + } + + public Properties getPropertyGroup(String prefix, boolean stripPrefix) { + return getPropertyGroup(prefix, stripPrefix, null); + } + + /** + * Get all properties that start with the given prefix. + * + * @param prefix The prefix for which to search. If it does not end in + * a "." then one will be added to it for search purposes. + * @param stripPrefix Whether to strip off the given prefix + * in the result's keys. + * @param excludedPrefixes Optional array of fully qualified prefixes to + * exclude. For example if prefix is "a.b.c", then + * excludedPrefixes might be "a.b.c.ignore". + * + * @return Group of Properties that start with the given prefix, + * optionally have that prefix removed, and do not include properties + * that start with one of the given excluded prefixes. + */ + public Properties getPropertyGroup(String prefix, boolean stripPrefix, String[] excludedPrefixes) { + Enumeration keys = props.propertyNames(); + Properties group = new Properties(); + + if (!prefix.endsWith(".")) { + prefix += "."; + } + + while (keys.hasMoreElements()) { + String key = (String) keys.nextElement(); + if (key.startsWith(prefix)) { + + boolean exclude = false; + if (excludedPrefixes != null) { + for (int i = 0; (i < excludedPrefixes.length) && (exclude == false); i++) { + exclude = key.startsWith(excludedPrefixes[i]); + } + } + + if (exclude == false) { + String value = getStringProperty(key, ""); + + if (stripPrefix) { + group.put(key.substring(prefix.length()), value); + } else { + group.put(key, value); + } + } + } + } + + return group; + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/StringKeyDirtyFlagMap.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/StringKeyDirtyFlagMap.java new file mode 100644 index 000000000..46dd4b819 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/StringKeyDirtyFlagMap.java @@ -0,0 +1,362 @@ +/* + * Copyright 2004-2006 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.fr.third.org.quartz.utils; + +import java.io.Serializable; +import java.util.Iterator; +import java.util.Map; + +/** + *

+ * An implementation of Map that wraps another Map + * and flags itself 'dirty' when it is modified, enforces that all keys are + * Strings. + *

+ * + *

+ * All allowsTransientData flag related methods are deprecated as of version 1.6. + *

+ */ +public class StringKeyDirtyFlagMap extends DirtyFlagMap { + static final long serialVersionUID = -9076749120524952280L; + + /** + * @deprecated JDBCJobStores no longer prune out transient data. If you + * include non-Serializable values in the Map, you will now get an + * exception when attempting to store it in a database. + */ + private boolean allowsTransientData = false; + + public StringKeyDirtyFlagMap() { + super(); + } + + public StringKeyDirtyFlagMap(int initialCapacity) { + super(initialCapacity); + } + + public StringKeyDirtyFlagMap(int initialCapacity, float loadFactor) { + super(initialCapacity, loadFactor); + } + + /** + * Get a copy of the Map's String keys in an array of Strings. + */ + public String[] getKeys() { + return (String[]) keySet().toArray(new String[size()]); + } + + /** + * Tell the StringKeyDirtyFlagMap that it should + * allow non-Serializable values. Enforces that the Map + * doesn't already include transient data. + * + * @deprecated JDBCJobStores no longer prune out transient data. If you + * include non-Serializable values in the Map, you will now get an + * exception when attempting to store it in a database. + */ + public void setAllowsTransientData(boolean allowsTransientData) { + + if (containsTransientData() && !allowsTransientData) { + throw new IllegalStateException( + "Cannot set property 'allowsTransientData' to 'false' " + + "when data map contains non-serializable objects."); + } + + this.allowsTransientData = allowsTransientData; + } + + /** + * Whether the StringKeyDirtyFlagMap allows + * non-Serializable values. + * + * @deprecated JDBCJobStores no longer prune out transient data. If you + * include non-Serializable values in the Map, you will now get an + * exception when attempting to store it in a database. + */ + public boolean getAllowsTransientData() { + return allowsTransientData; + } + + /** + * Determine whether any values in this Map do not implement + * Serializable. Always returns false if this Map + * is flagged to not allow transient data. + * + * @deprecated JDBCJobStores no longer prune out transient data. If you + * include non-Serializable values in the Map, you will now get an + * exception when attempting to store it in a database. + */ + public boolean containsTransientData() { + if (!getAllowsTransientData()) { // short circuit... + return false; + } + + String[] keys = getKeys(); + for (int i = 0; i < keys.length; i++) { + Object o = super.get(keys[i]); + if (!(o instanceof Serializable)) { + return true; + } + } + + return false; + } + + /** + * Removes any data values in the map that are non-Serializable. Does + * nothing if this Map does not allow transient data. + * + * @deprecated JDBCJobStores no longer prune out transient data. If you + * include non-Serializable values in the Map, you will now get an + * exception when attempting to store it in a database. + */ + public void removeTransientData() { + if (!getAllowsTransientData()) { // short circuit... + return; + } + + String[] keys = getKeys(); + for (int i = 0; i < keys.length; i++) { + Object o = super.get(keys[i]); + if (!(o instanceof Serializable)) { + remove(keys[i]); + } + } + } + + /** + *

+ * Adds the name-value pairs in the given Map to the + * StringKeyDirtyFlagMap. + *

+ * + *

+ * All keys must be Strings. + *

+ */ + public void putAll(Map map) { + for (Iterator entryIter = map.entrySet().iterator(); entryIter.hasNext();) { + Map.Entry entry = (Map.Entry) entryIter.next(); + + // will throw IllegalArgumentException if key is not a String + put(entry.getKey(), entry.getValue()); + } + } + + /** + *

+ * Adds the given int value to the StringKeyDirtyFlagMap. + *

+ */ + public void put(String key, int value) { + super.put(key, new Integer(value)); + } + + /** + *

+ * Adds the given long value to the StringKeyDirtyFlagMap. + *

+ */ + public void put(String key, long value) { + super.put(key, new Long(value)); + } + + /** + *

+ * Adds the given float value to the StringKeyDirtyFlagMap. + *

+ */ + public void put(String key, float value) { + super.put(key, new Float(value)); + } + + /** + *

+ * Adds the given double value to the StringKeyDirtyFlagMap. + *

+ */ + public void put(String key, double value) { + super.put(key, new Double(value)); + } + + /** + *

+ * Adds the given boolean value to the StringKeyDirtyFlagMap. + *

+ */ + public void put(String key, boolean value) { + super.put(key, new Boolean(value)); + } + + /** + *

+ * Adds the given char value to the StringKeyDirtyFlagMap. + *

+ */ + public void put(String key, char value) { + super.put(key, new Character(value)); + } + + /** + *

+ * Adds the given String value to the StringKeyDirtyFlagMap. + *

+ */ + public void put(String key, String value) { + super.put(key, value); + } + + /** + *

+ * Adds the given Object value to the StringKeyDirtyFlagMap. + *

+ */ + public Object put(Object key, Object value) { + if (!(key instanceof String)) { + throw new IllegalArgumentException( + "Keys in map must be Strings."); + } + + return super.put(key, value); + } + + /** + *

+ * Retrieve the identified int value from the StringKeyDirtyFlagMap. + *

+ * + * @throws ClassCastException + * if the identified object is not an Integer. + */ + public int getInt(String key) { + Object obj = get(key); + + try { + return ((Integer) obj).intValue(); + } catch (Exception e) { + throw new ClassCastException("Identified object is not an Integer."); + } + } + + /** + *

+ * Retrieve the identified long value from the StringKeyDirtyFlagMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a Long. + */ + public long getLong(String key) { + Object obj = get(key); + + try { + return ((Long) obj).longValue(); + } catch (Exception e) { + throw new ClassCastException("Identified object is not a Long."); + } + } + + /** + *

+ * Retrieve the identified float value from the StringKeyDirtyFlagMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a Float. + */ + public float getFloat(String key) { + Object obj = get(key); + + try { + return ((Float) obj).floatValue(); + } catch (Exception e) { + throw new ClassCastException("Identified object is not a Float."); + } + } + + /** + *

+ * Retrieve the identified double value from the StringKeyDirtyFlagMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a Double. + */ + public double getDouble(String key) { + Object obj = get(key); + + try { + return ((Double) obj).doubleValue(); + } catch (Exception e) { + throw new ClassCastException("Identified object is not a Double."); + } + } + + /** + *

+ * Retrieve the identified boolean value from the StringKeyDirtyFlagMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a Boolean. + */ + public boolean getBoolean(String key) { + Object obj = get(key); + + try { + return ((Boolean) obj).booleanValue(); + } catch (Exception e) { + throw new ClassCastException("Identified object is not a Boolean."); + } + } + + /** + *

+ * Retrieve the identified char value from the StringKeyDirtyFlagMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a Character. + */ + public char getChar(String key) { + Object obj = get(key); + + try { + return ((Character) obj).charValue(); + } catch (Exception e) { + throw new ClassCastException("Identified object is not a Character."); + } + } + + /** + *

+ * Retrieve the identified String value from the StringKeyDirtyFlagMap. + *

+ * + * @throws ClassCastException + * if the identified object is not a String. + */ + public String getString(String key) { + Object obj = get(key); + + try { + return (String) obj; + } catch (Exception e) { + throw new ClassCastException("Identified object is not a String."); + } + } +} diff --git a/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/TriggerStatus.java b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/TriggerStatus.java new file mode 100644 index 000000000..f3a9b6b29 --- /dev/null +++ b/fine-quartz-old/src/main/java/com/fr/third/org/quartz/utils/TriggerStatus.java @@ -0,0 +1,127 @@ +/* + * Copyright 2004-2005 OpenSymphony + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Previously Copyright (c) 2001-2004 James House + */ + +package com.fr.third.org.quartz.utils; + +import java.util.Date; + +/** + *

+ * Object representing a job or trigger key. + *

+ * + * @author James House + */ +public class TriggerStatus extends Pair { + + // TODO: Repackage under spi or root pkg ?, put status constants here. + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Data members. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + private Key key; + + private Key jobKey; + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Constructors. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + /** + * Construct a new TriggerStatus with the status name and nextFireTime. + * + * @param status + * the trigger's status + * @param nextFireTime + * the next time the trigger will fire + */ + public TriggerStatus(String status, Date nextFireTime) { + super(); + super.setFirst(status); + super.setSecond(nextFireTime); + } + + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Interface. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + public Key getJobKey() { + return jobKey; + } + + public void setJobKey(Key jobKey) { + this.jobKey = jobKey; + } + + public Key getKey() { + return key; + } + + public void setKey(Key key) { + this.key = key; + } + + /** + *

+ * Get the name portion of the key. + *

+ * + * @return the name + */ + public String getStatus() { + return (String) getFirst(); + } + + /** + *

+ * Get the group portion of the key. + *

+ * + * @return the group + */ + public Date getNextFireTime() { + return (Date) getSecond(); + } + + /** + *

+ * Return the string representation of the TriggerStatus. + *

+ * + */ + public String toString() { + return "status: " + getStatus() + ", next Fire = " + getNextFireTime(); + } +} + +// EOF